对象的引用保存在栈内存,对象的属性名和属性值保存在堆内存。
所以在data中绑定的get和set的是对象的引用,当对象属性修改,添加或删除时,对象的引用并没有改变,改变发生在堆上,所以检测不到,就不会触发页面更新
data:{
a:1
}
改变方式:
Vue.set(vm.someObject, 'b', 2)
this.$set(this.someObject,'b',2)
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
注意Object.assign() 是浅拷贝
1、修改c = 4 ,让页面渲染怎么做?
data{
obj:{
o:{
a:{
c:1
}
},
b:3
}
}
// 第一种
this.obj.o.a.c = 4
this.obj = Object.assign({}, this.obj) // 这个其实是改变了引用值
// 第二种
this.$set(this.obj.o.a, 'c', 4)
上面两种都可以更新视图
2、当对象的某个属性变化的时候,如何触发自定义的回调函数?
ES5中新添加了一个方法:Object.defineProperty,通过这个方法,可以自定义getter和setter函数,
从而在获取对象属性和设置对象属性的时候能够执行自定义的回调函数。(set触发,从而watch触发重新渲染页面)
3、对象往往是一个深层次的结构,对象的某个属性可能仍然是一个对象,这种情况怎么处理?
通过递归算法,判断一个属性是不是对象,如果是就通过递归拷贝,直到到达最底层的属性位置。
https://www.jianshu.com/p/0f833d3881b9?from=groupmessage
https://www.cnblogs.com/liuhao-web/p/8919623.html
nextTick:更新循环结束之后执行延迟回调。
一般是在数据变化之后,执行nextTick获取更新后的DOM。
原理:
MutationObserver是HTML5新增的属性,用于监听DOM修改事件,能检测到节点内容、属性、子节点的改变。
但vue用MO是想利用它的microtask特性,而不是想做DOM监听。(所以promise.then 也可以)
每次event loop的最后,会有一个UI render步骤,只要让nextTick里的代码放在UI render步骤后面执行,就能访问到更新后的DOM。
数据改变的代码都是普通代码(同步代码),在宏任务执行后,会去执行微任务,所以只要在微任务中执行nextTickHandler就行。
vue实现:
在nextTick中有个callbacks用来做事件队列,里面是通过vue.nextTick(事件)传参进去的函数。
callbacks中的事件都会在nextTickHandler通过遍历事件队列,一个个执行
DOM更新之后会执行nextTickHandler
vue用了三个方法来执行nextTickHandler函数,分别是:
Promise、MutationObserver、setTimeout。
当浏览器支持哪个的时候就是用哪个(从上至下,降级策略)
1、p.then(nextTickHandler).catch(logError);
2、var observer = new MutationObserver(nextTickHandler);
3、setTimeout(nextTickHandler, 0);
可以看到1,2是微任务,因为兼容性问题,vue不得不做了microtask向macrotask的降级方案,就利用了3(是宏任务)。但是归根结底都是利用事件执行机制。
双向绑定,就是view的变化能反映到ViewModel上,ViewModel的变化能同步到view上
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。
- 数据劫持: Object.defineProperty中的getter/setter
- 发布订阅者模式:数据改变就触发setter,执行watcher更新视图
使用:
1、model这个语法糖
<input
:value="text"
@input="e => text = e.target.value"
/>
2、或者.sync 修饰符
<my-dialog :visible.sync="dialogVisible" />
this.$emit('update:visible', newVisible)
响应式和双向绑定原理一样
响应式原理是利用了数据劫持和订阅发布的模式。
响应式原理,也就是通过Object.defineProperty 把这些data中的属性全部转为 getter/setter,
每个组件都有一个watcher进行监听,当setter触发时,通知watcher去更新视图
只有data中的数据才是响应式的,所以想要数据是响应式的,就要在data中初始化。
非响应式想变成响应式数据?
将非响应式属性合并到响应式属性,方法如下:
Vue.set(vm.someObj,key,value)
this.$set(vm.someObj,key,value)
两个其实是一样的
不能,这个时候DOM还没渲染完成。获取DOM一般要在mounted内即可获取
1、computed是属性调用,methods是函数调用
2、computed的属性改变依赖于data,而methos一般是执行某些事件改变data值
3、computed有缓存,如果依赖的data中的属性没有改变,就不会重新计算,也就是html中有n个计算属性,但是也只计算一次。
而methods只是单纯的调用函数,调用多少次就重新执行多少次。
computed 原理,Vue 内部是怎么知道 computed 依赖的?
vue中定义一个dependencies的数组来收集依赖,每次data中属性的get被调用,就把该属性放入dependencies中。
在得知 computed 属性发生变化之后, Vue 内部并不立即去重新计算出新的 computed 属性值,而是仅仅标记为 dirty ,下次访问的时候,再重新计算,然后将计算结果缓存起来。
因为对象是一个引用数据类型,如果data是一个对象的情况下会造成所有组件共用一个data。
也就是每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;
而当data是一个函数的情况下,每次函数执行完毕后都会返回一个新的对象,这样的话每个组件都会维护一份独立的对象(data)
父组件:
<MyChild>
<div>
{this.state.date}
{this.state.year}
</div>
</MyChild>
vue子组件:
<div>
<slot></slot>
</div>
react子组件:
render() {
return <div>
{this.props.children}
</div>
}