vue源码逐行注释分析+40多m的vue源码程序流程图思维导图 (ddf部分待后续更新)
vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了,差ddf那部分,因为考虑到自己要换工作了,所以暂缓下来先,ddf那块后期我会补上去。这个vue源码逐行分析,我基本每一行都打上注释,加上整个框架的流程思维导图,基本上是小白也能看懂的vue源码了。
说的非常的详细,里面的源码注释,有些是参考网上帖子的,有些是自己多年开发vue经验而猜测的,有些是自己跑上下文程序知道的,本人水平可能有限,不一定是很正确,如果有不足的地方可以联系我qq群 :302817612 修改,或者发邮件给我281113270@qq.com 谢谢。
1.vue源码解读流程 1.nwe vue 调用的是 vue.prototype._init 从该函数开始 经过 $options 参数合并之后 initlifecycle 初始化生命周期标志 初始化事件,初始化渲染函数。初始化状态就是数据。把数据添加到观察者中实现双数据绑定。
2.双数据绑定原理是:obersve()方法判断value没有没有__ob___属性并且是不是obersve实例化的,
value是不是vonde实例化的,如果不是则调用obersve 去把数据添加到观察者中,为数据添加__ob__属性, obersve 则调用definereactive方法,该方法是连接dep和wacther方法的一个通道,利用object.definpropty() 中的get和set方法 监听数据。get方法中是new dep调用depend()。为dep添加一个wacther类,watcher中有个方法是更新视图的是run调用update去更新vonde 然后更新视图。 然后set方法就是调用dep中的ontify 方法调用wacther中的run 更新视图
3.vue从字符串模板怎么到真实的dom呢?是通过$mount挂载模板,就是获取到html,然后通过pasehtml这个方法转义成ast模板,他大概算法是 while(html) 如果匹配到开始标签,结束标签,或者是属性,都会截取掉html,然后收集到一个对象中,知道循环结束 html被截取完。最后变成一个ast对象,ast对象好了之后,在转义成vonde 需要渲染的函数,比如_c('div' s('')) 等这类的函数,编译成vonde 虚拟dom。然后到updata更新数据 调用__patch__ 把vonde 通过ddf算法变成正真正的dom元素。
具体看我源码和流程图,这里文字就不描述这么多了,流程图是下面这中的网盘,源码是vue.js,基本每一行都有注释,然后ddf待更新中。
程序流程图太大了没法在线看,只能网盘下载到本地看了,给一个大概图
链接:https://pan.baidu.com/s/10ixv6mq2tiwkracku2t0ng
提取码:1fnu
github源码,包括平常我看vue中的一些小demo
/*!
* vue.js v2.5.16
* (c) 2014-2018 evan you
* released under the mit license.
* development 开发
* production 生产
/*
* 兼容 amd cmd 模块写法
* */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.vue = factory());
}(this, (function () {
'use strict';
/* */
//object.freeze()阻止修改现有属性的特性和值,并阻止添加新属性。
var emptyobject = object.freeze({});
// these helpers produces better vm code in js engines due to their
// explicitness and function inlining
// these helpers produces better vm code in js engines due to their
// explicitness and function inlining
//判断数据 是否是undefined或者null
function isundef(v) {
return v === undefined || v === null
}
//判断数据 是否不等于 undefined或者null
function isdef(v) {
return v !== undefined && v !== null
}
//判断是否真的等于true
function istrue(v) {
return v === true
}
// 判断是否是false
function isfalse(v) {
return v === false
}
/**
* check if value is primitive
* //判断数据类型是否是string,number,symbol,boolean
*/
function isprimitive(value) {
//判断数据类型是否是string,number,symbol,boolean
return (
typeof value === 'string' ||
typeof value === 'number' ||
// $flow-disable-line
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}
/**
* quick object check - this is primarily used to tell
* objects from primitive values when we know the value
* is a json-compliant type.
*/
function isobject(obj) {
//判断是否是对象
return obj !== null && typeof obj === 'object'
}
/**
* get the raw type string of a value e.g. [object object]
*/
//获取tostring 简写
var _tostring = object.prototype.tostring;
function torawtype(value) {
//类型判断 返会array ,function,string,object,re 等
return _tostring.call(value).slice(8, -1)
}
/**
* strict object type check. only returns true
* for plain javascript objects.
*/
function isplainobject(obj) {
//判断是否是对象
return _tostring.call(obj) === '[object object]'
}
function isregexp(v) {
//判断是否是正则对象
return _tostring.call(v) === '[object regexp]'
}
/**
* check if val is a valid array index.
*/
/**
* check if val is a valid array index.
* 检查val是否是有效的数组索引。
*/
function isvalidarrayindex(val) {
//isfinite 检测是否是数据
//math.floor 向下取整
var n = parsefloat(string(val));
//isfinite 如果 number 是有限数字(或可转换为有限数字),那么返回 true。否则,如果 number 是 nan(非数字),或者是正、负无穷大的数,则返回 false。
return n >= 0 && math.floor(n) === n && isfinite(val)
}
/**
* convert a value to a string that is actually rendered.
*/
function tostring(val) {
//将对象或者其他基本数据 变成一个 字符串
return val == null
? ''
: typeof val === 'object'
? json.stringify(val, null, 2)
: string(val)
}
/**
* convert a input value to a number for persistence.
* if the conversion fails, return original string.
*/
function tonumber(val) {
//字符串转数字,如果失败则返回字符串
var n = parsefloat(val);
return isnan(n) ? val : n
}
/**
* make a map and return a function for checking if a key
* is in that map.
*
* //map 对象中的[name1,name2,name3,name4] 变成这样的map{name1:true,name2:true,name3:true,name4:true}
* 并且传进一个key值取值,这里用到策略者模式
*/
function makemap(str,
expectslowercase) {
var map = object.create(null); //创建一个新的对象
var list = str.split(','); //按字符串,分割
for (var i = 0; i < list.length; i++) {
map[list[i]] = true; //map 对象中的[name1,name2,name3,name4] 变成这样的map{name1:true,name2:true,name3:true,name4:true}
}
return expectslowercase
? function (val) {
return map[val.tolowercase()];
} //返回一个柯里化函数 tolowercase转换成小写
: function (val) {
return map[val];
} //返回一个柯里化函数 并且把map中添加一个 属性建
}
/**
* check if a tag is a built-in tag.
* 检查标记是否为内置标记。
*/
var isbuiltintag = makemap('slot,component', true);
/**
* check if a attribute is a reserved attribute.
* 检查属性是否为保留属性。
* isreservedattribute=function(vale){ map{key:true,ref:true,slot-scope:true,is:true,vaule:undefined} }
*/
var isreservedattribute = makemap('key,ref,slot,slot-scope,is');
/**
* remove an item from an array
* //删除数组
*/
function remove(arr, item) {
if (arr.length) {
var index = arr.indexof(item);
if (index > -1) {
return arr.splice(index, 1)
}
}
}
/**
* check whether the object has the property.
*检查对象属性是否是实例化还是原型上面的
*/
var hasownproperty = object.prototype.hasownproperty;
function hasown(obj, key) {
return hasownproperty.call(obj, key)
}
/**
* create a cached version of a pure function.
*/
/**
* create a cached version of a pure function.
* 创建纯函数的缓存版本。
* 创建一个函数,缓存,再return 返回柯里化函数
* 闭包用法
*/
/***********************************************************************************************
*函数名 :cached
*函数功能描述 : 创建纯函数的缓存版本。 创建一个函数,缓存,再return 返回柯里化函数 闭包用法
*函数参数 : fn 函数
*函数返回值 : fn
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
/*
* var afn = cached(function(string){
*
* return string
* })
* afn(string1);
* afn(string2);
* afn(string);
* afn(string1);
* afn(string2);
*
* afn 函数会多次调用 里面就能体现了
* 用对象去缓存记录函数
* */
function cached(fn) {
var cache = object.create(null);
return (function cachedfn(str) {
var hit = cache[str];
return hit || (cache[str] = fn(str))
})
}
/**
* camelize a hyphen-delimited string.
* 用连字符分隔的字符串。
* camelize = cachedfn(str)=>{ var hit = cache[str];
return hit || (cache[str] = fn(str))}
调用一个camelize 存一个建进来 调用两次 如果建一样就返回 hit
横线-的转换成驼峰写法
可以让这样的的属性 v-model 变成 vmodel
*/
var camelizere = /-(\w)/g;
var camelize = cached(function (str) {
return str.replace(camelizere, function (_, c) {
return c ? c.touppercase() : '';
})
});
/**
* capitalize a string. 将首字母变成大写。
*/
var capitalize = cached(function (str) {
return str.charat(0).touppercase() + str.slice(1)
});
/**
* hyphenate a camelcase string.
* \b的用法
\b是非单词分界符,即可以查出是否包含某个字,如“abcdefghijk”中是否包含“bcdefghijk”这个字。
*/
var hyphenatere = /\b([a-z])/g;
var hyphenate = cached(function (str) {
//大写字母,加完减号又转成小写了 比如把驼峰 abc 变成了 a-bc
//匹配大写字母并且两面不是空白的 替换成 '-' + '字母' 在全部转换成小写
return str.replace(hyphenatere, '-$1').tolowercase();
});
/**
* simple bind polyfill for environments that do not support it... e.g.
* phantomjs 1.x. technically we don't need this anymore since native bind is
* now more performant in most browsers, but removing it would be breaking for
* code that was able to run in phantomjs 1.x, so this must be kept for
* backwards compatibility.
* 改变this 上下文
* 执行方式
*/
/* istanbul ignore next */
//绑定事件 并且改变上下文指向
function polyfillbind(fn, ctx) {
function boundfn(a) {
var l = arguments.length;
return l
? l > 1
? fn.apply(ctx, arguments)
: fn.call(ctx, a)
: fn.call(ctx)
}
boundfn._length = fn.length;
return boundfn
}
//执行方式
function nativebind(fn, ctx) {
return fn.bind(ctx)
}
//bing 改变this上下文
var bind = function.prototype.bind
? nativebind
: polyfillbind;
/**
* convert an array-like object to a real array.
* 将假的数组转换成真的数组
*/
function toarray(list, start) {
start = start || 0;
var i = list.length - start;
var ret = new array(i);
while (i--) {
ret[i] = list[i + start];
}
return ret
}
/**
* mix properties into target object.
* * 浅拷贝
*/
/***********************************************************************************************
*函数名 :extend
*函数功能描述 : 浅拷贝
*函数参数 : to 超类, _from 子类
*函数返回值 : 合并类
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
//对象浅拷贝,参数(to, _from)循环_from的值,会覆盖掉to的值
function extend(to, _from) {
for (var key in _from) {
to[key] = _from[key];
}
return to
}
/**
* merge an array of objects into a single object.
*
*/
/***********************************************************************************************
*函数名 :toobject
*函数功能描述 : 和并对象数组合并成一个对象
*函数参数 : arr 数组对象类
*函数返回值 :
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
function toobject(arr) {
var res = {};
for (var i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i]);
}
}
return res
}
/**
* perform no operation.
* stubbing args to make flow happy without leaving useless transpiled code
* with ...rest (https://flow.org/blog/2017/05/07/strict-function-call-arity/)
*/
function noop(a, b, c) {
}
/**
* always return false.
* 返回假的
*/
var no = function (a, b, c) {
return false;
};
/**
* return same value
*返回相同值
*/
var identity = function (_) {
return _;
};
/**
* generate a static keys string from compiler modules.
*
* [{ statickeys:1},{statickeys:2},{statickeys:3}]
* 连接数组对象中的 statickeys key值,连接成一个字符串 str=‘1,2,3’
*/
function genstatickeys(modules) {
return modules.reduce(
function (keys, m) {
//累加statickeys的值变成数组
return keys.concat(m.statickeys || [])
},
[]
).join(',') //转换成字符串
}
/**
* check if two values are loosely equal - that is,
* if they are plain objects, do they have the same shape?
* 检测a和b的数据类型,是否是不是数组或者对象,对象的key长度一样即可,数组长度一样即可
*/
function looseequal(a, b) {
if (a === b) {
return true
} //如果a和b是完全相等 则true
var isobjecta = isobject(a);
var isobjectb = isobject(b);
if (isobjecta && isobjectb) { //如果a和都是对象则让下走
try {
var isarraya = array.isarray(a);
var isarrayb = array.isarray(b);
if (isarraya && isarrayb) { //如果a和b都是数组
// every 条件判断
return a.length === b.length && a.every(function (e, i) { //如果a长度和b长度一样的时候
return looseequal(e, b[i]) //递归
})
} else if (!isarraya && !isarrayb) { //或者a和b都不是数组
var keysa = object.keys(a); // 获取到a的key值 变成一个数组
var keysb = object.keys(b); // 获取到b的key值 变成一个数组
//他们的对象key值长度是一样的时候 则加载every 条件函数
return keysa.length === keysb.length && keysa.every(function (key) {
//递归 a和b的值
return looseequal(a[key], b[key])
})
} else {
//如果不是对象跳槽循环
/* istanbul ignore next */
return false
}
} catch (e) {
//如果不是对象跳槽循环
/* istanbul ignore next */
return false
}
} else if (!isobjecta && !isobjectb) { //b和a 都不是对象的时候
//把a和b变成字符串,判断他们是否相同
return string(a) === string(b)
} else {
return false
}
}
// 判断 arr数组中的数组 是否和val相等。
// 或者 arr数组中的对象,或者对象数组 是否和val 相等
function looseindexof(arr, val) {
for (var i = 0; i < arr.length; i++) {
if (looseequal(arr[i], val)) {
return i
}
}
return -1
}
/**
* ensure a function is called only once.
* 确保该函数只调用一次 闭包函数
*/
function once(fn) {
var called = false;
return function () {
if (!called) {
called = true;
fn.apply(this, arguments);
}
}
}
//ssr标记属性
var ssr_attr = 'data-server-rendered';
var asset_types = [
'component', //组建指令
'directive', //定义指令 指令
'filter' //过滤器指令
];
var lifecycle_hooks = [
'beforecreate', // 生命周期 开始实例化 vue 指令
'created', //生命周期 结束实例化完 vue 指令
'beforemount', //生命周期 开始渲染虚拟dom ,挂载event 事件 指令
'mounted', //生命周期 渲染虚拟dom ,挂载event 事件 完 指令
'beforeupdate', //生命周期 开始更新wiew 数据指令
'updated', //生命周期 结束更新wiew 数据指令
'beforedestroy', //生命周期 开始销毁 new 实例 指令
'destroyed', //生命周期 结束销毁 new 实例 指令
'activated', //keep-alive组件激活时调用。
'deactivated', //deactivated keep-alive组件停用时调用。
'errorcaptured' // 具有此钩子的组件捕获其子组件树(不包括其自身)中的所有错误(不包括在异步回调中调用的那些)。
];
/* */
var config = ({
/**
* option merge strategies (used in core/util/options)
*/
// $flow-disable-line
//合并对象 策略
optionmergestrategies: object.create(null),
/**
* whether to suppress warnings.
* * 是否禁止警告。
*/
silent: false,
/**
* show production mode tip message on boot?
* 在引导时显示生产模式提示消息?
* webpack打包判断执行环境是不是生产环境,如果是生产环境会压缩并且没有提示警告之类的东西
*/
productiontip: "development" !== 'production',
/**
* whether to enable devtools
* 是否启用devtools
*/
devtools: "development" !== 'production',
/**
* whether to record perf
* 是否记录perf
*/
performance: false,
/**
* error handler for watcher errors
*监视器错误的错误处理程序
*/
errorhandler: null,
/**
* warn handler for watcher warns
* 观察加警告处理。
*/
warnhandler: null,
/**
* ignore certain custom elements
* 忽略某些自定义元素
*/
ignoredelements: [],
/**
* custom user key aliases for v-on
* 用于v-on的自定义用户密钥别名 键盘码
*/
// $flow-disable-line
keycodes: object.create(null),
/**
* check if a tag is reserved so that it cannot be registered as a
* component. this is platform-dependent and may be overwritten.
* 检查是否保留了一个标签,使其不能注册为组件。这是平台相关的,可能会被覆盖。
*/
isreservedtag: no,
/**
* check if an attribute is reserved so that it cannot be used as a component
* prop. this is platform-dependent and may be overwritten.
* 检查属性是否被保留,使其不能用作组件支持。这是平台相关的,可能会被覆盖。
*/
isreservedattr: no,
/**
* check if a tag is an unknown element.
* platform-dependent.
* check if a tag is an unknown element. platform-dependent.
* 检查标签是否为未知元素依赖于平台的检查,如果标签是未知元素。平台相关的
*
*/
isunknownelement: no,
/**
* get the namespace of an element
* 获取元素的命名空间
*/
gettagnamespace: noop,
/**
* parse the real tag name for the specific platform.
* 解析真实的标签平台
*/
parseplatformtagname: identity,
/**
* check if an attribute must be bound using property, e.g. value
* platform-dependent.
* 检查属性是否必须使用属性绑定,例如依赖于依赖于平台的属性。
*/
mustuseprop: no,
/**
* exposed for legacy reasons
* 因遗产原因暴露
* 声明周期对象
*/
_lifecyclehooks: lifecycle_hooks
})
/* */
/**
* check if a string starts with $ or _
* 检查一个字符串是否以$或者_开头
*/
function isreserved(str) {
var c = (str + '').charcodeat(0);
return c === 0x24 || c === 0x5f
}
/**
* define a property.
* 用defineproperty 定义属性
* 详细地址 https://developer.mozilla.org/zh-cn/docs/web/javascript/reference/global_objects/object/defineproperty
第一个参数是对象
第二个是key
第三个是vue
第四个是 是否可以枚举
*/
function def(obj, key, val, enumerable) {
object.defineproperty(obj, key, {
value: val, //值
enumerable: !!enumerable, //定义了对象的属性是否可以在 for...in 循环和 object.keys() 中被枚举。
writable: true, //可以 改写 value
configurable: true //configurable特性表示对象的属性是否可以被删除,以及除writable特性外的其他特性是否可以被修改。
});
}
/**
* parse simple path.
* 解析。
*/
var bailre = /[^\w.$]/; //匹配不是 数字字母下划线 $符号 开头的为true
function parsepath(path) {
console.log(path)
if (bailre.test(path)) { //匹配上 返回 true
return
}
//匹配不上 path在已点分割
var segments = path.split('.');
return function (obj) {
for (var i = 0; i < segments.length; i++) {
//如果没有参数则返回
if (!obj) {
return
}
//将对象中的一个key值 赋值给该对象 相当于 obj = obj[segments[segments.length-1]];
obj = obj[segments[i]];
}
//否则返回一个对象
return obj
}
}
/* */
// can we use __proto__?
var hasproto = '__proto__' in {};
// browser environment sniffing
//判断设备和浏览器
var inbrowser = typeof window !== 'undefined';
//如果不是浏览器
var inweex = typeof wxenvironment !== 'undefined' && !!wxenvironment.platform; //weex 环境 一个 vue做app包的框架
var weexplatform = inweex && wxenvironment.platform.tolowercase();//weex 环境 一个 vue做app包的框架
//window.navigator.useragent属性包含了浏览器类型、版本、操作系统类型、浏览器引擎类型等信息,通过这个属性来判断浏览器类型
var ua = inbrowser && window.navigator.useragent.tolowercase(); //获取浏览器
var isie = ua && /msie|trident/.test(ua); //ie
var isie9 = ua && ua.indexof('msie 9.0') > 0; //ie9
var isedge = ua && ua.indexof('edge/') > 0; //ie10 以上
var isandroid = (ua && ua.indexof('android') > 0) || (weexplatform === 'android'); //安卓
var isios = (ua && /iphone|ipad|ipod|ios/.test(ua)) || (weexplatform === 'ios'); //ios
var ischrome = ua && /chrome\/\d+/.test(ua) && !isedge; //谷歌浏览器
// firefox has a "watch" function on object.prototype...
var nativewatch = ({}).watch;
//兼容火狐浏览器写法
var supportspassive = false;
if (inbrowser) {
try {
var opts = {};
object.defineproperty(opts, 'passive', ({
get: function get() {
/* istanbul ignore next */
supportspassive = true;
}
})); // https://github.com/facebook/flow/issues/285
window.addeventlistener('test-passive', null, opts);
} catch (e) {
}
}
// this needs to be lazy-evaled because vue may be required before
// vue-server-renderer can set vue_env
//vue 服务器渲染 可以设置 vue_env
var _isserver;
//判断是不是node 服务器环境
var isserverrendering = function () {
if (_isserver === undefined) {
/* istanbul ignore if */
//如果不是浏览器 并且global 对象存在,那么有可能是node 脚本
if (!inbrowser && typeof global !== 'undefined') {
//
// detect presence of vue-server-renderer and avoid
// webpack shimming the process
//_isserver 设置是服务器渲染
_isserver = global['process'].env.vue_env === 'server';
} else {
_isserver = false;
}
}
return _isserver
};
// detect devtools
//检测开发者工具。
var devtools = inbrowser && window.__vue_devtools_global_hook__;
/* istanbul ignore next */
function isnative(ctor) {
//或者判断该函数是不是系统内置函数
//判断一个函数中是否含有 'native code' 字符串 比如
// function code(){
// var native='native code'
// }
// 或者
// function code(){
// var native='native codeasdfsda'
// }
return typeof ctor === 'function' && /native code/.test(ctor.tostring())
}
//判断是否支持symbol 数据类型
var hassymbol =
//symbol es6新出来的一种数据类型,类似于string类型,声明唯一的数据值
typeof symbol !== 'undefined' && isnative(symbol) &&
// reflect.ownkeys
// reflect.ownkeys方法用于返回对象的所有属性,基本等同于object.getownpropertynames与object.getownpropertysymbols之和。
typeof reflect !== 'undefined' && isnative(reflect.ownkeys);
var _set;
/* istanbul ignore if */ // $flow-disable-line
//es6 提供了新的数据结构 set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
// set 本身是一个构造函数,用来生成 set 数据结构。
//判断是否有set这个方法
if (typeof set !== 'undefined' && isnative(set)) {
// use native set when available.
_set = set;
} else {
// a non-standard set polyfill that only works with primitive keys.
//如果没有他自己写一个
_set = (function () {
function set() {
this.set = object.create(null);
}
set.prototype.has = function has(key) {
return this.set[key] === true
};
set.prototype.add = function add(key) {
this.set[key] = true;
};
set.prototype.clear = function clear() {
this.set = object.create(null);
};
return set;
}());
}
var warn = noop;
var tip = noop;
var generatecomponenttrace = (noop); // work around flow check 绕流检查
var formatcomponentname = (noop);
{
//判断是否有console 打印输出属性
var hasconsole = typeof console !== 'undefined';
var classifyre = /(?:^|[-_])(\w)/g;
//非捕获 匹配不分组 。 就是可以包含,但是不匹配上
//过滤掉class中的 -_ 符号 并且把字母开头的改成大写
var classify = function (str) {
return str.replace(classifyre,
function (c) {
return c.touppercase();
}).replace(/[-_]/g, '');
};
/***********************************************************************************************
*函数名 :warn
*函数功能描述 : 警告信息提示
*函数参数 : msg: 警告信息, vm:vue对象
*函数返回值 : void
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
warn = function (msg, vm) {
//vm 如果没有传进来就给空, 不然给执行generatecomponenttrace 收集 vue错误码
var trace = vm ? generatecomponenttrace(vm) : '';
//warnhandler 如果存在 则调用他
if (config.warnhandler) {
config.warnhandler.call(null, msg, vm, trace);
} else if (hasconsole && (!config.silent)) {
//如果config.warnhandler 不存在则 console 内置方法打印
console.error(("[vue warn]: " + msg + trace));
}
};
//也是个警告输出方法
tip = function (msg, vm) {
if (hasconsole && (!config.silent)) {
//
console.warn("[vue tip]: " + msg + (
vm ? generatecomponenttrace(vm) : ''
));
}
};
/***********************************************************************************************
*函数名 :formatcomponentname
*函数功能描述 : 格式组件名
*函数参数 : msg: 警告信息, vm:vue对象
*函数返回值 : void
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
formatcomponentname = function (vm, includefile) {
if (vm.$root === vm) {
return '<root>'
}
/*
* 如果 vm === 'function' && vm.cid != null 条件成立 则options等于vm.options
* 当vm === 'function' && vm.cid != null 条件不成立的时候 vm._isvue ? vm.$options || vm.constructor.options : vm || {};
* vm._isvue为真的时候 vm.$options || vm.constructor.options ,vm._isvue为假的时候 vm || {}
* */
var options =
typeof vm === 'function' && vm.cid != null
? vm.options : vm._isvue ? vm.$options || vm.constructor.options : vm || {};
var name = options.name || options._componenttag;
console.log('name=' + name);
var file = options.__file;
if (!name && file) {
//匹配.vue 后缀的文件名
//如果文件名中含有vue的文件将会被匹配出来 但是会多虑掉 \符号
var match = file.match(/([^/\\]+)\.vue$/);
name = match && match[1];
}
//可能返回 classify(name)
//name 组件名称或者是文件名称
/*
* classify 去掉-_连接 大些字母连接起来
* 如果name存在则返回name
* 如果name不存在那么返回‘<anonymous>’+ 如果file存在并且includefile!==false的时候 返回" at " + file 否则为空
*
* */
return (
(name ? ("<" + (classify(name)) + ">") : "<anonymous>") +
(file && includefile !== false ? (" at " + file) : '')
)
};
/*
*重复 递归 除2次 方法+ str
* */
var repeat = function (str, n) {
var res = '';
while (n) {
if (n % 2 === 1) {
res += str;
}
if (n > 1) {
str += str;
}
n >>= 1;
//16 8
//15 7 相当于除2 向下取整2的倍数
//console.log( a >>= 1)
}
return res
};
/***********************************************************************************************
*函数名 :generatecomponenttrace
*函数功能描述 : 生成组建跟踪 vm=vm.$parent递归收集到msg出处。
*函数参数 : vm 组建
*函数返回值 :
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
generatecomponenttrace = function (vm) {
if (vm._isvue && vm.$parent) { //如果_isvue 等于真,并且有父亲节点的
var tree = []; //记录父节点
var currentrecursivesequence = 0;
while (vm) { //循环 vm 节点
if (tree.length > 0) {//tree如果已经有父节点的
var last = tree[tree.length - 1];
if (last.constructor === vm.constructor) { //上一个节点等于父节点 个人感觉这里用户不会成立
currentrecursivesequence++;
vm = vm.$parent;
continue
} else if (currentrecursivesequence > 0) { //这里也不会成立
tree[tree.length - 1] = [last, currentrecursivesequence];
currentrecursivesequence = 0;
}
}
tree.push(vm); //把vm添加到队列中
vm = vm.$parent;
}
return '\n\nfound in\n\n' + tree
.map(function (vm, i) {
//如果i是0 则输出 ‘---->’
//如果i 不是0的时候输出组件名称
return ("" + (i === 0 ?
'---> ' : repeat(' ', 5 + i * 2)) +
(
array.isarray(vm) ?
((formatcomponentname(vm[0])) + "... (" + (vm[1]) + " recursive calls)")
: formatcomponentname(vm)
)
);
})
.join('\n')
} else {
//如果没有父组件则输出一个组件名称
return ("\n\n(found in " + (formatcomponentname(vm)) + ")")
}
};
}
/* */
/* */
var uid = 0;
/**
* a dep is an observable that can have multiple dep是可观察到的,可以有多个
* directives subscribing to it.订阅它的指令。
*
*/
//主题对象dep构造函数 主要用于添加发布事件后,用户更新数据的 响应式原理之一函数
var dep = function dep() {
//uid 初始化为0
this.id = uid++;
/* 用来存放watcher对象的数组 */
this.subs = [];
};
dep.prototype.addsub = function addsub(sub) {
/* 在subs中添加一个watcher对象 */
this.subs.push(sub);
};
dep.prototype.removesub = function removesub(sub) {
/*删除 在subs中添加一个watcher对象 */
remove(this.subs, sub);
};
//this$1.deps[i].depend();
//为watcher 添加 为watcher.newdeps.push(dep); 一个dep对象
dep.prototype.depend = function depend() {
//添加一个dep target 是watcher dep就是dep对象
if (dep.target) {
//像指令添加依赖项
dep.target.adddep(this);
}
};
/* 通知所有watcher对象更新视图 */
dep.prototype.notify = function notify() {
// stabilize the subscriber list first
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
//更新数据
subs[i].update();
}
};
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
//当前正在评估的目标监视程序。
//这在全球是独一无二的,因为只有一个
//观察者在任何时候都被评估。
dep.target = null;
var targetstack = [];
function pushtarget(_target) {
//target 是watcher dep就是dep对象
if (dep.target) { //静态标志 dep当前是否有添加了target
//添加一个pushtarget
targetstack.push(dep.target);
}
dep.target = _target;
}
//
function poptarget() {
// 出盏一个pushtarget
dep.target = targetstack.pop();
}
/*
* 创建标准的vue vnode
*
* */
var vnode = function vnode(
tag, /*当前节点的标签名*/
data, /*当前节点对应的对象,包含了具体的一些数据信息,是一个vnodedata类型,可以参考vnodedata类型中的数据信息*/
children, //子节点
text, //文本
elm, /*当前节点的dom */
context, /*编译作用域*/
componentoptions, /*组件的option选项*/
asyncfactory/*异步工厂*/) {
/*当前节点的标签名*/
this.tag = tag;
/*当前节点对应的对象,包含了具体的一些数据信息,是一个vnodedata类型,可以参考vnodedata类型中的数据信息*/
this.data = data;
/*当前节点的子节点,是一个数组*/
this.children = children;
/*当前节点的文本*/
this.text = text;
/*当前虚拟节点对应的真实dom节点*/
this.elm = elm;
/*当前节点的名字空间*/
this.ns = undefined;
/*编译作用域 vm*/
this.context = context;
this.fncontext = undefined;
this.fnoptions = undefined;
this.fnscopeid = undefined;
/*节点的key属性,被当作节点的标志,用以优化*/
this.key = data && data.key;
/*组件的option选项*/
this.componentoptions = componentoptions;
/*当前节点对应的组件的实例*/
this.componentinstance = undefined;
/*当前节点的父节点*/
this.parent = undefined;
/*简而言之就是是否为原生html或只是普通文本,innerhtml的时候为true,textcontent的时候为false*/
this.raw = false;
/*静态节点标志*/
this.isstatic = false;
/*是否作为跟节点插入*/
this.isrootinsert = true;
/*是否为注释节点*/
this.iscomment = false;
/*是否为克隆节点*/
this.iscloned = false;
/*是否有v-once指令*/
this.isonce = false;
/*异步工厂*/
this.asyncfactory = asyncfactory;
this.asyncmeta = undefined;
this.isasyncplaceholder = false;
};
//当且仅当该属性描述符的类型可以被改变并且该属性可以从对应对象中删除。默认为 false
var prototypeaccessors = {child: {configurable: true}};
// deprecated: alias for componentinstance for backwards compat.
/* istanbul ignore next */
prototypeaccessors.child.get = function () {
return this.componentinstance
};
/*设置所有vnode.prototype 属性方法 都为
{
'child':{
configurable: true,
get:function(){
return this.componentinstance
}
}
}
*/
object.defineproperties(vnode.prototype, prototypeaccessors);
//创建一个节点 空的vnode
var createemptyvnode = function (text) {
if (text === void 0) text = '';
var node = new vnode();
node.text = text;
node.iscomment = true;
return node
};
//创建一个文本节点
function createtextvnode(val) {
return new vnode(
undefined,
undefined,
undefined,
string(val)
)
}
// optimized shallow clone
// used for static nodes and slot nodes because they may be reused across
// multiple renders, cloning them avoids errors when dom manipulations rely
// on their elm reference.
//优化浅克隆
//用于静态节点和时隙节点,因为它们可以被重用。
//多重渲染,克隆它们避免dom操作依赖时的错误
//他们的榆树参考。
//克隆节点 把节点变成静态节点
function clonevnode(vnode, deep) {
//
var componentoptions = vnode.componentoptions;
/*组件的option选项*/
var cloned = new vnode(
vnode.tag,
vnode.data,
vnode.children,
vnode.text,
vnode.elm,
vnode.context,
componentoptions,
vnode.asyncfactory
);
cloned.ns = vnode.ns;/*当前节点的名字空间*/
cloned.isstatic = vnode.isstatic;/*静态节点标志*/
cloned.key = vnode.key;/*节点的key属性,被当作节点的标志,用以优化*/
cloned.iscomment = vnode.iscomment;/*是否为注释节点*/
cloned.fncontext = vnode.fncontext; //函数上下文
cloned.fnoptions = vnode.fnoptions; //函数options选项
cloned.fnscopeid = vnode.fnscopeid; //函数范围id
cloned.iscloned = true;
/*是否为克隆节点*/
if (deep) { //如果deep存在
if (vnode.children) { //如果有子节点
//深度拷贝子节点
cloned.children = clonevnodes(vnode.children, true);
}
if (componentoptions && componentoptions.children) {
//深度拷贝子节点
componentoptions.children = clonevnodes(componentoptions.children, true);
}
}
return cloned
}
//克隆多个节点 为数组的
function clonevnodes(vnodes, deep) {
var len = vnodes.length;
var res = new array(len);
for (var i = 0; i < len; i++) {
res[i] = clonevnode(vnodes[i], deep);
}
return res
}
/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on array prototype
*/
var arrayproto = array.prototype;
var arraymethods = object.create(arrayproto);
var methodstopatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
/**
* intercept mutating methods and emit events
*/
/***********************************************************************************************
*函数名 :methodstopatch
*函数功能描述 : 更新数据时候如果是数组拦截方法,如果在数据中更新用的是'push','pop','shift','unshift','splice','sort','reverse' 方法则会调用这里
*函数参数 :
*函数返回值 :
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
methodstopatch.foreach(function (method) {
console.log('methodstopatch')
// cache original method
var original = arrayproto[method];
console.log('==method==')
console.log(method)
console.log('==original==')
console.log(original)
def(arraymethods, method, function mutator() {
console.log('==def_original==')
console.log(original)
var args = [], len = arguments.length;
while (len--) args[len] = arguments[len];
var result = original.apply(this, args);
var ob = this.__ob__;
console.log('this.__ob__')
console.log(this.__ob__)
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) {
//观察数组数据
ob.observearray(inserted);
}
// notify change
//更新通知
ob.dep.notify();
console.log('====result====')
console.log(result)
return result
});
});
/* */
// 方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括symbol值作为名称的属性)组成的数组,只包括实例化的属性和方法,不包括原型上的。
var arraykeys = object.getownpropertynames(arraymethods);
/**
* in some cases we may want to disable observation inside a component's
* update computation.
*在某些情况下,我们可能希望禁用组件内部的观察。
*更新计算。
*/
var shouldobserve = true; //标志是否禁止还是添加到观察者模式
function toggleobserving(value) {
shouldobserve = value;
}
/**
* observer class that is attached to each observed
* object. once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
* *每个观察到的观察者类
*对象。一旦被连接,观察者就转换目标。
*对象的属性键为吸收器/设置器
*收集依赖关系并发送更新。
*
* 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性
*/
var observer = function observer(value) {
this.value = value;
this.dep = new dep();
this.vmcount = 0;
//设置监听 value 必须是对象
def(value, '__ob__', this);
if (array.isarray(value)) { //判断是不是数组
var augment = hasproto //__proto__ 存在么 高级浏览器都会有这个
? protoaugment
: copyaugment;
augment(value, arraymethods, arraykeys);
this.observearray(value);
} else {
this.walk(value);
}
};
/**
* walk through each property and convert them into
* getter/setters. this method should only be called when
* value type is object.
* *遍历每个属性并将其转换为
* getter / setter。此方法只应在调用时调用
*值类型是object。
*/
observer.prototype.walk = function walk(obj) {
var keys = object.keys(obj);
for (var i = 0; i < keys.length; i++) {
definereactive(obj, keys[i]);
}
};
/**
* observe a list of array items.
* 观察数组项的列表。
* 把数组拆分一个个 添加到观察者 上面去
*/
observer.prototype.observearray = function observearray(items) {
for (var i = 0, l = items.length; i < l; i++) {
console.log('items[i]')
console.log(items[i])
observe(items[i]);
}
};
// helpers
/**
* augment an target object or array by intercepting
* the prototype chain using __proto__
* 通过拦截来增强目标对象或数组
* 使用原型原型链
* target 目标对象
* src 原型 对象或者属性、
* keys key
*
*/
function protoaugment(target, src, keys) {
/* eslint-disable no-proto */
target.__proto__ = src;
/* eslint-enable no-proto */
}
/**
* augment an target object or array by defining
* hidden properties.
* 复制扩充
* 定义添加属性 并且添加 监听
*target 目标对象
* src对象
* keys 数组keys
*/
/* istanbul ignore next */
function copyaugment(target, src, keys) {
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
def(target, key, src[key]);
}
}
/**
* attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*尝试为值创建一个观察者实例,
*如果成功观察,返回新的观察者;
*或现有的观察员,如果值已经有一个。
*
* 判断value 是否有__ob__ 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性 返回 new observer 实例化的对象
*/
function observe(value, asrootdata) {
if (!isobject(value) || value instanceof vnode) {
//value 不是一个对象 或者 实例化 的vnode
console.log(value)
return
}
var ob;
if (hasown(value, '__ob__') && value.__ob__ instanceof observer) {
console.log('hasown value')
console.log(value)
ob = value.__ob__;
} else if (
shouldobserve && //shouldobserve 为真
!isserverrendering() && //并且不是在服务器node环境下
(array.isarray(value) || isplainobject(value)) && //是数组或者是对象
object.isextensible(value) && //object.preventextensions(o) 方法用于锁住对象属性,使其不能够拓展,也就是不能增加新的属性,但是属性的值仍然可以更改,也可以把属性删除,object.isextensible用于判断对象是否可以被拓展
!value._isvue //_isvue为假
) {
console.log('new observer value')
console.log(value)
//实例化 dep对象 为 value添加__ob__ 属性
ob = new observer(value);
}
console.log(value)
//如果是rootdata,即咱们在新建vue实例时,传到data里的值,只有rootdata在每次observe的时候,会进行计数。 vmcount是用来记录此vue实例被使用的次数的, 比如,我们有一个组件logo,页面头部和尾部都需要展示logo,都用了这个组件,那么这个时候vmcount就会计数,值为2
if (asrootdata && ob) { //是根节点数据的话 并且 ob 存在
ob.vmcount++; //统计有几个vm
}
// * 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性
return ob
}
/**
* define a reactive property on an object.
* 在对象上定义一个无功属性。
* 更新数据
* 通过defineproperty的set方法去通知notify()订阅者subscribers有新的值修改
* 添加观察者 get set方法
*/
function definereactive(obj, //对象
key,//对象的key
val, //监听的数据 返回的数据
customsetter, // 日志函数
shallow //是否要添加__ob__ 属性
) {
//实例化一个主题对象,对象中有空的观察者列表
var dep = new dep();
//获取描述属性
var property = object.getownpropertydescriptor(obj, key);
var _property = object.getownpropertynames(obj); //获取实力对象属性或者方法,包括定义的描述属性
console.log(property);
console.log(_property);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
console.log('arguments.length=' + arguments.length)
if (!getter && arguments.length === 2) {
val = obj[key];
}
var setter = property && property.set;
console.log(val)
//判断value 是否有__ob__ 实例化 dep对象,获取dep对象 为 value添加__ob__ 属性递归把val添加到观察者中 返回 new observer 实例化的对象
var childob = !shallow && observe(val);
//定义描述
object.defineproperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactivegetter() {
var value = getter ? getter.call(obj) : val;
if (dep.target) { //dep.target 静态标志 标志了dep添加了watcher 实例化的对象
//添加一个dep
dep.depend();
if (childob) { //如果子节点存在也添加一个dep
childob.dep.depend();
if (array.isarray(value)) { //判断是否是数组 如果是数组
dependarray(value); //则数组也添加dep
}
}
}
return value
},
set: function reactivesetter(newval) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare 新旧值比较 如果是一样则不执行了*/
if (newval === value || (newval !== newval && value !== value)) {
return
}
/* eslint-enable no-self-compare
* 不是生产环境的情况下
* */
if ("development" !== 'production' && customsetter) {
customsetter();
}
if (setter) {
//set 方法 设置新的值
setter.call(obj, newval);
} else {
//新的值直接给他
val = newval;
}
console.log(newval)
//observe 添加 观察者
childob = !shallow && observe(newval);
//更新数据
dep.notify();
}
});
}
/**
* set a property on an object. adds the new property and
* triggers change notification if the property doesn't
* already exist.
**在对象上设置属性。添加新属性和
*触发器更改通知,如果该属性不
*已经存在。
*/
//如果是数组 并且key是数字 就更新数组
//如果是对象则重新赋值
//如果 (target).__ob__ 存在则表明该数据以前添加过观察者对象中 //通知订阅者ob.value更新数据 添加观察者 define set get 方法
function set(target, key, val) {
if ("development" !== 'production' &&
//判断数据 是否是undefined或者null
(isundef(target) || isprimitive(target)) //判断数据类型是否是string,number,symbol,boolean
) {
//必须是对象数组才可以 否则发出警告
warn(("cannot set reactive property on undefined, null, or primitive value: " + ((target))));
}
//如果是数组 并且key是数字
if (array.isarray(target) && isvalidarrayindex(key)) {
//设置数组的长度
target.length = math.max(target.length, key);
//像数组尾部添加一个新数据,相当于push
target.splice(key, 1, val);
return val
}
//判断key是否在target 上,并且不是在object.prototype 原型上,而不是通过父层原型链查找的
if (key in target && !(key in object.prototype)) {
target[key] = val; //赋值
return val
}
var ob = (target).__ob__; //声明一个对象ob 值为该target对象中的原型上面的所有方法和属性 ,表明该数据加入过观察者中
//vmcount 记录vue被实例化的次数
//是不是vue
if (target._isvue || (ob && ob.vmcount)) {
//如果不是生产环境,发出警告
"development" !== 'production' && warn(
'avoid adding reactive properties to a vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
);
return val
}
//如果ob不存在 说明他没有添加观察者 则直接赋值
if (!ob) {
target[key] = val;
return val
}
//通知订阅者ob.value更新数据 添加观察者 define set get 方法
definereactive(ob.value, key, val);
//通知订阅者ob.value更新数据
ob.dep.notify();
return val
}
/**
* delete a property and trigger change if necessary.
* 删除属性并在必要时触发更改数据。
*/
function del(target, key) {
//如果不是生产环境
if ("development" !== 'production' &&
(isundef(target) || isprimitive(target))
) {
//无法删除未定义的、空的或原始值的无功属性:
warn(("cannot delete reactive property on undefined, null, or primitive value: " + ((target))));
}
//如果是数据则用splice方法删除
if (array.isarray(target) && isvalidarrayindex(key)) {
target.splice(key, 1);
return
}
var ob = (target).__ob__;
//vmcount 记录vue被实例化的次数
//是不是vue
if (target._isvue || (ob && ob.vmcount)) {
//如果是开发环境就警告
"development" !== 'production' && warn(
'avoid deleting properties on a vue instance or its root $data ' +
'- just set it to null.'
);
return
}
//如果不是target 实例化不删除原型方法
if (!hasown(target, key)) {
return
}
//删除对象中的属性或者方法
delete target[key];
if (!ob) {
return
}
//更新数据
ob.dep.notify();
}
/**
* collect dependencies on array elements when the array is touched, since
* we cannot intercept array element access like property getters.
* 在数组被触摸时收集数组元素的依赖关系,因为
* 我们不能拦截数组元素访问,如属性吸收器。
* 参数是数组
*/
function dependarray(value) {
for (var e = (void 0), i = 0, l = value.length; i < l; i++) {
e = value[i];
//添加一个dep
e && e.__ob__ && e.__ob__.dep.depend();
//递归
if (array.isarray(e)) {
dependarray(e);
}
}
}
/* */
/**
* option overwriting strategies are functions that handle
* how to merge a parent option value and a child option
* value into the final value.
* *选项重写策略是处理的函数
*如何合并父选项值和子选项
*值为最终值。
*/
//选择策略
var strats = config.optionmergestrategies;
/**
* options with restrictions
* 选择与限制
*/
{
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)
};
}
/**
* helper that recursively merges two data objects together.
* 递归合并数据 深度拷贝
*/
function mergedata(to, from) {
if (!from) {
return to
}
var key, toval, fromval;
var keys = object.keys(from); //获取对象的keys 变成数组
for (var i = 0; i < keys.length; i++) {
key = keys[i]; //获取对象的key
toval = to[key]; //
fromval = from[key]; //获取对象的值
if (!hasown(to, key)) { //如果from对象的key在to对象中没有
set(to, key, fromval);
} else if (isplainobject(toval) &&
相关文章:
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论