搞事!搞事!

  截止2017.5.16,终于把vue的源码全部抄完,总共有9624行,花时大概一个月时间,中间迭代了一个版本(2.2-2.3),部分代码可能不一致,不过没关系!
  上一个链接https://github.com/pflhm2005/vue
  进入阶段2:尝试一下,从小案例看一看代码在vue源码中的走向,Go!(语文不好,将就看看)

  从最简单的案例开始,摘抄官网的起步:

    <body>
<div id='app'>
{{message}}
</div>
</body>
<script src='./vue.js'></script>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
</script>

  打断点,开始执行!

初始化函数

  html代码中,包含2大部分,挂载DOM节点,以及初始化vue的js代码。

  

  有几个小地方,虽然按照官网的案例不会出现问题,但是还是说明一下:

  (1)、el不能挂载到html或者body标签上

    // Line-9547
if (el === document.body || el === document.documentElement) {
"development" !== 'production' && warn(
"Do not mount Vue to <html> or <body> - mount to normal elements instead."
);
return this
}

  (2)、关于代码各处可见的"development" !== 'production'     

  这个是dev模式才有,vue.js文件中对所有警告的代码判断条件进行了替换,报错方便调试,在发布模式中会自动清除,方便开发。  

  与jQuery不一样,这里需要手动new才会创建一个vue实例。

  直接上源码。

  jQuery:

    // Line-94 jQuery 3.2.1
// 顺便吐槽一下 这个版本终于把初始化提前了 代码结构应该棒棒的
var jQuery = function(selector, context) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init(selector, context);
}

  Vue:

    // Line-9622
return Vue$3;

  

  但是我们看到源码最后其实返回的是Vue$3,至于为什么new的是Vue也行呢?看一下源码开头的整个IIFE表达式也就明白了。

    (function(global, factory) {
// 兼容各种宿主环境
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
// 浏览器环境
(global.Vue = factory());
}(this, /*vue*/ ));

  基本上框架都是这个套路,引入一个宿主环境的对象以及框架本身。

  上述代码形参中,global在浏览器环境中相当于window,由于有时会在node、webpack等环境中运行,所以需要进行兼容处理,于是有很长的typeof。

  对于浏览器来讲,上述代码其实就是window.Vue = Vue$3({options}),所以这就很明了了。

  起步流程两个框架都是一样的,首先通过一个init函数进行全局初始化。

    // Line-4055
function Vue$3(options) {
if ("development" !== 'production' &&
!(this instanceof Vue$3)) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}

  这里的options参数,很明显就是我们在new对象的时候传进去的对象,目前只有el和data两个。

  入口函数只是简单的判断了一下有没有new,然后自动调用了原型函数_init。

  _init函数的定义地点有点意思,是在一个函数内部定义,然后在后面调用了这个函数。

    // Line-3924
function initMixin(Vue) {
Vue.prototype._init = function(options) {
//....
};
}
// Line-4063
initMixin(Vue$3);

  整个函数只定义了_init这个初始化原型函数,原因在某个注释中写,直接定义原型会出现问题,所以采用这种方法进行规避。

  至于具体什么问题,我找不到那行注释了。。。

  接下来看看初始化函数里面都干了啥事。

    // Line-3926
// 生成的实例保存为别名vm
var vm = this;
// 全局记数 表示有几个vue实例
vm._uid = uid$1++;
var startTag, endTag;
// 这里的config.performance开发版默认是false
if ("development" !== 'production' && config.performance && mark) {
startTag = "vue-perf-init:" + (vm._uid);
endTag = "vue-perf-end:" + (vm._uid);
mark(startTag);
}
// 代表这是一个vue实例
vm._isVue = true;
// 非组件 跳过
if (options && options._isComponent) {
initInternalComponent(vm, options);
}
// 正常实例初始化
// 在这里对参数进行二次加工
else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
// ...more

  前面基本上没做什么事,对于config对象,在开发版中的默认参数如下:

    // 开发版的默认配置
