vue源码学习(一)实例初始化

vue源码版本为2.6.11(cdn地址为: https://lib.baomitu.com/vue/2.6.11/vue.js

new Vue()

一般我们用vue都采用模板语法来声明:

<div id="app">{{message}}</div>

// js
var app = new Vue({
    el: '#app',
    data: {
        message: 'hello world!'
    }
});

 当new Vue()时,vue做了哪些处理? 

vue源码里有这样一段代码:

其中定义了Vue构造函数,然后依次调用initMixin、stateMixin、eventsMixin、lifecycleMixin、renderMixin方法,并将Vue构造函数作为参数。

注: this instanceof Vue 用于判断this是否是Vue对象构造函数的实例。

initMixin(Vue)

initMixin方法:实际上是在prototype上挂载_init方法

// 负责 Vue 的初始化过程
Vue.prototype._init = function (options) {
    // 缓存当前的上下文到vm变量中
    var vm = this;
    // 每个 vue 实例都有一个 _uid,并且是依次递增的
    // 当触发init方法,新建Vue实例时(当渲染组件时也会触发)uid都会递增。
    vm._uid = uid$3++;
    // 下面这段代码主要是用来测试代码性能的,在这个时候相当于打了一个"标记点"来测试性能。
    var startTag, endTag;
    /* istanbul ignore if */
    if (config.performance && mark) {
        startTag = "vue-perf-start:" + (vm._uid);
        endTag = "vue-perf-end:" + (vm._uid);
        mark(startTag);
    }

    // 加上标识防止this被observed
    vm._isVue = true;
    // 处理组件配置项
    if (options && options._isComponent) {
        /**
        * 每个子组件初始化时走这里,这里只做了一些性能优化
        * 将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码的执行效率
        */
        initInternalComponent(vm, options);
    } else {
        // 当前Vue实例不是组件,而是实例化Vue对象时,调用mergeOptions方法,合并options
        /**
       * 初始化根组件时走这里,合并 Vue 的全局配置到根组件的局部配置,比如 Vue.component 注册的全局组件会合并到 根实例的 components 选项中
       * 至于每个子组件的选项合并则发生在两个地方:
       *   1、Vue.component 方法注册的全局组件在注册时做了选项合并
       *   2、{ components: { xx } } 方式注册的局部组件在执行编译器生成的 render 函数时做了选项合并,包括根组件中的 components 配置
       */
        vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
        );
    }
    /* istanbul ignore else */
    {
        initProxy(vm);
    }
    // expose real self
    vm._self = vm;
    // 初始化组件实例关系属性,比如 $parent、$children、$root、$refs 等
    initLifecycle(vm);
    /**
     * 初始化自定义事件,这里需要注意一点,所以我们在 <comp @click="handleClick" /> 上注册的事件,监听者不是父组件,
     * 而是子组件本身,也就是说事件的派发和监听者都是子组件本身,和父组件无关
     */
    initEvents(vm);
    // 解析组件的插槽信息,得到 vm.$slot,处理渲染函数,得到 vm.$createElement 方法,即 h 函数
    initRender(vm);
    // 调用 beforeCreate 钩子函数
    callHook(vm, 'beforeCreate');
    // 初始化组件的 inject 配置项,得到 result[key] = val 形式的配置对象,然后对结果数据进行响应式处理,并代理每个 key 到 vm 实例
    initInjections(vm); // resolve injections before data/props
    // 数据响应式的重点,处理 props、methods、data、computed、watch
    initState(vm);
    // 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
    initProvide(vm); // resolve provide after data/props
    // 调用 created 钩子函数
    callHook(vm, 'created');

    /* istanbul ignore if */
    if (config.performance && mark) {
        vm._name = formatComponentName(vm, false);
        mark(endTag);
        measure(("vue " + (vm._name) + " init"), startTag, endTag);
    }
    // 如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了 el 选项,就不需要再手动调用 $mount,反之,没有 el 则必须手动调用 $mount
    if (vm.$options.el) {
        // 调用 $mount 方法,进入挂载阶段
        vm.$mount(vm.$options.el);
    }
};

// 从组件构造函数中解析配置对象 options,并合并基类选项
function resolveConstructorOptions(Ctor) {
    var options = Ctor.options;
    if (Ctor.super) {
        // 存在基类,递归解析基类构造函数的选项
        var superOptions = resolveConstructorOptions(Ctor.super);
        var cachedSuperOptions = Ctor.superOptions;
        if (superOptions !== cachedSuperOptions) {
            // 说明基类构造函数选项已经发生改变,需要重新设置
            Ctor.superOptions = superOptions;
            // 检查 Ctor.options 上是否有任何后期修改/附加的选项
            var modifiedOptions = resolveModifiedOptions(Ctor);
            // 如果存在被修改或增加的选项,则合并两个选项
            if (modifiedOptions) {
                extend(Ctor.extendOptions, modifiedOptions);
            }
            // 选项合并,将合并结果赋值为 Ctor.options
            options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
            if (options.name) {
                options.components[options.name] = Ctor;
            }
        }
    }
    return options
}

// 解析构造函数选项中后续被修改或者增加的选项
function resolveModifiedOptions(Ctor) {
    var modified;
    // 构造函数选项
    var latest = Ctor.options;
    // 密封的构造函数选项,备份
    var sealed = Ctor.sealedOptions;
    for (var key in latest) {
        // 对比两个选项,记录不一致的选项
        if (latest[key] !== sealed[key]) {
            if (!modified) { modified = {}; }
            modified[key] = latest[key];
        }
    }
    return modified
}

initSate() 初始化data数据绑定

_init()中通过initState()来绑定数据到vm上,

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

initData()

function initData (vm) {
    var data = vm.$options.data;
    // 先判断data是不是function类型,是则调用getData,返回data的自调用;    
    // 不是则直接返回data
    // 最后将data赋值到vm._data上
    data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {};
    
    var keys = Object.keys(data);
    var props = vm.$options.props;
    var methods = vm.$options.methods;
    var i = keys.length;
    // 对data、props、methods,一一做校验判断是否存在相同的key
    // 因为它们最终都会挂载在vm上,都是通过vm.key来调用
    while (i--) {
        var key = keys[i];
        {
          if (methods && hasOwn(methods, key)) {
            warn(
              ("Method \"" + key + "\" has already been defined as a data property."),
              vm
            );
          }
        }
        if (props && hasOwn(props, key)) {
          warn(
            "The data property \"" + key + "\" is already declared as a prop. " +
            "Use prop default value instead.",
            vm
          );
        } else if (!isReserved(key)) {
          // 把每个key都挂载在vm上
          proxy(vm, "_data", key);
        }
    }
    // 对数据作响应式处理
    observe(data, true /* asRootData */);
}

// proxy函数
var sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
};
function proxy (target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
    };
    sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val;
    };
    Object.defineProperty(target, key, sharedPropertyDefinition);
}

proxy()本质上还是通过Object.defineProperty定义/修改属性(不了解用法的可以参考这一篇),将对target的key访问加了一层get/set,即当访问vm.key时,实际上是调用了sharedPropertyDefinition.get,返回this._data.key,这样就实现了通过vm.key来调用vm._data上的属性。

如果传入值的_isVue为ture时(即传入的值是Vue实例本身)不会新建observer实例(这里可以暂时理解新建observer实例就是让数据响应式)。 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值