# 原理解析

# computed 和 watch 实现原理

两者区别

computed是计算一个新属性,并将该属性挂载到vue实例上
而watch是监听已经存在且已经挂载到vue实例上的数据
所以用watch同样可以监听computed计算属性的变化(其他还有data, props)

# 原理

vue 数据模型仅仅是普通的 JavaScript 对象,而当你修改它们时,视图便会进行自动更新。

重要

当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项时,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter,这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化,每个组件实例都有相应的 观察者(watcher) 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 观察者(watcher) 重新计算,从而致使它关联的组件得以更新。

# Vue 响应系统,其核心有三点:observe、watcher、dep:

  1. observe:遍历 data 中的属性,使用 Object.defineProperty 的 get/set 方法对其进行数据劫持
  2. dep:每个属性拥有自己的消息订阅器 dep,用于存放所有订阅了该属性的观察者对象
  3. watcher:观察者(对象),通过 dep 实现对响应属性的监听,监听到结果后,主动触发自己的回调进行响应

# vue双向绑定原理

重点:

  • vue怎么检测到数据变化
  • vue怎么检测到视图变化

vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的

双向绑定原理

  1. 一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

  2. 一个订阅者Watcher,每一个Watcher都绑定一个更新函数,watcher可以收到属性的变化通知并执行相应的函数,从而更新视图。

  3. 一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)。

# vue数组/对象的更新检测

重要

Vue不能检测到对象的添加或者删除。Vue在初始化实例时就对属性执行了setter/getter转化过程,所以属性必须开始就在对象上,这样才能让Vue转化它

vue检测数据(数组)变动靠的是settergetter这两个属性,而这两个属性,使用了js原生的Object.defineProperty(),第一个实现Object.defineProperty方法的浏览器是IE8,这也是为什么vuejs不支持ie8以下的原因

# 数组更新检测

Vue 包含一组观察数组的变异方法,它们也将会触发视图更新:

push()
pop()
shift()
unshift()
splice()
sort()
reverse()
1
2
3
4
5
6
7

由于JavaScript 的限制,vue不能检测到下面数组的变化:

  1. 直接用索引设置元素,如 vm.items[0] = {}
  2. 修改数据的长度,如 vm.items.length = 0

为了解决数组变化的第一类问题,以下两种方式都可以实现:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
1
2
3
4
5

也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:

vm.$set(vm.items, indexOfItem, newValue)

为了解决数组变化的第二类问题,你可以使用 splice:

vm.items.splice(newLength)

# 对象更新检测

由于JavaScript 的限制,Vue不能检测到对象的添加或者删除

对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。

但是,可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。例如,对于:

var vm = new Vue({
  data: {
    userProfile: {
      name: 'Anika'
    }
  }
})
1
2
3
4
5
6
7

你可以添加一个新的 age 属性到嵌套的 userProfile 对象:

Vue.set(vm.userProfile, 'age', 27)

你还可以使用 vm.$set 实例方法,它只是全局 Vue.set 的别名:

vm.$set(vm.userProfile, 'age', 27)

有时可能需要为已有对象赋予多个新属性,比如使用Object.assign()。在这种情况下,应该用两个对象的属性创建一个新的对象。所以,如果想添加新的响应式属性,不要像这样

Object.assign(vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})
1
2
3
4

应该这样:

vm.userProfile = Object.assign({}, vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})
1
2
3
4

# 让子组件的props不受父组件影响

如何让子组件接收的props值不受父组件值变化的影响

在子组件中,将接收的props的值赋值给一个新定义的字段

父组件

<template>
  <div>
    <p>我是父组件:{{numberData}}</p>
    <el-button @click="open">操作</el-button>
    <el-button @click="resite">重置</el-button>
    <child-dialog :number-data="numberData"></child-dialog>
  </div>
</template>
<script>
import ChildDialog from './ChildDialog'
export default {
  data () {
    return {
      numberData: 0
    }
  },
  methods: {
    open () {
      this.numberData = this.numberData + 1
    },
    resite () {
      this.numberData = 0
    }
  },
  components: {
    ChildDialog
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

子组件ChildDialog.vue

<template>
  <div>
    <p>我是子组件props: {{numberData}}</p>
    <p>我是子组件data: {{handleData}}</p>
  </div>
</template>
<script>
export default {
  props: ['numberData'],
  data () {
    return {
      createdNumData: '',
      handleData: this.numberData
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

演示:

An image

# Object.defineProperty

Object.defineProperty

javascript提供的一个强大的方法,它可以定义当对象的某个值访问和赋值时会先执行自定义的钩子方法,由此实现了控制属性的访问和赋值

# 示例:

var obj = new Object();
var value;
Object.defineProperty(obj,'name',{
    get: function () {
        console.log('get it');
        return value;//必须return一个值,作为name属性的值
    },
    set: function (newvalue) {
        console.log('set it');
        value = newvalue;//同步把value的值进行更新
    }
});
console.log(obj.name);//get it
obj.name = 1234;//set it
console.log(obj.name);//get it
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#