var config = ({
optionMergeStrategies: Object.create(null),
silent: false,
productionTip: "development" !== 'production',
devtools: "development" !== 'production',
performance: false,
errorHandler: null,
ignoredElements: [],
keyCodes: Object.create(null),
isReservedTag: no,
isReservedAttr: no,
isUnknownElement: no,
getTagNamespace: noop,
parsePlatformTagName: identity,
mustUseProp: no,
// 历史遗留
_lifecycleHooks: LIFECYCLE_HOOKS
});

  由于提示信息不是重点,所以第一步直接可以走到mergeOptions这里,从名字就可以看出这是一个参数合并的函数,接受3个参数:

  1、resolveConstructorOptions(vm.constructor)

  这个函数属于内部初始化,接受的参数就是Vue函数自身,如下:

    // Line-4136
Sub.prototype.constructor = Sub;

  跳进去看一眼这个函数做了什么:

    // Line-3998
function resolveConstructorOptions(Ctor) {
// Ctor=Constructor
// options为所有vue实例基础参数
// 包含components,directives,filters,_base
var options = Ctor.options;
// 这个属性比较麻烦 暂时没有 跳过
if (Ctor.super) {
//...
}
// 返回修正后的options
return options
}

  如果忽略那个super属性的话,返回的其实就是Vue$3.constructor.options,该对象包含4个属性,如图所示。

  

    // Line-4368
// Vue函数自身的引用
Vue.options._base = Vue; // Line-7523
extend(Vue$3.options.directives, platformDirectives);
extend(Vue$3.options.components, platformComponents); // Line-7161
// 指令相关方法
var platformDirectives = {
model: model$1,
show: show
};
// Line-7509
// 组件相关
var platformComponents = {
Transition: Transition,
TransitionGroup: TransitionGroup
};

  其中filters属性暂时是空的,其余3个属性在2个地方有定义,一个是组件、指令方法集,一个是vue函数自身引用。

  2、options || {} => 传进来的参数

  3、vm => 当前vue实例

  最后,总览3个参数如下:

  带着3个小东西,跳进了mergeOptions函数进行参数合并。

    // Line-1298
// 父子组件合并参数 本案例父组件为默认对象
function mergeOptions(parent, child, vm) {
// 检测components参数中键是否合法
checkComponents(child);
if (typeof child === 'function') {
child = child.options;
}
// 格式化props,directives参数
normalizeProps(child);
normalizeDirectives(child);
// 格式化extends参数
var extendsFrom = child.extends;
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm);
}
// mixins参数
if (child.mixins) {
for (var i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
// 本案例中上面的都会跳过
var options = {};
var key;
// 遍历父组件对象 合并键
for (key in parent) {
mergeField(key);
}
// 遍历子组件对象 若有父组件没有的 合并键
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key);
}
}
// 合并函数
function mergeField(key) {
var strat = strats[key] || defaultStrat;
options[key] = strat(parent[key], child[key], vm, key);
}
return options
}

  这个函数中前半部分可以跳过,因为只有简单的el、data参数,所以直接从mergeField开始执行。

  上面已经列举出父组件的键,有components、directives、_filters、_base四个。

  这里又多出一个新的东西,叫strats,英文翻译成战略,所以应该怎么叫我也是很懵逼的。这个对象内容十分丰富,从生命周期到data、computed、methods都有,如下所示:

  

  方法太多,就不一个一个讲了,说说本案例相关的几个方法。

  看起来非常吓人,其实定义简单粗暴,上代码看看就明白了。

    // Line-281
var ASSET_TYPES = [
'component',
'directive',
'filter'
]; // Line-1182
ASSET_TYPES.forEach(function(type) {
strats[type + 's'] = mergeAssets;
}); // Line-1175
function mergeAssets(parentVal, childVal) {
var res = Object.create(parentVal || null);
return childVal ?
extend(res, childVal) :
res
}

  简单讲就是,3个键对应的是同一个方法,接受2个参数,方法还贼简单。

  所以,对上面的mergeOptions函数进行简化,可以转换成如下代码:

    // parent键:components、directives、_filters、_base
