Vue2.X官网文档中曾经论述了深刻响应式原理,简略来讲就是数据批改之后,被es5里边Object .defineProperty,setter拦挡到了,告诉watcher,watcher对函数进行渲染,这个过程种要创立新的虚构dom节点,比照旧的虚构dom节点,比照完之后做成一个补丁,把补丁打在实在dom构造中,实在dom再更新,视图产生扭转。

Object.defineProperty()数据劫持/数据代理

利用javascript引擎赋予的性能,检测对象属性变动
Object.defineProperty()办法会间接在一个对象上定义一个新属性,或者批改一个对象的现有属性,并返回此对象。

var obj = {};Object.defineProperty(obj, 'a', {    value: 3})Object.defineProperty(obj, 'b', {    value: 5})console.log(obj) // {a:3,b:5}console.log(obj.a, obj.b) // 3 5

Object.defineProperty()能够设置额定暗藏的属性

Object.defineProperty(obj, 'a', {    // value: 3,        get(){},    // 是否可写    writable: true})

Object.defineProperty()真正对数据的操作是他它本身的getter函数(读取)和setter函数(设置)来进行的:

Object.defineProperty(obj, 'a', {    // getter函数    get(){        console.log(ole.log('拜访a属性');        return 7;    },    // setter函数    set(nVal) {        console.log('批改a属性为'+nVal)    }})console.log(obj.a); // 7obj.a = 10;console.log(obj.a); // 7

由以上示例可知,当拜访obj的a属性时,值为7,当批改a属性的值之后,从打印后果看出,setter函数的确执行了,然而新值并没有赋给getter的返回值,此时的getter和setter短少了一个连贯的桥梁:变量,所以下面的代码稍作改变:

var temp = '';Object.defineProperty(obj, 'a', {    // getter函数    get(){        console.log(ole.log('拜访a属性');        return temp;    },    // setter函数    set(nVal) {        console.log('批改a属性为'+nVal);        temp = nVal;    }})console.log(obj.a); // 7obj.a = 10;console.log(obj.a); // 10

到这里Object.defineProperty()的用法就曾经很分明了,接下来要做的就是怎么让它更好看优雅~

defineReactive函数

var obj = {};function defineReactive (data, key, val) {// val 在defineReactive函数里给getter,setter函数营造了一个闭包环境,这样就不必再申明一个长期变量了    Object.defineProperty(data, key, {        // 可枚举        enumerable: true,        // 能够被配置,比方能够被delete        configurable: true,        // getter函数        get(){            console.log(ole.log('拜访a属性');            return val;        },        // setter函数        set(nVal) {            console.log('批改a属性为'+nVal);            if (nVal === val) return;            val = nVal;        }  })}defineReactive(obj, 'a', 10);conso.log(obj.a); // 10obj.a++; // 赋值时调用了setter函数console.log(obj.a); // 11 

这个代码里边解决变量的问题,不过这个例子只满足单层的对象,那么像obj:{a:{m:{n:5}}}这种简单构造的数据,就须要进行逐层遍历,递归地调用Object.defineProperty()去解决~

var obj = { a:{   m:{     n:5   } }}

拜访obj.a.m.n属性,不能每次都设置a属性,所以当有简单构造的对象时,defineReactive函数须要作参数判断:

function defineReactive (data, key, val) {    if (arguments.length == 2) {        val = data[key]    }    ...}defineReactive(obj, 'a')console.log(obj.a.m.n); // 当拜访obj.a.m.n属性,只拜访到了a这一层

递归侦测对象全副属性

对于obj的a.m.n属性,须要循环递归地实现Object.defineProperty(),此时创立一个类Observer,它次要是将一个一般对象的任何属性都能被侦测到的工具类:

stateDiagram-v2    Observer(观察者) --> 将一个失常的object转换为每个层级的属性都是响应式(能够被侦测的)的object            
Observer.jsexport default class Observer {        constructor(value) {        // 每一个Observer的实例身上,都有一个dep        this.dep = new Dep()        // 给实例(this,构造函数中的this不是示意类自身,而是示意实例)增加了__ob__属性,值就是value(这次new的实例)        def(value, '__ob__', this, false)        // console.log('我是Observer结构器', value)        // Observer 类的目标:将一个失常的object转换为每个层级的属性都是响应式(能够被侦测的)的object        // 查看它是数组还是对象        if (Array.isArray(value)) {            // 如果是数组,要十分强行,将这个数组的原型,指向arrayMethods            // setPrototypeOf强制地定义value的原型            Object.setPrototypeOf(value, arrayMethods)            // 让这个数组变得 observe            this.observeArray(value)        } else {            this.walk(value)        }    }    // 遍历    walk(value) {        for (let k in value) {            defineReactive(value, k)        }    }    // 数组的非凡遍历    observeArray(arr) {        for (let i = 0, l = arr.length; i < l;i++) {            // 逐项进行observe            observe(arr[i])        }    }}

这个类被创立进去被实例化为对象才有意义,所以如何被实例化值得思考--创立一个observe函数

observe.jsimport Observer from "./Observer";// 这个函数只为对象服务export const observe = function (value) {    // 如果value不是对象,什么都不做    if (typeof value !== object) return    // 定义ob,ob就是要存储Observer的实例    var ob;    if (typeof value.__ob__ !== 'undefined') {        // __ob__ 就是存储Observer类的实例的,区别于其余常见的属性        ob = value.__ob__; // value就是要侦测的对象,就是defineReactive中传入的data,然而实用于val = data[key]    } else {        ob = new Observer(value);    }    return ob;}

obj必然是先调用observe触发,再看这个对象有没有__ob__,如果没有,调用New Observer(),将产生的实例增加到__ob__上,此时obj的a属性active的闭包环境了,因为a属性的值是m:{n:5}},在setter函数中被设置的时候,m:{n:5}}作为新的对象又触发了observe,也就是图上的遍历下一层属性。

总结起来就是对象obj先触发observe,在observe实例化Observer作为obj的__ob__属性,而在Observer类的构造函数中,对对象进行了遍历,每一次遍历又调用了defineReactive设置属性,在设置属性时对子元素进行了observe,至此造成了递归。

数组的响应式解决

Vue2以Array.prototype为原型,创立了一个arrayMethods对象,而后用ES6中的setPrototypeOf强制地使arr的__proto__指向了arrayMethods对象,这样就能够调用arrayMethods对象中被重写的七个数组办法了,它们别离是push、pop、shift、unshift、splice、sort、reverse。

array.jsimport { def} from './utils.js';const arrayPrototype = Array.prototype;// 以Array.prototype为原型创立arrayMethods对象// 裸露arrayMethodsexport const arrayMethods = Object.create(arrayPrototype)// console.log(arrayMethods)// 要被改写的七个数组办法const methodsNeedChange = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];methodsNeedChange.forEach((methodName) => {    // console.log('methodName', methodName)    // 备份原来的办法, 因为push,pop等7个函数的性能不能被剥夺    const original = arrayPrototype[methodName];    // 把这个数组身上的__ob__取出来,__ob__曾经被增加了,因为数组必定不是最高层,比方obj.g属性是数组,obj不能是数组,第一次遍历obj这个对象的第一层的时候,曾经给g属性(就是这个数组)增加了__ob__属性    // 定义新的办法    def(arrayMethods, methodName, function(){        // 复原原来的性能        const result =  original.apply(this, arguments);        // 把类数组对象变成数组        const args = [...arguments];        // console.log(arguments);        const ob = this.__ob__;        // 有三种办法push/unshift/splice可能插入新项,当初要把插入的新项也要变为observe的        let inserted = [];        switch (methodName) {            case 'push':            case 'unshift':                inserted = args;                break;            case 'splice':                // splice 格局是splice(下标,数量,插入的新项)                inserted = args.slice(2)                break;        }        // 判断有没有要插入的新项,让新项也变成响应式的        if (inserted) {            ob.observeArray(inserted);        }        console.log('lalala')        ob.dep.notify()        return result;    }, false)})

依赖收集

在getter中收集依赖,在setter中触发依赖

  • 把依赖收集的代码封装成一个Dep类,它专门用来治理依赖,每个Observer的实例,成员中都有一个Dep的实例
  • Watcher是一个中介,数据发生变化时通过Watcher直达,告诉组件;
  • 依赖就是Watcher。只有Watcher触发的getter才会收集依赖,哪个Watcher触发了getter,就把哪个Watcher收集到Dep中;
  • Dep应用公布订阅模式,当数据发生变化时,会循环依赖列表,把所有的Watcher都告诉一遍。
    Dep类就是用来收集依赖,Watcher就是依赖。

    Dep.jsvar uid = 0;export default class Dep {  constructor() {      // console.log('我是dep类的结构器')      this.id = uid++;      // 用数组存储本人的订阅者。subs是英语subscribes订阅者的意思。      // 这个数组外面放的是Watcher的实例      this.subs = [];  }  // 增加订阅  addSub (sub) {      this.subs.push(sub)  }  // 增加依赖  depend () {      // Dep.target 就是一个咱们本人指定的全局的地位,你用window.target也行,只有全局惟一,没有歧义就行      if (Dep.target) {          // getter函数就会从全局惟一的这个中央读取正在读取数据的Watcher,并把这个Watcher收集到Dep当中          this.addSub(Dep.target)      }  }  // 告诉更新  notify () {      console.log('我是notify')      // 浅克隆一份      const subs = this.subs.slice();      // 遍历      for (let i = 0, l = subs.length; i < l; i++) {          subs[i].update()      }  }}
    import Dep from "./Dep";var uid = 0;export default class Watcher {  constructor(target, expression, callback) {      // console.log('我是Watcher类的结构器')      this.id = uid++;      this.target = target;      this.getter = parsePath(expression)      this.callback = callback;      this.value = this.get()  }  update () {      this.run()  }  get() {      // 进入依赖收集阶段。让全局的Dep.target设置为Watcher自身,那么就是进入依赖收集阶段      Dep.target = this;      const obj = this.target;      var value;      // 只有能找,就始终找,try{}避免找不到      try {          value = this.getter(obj)      } finally {          // 退出依赖收集阶段,此Watcher把依赖收集阶段的资格让给别的Watcher          // 所有Watcher都在竞争,以后哪个Watcher正在读getter,哪个Watcher就是Dep的target          Dep.target = null;      }      return value  }  run() {      this.getAndInvoke(this.callback)  }  getAndInvoke(cb) {      const value = this.get()      if (value !== this.value || typeof value === 'object') {          const oldValue = this.value;          this.value = value;          // this.callback()          cb.call(this.target, value, oldValue)      }  }}function parsePath(str) {  var segments = str.split('.');  console.log('segments:', segments)  return (obj) => {      for (let i =0; i < segments.length; i++) {          if (!obj) return;          obj = obj[segments[i]]      }      return obj  }}


    残缺代码:vue2数据响应式原理