本文内容来自网络,整理出来分享于大家~~

一、剖析 Vue.js 内部运行机制

参考至小册剖析 Vue.js 内部运行机制

先来一张总体图,然后我们对每一部分详细分析。

1、初始化与挂载

new Vue之后回调用一个_init方法去初始化,会初始化datapropsmethods声明周期watchcomputed事件等。其中最重要的一点就是通过Object.defineProperty来设置gettersetter,从而实现数据的【双向绑定响应式】【依赖收集】

初始化完之后会调用一个$mount来实现挂载。如果是运行时编译,则不存在render function ,存在template的情况需要重新编译。(我理解的意思:最开始我们需要去解析编译template中的内容,实现依赖收集和数据绑定,最后会生成一个render function.但是如果是运行时候比如响应数据的更改等,则不会在生成render function,而是通过diff算法直接操作虚拟DOM,实现正式结点的更新)。

2、响应式系统的实现原理

Vue是一款MVVM的框架,数据模型仅仅是普通的js对象,但是在操作这些对象的时候确可以及时的响应视图的变化。依赖的就是Vue的【响应式系统】。

面试题 —— 你了解Vue的MVVM吗?

MVVM包含三层:模型层Model,视图层View,控制层ViewModel.

联系:
  • 视图层变化可以被viewModel监听到,从而更改Model中的数据。是通过DOM事件监听实现。
  • Model层发生变化,可以被viewModel响应到view层,从而更新视图。是通过数据绑定
总之:DOM事件监听和数据绑定是MVVM的关键。DOM Listeners监听页面所有View层DOM元素的变化,当发生变化,Model层的数据随之变化;Data Bindings监听Model层的数据,当数据发生变化,View层的DOM元素随之变化。

(1)Object.defineProperty

首先我们来介绍一下 Object.defineProperty,Vue.js就是基于它实现「响应式系统」的。

Object.defineProperty(obj, prop, descriptor);

descriptor的一些属性,简单介绍几个属性:

  • enumerable,属性是否可枚举,默认 false。
  • configurable,属性是否可以被修改或者删除,默认 false。
  • get,获取属性的方法。
  • set,设置属性的方法。
  • writable,当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。
var o = {}; // 创建一个新对象// 【1】在对象中添加一个属性与数据描述符的示例Object.defineProperty(o, "a", {  value : 37,  writable : true,  enumerable : true,  configurable : true});// 对象o拥有了属性a,值为37// 【2】在对象中添加一个属性与存取描述符的示例var bValue;Object.defineProperty(o, "b", {  get : function(){    return bValue;  },  set : function(newValue){    bValue = newValue;  },  enumerable : true,  configurable : true});o.b = 38;// 对象o拥有了属性b,值为38// o.b的值现在总是与bValue相同,除非重新定义o.b

(2)实现数据的观察(observer)

这是响应式系统最为重要的一步。利用的便是我们上面提到的Object.defineProperty

实现一个简单的对数据的getter和setter监听:

// 遍历数据对象的每个属性,这里我们只做了一层,实际上会使用递归去处理深层次的数据// 这里为了我们的方便理解,就假设是单层对象function observer (value) {    if (!value || (typeof value !== 'object')) {        return;    }        Object.keys(value).forEach((key) => {        defineReactive(value, key, value[key]);    });}// 函数模拟视图更新function cb (val) {    console.log("视图更新啦~", val);}// 数据对象成员的响应式监听function defineReactive (obj, key, val) {    Object.defineProperty(obj, key, {        enumerable: true, // 可枚举        configurable: true, // 可配置        get: function reactiveGetter () {            return val;     // 当使用到我们的这个属性的时候会触发get方法,这里用来依赖收集,我们之后实现        },        set: function reactiveSetter (newVal) {            // 监听数据的修改,模拟视图更新,其实这里的过程相当的复杂,diff是一个必经过程            if (newVal === val) return;            val = newVal;            cb(newVal);        }    });}class Vue {    constructor(options) {        this._data = options.data; // 获取数据对象        observer(this._data); // 实现对数据中每个元素的观察,即为每个属性去设置get和set。    }  }// 测试案例let o = new Vue({    data: {        test: "I am test."    }});o._data.test = "hello,test.";

上面我们实现的是一个简单的响应式原理案例,我们只是实现了对数据对象的观察。当我们的数据使用和被修改的时候会调用我们的自定义get和set方法。下面我们去了解一下,数据【依赖收集】。

(3)依赖收集

为什么要进行依赖收集呢?

new Vue({    template:         `<div>            <span>{{text1}}</span>             <span>{{text2}}</span>         <div>`,    data: {        text1: 'text1',        text2: 'text2',        text3: 'text3'    }});

上面例子中,text1,text2使用了一次,text3未使用。

如果我们对某一个数据进行了修改,那么我们应该知道的哪些地方使用了该数据,为了我们视图的更新做好准备。

「依赖收集」会让 text1 这个数据知道“哦~有两个地方依赖我的数据,我变化的时候需要通知它们~”。

订阅者Dep

class Dep {    constructor () {        /* 用来存放Watcher对象的数组 */        this.subs = [];    }    /* 在subs中添加一个Watcher对象 */    addSub (sub) {        this.subs.push(sub);    }    /* 通知所有Watcher对象更新视图 */    notify () {        this.subs.forEach((sub) => {            sub.update();        })    }}

订阅者对象含有两个方法,addSub用来收集watcher对象,notify用来通知watcher对象去更新视图。

观察者Watcher

class Watcher {    constructor () {        /* 在new一个Watcher对象时将该对象赋值给Dep.target,在get中会用到 */        Dep.target = this;    }    /* 更新视图的方法 */    update () {        console.log("视图更新啦~");    }}Dep.target = null;

观察者对象在实例化的时候就需要绑定它所属的Dep。同时还有一个update方法去更新视图。

依赖收集原理

function defineReactive (obj, key, val) {    /* 一个Dep类对象 */    const dep = new Dep();        Object.defineProperty(obj, key, {        enumerable: true,        configurable: true,        get: function reactiveGetter () {            /* 将Dep.target(即当前的Watcher对象存入dep的subs中) */            dep.addSub(Dep.target);            return val;                 },        set: function reactiveSetter (newVal) {            if (newVal === val) return;            /* 在set的时候触发dep的notify来通知所有的Watcher对象更新视图 */            dep.notify();        }    });}class Vue {    constructor(options) {        this._data = options.data;        observer(this._data);        /* 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象*/        // 实例化一个观察者        new Watcher();        /* 在这里模拟render的过程,为了触发test属性的get函数 */        console.log('render~', this._data.test);        // 触发get之后,会将上面刚实例化的watcher对象,添加到Dep对象中。                // 注:这里只实例化了一个watcher,其实watcher对象没有我们上诉的那么简单,它记录的是当前引用的相关信息。为方便下次数据的更新时候,去更新视图    }}

当触发一个属性的get方法后,会执行我们的依赖收集。首先实例化一个watcher对象,这个watcher对象有这个属性的更新视图的方法。然后通过Dep的addSub方法将该watcher对象添加到Dep订阅者中。

【依赖收集】的关键条件:(1)触发get方法 (2)新建一个watcher对象
总结: 到了这里我们已经吧响应式系统学了,主要是get进行依赖收集,set中用过watcher观察者去更新视图。