// child键:data、el
function mergeOptions(parent, child, vm) {
var options = {};
var key;
// 父子对象键没有重复 参数直接可以写undefined 一步一步简化
for (key in parent) {
//options[key] = mergeAssets(parent[key], child[key], vm, key);
//options[key] = mergeAssets(parent[key], undefined);
options[key] = Object.create(parent[key]);
}
// 子键data和el需要额外分析 第一个参数同样可以写成undefined
for (key in child) {
if (!hasOwn(parent, key)) {
//options[key] = strats[key](parent[key], child[key], vm, key);
options[key] = strats[key](undefined, child[key], vm, key);
}
}
return options
} function mergeAssets(parentVal, childVal) {
var res = Object.create(parentVal || null);
return childVal ?
extend(res, childVal) :
res
}

  遍历父对象其实啥也没做,直接把几个方法加到了options上面,然后开始遍历子对象,子对象包含我们传进去的el、data。

  el比较简单,只是做个判断然后丢回来。

    // Line-1064
// 简单判断是否是vue实例挂载的el
strats.el = strats.propsData = function(parent, child, vm, key) {
if (!vm) {
warn(
"option \"" + key + "\" can only be used during instance " +
'creation with the `new` keyword.'
);
}
return defaultStrat(parent, child)
};

  data则分两种情况,一种是未挂载的组件,一种是实例化的vue。

  不管未挂载,直接看实例化vue是如何处理data参数。

    // Line-1098
strats.data = function(parentVal, childVal, vm) {
// 未挂载
if (!vm) {
//...
}
// new出来的
// 传进来的parentVal、childVal分别为undefined、{message:'Hello Vue!}
else if (parentVal || childVal) {
return function mergedInstanceDataFn() {
var instanceData = typeof childVal === 'function' ?
childVal.call(vm) :
childVal;
var defaultData = typeof parentVal === 'function' ?
parentVal.call(vm) :
undefined;
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
};

  这里直接返回了一个函数,暂时不做分析,后面执行时候再来看。

  到此,整个mergeOptions函数执行完毕,返回一个处理过的options,将这个结果给了实例的$options属性:

  最后,用一张图结束这个乱糟糟的源码小跑第一节吧。

  撒花!撒花!

  

.1-Vue源码起步的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. SpringSecurity 登录 - 以及Md5加密

    我们现在开放一个链接给其他系统,来访问我们的系统 http://localhost:8080/hulk-teller-web/haihui!init.jspa?loginId=teller01& ...

  2. MyEclipse 快捷键问题

    解决Myeclipse提示快捷键Alt+/不可用问题 http://blog.163.com/cd-key666/blog/static/648929422011229123826/ 解决Myecli ...

  3. Linux下识别分区文件系统类型

    Linux下挂载文件系统有时候需要填写文件系统.但有的设备拿到手还不知道文件系统,这种情况,可以用 parted命令 # parted /dev/vda GNU Parted 3.2 Using /d ...

  4. c# 第一节课 一些简单的应用

    注册要钱 我没钱

  5. 【编程之外】还记得曾经给'大学导师'写过的报告嘛 --> 前方高能

    写在前面 本文不是讲技术的,也没什么代码可看 本文不是讲技术的,也没什么代码可看 本文不是讲技术的,也没什么代码可看 还记得我们曾经给我们大学''导师''写过的报告嘛? 大学他愿意在凌晨6点向你询问近 ...

  6. mybatis错误——java.io.IOException: Could not find resource com/xxx/xxxMapper.xml

    在学习Mybatis的时候,参考网上的教程进行简单demo的搭建,配置的没有问题,然后出现了下面的错误! Exception in thread "main" java.lang. ...

  7. 配置 VirtualBox backend - 每天5分钟玩转 Docker 容器技术(75)

    Rexy-Ray 支持多种 backend,上一节我们已经安装配置了 Rex-Ray,今天演示如何配置 VirtualBox backend. 在 VirtualBox 宿主机,即我的笔记本上启动 v ...

  8. CodeForces 242E二维线段树

                                                                                           E. XOR on Seg ...

  9. Scala 中的隐式转换和隐式参数

    隐式定义是指编译器为了修正类型错误而允许插入到程序中的定义. 举例: 正常情况下"120"/12显然会报错,因为 String 类并没有实现 / 这个方法,我们无法去决定 Stri ...

  10. 课程作业02(关于Java的几点讨论)

    ---恢复内容开始--- 1.一个Java类文件中真的只能有一个公有类吗? public class Test { public static void main(String[] args) { } ...