We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
起源:在 Vue 的数据绑定中会对一个对象属性的变化进行监听,并且通过依赖收集做出相应的视图更新等等。
问题:一个对象所有类型的属性变化都能被监听到吗?
之前用 Object.defineProperty通过对象的 getter/setter简单的实现了对象属性变化的监听,并且去通过依赖关系去做相应的依赖处理。
Object.defineProperty
getter/setter
但是,这是存在问题的,尤其是当对象中某个属性的值是数组的时候。正如 Vue 文档所说:
由于 JavaScript 的限制,Vue 无法检测到以下数组变动: 当你使用索引直接设置一项时,例如 vm.items[indexOfItem] = newValue 当你修改数组长度时,例如 vm.items.length = newLength
由于 JavaScript 的限制,Vue 无法检测到以下数组变动:
vm.items[indexOfItem] = newValue
vm.items.length = newLength
从 Vue 源码中也可以看到确实是对数组做了特殊处理的。原因就是 ES5 及以下的版本无法做到对数组的完美继承 。
用之前写好的 observe做了一个简单的实验,如下:
observe
import { observe } from './mvvm' const data = { name: 'Jiang', userInfo: { gender: 0 }, list: [] } // 此处直接使用了前面写好的 getter/setter observe(data) data.name = 'Solo' data.userInfo.gender = 1 data.list.push(1) console.log(data)
结果是这样的:
从结果可以看出问题所在,data中 name、userInfo、list 属性的值均发生了变化,但是数组 list 的变化并没有被 observe监听到。原因是什么呢?简单来说,操作数组的方法,也就是 Array.prototype上挂载的方法并不能触发该属性的 setter,因为这个属性并没有做赋值操作。
data
Array.prototype
Vue 中解决这个问题的方法,是将数组的常用方法进行重写,通过包装之后的数组方法就能够去在调用的时候被监听到。
在这里,我想的一种方法与它类似,大概就是通过原型链去拦截对数组的操作,从而实现对操作数组这个行为的监听。
实现如下:
// 让 arrExtend 先继承 Array 本身的所有属性 const arrExtend = Object.create(Array.prototype) const arrMethods = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * arrExtend 作为一个拦截对象, 对其中的方法进行重写 */ arrMethods.forEach(method => { const oldMethod = Array.prototype[method] const newMethod = function(...args) { oldMethod.apply(this, args) console.log(`${method}方法被执行了`) } arrExtend[method] = newMethod }) export default { arrExtend }
需要在 defineReactive 函数中添加的代码为:
defineReactive
if (Array.isArray(value)) { value.__proto__ = arrExtend }
测试一下:data.list.push(1)
data.list.push(1)
我们看看结果:
上面代码的逻辑一目了然,也是 Vue 中实现思路的简化。将 arrExtend 这个对象作为拦截器。首先让这个对象继承 Array 本身的所有属性,这样就不会影响到数组本身其他属性的使用,后面对相应的函数进行改写,也就是在原方法调用后去通知其它相关依赖这个属性发生了变化,这点和 Object.defineProperty 中 setter所做的事情几乎完全一样,唯一的区别是可以细化到用户到底做的是哪一种操作,以及数组的长度是否变化等等。
arrExtend
Array
setter
ES6 中我们看到了一个让人耳目一新的属性——Proxy。我们先看一下概念:
Proxy
通过调用 new Proxy() ,你可以创建一个代理用来替代另一个对象(被称为目标),这个代理对目标对象进行了虚拟,因此该代理与该目标对象表面上可以被当作同一个对象来对待。 代理允许你拦截在目标对象上的底层操作,而这原本是 JS 引擎的内部能力。拦截行为使用了一个能够响应特定操作的函数(被称为陷阱)。
通过调用 new Proxy() ,你可以创建一个代理用来替代另一个对象(被称为目标),这个代理对目标对象进行了虚拟,因此该代理与该目标对象表面上可以被当作同一个对象来对待。
代理允许你拦截在目标对象上的底层操作,而这原本是 JS 引擎的内部能力。拦截行为使用了一个能够响应特定操作的函数(被称为陷阱)。
Proxy顾名思义,就是代理的意思,这是一个能让我们随意玩弄对象的特性。当我们,通过Proxy去对一个对象进行代理之后,我们将得到一个和被代理对象几乎完全一样的对象,并且可以对这个对象进行完全的监控。
什么叫完全监控?Proxy所带来的,是对底层操作的拦截。前面我们在实现对对象监听时使用了Object.defineProperty,这个其实是 JS 提供给我们的高级操作,也就是通过底层封装之后暴露出来的方法。Proxy的强大之处在于,我们可以直接拦截对代理对象的底层操作。这样我们相当于从一个对象的底层操作开始实现对它的监听。
改进一下我们的代码?
const createProxy = data => { if (typeof data === 'object' && data.toString() === '[object Object]') { for (let k in data) { if (typeof data[k] === 'object') { defineObjectReactive(data, k, data[k]) } else { defineBasicReactive(data, k, data[k]) } } } } function defineObjectReactive(obj, key, value) { // 递归 createProxy(value) obj[key] = new Proxy(value, { set(target, property, val, receiver) { if (property !== 'length') { console.log('Set %s to %o', property, val) } return Reflect.set(target, property, val, receiver) } }) } function defineBasicReactive(obj, key, value) { Object.defineProperty(obj, key, { enumerable: true, configurable: false, get() { return value }, set(newValue) { if (value === newValue) return console.log(`发现 ${key} 属性 ${value} -> ${newValue}`) value = newValue } }) } export default { createProxy }
对于一个对象中的基础类型的属性,我们还是通过Object.defineProperty来实现响应式的属性,因为这里并不存在痛点,但是在实现对Object类型的属性进行监听的时候,我采用的是创建代理,因为我们之前的痛点在于无法去有效监听数组的变化。当我们使用这种改进方法之后,我们不用像之前通过重写数组的方法来实现对数组操作的监听了,因为之前这种方法存在很多的局限性,我们不能覆盖所有的数组操作,同时,我们也不能响应到类似于data.array.length = 0这种操作。通过代理实现之后,一切都不一样了。我们可以从底层就实现对数组的变化进行监听。甚至能watch到数组长度的变化等等各种更加细节的东西。这无疑解决了很大的问题。
Object
data.array.length = 0
watch
我们调用一下刚才的方法,试试看?
let data = { name: 'Jiang', userInfo: { gender: 0, movies: [] }, list: [] } createProxy(data) data.name = 'Solo' data.userInfo.gender = 0 data.userInfo.movies.push('星际穿越') data.list.push(1)
输出为:
结果非常完美~我们实现了对对象所有属性变化的监听Proxy的骚操作还有很多很多,比如说将代理当作原型放到原型链上,这样一来就可以只对子类不含有的属性进行监听,非常的强大。Proxy可以得到更加广泛的应用,而且场景很多。这也是我第一次去使用,还需要多加巩固( ;´Д`)
The text was updated successfully, but these errors were encountered:
No branches or pull requests
起源:在 Vue 的数据绑定中会对一个对象属性的变化进行监听,并且通过依赖收集做出相应的视图更新等等。
问题:一个对象所有类型的属性变化都能被监听到吗?
之前用
Object.defineProperty
通过对象的getter/setter
简单的实现了对象属性变化的监听,并且去通过依赖关系去做相应的依赖处理。但是,这是存在问题的,尤其是当对象中某个属性的值是数组的时候。正如 Vue 文档所说:
从 Vue 源码中也可以看到确实是对数组做了特殊处理的。原因就是 ES5 及以下的版本无法做到对数组的完美继承 。
实验一下?
用之前写好的
observe
做了一个简单的实验,如下:结果是这样的:
从结果可以看出问题所在,
data
中 name、userInfo、list 属性的值均发生了变化,但是数组 list 的变化并没有被observe
监听到。原因是什么呢?简单来说,操作数组的方法,也就是Array.prototype
上挂载的方法并不能触发该属性的 setter,因为这个属性并没有做赋值操作。如何解决这个问题?
Vue 中解决这个问题的方法,是将数组的常用方法进行重写,通过包装之后的数组方法就能够去在调用的时候被监听到。
在这里,我想的一种方法与它类似,大概就是通过原型链去拦截对数组的操作,从而实现对操作数组这个行为的监听。
实现如下:
需要在
defineReactive
函数中添加的代码为:测试一下:
data.list.push(1)
我们看看结果:
上面代码的逻辑一目了然,也是 Vue 中实现思路的简化。将
arrExtend
这个对象作为拦截器。首先让这个对象继承Array
本身的所有属性,这样就不会影响到数组本身其他属性的使用,后面对相应的函数进行改写,也就是在原方法调用后去通知其它相关依赖这个属性发生了变化,这点和Object.defineProperty
中setter
所做的事情几乎完全一样,唯一的区别是可以细化到用户到底做的是哪一种操作,以及数组的长度是否变化等等。还有什么别的办法吗?
ES6 中我们看到了一个让人耳目一新的属性——
Proxy
。我们先看一下概念:Proxy
顾名思义,就是代理的意思,这是一个能让我们随意玩弄对象的特性。当我们,通过Proxy
去对一个对象进行代理之后,我们将得到一个和被代理对象几乎完全一样的对象,并且可以对这个对象进行完全的监控。什么叫完全监控?
Proxy
所带来的,是对底层操作的拦截。前面我们在实现对对象监听时使用了Object.defineProperty
,这个其实是 JS 提供给我们的高级操作,也就是通过底层封装之后暴露出来的方法。Proxy
的强大之处在于,我们可以直接拦截对代理对象的底层操作。这样我们相当于从一个对象的底层操作开始实现对它的监听。改进一下我们的代码?
对于一个对象中的基础类型的属性,我们还是通过
Object.defineProperty
来实现响应式的属性,因为这里并不存在痛点,但是在实现对Object
类型的属性进行监听的时候,我采用的是创建代理,因为我们之前的痛点在于无法去有效监听数组的变化。当我们使用这种改进方法之后,我们不用像之前通过重写数组的方法来实现对数组操作的监听了,因为之前这种方法存在很多的局限性,我们不能覆盖所有的数组操作,同时,我们也不能响应到类似于data.array.length = 0
这种操作。通过代理实现之后,一切都不一样了。我们可以从底层就实现对数组的变化进行监听。甚至能watch
到数组长度的变化等等各种更加细节的东西。这无疑解决了很大的问题。我们调用一下刚才的方法,试试看?
输出为:
结果非常完美~我们实现了对对象所有属性变化的监听
Proxy
的骚操作还有很多很多,比如说将代理当作原型放到原型链上,这样一来就可以只对子类不含有的属性进行监听,非常的强大。Proxy
可以得到更加广泛的应用,而且场景很多。这也是我第一次去使用,还需要多加巩固( ;´Д`)The text was updated successfully, but these errors were encountered: