Vue源码相关函数分析
// 判断属性是否是内置属性
function isNative (Ctor) {
return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}
// 返回一个str循环n次的字符串
// 比如str=‘’a”,n=5,二进制中为101,while第一次循环,res+=‘a’,str+=‘a’,n的二进制右移一位,变成10,也就是十进制的2
// 第二轮循环,str=‘aa’,res不变,str+=‘aa’,再右移一位变成1,等于十进制的1
// 第三轮循环,str=‘aaaa’,res += ‘aaaa’,变成‘aaaaa’,二进制再右移一位变成0,此时循环条件不再满足,循环结束
// tip: 由此可知二进制每右移一位相当于Math.floor(n/2)用个for循环就能实现,不知道为啥作者非要这么骚气
var repeat = function (str, n) {
var res = '';
while (n) {
if (n % 2 === 1) { res += str; }
if (n > 1) { str += str; }
n >>= 1;
}
return res
};
// js every和some方法
// every()是对数组中每一项运行给定函数,如果该函数对每一项返回true,则返回true。
// some()是对数组中每一项运行给定函数,如果该函数对任一项返回true,则返回true。
var arr = ['1',2, '3']
// every, 结果: false
console.log(
arr.every((item, index,array) => {
return typeof item === 'string'
})
)
// some, 结果:true
console.log(
arr.some((item, index,array) => {
return typeof item === 'string'
})
)
// looseEqual意为 ‘宽松比较’,用来比较传入值a和b是否相等
function looseEqual (a, b) {
var isObject = function (obj) {
return obj !== null && typeof obj === 'object'
}
if (a === b) { return true } // 基本数据类型相等直接返回true
var isObjectA = isObject(a);
var isObjectB = isObject(b);
if (isObjectA && isObjectB) { // a和b都为对象
try {
var isArrayA = Array.isArray(a);
var isArrayB = Array.isArray(b);
if (isArrayA && isArrayB) { //a和b都为数组
return a.length === b.length && a.every(function (e, i) { // 长度相等并且递归的每个元素相等
return looseEqual(e, b[i])
})
} else if (a instanceof Date && b instanceof Date) { // a和b都为Date类型
return a.getTime() === b.getTime()
} else if (!isArrayA && !isArrayB) { // a和b都是对象
var keysA = Object.keys(a);
var keysB = Object.keys(b);
return keysA.length === keysB.length && keysA.every(function (key) { // key值长度相等并且递归的每个对象元素相等
return looseEqual(a[key], b[key])
})
} else {
/* istanbul ignore next */
return false
}
} catch (e) {
/* istanbul ignore next */
return false
}
} else if (!isObjectA && !isObjectB) {
return String(a) === String(b) // 转换为String,比较是否相等
} else {
return false
}
}
// 闭包实现一个函数只被访问一次
function once (fn) {
var called = false;
return function () {
if (!called) {
called = true;
fn.apply(this, arguments);
}
}
}
// 调用demo
function test () {
console.log(111111)
}
var a = once(test)
a() // 输出111111
a() // 不输出
// 检查一个字符串是否以
或
者
开
头
,
s
t
r
+
′
′
是
将
s
t
r
转
换
为
字
符
串
类
型
。
实
际
上
打
印
一
个
以
或者_开头,str+''是将str转换为字符串类型。实际上打印一个以
或者开头,str+′′是将str转换为字符串类型。实际上打印一个以开头的字符串会发现值为36,而36 === 0x24又是true,why? 因为0x24是ASCII的‘
’
,
而
36
是
u
n
i
c
o
d
e
的
‘
’, 而36是unicode的‘
’,而36是unicode的‘’,不同编码格式而已。
// 说到这里就再缀几句,ASCII编码为8位二进制格式,有一位为0,所以一共128个字符,占一个byte;Unicode能够表示全世界所有的字节,Unicode最常用的是用两个byte表示一个字符(如果要用到非常偏僻的字符,就需要4个byte
function isReserved (str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5F
}
// 又是一段很有意思的代码,先从字面理解吧,所谓supportsPassive中文指的就是是否支持passive,那passive又是什么呢?
// **关于passive的解释:**很多移动端的页面都会监听 touchstart 等 touch 事件,由于 touchstart 事件对象的 cancelable 属性为 true,也就是说它的默认行为可以被监听器通过 preventDefault() 方法阻止,那它的默认行为是什么呢,通常来说就是滚动当前页面(还可能是缩放页面),如果它的默认行为被阻止了,页面就必须静止不动。但浏览器无法预先知道一个监听器会不会调用 preventDefault(),它能做的只有等监听器执行完后再去执行默认行为,而监听器执行是要耗时的,有些甚至耗时很明显,这样就会导致页面卡顿。即便监听器是个空函数,也会产生一定的卡顿,毕竟空函数的执行也会耗时。
// 有 80% 的滚动事件监听器是不会阻止默认行为的,也就是说大部分情况下,浏览器是白等了。所以,passive 监听器诞生了,passive 的意思是“顺从的”,表示它不会对事件的默认行为说 no,浏览器知道了一个监听器是 passive 的,它就可以在两个线程里同时执行监听器中的 JavaScript 代码和浏览器的默认行为了。
具体分析请看注释:)
var supportsPassive = false;
if (inBrowser) {
try {
var opts = {};
Object.defineProperty(opts, 'passive', ({ // 空对象定义一个passive属性
get: function get() {
/* istanbul ignore next */
supportsPassive = true;
}
}));
// 监听test-passive和事件函数,两者本身都不存在,opts调用了一次,如果opts的passive被触发,那么就会触发passive属性的get方法,修改supportsPassive为true;
window.addEventListener('test-passive', null, opts);
} catch (e) {}
}
// formatComponentName:规范化组件名字
formatComponentName = function (vm, includeFile) { // 组件的$root为自身时,也就代表不是子组件
if (vm.$root === vm) {
return '<Root>'
}
// options包含组件的data对象、methods对象、template、挂载元素el、生命周期钩子等等当前组件的所有信息
var options = typeof vm === 'function' && vm.cid != null
? vm.options
: vm._isVue
? vm.$options || vm.constructor.options
: vm;
// name为定义的组件名称
var name = options.name || options._componentTag;
// options.__file为文件路径
var file = options.__file;
if (!name && file) {
// 只有路径没有名称时,匹配不存在正反斜杠,并以.vue结尾的字符串,也就是文件名
var match = file.match(/([^/\\]+)\.vue$/);
name = match && match[1]; // name 为不包含后缀的文件名
}
return (
// classify为另一个正则的函数,用来将字符串首字母大写并且去掉 "-" 及 "_" 字符
(name ? ("<" + (classify(name)) + ">") : "<Anonymous>") +
(file && includeFile !== false ? (" at " + file) : '') // 提示文件具体位置,很明显,该函数输出提示语并提示该如何书写标签名
)
};
// 767到853行,定义VNode和原型方法,在此不缀了,比较容易理解
// 定义一个以数组原型为原型的对象
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
// 该语句判断‘proto‘隐式原型是否在空对象中
// in :判断属性是否在指定的对象或者其原型链中
var hasProto = '__proto__' in {};
// 定义一个响应式的属性,入参:对象、属性key值、属性的值、警告函数,浅定义
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) { // 属性描述符不能改变,且属性不能删除
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
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.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
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 (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
// TODO…