接着第一节开始继续吧(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属性是这样的:

  

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

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

.2-Vue源码起步(2)的更多相关文章

  1. .1-Vue源码起步

    搞事!搞事! 截止2017.5.16,终于把vue的源码全部抄完,总共有9624行,花时大概一个月时间,中间迭代了一个版本(2.2-2.3),部分代码可能不一致,不过没关系! 上一个链接https:/ ...

  2. 【一套代码小程序&Native&Web阶段总结篇】可以这样阅读Vue源码

    前言 前面我们对微信小程序进行了研究:[微信小程序项目实践总结]30分钟从陌生到熟悉 在实际代码过程中我们发现,我们可能又要做H5站又要做小程序同时还要做个APP,这里会造成很大的资源浪费,如果设定一 ...

  3. VUE 源码学习01 源码入口

    VUE[version:2.4.1] Vue项目做了不少,最近在学习设计模式与Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后 ...

  4. Vue源码后记-其余内置指令(3)

    其实吧,写这些后记我才真正了解到vue源码的精髓,之前的跑源码跟闹着玩一样. go! 之前将AST转换成了render函数,跳出来后,由于仍是字符串,所以调用了makeFunction将其转换成了真正 ...

  5. Vue源码后记-钩子函数

    vue源码的马拉松跑完了,可以放松一下写点小东西,其实源码讲20节都讲不完,跳了好多地方. 本人技术有限,无法跟大神一样,模拟vue手把手搭建一个MVVM框架,然后再分析原理,只能以门外汉的姿态简单过 ...

  6. 大白话Vue源码系列(01):万事开头难

    阅读目录 Vue 的源码目录结构 预备知识 先捡软的捏 Angular 是 Google 亲儿子,React 是 Facebook 小正太,那咱为啥偏偏选择了 Vue 下手,一句话,Vue 是咱见过的 ...

  7. 大白话Vue源码系列(02):编译器初探

    阅读目录 编译器代码藏在哪 Vue.prototype.$mount 构建 AST 的一般过程 Vue 构建的 AST 题接上文,上回书说到,Vue 的编译器模块相对独立且简单,那咱们就从这块入手,先 ...

  8. 大白话Vue源码系列(03):生成AST

    阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 AST ...

  9. 大白话Vue源码系列(03):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

  10. 大白话Vue源码系列(04):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

随机推荐

  1. Java简单实用方法一

    整理以前的笔记,在学习Java时候,经常会用到一些方法.虽然简单但是经常使用.因此做成笔记,方便以后查阅 这篇博文先说明构造和使用这些方法. 1,判断String类型数据是否为空 String类型的数 ...

  2. Piggy Back_KEY

    Piggy Back (piggyback.pas/c/cpp) [问题描述] Bessie 和她的姐姐 Elsie 在不同的田块吃草,晚上她们都返回牛棚休息.作为聪明的奶牛,她们想设计一个方案使得步 ...

  3. BZOJ-1012-[JSOI2008]最大数maxnumber(线段树)

    Description 现在请求你维护一个数列,要求提供以下两种操作:1. 查询操作.语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值.限制:L不超过当前数列的长度.2. 插 ...

  4. 编译httpd细节

    html { font-family: sans-serif } body { margin: 0 } article,aside,details,figcaption,figure,footer,h ...

  5. SVG轨迹回放实践

    最近做了埋点方案XTracker的轨迹回放功能,大致效果就是,在指定几个顺序的点之间形成轨迹,来模拟用户在页面上的先后行为(比如一个用户先点了啥,后点了啥).效果图如下: 在这篇文章中,我们来聊聊轨迹 ...

  6. JS -- Variables As Properties

    Variables As Properties When you declare a global JavaScript variable, what you are actually doing i ...

  7. Spring常用注解介绍【经典总结】

    Spring的一个核心功能是IOC,就是将Bean初始化加载到容器中,Bean是如何加载到容器的,可以使用Spring注解方式或者Spring XML配置方式. Spring注解方式减少了配置文件内容 ...

  8. ElasticSearch入门(1) —— 集群搭建

    一.环境介绍与安装准备 1.环境说明 2台虚拟机,OS为ubuntu13.04,ip分别为xxx.xxx.xxx.140和xxx.xxx.xxx.145. 2.安装准备 ElasticSearch(简 ...

  9. JSP入门3 Servlet

    需要继承类HttpServlet 服务器在获得请求的时候会先根据jsp页面生成一个java文件,然后使用jdk的编译器将此文件编译,最后运行得到的class文件处理用户的请求返回响应.如果再有请求访问 ...

  10. Windows开启telnet服务 + 连接失败处理

    一.控制面板中安装Telnet相关组件 单击"开始"菜单,单击"控制面板"     在控制面板中单击打开"程序和功能"项目   在左侧的蓝色 ...