博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.2-Vue源码起步(2)
阅读量:4363 次
发布时间:2019-06-07

本文共 10635 字,大约阅读时间需要 35 分钟。

接着第一节开始继续吧(GoGoGo)

 

 

  上一节把mergeOptions函数弄完了,最后返回一个options赋给了vm.$options。

  这一节继续跑代码:

function initMixin(Vue) {        Vue.prototype._init = function(options) {            //..上一节            vm.$options = mergeOptions(                resolveConstructorOptions(vm.constructor),                options || {},                vm            );            // 从这里开始跑            /* istanbul ignore else */            {                initProxy(vm);            }            // 疯狂保存自身引用            vm._self = vm;            // 初始化跑起来            initLifecycle(vm);            initEvents(vm);            initRender(vm);            callHook(vm, 'beforeCreate');            initInjections(vm);            initState(vm);            initProvide(vm);            callHook(vm, 'created');            /* istanbul ignore if */            if ("development" !== 'production' && config.performance && mark) {                vm._name = formatComponentName(vm, false);                mark(endTag);                measure(((vm._name) + " init"), startTag, endTag);            }            // 判断是否有el属性 进行挂载            if (vm.$options.el) {                vm.$mount(vm.$options.el);            }        };    }

  剩余的代码主要有3件事:initProxy()、各种初始化函数、挂载vue。

  

  在讲initProxy()之前,有必要先讲讲这个奇怪的注释:/* istanbul ignore else */。

  这个注释代码段出现的频率和"development" !== 'production'不相上下,一开始我是无视的,但是慢慢的觉得很奇怪,这个注释可能并不简单。

  首先百度了一下istanbul,是个国家,天气不错,各种旅游攻略。发现不对劲,果断在前面加个js关键字,发现了这其实是一个代码覆盖率工具,即是否所有代码都测试到了。

  这个工具我就不介绍了,总之这行注释的意思就是,下一个代码段中的else语句不计入代码覆盖率计算。

  

initProxy()

  开始跑initProxy()函数,只接受一个参数,即当前vue实例。

// Line-1620    initProxy = function initProxy(vm) {        // 是否支持es6的代理        if (hasProxy) {            // 根据参数判断调用哪一个代理            var options = vm.$options;            var handlers = options.render && options.render._withStripped ?                getHandler :                hasHandler;            vm._renderProxy = new Proxy(vm, handlers);        }        // 不支持        else {            vm._renderProxy = vm;        }    };

  首先会判断是否支持Proxy,这是ES6新出的语法代理,详情可见阮一峰的书:http://es6.ruanyifeng.com/#docs/proxy。

  若支持,再进行传进来的vm进行参数判断,选择代理。

  当前vm只有三个参数(其实不止,先不管了):$options、_isVue、_uid,所以很明显,调用的是hasHandelr这个代理。

// Line-1562    var initProxy; {        //..一些字符匹配集        if (hasProxy) {            var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta');            config.keyCodes = new Proxy(config.keyCodes, {                set: function set(target, key, value) {                    // ...定义事件别名                }            });        }        var hasHandler = {            has: function has(target, key) {                var has = key in target;                // 判断key是否跟内置全局变量冲突                var isAllowed = allowedGlobals(key) || key.charAt(0) === '_';                // 不存在或者非法                if (!has && !isAllowed) {                    warnNonPresent(target, key);                }                return has || !isAllowed            }        };        var getHandler = {            //..不管        };        initProxy = function initProxy(vm) {            // 是否支持es6的Proxy代理            if (hasProxy) {                // 根据参数判断调用哪一个代理函数                var options = vm.$options;                var handlers = options.render && options.render._withStripped ?                    getHandler :                    hasHandler;                vm._renderProxy = new Proxy(vm, handlers);            }            // 不支持            else {                vm._renderProxy = vm;            }        };    }

  Proxy这个说实话我看不太懂,属于那种看看就好的语法,可能境界没达到吧,但不怎么影响我理解这段代码,有错欢迎指出来。

  initProxy(vm)这个函数主要就做了一个方法劫持,相当于ng里面的拦截器,在vm._renderProxy中,每一个键都会进行合法性检测,如果与内置全局对象冲突,就会调用warnNonPresent()报警。

  

  如果不支持,就算了。

 

初始化家族

  这一部分包含8个初始化,个个都是怪物,然而由于案例比较简单,大部门都会直接跳过。

  1、initLifecycle

// Line-2259    function initLifecycle(vm) {        var options = vm.$options;        // 没有parent 跳        var parent = options.parent;        if (parent && !options.abstract) {            while (parent.$options.abstract && parent.$parent) {                parent = parent.$parent;            }            parent.$children.push(vm);        }        vm.$parent = parent;        vm.$root = parent ? parent.$root : vm;        vm.$children = [];        vm.$refs = {};        vm._watcher = null;        vm._inactive = null;        vm._directInactive = false;        vm._isMounted = false;        vm._isDestroyed = false;        vm._isBeingDestroyed = false;    }

  这个函数主要对vm.$options.parent属性进行处理,很遗憾,我没有。

  目前,vm的属性是5个,多了_self、_renderProxy,跑完这个函数,又多了一串,暂时不管。

  

  2、initEvents

// Line-2074    function initEvents(vm) {        vm._events = Object.create(null);        vm._hasHookEvent = false;        // 又搞parent 没有        var listeners = vm.$options._parentListeners;        if (listeners) {            updateComponentListeners(vm, listeners);        }    }

  多了2个事件相关属性。

 

  3、initRender

// Line-3824    function initRender(vm) {        vm._vnode = null; // the root of the child tree        vm._staticTrees = null;        var parentVnode = vm.$vnode = vm.$options._parentVnode; // the placeholder node in parent tree        var renderContext = parentVnode && parentVnode.context;        // 由于传了2个undefined 返回一个空对象        vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext);        vm.$scopedSlots = emptyObject;        // 2个属性方法 之后再看        vm._c = function(a, b, c, d) {            return createElement(vm, a, b, c, d, false);        };        vm.$createElement = function(a, b, c, d) {            return createElement(vm, a, b, c, d, true);        };    }

 

  4、callHook

// Line-2532    function callHook(vm, hook) {        // hook => beforeCreate        // 没有这个钩子函数        var handlers = vm.$options[hook];        if (handlers) {            for (var i = 0, j = handlers.length; i < j; i++) {                try {                    handlers[i].call(vm);                } catch (e) {                    handleError(e, vm, (hook + " hook"));                }            }        }        // 这个也没有        if (vm._hasHookEvent) {            vm.$emit('hook:' + hook);        }    }

  钩子函数都没有,这个初始化跳过。

 

  5、initInjections

// Line-3218    function initInjections(vm) {        // 由于没有inject属性 跳过        var result = resolveInject(vm.$options.inject, vm);        if (result) {            Object.keys(result).forEach(function(key) {                /* istanbul ignore else */                {                    defineReactive$$1(vm, key, result[key], function() {                        warn(                            "Avoid mutating an injected value directly since the changes will be " +                            "overwritten whenever the provided component re-renders. " +                            "injection being mutated: \"" + key + "\"",                            vm                        );                    });                }            });        }    }

  

  6、initState

// Line-2947    function initState(vm) {        vm._watchers = [];        var opts = vm.$options;        // 针对参数做初始化        if (opts.props) {            initProps(vm, opts.props);        }        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);        }        if (opts.watch) {            initWatch(vm, opts.watch);        }    }

  这个地方开始对传进来的参数做初始化挂载,由于只传了el和data,而el参数在最后面才处理,所以目前只会执行initData函数。

  来看看initData函数运作,这可能是最关键的部分了。

// Line-3011    function initData(vm) {        // 取出data参数 这里的data不是传进来的对data象 而是一个函数        var data = vm.$options.data;        // 判断类型 格式化data        data = vm._data = typeof data === 'function' ?            getData(data, vm) :            data || {};        // data属性必须返回对象        if (!isPlainObject(data)) {            data = {};            "development" !== 'production' && warn(                'data functions should return an object:\n' +                'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',                vm            );        }        // 代理data属性        var keys = Object.keys(data);        var props = vm.$options.props;        var i = keys.length;        while (i--) {            if (props && hasOwn(props, keys[i])) {                "development" !== 'production' && warn(                    "The data property \"" + (keys[i]) + "\" is already declared as a prop. " +                    "Use prop default value instead.",                    vm                );            }            // 禁止以$,_开头            else if (!isReserved(keys[i])) {                proxy(vm, "_data", keys[i]);            }        }        // 双绑入口        observe(data, true /* asRootData */ );    }

  整理一下,data属性初始化主要有格式化、代理、双绑三个部分。

 

  首先来看看格式化,在第一节中的mergeOptions函数最后面,options中的data属性变成了一个叫mergedInstanceDataFn的函数,因此三元表示式返回的是getData(data, vm)。

  getData函数比较简单, 所以把两个弄一起得了。

// Line-3043    function getData(data, vm) {        try {            // 尝试执行data函数            return data.call(vm)        } catch (e) {            handleError(e, vm, "data()");            return {}        }    }    // Line-1132    // 传进来的parentVal、childVal分别为undefined、{message:'Hello Vue!}     function mergedInstanceDataFn() {        // 这个是对象        var instanceData = typeof childVal === 'function' ?            childVal.call(vm) :            childVal;        // 这个是undefined        var defaultData = typeof parentVal === 'function' ?            parentVal.call(vm) :            undefined;        // 合并数据 由于第二个参数为undefined 直接返回原data对象        if (instanceData) {            return mergeData(instanceData, defaultData)        } else {            return defaultData        }    }

 

  在getData函数中,会尝试以vm为上下文执行data函数,根据参数执行mergedInstanceDataFn函数后,最后返回的仍然是传进来的data对象。

  

  再来看代理部分,首先会遍历data对象的键,判断是否不以$,_开头,然后进行代理操作。尝试了一下,如果使用$message,_message会报错,后面再看为什么。

  这部分的关键在于proxy这个函数,ES6自带的是Proxy,不是一个东西,是一个正常的函数。

// Line-2930    var sharedPropertyDefinition = {        enumerable: true,        configurable: true,        get: noop,        set: noop    };    // Line-2937    // target => vm | sourceKey => _data | key => 'message'    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);    }

  简单来讲,这个方法就是给vm添加了一个_data属性,值是data。

  因为只传了一个message,所以 _data属性是这样的:

  

  第三部分属于比较核心的部分,即数据监测,双绑的一部分,下次写吧!

 

  用一张图片梳理一下第二节吧。

 

转载于:https://www.cnblogs.com/QH-Jimmy/p/6868568.html

你可能感兴趣的文章
电子书搜索
查看>>
SQO2008配置管理工具服务显示远程过程调用失败
查看>>
【HDOJ】1009 FatMouse' Trade
查看>>
谷歌跨域
查看>>
使用葡萄城报表,轻松实现高度精准的报表套打
查看>>
Linux命令
查看>>
unicode ascii 互转 函数 C实现 MultiByteToWideChar/WideCharToMultiByte 详解
查看>>
大三第一学期实验报告
查看>>
mysql远程链接
查看>>
nginx location配置
查看>>
Easy Install详细参数
查看>>
选课系统
查看>>
最简实例演示asp.net5中用户认证和授权(2)
查看>>
ubuntu rhythmbox乱码解决方法
查看>>
LeetCode题解之Univalued Binary Tree
查看>>
线程池学习研究-(自实现)2
查看>>
ubuntu下安装新字体
查看>>
Django连接MySQL数据库
查看>>
漫游Kafka入门篇之简单介绍(1)
查看>>
redis学习之旅-初识Redis
查看>>