vueconf(2018hangzhou)大会刚刚过去, vue 作者尤大大向我们展示了 vue3.0 的进展, 并介绍 vue3.0 的一些改动, 其中最令我期待的就是重写数据监听机制.
回顾 vue2.x 的双向数据绑定
谈起 vue 的双向数据绑定, 我们首先能想到的就是 ES5 中 Object.defineProperty, 利用重写属性的 get,set, 我们可以完成数据劫持监听, 使用观察者模式, 在数据发生改变的时候, 通知订阅者更新状态.
我们就针对 Observer 观察者部分写了一个简易的代码如下:
- function Observer (data) {
- this.data = data
- this.walk(data)
- }
- Observer.prototype = {
- walk: function (data) {
- let me = this
- Object.keys(data).forEach(function (key) {
- me.convert(key, data[key])
- })
- },
- convert: function (key, val) {
- this.defineReactive(this.data, key, val)
- },
- defineReactive: function (data, key, val) {
- let dep = new Dep()
- let childObj = observe()
- Object.defineProperty(data, key, {
- enumerable: true,
- configurable: false,
- get: function () {
- if (Dep.target) {
- dep.depend() // 添加订阅者
- }
- return val
- },
- set: function (newVal) {
- if (newVal === val) return
- val = newVal
- childObj = observe(newVal)
- dep.notify() // 通知订阅器
- }
- })
- }
- }
- function observe (value, vm) {
- if (!value || typeof value !== 'object') {
- return
- }
- return new Observer(value)
- }
以上代码中我们定义了一个 Observer 构造函数, 即观察者. 利用 Object.defineProperty 我们将传入对象的所有属性 (包含子属性) 全部进行数据监听, 并在 get 方法中, 在订阅器里添加一条订阅. 一旦某属性发生改变, 通知到订阅器.
Dep 订阅器, Compile 指令, Watcher 订阅者的代码就不再分析, mvvm 的总体结构可以由下图看出
整个过程是一个观察者, 订阅器, 订阅者, 指令器四部分的事情, 各司其职.
简单介绍 Proxy
Proxy 是 ES6 中新增的构造函数, 它可以理解为在目标对象之前架设一层 "拦截", 外界对该对象的访问, 都必须通过这层拦截, 因此提供了一种机制, 可以对外界的访问进行过滤改写. Proxy 原意是代理, 在这里我们可以理解为 "代理" 某些操作.
- var obj = new Proxy({}, {
- get: function (target, key, receiver) {
- console.log(`proxy get ${key}`)
- return Reflect.get(target, key, receiver)
- },
- set: function (target, key, value, receiver) {
- console.log(`proxy set ${key}`)
- return Reflect.set(target, key, value, receiver)
- }
- })
上面代码对一个空对象架设了一层拦截, 我们可以在 Proxy 的第二个参数中传入一个 handler 对象, 对象中可以定义拦截行为.
在 get 和 set 中, 我们都用到了 Reflect.Reflect 对象与 Proxy 对象一样, 也是 ES6 位了操作对象而提供的新 API.Reflect 对象的方法与 Proxy 对象的方法一一对应, 比如 Proxy 方法拦截 target 对象的属性赋值行为. 它采用 Reflect.set 方法将值赋值给对象的属性, 确保完成原有的行为, 然后再部署额外的功能.
根据以上代码我们写一段测试代码:
- obj.text = 'hello world!'
- // proxy set text
- var _text = obj.text
- // proxy get text
Proxy 改写观察者
利用以上 Proxy 的一些特性, 我们修改代码如下:
- function observe (value, vm) {
- if (!value || typeof value !== 'object') {
- return
- }
- let dep = new Dep()
- return new Proxy(value, {
- get: function (target, key, receiver) {
- if (Dep.target) {
- dep.depend()
- }
- return Reflect.get(target, key, receiver)
- },
- set: function (target, key, value, receiver) {
- dep.notify()
- return Reflect.set(target, key, value, receiver)
- }
- })
- }
我们将传入的对象直接替换为 Proxy 对象, 入参 handler 的 get 和 set 中的添加订阅者和通知订阅器逻辑保持不变.
整个过程没有做其他多余的判断, 由于 Vue3.0 还没有发布, 没有实际源码可以借鉴, 所以以上只是个人实现的简单版本(完整代码). 将整个 mvvm 运用到 html 中, 以下是运行后的效果(没做 gif, 凑合看吧):
重写数据监听机制的好处
放弃了
Object.defineProperty
, 基于 Proxy 观察者机制以满足全语言覆盖及更好的性能. 加上其它方法的优化改动, vue3.0 可以提速一倍 / 内存使用降低一半;
Observer 模块将可以单独作为一个库来使用.
可能产生的问题
很遗憾的是, ES6 的 Proxy 无法被转译为 ES5, 所以它将不被 IE 所支持. 对于这个问题, Vue3.0 将给出 IE11 的兼容方案, 即在 IE11 下, 还是使用的 Object.defineProperty 机制.
来源: https://juejin.im/post/5bffb361e51d451d4c1d3bb1