响应式原理
Vue2\3响应式B站拉钩老师视频,讲的挺久,但是挺好,挺深奥,6小节
Object.create ?
Object.definproprty,?
观察者模式 和发布订阅模式的区别,?
依赖收集?
在一些技术博客上,我看到过这样一种说法,认为 Object.defineProperty
有一个缺陷是无法监听数组变化:
无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。所以 Vue 才设置了 7 个变异数组(
push、pop、shift、unshift、splice、sort、reverse
)的 hack 方法来解决问题。
Object.defineProperty
的第一个缺陷是无法监听数组变化。然而 Vue 的文档提到了 Vue 是可以检测到数组变化的,但是只有以下八种方法,vm.items[indexOfItem] = newValue 这种是无法检测的。
这种说法是有问题的,事实上,Object.defineProperty
本身是可以监控到数组下标的变化的,只是在 Vue 的实现中,从性能 / 体验的性价比考虑,放弃了这个特性。
==下面开始正式的学习===
为什么vue3重写vue2的响应式
vue2 Object.defineProperty;
vue3 proxy
vue2 defineProperty
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data
选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty
把这些 property 全部转为 getter/setter。Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例
都对应一个 watcher
实例,它会在组件渲染的过程中把“接触”过的数据data的 property 记录为依赖。之后当依赖项的 setter 触发时
,会通知 watcher
,从而使它关联的组件重新渲染。getter触发进行依赖收集
对于对象
Vue 无法检测 property 的添加或移除
vm.$set
实例方法,这也是全局 Vue.set
方法的别名
对于数组
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[1] = 'x' // 不是响应性的
- 当你修改数组的长度时,例如:
vm.items.length = 2 //不是响应性的
为了解决第一类问题,以下两种方式
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
vm.$set(vm.items, indexOfItem, newValue)
// vm.$set 实例方法,是全局方法 Vue.set 的一个别名:
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
解决第二类问题,你可以使用 splice
:
vm.items.splice(newLength)
异步更新队列
Vue 在更新 DOM 时是异步执行的
如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的
vue2响应式原理流程
- Object.defineProperty转换为getter/setter
data(){
const obj= {message:'hello word'}
}
VUE内部使用Object.defineProperty(),将每一个成员都转换为getter/setter 形式
访问值getter执行,改变值 setter执行
- render 触发getter,进行依赖收集
<div id="app">
<h1>{{message}}</h1>
<button @click="clickHandler"></button>
</div>
// 转换成render函数
function render(){
....
return (
id:app,
message
click:clickHandler
)
}
html模板会转化成render函数,render函数执行的时候,就会访问message函数,这个时候就会触发getter,进行依赖收集,render函数就会作为message成员的依赖,从而被收集
- setter触发 通知watcher,进行更新
methods:{
clickHandler(){this.message=Math.random()}
}
message 被修改,setter触发,自动通知之前收集到的依赖,进行更新,也就是之前收集到的render函数,然后通知,然后改变了
- watch 【官网有讲】
每个组件实例
都对应一个 watcher
实例,它会在组件渲染的过程中把“接触”过的数据data的 property 记录为依赖。之后当依赖项的 setter 触发时
,会通知 watcher
,从而使它关联的组件重新渲染。getter触发进行依赖收集
- vue2.0响应式原理全流程
render
↓touch【render的时候触发】
data里面的每一个数据都绑定getter 和 setter
↓getter【collect as dependency依赖收集】
↓setter【notify通知】
Watcher
↓trigger re-render【触发重新render】
render【这里跟上面就连上了知道了吧】
Vue2响应式原理简易实现
-
1.new vue
-
2.执行vue的构造函数,传入data
-
3.构造函数里面执行new Observer(data);Oberser里面for 循环 执行defineReactive;defineReactive 里面进行依赖收集;执行Object.defineProperty定义get的时候添加订阅对象,该对象放到dep依赖数组里面,也就是
依赖收集
, set的时候通知更新数据, -
4.构造函数里面执行 new Compiler(data);_compile里面遇到v-modle进行监听执行new Watcher,
-
5.当数据发生变化,执行set函数,notify->dep;通知依赖收集里面数据更新updata(),updata就是watcher里面的方法,实现更新视图的功能
Object.defineProprty
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProprty的问题?
-
- 每次都循环的话,性能不好,开销大
-
- 新增/删除属性,数据无响应;需要额外方法实现(Vue.set/Vue.delete)
-
- vue2重写了7个数组方法【重写的push/pop/shift/unshift/splice/sort/reverse方法实现】,不能检测两种数组的变动 1.利用索引改变数据 2.操作数组长度【尤雨溪说因为性能问题,性能跟用户体验收益不成正比,所以没有解决,可以实现但是开发量太大,投入产出不成正比,既然可以规避掉,那就不解决算了】
vue3 compositionAPI
setup
ref
reactive
vue如何追踪变化
当我们从一个组件的 data
函数中返回一个普通的 JavaScript 对象时,Vue 会将该对象包裹在一个带有 get
和 set
处理程序的 Proxy 中。Proxy 是在 ES6 中引入的,它使 Vue 3 避免了 Vue 早期版本中存在的一些响应性问题。也使用了Object.defineProperty来支持IE浏览器,但是不支持不支持 IE8 以及更低版本浏览器,因为Object.defineProperty
是 ES5 中一个无法 shim 的特性
Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义
.点的时候,就改了原来的数据,直接把语法规则改掉了,.点的时候进行操作
proxy是v8引擎,C++实现的
new Proxy(data,{
get
set
})
缺点:低版本浏览器不兼容, 优点,不需要遍历
vue3代码实现原文链接
function defineReactive(obj) {
if (typeof obj !== 'object') return obj
return new Proxy(obj, {
get(target, key, receiver) {
// 收集依赖代码...
var res = Reflect.get(target, key, receiver)
// 子属性若是对象,需要代理子属性,注:只有使用了该子属性才会执行是否要递归代理
return (typeof res === 'object') ? defineReactive(res) : res
},
set(target, key, val) {
Reflect.set(target, key, val)
// 通知更新视图代码...
},
deleteProperty(target, key) {
Reflect.deleteProperty(target, key)
// 通知更新视图代码...
}
})
}
Vue3 源码瞎**分析
-
- reactive方法 reactiv(object) 里面return createReactiveObject() 创建响应式对象
-
- createReactiveObject 里面 直接new Proxy