vue数据初始化--initState

数据初始化

Vue 实例在创建的时候会运行一系列的初始化操做,而在这些初始化操做里面,和数据绑定关联最大的是 initState。vue

首先,来看一下他的代码:segmentfault

function initState(vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if(opts.props) {
        initProps(vm, opts.props); //初始化props
    }
    if(opts.methods) {
        initMethods(vm, opts.methods); //初始化methods
    }
    if(opts.data) {
        initData(vm); //初始化data
    } else {
        observe(vm._data = {}, true /* asRootData */ );
    }
    if(opts.computed) {
        initComputed(vm, opts.computed); //初始化computed
    }
    if(opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch); //初始化watch
    }
}

在这么多的数据的初始化中,props、methods和data是比较简单的(因此我就不详细介绍了☺),而computed 和 watch则相对较难,逻辑较复杂,因此我下面主要讲下computed 和 watch(如下代码部分为简化后的)。数组

initState里面主要是对vue实例中的 props, methods, data, computed 和 watch 数据进行初始化。缓存

在初始化props的时候(initProps),会遍历props中的每一个属性,而后进行类型验证,数据监测等(提供为props属性赋值就抛出警告的钩子函数)。异步

在初始化methods的时候(initMethods),主要是监测methods中的方法名是否合法。函数

在初始化data的时候(initData),会运行 observe 函数深度遍历数据中的每个属性,进行数据劫持。oop

在初始化computed的时候(initComputed),会监测数据是否已经存在data或props上,若是存在则抛出警告,不然调用defineComputed函数,监听数据,为组件中的属性绑定getter及setter。若是computed中属性的值是一个函数,则默认为属性的getter函数。此外属性的值还能够是一个对象,他只有三个有效字段set、get和cache,分别表示属性的setter、getter和是否启用缓存,其中get是必须的,cache默认为true。this

function initComputed(vm, computed) {
    var watchers = vm._computedWatchers = Object.create(null);

    for(var key in computed) {
        var userDef = computed[key];
        var getter = typeof userDef === 'function' ? userDef : userDef.get;

        //建立一个计算属性 watcher
        watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            computedWatcherOptions
        );

        if(!(key in vm)) {
            //若是定义的计算属性不在组件实例上,对属性进行数据劫持
            //defineComputed 很重要,下面咱们再说
            defineComputed(vm, key, userDef);
        } else {
            //若是定义的计算属性在data和props有,抛出警告
        }
    }
}

在初始化watch的时候(initWatch),会调用vm.$watch函数为watch中的属性绑定setter回调(若是组件中没有该属性则不能成功监听,属性必须存在于props、data或computed中)。若是watch中属性的值是一个函数,则默认为属性的setter回调函数,若是属性的值是一个数组,则遍历数组中的内容,分别为属性绑定回调,此外属性的值还能够是一个对象,此时,对象中的handler字段表明setter回调函数,immediate表明是否当即先去执行里面的handler方法,deep表明是否深度监听。lua

vm.$watch函数会直接使用Watcher构建观察者对象。watch中属性的值做为watcher.cb存在,在观察者update的时候,在watcher.run函数中执行。想了解这一过程能够看我上一篇的 vue响应式系统--observe、watcher、dep中关于Watcher的介绍。prototype

function initWatch(vm, watch) {
    //遍历watch,为每个属性建立侦听器
    for(var key in watch) {
        var handler = watch[key];
        //若是属性值是一个数组,则遍历数组,为属性建立多个侦听器
        //createWatcher函数中封装了vm.$watch,会在vm.$watch中建立侦听器
        if(Array.isArray(handler)) {
            for(var i = 0; i < handler.length; i++) {
                createWatcher(vm, key, handler[i]);
            }
        } else {
            //为属性建立侦听器
            createWatcher(vm, key, handler);
        }
    }
}

function createWatcher(vm, expOrFn, handler, options) {
    //若是属性值是一个对象,则取对象的handler属性做为回调
    if(isPlainObject(handler)) {
        options = handler;
        handler = handler.handler;
    }
    //若是属性值是一个字符串,则从组件实例上寻找
    if(typeof handler === 'string') {
        handler = vm[handler];
    }
    //为属性建立侦听器
    return vm.$watch(expOrFn, handler, options)
}

computed

computed本质是一个惰性求值的观察者,具备缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值

下面将围绕这一句话来作解释。

