首先,响应式数据 不等于 双向数据绑定
- 双向数据绑定
通常就是指vue的内置指令v-model
,实际上就是v-bind
和v-on
的语法糖。<template> <input v-model='localValue'/> <!-- 等同于 <input @input='onInput' :value='localValue' /> --> <span>{{localValue}}</span> <template> <script> export default{ data(){ return { localValue:'', } }, methods:{ onInput(e){ //在input事件的处理函数中更新value的绑定值 this.localValue=e.target.value; } } } </script>
- 响应式数据
推荐博主链接:https://juejin.cn/post/6932659815424458760 。以下是我自己学习的笔记,没有链接内容全面,但或许会帮助理解。
-
数据劫持
Object.defineProperty:
Object.defineProperty(obj, prop, descriptor) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
此处主要是重写对象属性的set和get方法,重写了原有的行为,这也就是数据劫持的含义。//利用函数的闭包封装Object.defineProperty,将value当做闭包环境(小红包) //基本的设置setter和getter,value如果没有参数传入(读属性操作)则默认为obj[key] var defineReactive(obj,key,value=obj[key]){ Object.defineProperty(obj, key, { get: function reactiveGetter() { return value }, set: function reactiveSetter(newValue) { if (newValue === value) return value = newValue } }) }
以上这种写法没有直接用Object.defineProperty,而是放在了一个函数里,优点是不需要设置一个全局变量value,很有可能会污染环境。
<!--如果一个对象有多个属性需要响应式(设置setter和getter)--> class Observer { constructor(obj) { this.value = obj this.walk() } walk() { Object.keys(this.value).forEach((key) => defineReactive(this.value, key)) } } const obj = { a: 1, b: 2 } new Observer(obj)
其中,
Object.keys(obj)
的结果是将对象obj中可遍历的属性以字符串形式组成数组返回。
这种写法只能为对象的最外层属性设置setter和getter,如果属性有嵌套是不能对内部属性进行设置的,优化写法如下:// 如果一个对象有多个属性且属性有嵌套需要响应式(设置setter和getter) // 入口函数 function observe(obj) { //如果不是对象就直接返回,给自己添加getter和setter if (typeof obj !== 'object') return // 调用Observer,遍历对象的属性 new Observer(obj) } // 类的作用是遍历对象的属性 class Observer { constructor(obj) { this.value = obj this.walk() } walk() { // 遍历该对象,并进行数据劫持 Object.keys(this.value).forEach((key) => defineReactive(this.value, key)) } } function defineReactive(data, key, value = data[key]) { // 1.处理value的子属性: // 如果value是对象,递归调用observe来监测该对象 // 如果value不是对象,observe函数会直接返回 observe(value) //2.给该属性本身添加getter/setter Object.defineProperty(data, key, { get: function reactiveGetter() { return value }, set: function reactiveSetter(newValue) { if (newValue === value) return value = newValue observe(newValue) } }) } const obj = { a: 1, b: { c: 2 } } observe(obj)
数组响应式:
vue底层改写了七个方法:push,pop,shift,unshift,splice,sort,reverse,这七个方法原生的定义在Array.prototype上,现在要重写(保留原有功能并且响应式),就构造一个子类继承原来的方法let newArrayPrototype = Object.create(Array.prototype) //准备好重写的内容 ['push','pop','shift','unshift','sort','reverse','splice'].array.forEach(item => { newArrayPrototype[item]=function(){ //更新视图 console.log('视图改变了') //执行原本的功能 Array.prototype[item].call(this,...arguments) } }); let arr=[1,2,3,4,5] //使用重写的内容 arr.__proto__=newArrayPrototype
-
依赖收集
数据state <——(订阅/依赖)watcher类
数据state ——>(消息发布) watcher类 ——>收到数据改变信息后的操作(渲染页面之类的)- 依赖:用到该数据的组件是依赖(组件中有虚拟DOM和diff算法);在getter中收集依赖(谁读取该数据就会触发该数据的getter,即可以利用getter收集依赖),在setter中触发依赖
- Dep类:依赖收集的代码封装的结果,用来管理依赖
- watcher类:数据变化时由watcher类通知组件,可以将watcher看做依赖
- 利用一个数组dep存放一个属性的所有依赖(watcher)
vue1.x中依赖收集的逻辑是,在页面渲染时,遇到一个数据,就实例化一个watcher。watcher的实例化中,会调用自身类的get函数,这个get函数里需要读取这个数据,所以同样会触发这个数据本身的getter。
`因此,可以在数据的getter中把watcher加入数据的dep数组,那么就需要在watcher类的get方法中把watcher放在windows上。
执行顺序是: 遇到页面数据——>实例化watcher,执行到get方法,先把watcher放到windows上,再访问数据(还没执行完)——>执行数据的getter,把watcher加入dep数组——>watcher实例化完毕
-
派发更新
派发更新依靠的是数据的setter。当数据改变时,就会触发自身的setter,因此,在setter中遍历dep数组,触发每一个watcher的回调函数即可。 -
代码总结:
是前面描述的细化优化版本,解释在注释中。
// 调用该方法来检测数据 function observe(data) { if (typeof data !== 'object') return new Observer(data) } class Observer { constructor(value) { this.value = value this.walk() } walk() { Object.keys(this.value).forEach((key) => defineReactive(this.value, key)) } } // 数据拦截 function defineReactive(data, key, value = data[key]) { //dep会作为闭包的一部分,因此每一个属性都会有独有的dep数组 const dep = new Dep() observe(value) Object.defineProperty(data, key, { get: function reactiveGetter() { //添加依赖 dep.depend() return value }, set: function reactiveSetter(newValue) { if (newValue === value) return value = newValue observe(newValue) //遍历依赖并执行回调函数 dep.notify() } }) } // 将dep抽象成为一个类 class Dep { constructor() { this.subs = [] } depend() { //只有Dep.target不为空时才会添加一个新的依赖,有效控制了只有watcher实例初始化时才会添加依赖 if (Dep.target) { this.addSub(Dep.target) } } notify() { const subs = [...this.subs] subs.forEach((s) => s.update()) } addSub(sub) { this.subs.push(sub) } } //这是一个自定义的属性 Dep.target = null const TargetStack = [] function pushTarget(_target) { TargetStack.push(Dep.target) Dep.target = _target } function popTarget() { Dep.target = TargetStack.pop() } // watcher class Watcher { constructor(data, expression, cb) { this.data = data this.expression = expression this.cb = cb //只有watcher实例化时才会调用该类的get方法 this.value = this.get() } get() { //只有调用了get方法才会给Dep.target赋值,并且将其放到全局环境中,加入TargetStack pushTarget(this) const value = parsePath(this.data, this.expression) popTarget() return value } update() { const oldValue = this.value this.value = parsePath(this.data, this.expression) this.cb.call(this.data, this.value, oldValue) } } // 工具函数,为了根据表达式找到真正的数据 function parsePath(obj, expression) { const segments = expression.split('.') for (let key of segments) { if (!obj) return obj = obj[key] } return obj }
-