1.首先根据正则获取组件中所有的{{}},将{{}}里的值取出在data中取出对应的值,以文本节点的方式放上去。
2.vue有内置的编译器,会将组件转译成AST
3.AST会根据generate得到render函数
4.然后根据 render function 方法最终返回string
5.最后通过vue的_update方法转化成对应的DOM
这是网上看到的,结合自己的理解

1.在dom中遍历所有的包含{{}}的元素

2.遍历这些元素,用正则表达式替换{{}}里面的属性,以文本节点的方式替换原来的属性。

3.vue的生命周期update()钩子函数更新dom的渲染效果。

双向绑定原理

1.将data中的数据指向Vue对象的实例(数据代理)。  有没有想过为什么vue中 this.可以访问data中的变量?

通过this能直接获取到data,是因为data里的属性最终被存储到new Vue的实例(vm)上的_data对象中,我们访问this.xxx,实际上是访问通过Object.defineProperty代理后的this._data.xxx。

简易实现:

function Person(options) {
    let vm = this;
    vm.$options = options;
    if (options.data) {
        initData(vm);
    }
    if (options.methods) {
        initMethods(vm, options.methods);
    }
}
 
function initData(vm) {
    let data = vm._data = vm.$options.data;
    let keys = Object.keys(data);
    let len = keys.length;
    while(len--) {
        let key = keys[len];
        proxy(vm, "_data", key);
    }
}
 
function proxy(vm, sourceKeys, key) {
    Object.defineProperties(vm, sourceKeys, {
        enumerable: true,
        configurable: true,
        get: function() {
            return vm[sourceKeys][key];
        },
        set: function(val) {
            return vm[sourceKeys][key] = val;
        }
    })
}
 
function initMethods(vm, methods) {
    for (let key in methods) {
        vm[key] = typeof methods[key] === "function" ? methods[key].bind(vm) : noop;
    }
}
 
function noop(a, b, c) { }
 
let p1 = new Person({
    data: {
        name: "123",
        age: 28
    },
    methods: {
        sayName() {
            console.log("I am" + this.name);
        }
    }
})
 
console.log(p1.name); // 123
p1.sayName(); // 'I am 123'

观察者模式(数据劫持)

Vue实现观察者模式主要通过三个类:Observer类、Watcher类、Dep类

  • Observer:通过defineProperty给数据添加getset方法并在get中收集依赖,在set中通知更新
  • Watcher:观察数据变化(接收Depnotify发出的通知),执行回调
  • Dep:一个可观察对象,收集订阅(在Observerget时收集)、发送通知(在Observerset时发布)
var app = new Vue({
    el: '#J_app',
    data: {
        message: 'hello world'
    },
    mounted: function(){
        console.log(this.message)
    }
})

app.message = 'hellp vue'

console.log(`'app.message:' ${app.message}`)

我们给data添加了message属性,然后,在数据初始化过程中:Observer通过definePropertymessage设置get/set属性,并在get中调用Depdepend方法,将message添加为可观察对象,之后在Watcher中,对message进行求值,调用其get方法,同时将Watcher实例添加到可观察对象message的观察者数组里;同时,在set时,通过Depnotify发布message更新的事件,通知调用观察者数组中的update方法。

在Vue中,上面流程中Observer负责的部分是通过defineReactive$$1实现的,可以简写如下:

/*
*参数含义:
*obj: 需要添加属性的对象
*key: 要添加的属性名
*value: 要添加的属性名对应的值
*customSetter: 在非生产环境,一些自定义的错误警告
*shallow: 属性值是否添加到代理到vm上
*/
function defineReactive(obj, key, value, customSetter, shallow) {

    var property = Object.getOwnPropertyDescriptor(obj, key);
    if (property && property.configurable === false) {
        return
    }

    var getter = property && property.get;
    var setter = property && property.set;

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            var value = getter ? getter.call(obj) : val;

            //收集依赖  交由Dep类负责

            return value
        },
        set: function () {
            var value = getter ? getter.call(obj) : val;

            if (newVal === value || (newVal !== newVal && value !== value)) {
                return
            }

            //开发环境属性检查并警告
            if ("development" !== 'production' && customSetter) {
                customSetter();
            }

            if (setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }

            observe(newVal);

            //发布更新消息
            dep.notify();
        }
    })

}

这里的代码相对好理解,get方法里的收集依赖其实也是一个曲折的过程。在watcher中,每添加一个新的watcher实例时,都会对相应的对象进行求值,也就是会主动触发一次defineReactiveget方法,然后就在get方法里进行依赖收集。同时,数据有变更时,也会触发defineReactiveset方法,然后在set方法里发布数据更新的消息dep.notify(),然后dep.notify里会对调相应的watcher进行update

Dep类负责依赖收集和观察者存储,来看下Dep类的代码:

/**
 * 每个Dep的实例都是一个可观察对象,可以被多个观察者订阅
*/

var uid = 0;

var Dep = function Dep () {
  this.id = uid++;
  this.subs = [];
};

Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};

Dep.prototype.removeSub = function removeSub (sub) {
  remove(this.subs, sub);
};

Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};

Dep.prototype.notify = function notify () {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null;
var targetStack = [];

function pushTarget (_target) {
  if (Dep.target) { targetStack.push(Dep.target); }
  Dep.target = _target;
}

function popTarget () {
  Dep.target = targetStack.pop();
}

初始每个可观察对象dep都有一个id和观察者数组(subs),可以对当前id的可观察对象:添加观察者(addSub)、移除观察者(removeSub)、设置为可观察对象(depend)、通知变更到观察者们(notify),添加和移除观察者相对简单,往数组pushsplice即可;设置为可观察对象调用的是Dep.target.addDep(this)Dep.target其实是Watcher的实例,下节再看;通知变更到观察者们只是执行了是观察者们的update方法。

总结一下:

  1. new Vue,执行初始化
  2. 挂载$mount方法,通过自定义Render方法、template、el等生成Render函数
  3. 通过Watcher监听数据的变化
  4. 当数据发生变化时,Render函数执行生成VNode对象
  5. 通过patch方法,对比新旧VNode对象,通过DOM Diff算法,添加、修改、删除真正的DOM元素

Logo

前往低代码交流专区

更多推荐