上面代码中提到过,当计算属性中的数据存在与data和props中时,会被警告,也就是这种作法是错误的。因此通常的,咱们都会直接在计算属性中声明数据。仍是那个代码片断中,若是定义的计算属性不在组件实例上,会运行defineComputed函数对数据进行数据劫持。下面咱们来看下defineComputed函数中作了什么。

function defineComputed(target, key, userDef) {
    //是否是服务端渲染
    var shouldCache = !isServerRendering();
    //若是咱们把计算属性的值写成一个函数,这时函数默认为计算属性的get
    if(typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache ?
            //若是不是服务端渲染,则默认使用缓存,设置get为createComputedGetter建立的缓存函数
            createComputedGetter(key) :
            //不然不使用缓存,直接设置get为userDef这个咱们定义的函数
            userDef;
        //设置set为空函数
        sharedPropertyDefinition.set = noop;
    } else {
        //若是咱们把计算属性的值写成一个对象,对象中可能包含set、get和cache三个字段
        sharedPropertyDefinition.get = userDef.get ?
            shouldCache && userDef.cache !== false ?
            //若是咱们传入了get字段,且不是服务端渲染,且cache不为false,设置get为createComputedGetter建立的缓存函数
            createComputedGetter(key) : 
            //若是咱们传入了get字段,可是是服务端渲染或者cache设为了false,设置get为userDef这个咱们定义的函数
            userDef.get :
            //若是没有传入get字段,设置get为空函数
            noop;
        //设置set为咱们传入的传入set字段或空函数
        sharedPropertyDefinition.set = userDef.set ?
            userDef.set :
            noop;
    }
    //虽然这里能够get、set均可以设置为空函数
    //可是在项目中,get为空函数对数据取值会报错,set为空函数对数据赋值会报错
    //而computed主要做用就是计算取值的,因此get字段是必须的
    
    //数据劫持
    Object.defineProperty(target, key, sharedPropertyDefinition);
}

在上一篇的 vue响应式系统--observe、watcher、dep 中,我有关于Watcher的介绍中提到,计算属性 watcher实例化的时候,会把options.lazy设置为true,这里是计算属性惰性求值,且可缓存的关键,固然前提是cache不为false。

cache不为false,会调用createComputedGetter函数建立计算属性的getter函数computedGetter,

先来看一段代码

function createComputedGetter(key) {
    return function computedGetter() {
        var watcher = this._computedWatchers && this._computedWatchers[key];
        if(watcher) {
            if(watcher.dirty) {
                //watcher.evaluate中更新watcher的值,并把watcher.dirty设置为false
                //这样等下次依赖更新的时候才会把watcher.dirty设置为true,而后进行取值的时候才会再次运行这个函数
                watcher.evaluate();
            }
            //依赖追踪
            if(Dep.target) {
                watcher.depend();
            }
            //返回watcher的值
            return watcher.value
        }
    }
}

//对于计算属性,当取值计算属性时,发现计算属性的watcher的dirty是true
//说明数据不是最新的了,须要从新计算,这里就是从新计算计算属性的值。
Watcher.prototype.evaluate = function evaluate() {
    this.value = this.get();
    this.dirty = false;
};

//当一个依赖改变的时候,通知它update
Watcher.prototype.update = function update() {
    //三种watcher,只有计算属性 watcher的lazy设置了true,表示启用惰性求值
    if(this.lazy) {
        this.dirty = true;
    } else if(this.sync) {
        //标记为同步计算的直接运行run,三大类型暂无,因此基本会走下面的queueWatcher
        this.run();
    } else {
        //将watcher推入观察者队列中,下一个tick时调用。
        //也就是数据变化不是当即就去更新的,而是异步批量去更新的
        queueWatcher(this);
    }
};

当options.lazy设置为true以后(仅计算属性watcher的options.lazy设置为true),每次依赖更新,都不会主动触发run函数,而是把watcher.dirty设置为true。这样,当对计算属性进行取值时,就会运行computedGetter函数,computedGetter函数中有一个关于watcher.dirty的判断,当watcher.dirty为true时会运行watcher.evaluate进行值的更新,并把watcher.dirty设置为false,这样就完成了惰性求值的过程。后面只要依赖不更新,就不会运行update,就不会把watcher.dirty为true,那么再次取值的时候就不会运行watcher.evaluate进行值的更新,从而达到了缓存的效果。

综上,咱们了解到cache不为false的时候,计算属性都是惰性求值且具备缓存性的,而cache默认是true,咱们也大多使用这个默认值,因此咱们说 computed本质是一个惰性求值的观察者,具备缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值

相关文章
相关标签/搜索