为什么开始?
很早就有阅读vue源码的想法,
迫于入行不久,功力太过浅认为啃不太动就一直搁置
年后有段时间看了lodash的部分源码
发现源码也不是完全看不懂
不过,当时看得懂,没做过什么笔记过段时间看又忘了
之前在网上断断续续的看过其他道友发的vue的源码理解,也是看完没有印象了,
索性这次直接给他看的时候加上备注和理解,
方便下次看的时候更快理解吧
- 看到现在有一些地方还是有些迷迷糊糊的
每天看个一点点,日拱一卒。 耶巴蒂莱维贝贝!!!!
特别提醒
每个方法都有备注 部分有理解
var SSR_ATTR = 'data-server-rendered';
// 全局函数
var ASSET_TYPES = [
'component',
'directive',
'filter'
];
// 声明周期
var LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured',
'serverPrefetch'
];
/* */
// vue的全局配置
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?
* 开发按摩时下控制台显示生产提示
*/
productionTip: "development" !== 'production',
/**
* Whether to enable devtools
* 浏览器vue插件调试工具检查代码
*/
devtools: "development" !== 'production',
/**
* Whether to record perf
* 性能追踪
*/
performance: false,
/**
* Error handler for watcher errors
* ;指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时,可获取错误信息和 Vue 实例。
*/
errorHandler: null,
/**
* Warn handler for watcher warns
* Vue 的运行时警告赋予一个自定义处理函数
*/
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.
*/
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,
/**
* Perform updates asynchronously. Intended to be used by Vue Test Utils
* This will significantly reduce performance if set to false.
*/
async: true,
/**
* Exposed for legacy reasons
*/
_lifecycleHooks: LIFECYCLE_HOOKS
});
/* */
/**
* unicode letters used for parsing html tags, component names and property paths.
* using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname
* skipping \u10000-\uEFFFF due to it freezing up PhantomJS
*/
var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;
/**
* Check if a string starts with $ or _
* 判断字符串是否以_开头
*/
function isReserved (str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5F
}
/**
* Define a property.
* 在一个对象上定义一个属性的构造函数
*/
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable, // 强制转换成布尔值
writable: true,
configurable: true
});
}
/**
* Parse simple path.
* unicodeRegExp.source 去掉了开头和结尾的/
* "a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD"
* 不太清除这个方法是干什么的
* 先匹配一个地址 如果匹配到bailRE的地址 return掉
* 没匹配到 通过.分隔 使用闭包将数组保存在内存中 返回一个方法
* 方法入参为一个对象 循环内存中的数组 如果入参中 对象为空直接return
* 如果入参不为空 将obj中的 数组中值对应key的值赋值给obj
* 其实不太明白具体是干什么用的
*/
var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]"));
function parsePath (path) {
if (bailRE.test(path)) {
return
}
var segments = path.split('.');
return function (obj) {
for (var i = 0; i < segments.length; i++) {
if (!obj) { return }
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;
var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
var UA = inBrowser && window.navigator.userAgent.toLowerCase();
var isIE = UA && /msie|trident/.test(UA);
var isIE9 = UA && UA.indexOf('msie 9.0') > 0;
var isEdge = UA && UA.indexOf('edge/') > 0;
var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android');
var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios');
var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge;
var isPhantomJS = UA && /phantomjs/.test(UA);
var isFF = UA && UA.match(/firefox\/(\d+)/);
// Firefox has a "watch" function on Object.prototype...
// 火狐浏览器中的对象原型上有一个watch方法
var nativeWatch = ({}).watch;
var supportsPassive = false;
// 这个代码块是用来检查浏览器是否支持passive属性的。
// passive能优化页面的滚动性能
// 在使用addEventListener添加事件时,
// 如下做,支持passive属性,则使用这一属性,
// 否则addEventListener的第三个参数按照旧版的DOM 标准,第三个参数为布尔值,默认为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
// 判断当前执行环境是不是服务器端 即node环境下执行的
var _isServer;
var isServerRendering = function () {
if (_isServer === undefined) {
/* istanbul ignore if */
if (!inBrowser && !inWeex && typeof global !== 'undefined') {
// detect presence of vue-server-renderer and avoid
// Webpack shimming the process
_isServer = global['process'] && global['process'].env.VUE_ENV === 'server';
} else {
_isServer = false;
}
}
return _isServer
};
// detect devtools
// vue的工具方法
var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
/* istanbul ignore next */
// 判断一个方法是否是js内置方法的 方法
function isNative (Ctor) {
return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}
// 判断 es6 Symbol 和 Reflect 属性是否支持
var hasSymbol =
typeof Symbol !== 'undefined' && isNative(Symbol) &&
typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys);
var _Set;
/* istanbul ignore if */ // $flow-disable-line
// 判断 es6 Set 是否支持
if (typeof Set !== 'undefined' && isNative(Set)) {
// use native Set when available.
_Set = Set;
} else {
// 不支持就手动实现一个set
// a non-standard Set polyfill that only works with primitive keys.
/*
手动实现set 的方法
使用自执行函数
*/
_Set = /*@__PURE__*/(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;
}());
}
/* */
// 给warn一个空方法
var warn = noop;
var tip = noop;
var generateComponentTrace = (noop); // work around flow check
var formatComponentName = (noop);
{
var hasConsole = typeof console !== 'undefined';
// 这个正则就是把连接符转换成的大驼峰写法, 并且第一个字符大写 ^|[-_] 的意思是 字符串的开头, 或者 -_ 后面的一个字符
var classifyRE = /(?:^|[-_])(\w)/g;
var classify = function (str) { return str
.replace(classifyRE, function (c) { return c.toUpperCase(); })
.replace(/[-_]/g, ''); };
/**
* 警告日志输出
*/
warn = function (msg, vm) {
var trace = vm ? generateComponentTrace(vm) : '';
if (config.warnHandler) {
config.warnHandler.call(null, msg, vm, trace);
} else if (hasConsole && (!config.silent)) {
console.error(("[Vue warn]: " + msg + trace));
}
};
/**
* 提示日志输出
*/
tip = function (msg, vm) {
if (hasConsole && (!config.silent)) {
console.warn("[Vue tip]: " + msg + (
vm ? generateComponentTrace(vm) : ''
));
}
};
// 格式化组件名
formatComponentName = function (vm, includeFile) {
// 判断是否是根组件
if (vm.$root === vm) {
return '<Root>'
}
var options = typeof vm === 'function' && vm.cid != null
? vm.options
: vm._isVue
? vm.$options || vm.constructor.options
: vm;
var name = options.name || options._componentTag;
var file = options.__file;
if (!name && file) {
var match = file.match(/([^/\\]+)\.vue$/);
name = match && match[1];
}
return (
(name ? ("<" + (classify(name)) + ">") : "<Anonymous>") +
(file && includeFile !== false ? (" at " + file) : '')
)
};
// n个字符串
var repeat = function (str, n) {
var res = '';
while (n) {
if (n % 2 === 1) { res += str; }
if (n > 1) { str += str; }
n >>= 1;
}
return res
};
/*
组件树规则
没太看明白
*/
generateComponentTrace = function (vm) {
if (vm._isVue && vm.$parent) {
var tree = [];
var currentRecursiveSequence = 0;
while (vm) {
if (tree.length > 0) {
var last = tree[tree.length - 1];
if (last.constructor === vm.constructor) {
currentRecursiveSequence++;
vm = vm.$parent;
continue
} else if (currentRecursiveSequence > 0) {
tree[tree.length - 1] = [3, currentRecursiveSequence];
currentRecursiveSequence = 0;
}
}
tree.push(vm);
vm = vm.$parent;
}
return '\n\nfound in\n\n' + tree
.map(function (vm, i) { 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
* directives subscribing to it.
*/
/**
* 注册订阅 订阅构造函数
* 订阅者数据收集
* this.subs 订阅者数组
*/
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
// 添加订阅者
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
// 删除一个订阅者
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
// 为Watcher 添加 为Watcher.newDeps.push(dep); 一个dep对象
/*
设置某个Watcher的依赖
理解成改变watcher执行的作用域
*/
Dep.prototype.depend = function depend () {
//添加一个dep target 是Watcher dep就是dep对象
if (Dep.target) {
Dep.target.addDep(this);
}
};
// 触发订阅 也就是发布
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
if (!config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
// 根据id升序排序
subs.sort(function (a, b) { return a.id - b.id; });
}
// 循环执行订阅者中的update方法,进行订阅状态更新通知
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
/**
* 全局唯一的观察者
*/
Dep.target = null;
var targetStack = [];
/**
* dep
*/
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
function popTarget () {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
/*
虚拟dom
*/
var VNode = function VNode (
tag,
data,
children,
text,
elm,
context,
componentOptions,
asyncFactory
) {
this.tag = tag;
this.data = data;
this.children = children;
this.text = text;
this.elm = elm;
this.ns = undefined;
this.context = context;
this.fnContext = undefined;
this.fnOptions = undefined;
this.fnScopeId = undefined;
this.key = data && data.key;
this.componentOptions = componentOptions;
this.componentInstance = undefined;
this.parent = undefined;
this.raw = false;
this.isStatic = false;
this.isRootInsert = true;
this.isComment = false;
this.isCloned = false;
this.isOnce = false;
this.asyncFactory = asyncFactory;
this.asyncMeta = undefined;
this.isAsyncPlaceholder = false;
};
var prototypeAccessors = { child: { configurable: true } };
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
prototypeAccessors.child.get = function () {
return this.componentInstance
};
Object.defineProperties( VNode.prototype, prototypeAccessors );
// 创建空的虚拟dom
var createEmptyVNode = function (text) {
if ( text === void 0 ) text = '';
var node = new VNode();
node.text = text;
node.isComment = true;
return node
};
// 创建文本dom 即一个字符串
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操作依赖于它们的elm引用时,克隆它们可以避免错误
function cloneVNode (vnode) {
var cloned = new VNode(
vnode.tag,
vnode.data,
// #7975
// clone children array to avoid mutating original in case of cloning
// a child.
vnode.children && vnode.children.slice(),
vnode.text,
vnode.elm,
vnode.context,
vnode.componentOptions,
vnode.asyncFactory
);
cloned.ns = vnode.ns;
cloned.isStatic = vnode.isStatic;
cloned.key = vnode.key;
cloned.isComment = vnode.isComment;
cloned.fnContext = vnode.fnContext;
cloned.fnOptions = vnode.fnOptions;
cloned.fnScopeId = vnode.fnScopeId;
cloned.asyncMeta = vnode.asyncMeta;
cloned.isCloned = true;
return cloned
}
/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on Array prototype
*/
// 对数组的原型方法进行重写
/*
tips:
数组和对象不同的是 数组不能通过 getter/setter 来监听变化
因此需要通过拦截器来实现监听变化
*/
// 拦截器 arrayProto
// 每当使用数组上的原型方法时
// 实际执行的是拦截器上面的方法 也就是使用拦截器上的 数组的原型方法
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.forEach(function (method) {
// cache original method
// 缓存原始方法 即 Array 上的方法
var original = arrayProto[method];
/*
tips:
下面代码等同于于
Object.defineProperty(arrayMethods, method, {
enumerable: false,
configurable: true,
writable: true,
value: function mutator () {
....
}
})
*/
def(arrayMethods, method, function mutator () {
// 将类数组对象 arguments 转换成真数组中
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
// 调用Array原型上的方法 拿到执行结果
var result = original.apply(this, args);
// 数组新插入的元素需要重新进行observe才能响应式
var ob = 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();
return result
});
});
/*
Object.getOwnPropertyNames
一个对象,其自身的可枚举和不可枚举属性的名称被返回。
Object.getOwnPropertyNames(arrayMethods); 返回 []
MDN上的例子
var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]
// 类数组对象
var obj = { 0: "a", 1: "b", 2: "c"};
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]
*/
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.
*/
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
};