前言
本文是参与若川的源码共读活动第24期
vue等开源项目一般在README..github/contributing.md里面有贡献指南
--贡献指南有关于参与项目开发的信息,例如如何运行项目,项目的目录结构,如何开发,需要哪些知识储备等。
本文的工具函数是vue的shared模块里的内容,文件路径是:vue/vue/src/shared, github在线路径是github1s vue/vue/vue/src/shared
工具函数
以下是打包后的vue.js14行到379行
1、polyfillBind bind 的··垫片
兼容不能使用bind的老版本浏览器 ----手写实现bind、apply、call
function polyfillBind(fn, ctx) {
function bondFn(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);
}
var bind = Function.prototype.bind ? nativeBind : polyfillBind
2、looseEqual 宽松相等
因为数组和对象是引用类似,所以两个类型、内容一样的数据是不相等的
该函数是对于同一类型、内容相比较相等的引用数据,进行宽松相等
// 1、简单类型直接查看是否全等
// 2、判断数值类型是否为对象或者数组引用类型
// 3、判断是两者是否为数组,若都为数组,取数组长度进行比较,后循环数组的每一项进行递归比较
// 4、判断数值是否为时间类型,把时间处理为毫秒数进行比较
// 5、判断两者数值是否为对象,若都为对象,取对象的键值长度比较,后进行键值数组循环递归比较
function looseEqual(a, b) {
if(a === b) { return true }
var isObjectA = isObject(a); // 判断是否为对象或者是数组
var isObjectB = isObject(b);
if(isObjectA && isObjectB) {
try {
var isArrayA = Array.isArray(a);
var isArrayB = Array.isArray(b);
if(isArrayA && isArrayB) {
return a.length === b.length && a.every(function (e, i) {
return looseEqual(e, b[i]) })
} else if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime()
} else if(!isArrayA && !isArrayB) {
var keysA = Object.keys(a);
var keysB = Object.keys(b);
return keysA.length === keysB.length && keysA.every(function(key) {
// every 所有条件都满足返回true
return looseEqual(a[key], b[key])
})
}
} catch (e) { console.log(e) }
}
}
3、looseIndexOf 宽松的indexOf
该函数实现的是宽松的indexof查找,原生的是严格相等的
function looseIndexOf(arr, val) {
for(var i = 0; i < arr.length; i ++) {
if(looseEqual(arr[i], val)) {
return i;
}
}
return -1;
}
4、makeMap生成一个map(对象)
传入一个以逗号分隔的字符串,生成一个map(键值对),并且返回一个函数,检测key值在不在这个map中。第二个参数是小写选项
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;
}
return expectsLowerCase ?
function (val) { return map[val.toLowerCase()]}; :
function (val) { return map[val]};
}
let mapObject = makeMap('str, list, test')
mapObject('str') // true
mapObject('value') // undefined
5、isBuiltinTag是否是内置的tag,检测map对象里面是否有某个键值,不区分大小写
var isBuiltInTag = makeMap('slot, component', true); // 第二个参数不区分大小写 isBuiltInTag('slot') // true
isBuiltInTag('Slot') // true
isBuiltInTag('component') // true
6、isReservedAttribute 是否是保留属性,同上 区分大小写
var isReservedAttribute = makeMap('key, ref, slot, slot-scope, is');
isReservedAttribute('key') // true
isReservedAttribute('KEy') // undefined
7、cached 缓存
利用闭包的特性,缓存数据
function cached(fn) {
var cache = Object.create(null); // 创造一个没有原型的对象
return (function cacheFn(str) {
var hit = cache[str];
return hit || (cache[str] = fn(str))
})
}
8、camelize连字符转小驼峰
连字符串->小驼峰 on-click->onClick
var camelizeRE = /-(\w)/g;
var camelize = cached(function(str) {
return str.replace(camelizeRE, function(_, c) {
return c ? c.toUpperCase(): '';
})
})
camelize('on-click') // onClick
9、 首字母转大写
var capitalize = cached(function (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
})
capitalize('list') // List
10、小驼峰转连字母
onClick -> on-click
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cached(function (str) {
return str.replace(hyphenateRE, '-$1').toLowerCase() // '-$1' 匹配-符号后面的第一个字母
})
hyphenate('oNClick') // o-n-click
vue的模板字符串原理-使用的正则表达式进行匹配更换再渲染
let name="张三"
let age = '18'
let desc = "${name}今年${age}岁了"
function replace(desc) {
return desc.replace(/\$\{([^}]+)\}/g,function(mathed,key,c,d){
console.log(mathed,key,c,d)
//${name} name 0 ${name}今年${age}岁了
//${age} age 9 ${name}今年${age}岁了
return eval(key)
})
}
replace(desc) //'张三今年18岁了'
11、once函数只执行一次
// 利用闭包的特性存储一个布尔值,第一次的时候改变了布尔值,下次就不能进行调用了
function once(fn) {
var called = false;
return function () {
if(!called) {
called = true;
fn.apply(this, arguments)
}
}
}
const fn1 = once(function() { console.log('只调用一次哦') })
fn1(); // 只调用一次哦
fn1(); // 不输出
12、emptyObject 空对象
var emptyObject = Object.freeze({}) // 冻结对象--对象的第一层无法更改,第二层数据可以更改
Object.isFrozen(emptyObject); // true 判断对象是否被冻结
13、isUndef是否未定义
function isUndef(v) {
return v === undefined || v === null
}
14、isDef是否已定义
JavaScript中假值有六个---false、null、undefined、0、‘’(空字符串)、NaN
为了进行准确的判断,vue2源码中封装了isDef、isTrue、isFalse函数来准确判断数值
function isDef(v) {
return v !== undefined && v !== null
}
15、isTrue 是否是true
function isTrue(v) {
return v === true
}
16、isFalse是否是false
function isFalse(v) {
return v === false
}
17、isRegExp 是否是正则表达式
let _toSring = Object.prototype.toString;
function isRegExp(v) {
return _toString.call(v) === '[Object RegExp]'
}
isRegExp(/text/)
18、isObject 判断是否为对象
// typeof null 为‘object’。可以用来判断数组、对象或者函数
function isObject(obj) {
return obj !== null && typeof obj === 'object'
}
isObject([]) // true // 有时候不需要严格区分数组和对象
19、isPlainObject 是否是纯对象
function isPlainObject(obj) {
return _toString.call(obj) === '[Object Object]'
}
isObject([]) // true ---typeof [] /typeof {} 都为object
isPlainObject([]) // false
isPlainObject({}) // true
20、isPrimitive判断值是否是原始值
判断是否是字符串、或者数字、或者symbol、或者布尔值
function isPrimitive(value) {
return (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}
21、toRawType 转换成原始类型
// object.prototype.toString.call() 返回一个表示该对象的字符串
var _toString = Object.prototype.toString; // [Object Object]
function toRawType(value) {
return _toString.call(value).slice(8, -1) // slice(start, end) end为负数,表示倒数第几个
}
toRawType('') // string
toRawType() // undefined
toRawType([]) // Object
22、remove移除数组中的某一项
function remove(arr, item) {
if(arr.length) {
var index = arr.indexOf(item);
if(index > -1) {
return arr.splice(index, 1);
}
}
}
// splice 是一个很耗性能的方法,删除数组中的一项,其他元素都要移动位置
// 因为有许多拦截器,所以要做性能处理
// axios拦截器中的移除拦截器,采用的是把需要移除的拦截器设置为null,当执行到null时就跳过
23、hasOwn 检测是否是自己本身的属性
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key) // call改变this指向,把this指向为第一个参数,并立刻执行
}
hasOwn({__proto__: {a: 1}}, 'a'); // true __proto__属于原型上面的属性,不属于自身属性
hasOwn({a: undefined}) // true
hasOwn({}, 'hasOwnProperty') // false
24、toArray把类数组转成真正的数组
支持起始位置的传参,默认为0
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
}
function fn() {
var arr1 = toArray(arguments); // [1,2,3,4,5] console.log(arr1);
var arr2 = toArray(arguments, 2); // [3,4,5] console.log(arr2)
}
fn(1,2,3,4,5)
25、toObject转对象
function toObject(arr) {
var res = {};
for(var i = 0; i < arr.length; i++) {
if(arr[i]) {
extend(res, arr[i])
}
}
return res
}
// 数组转对象,拆分了数组的每一项的每个值
toObject(['array','list']) // {0: "l", 1: "i", 2: "s", 3: "t", 4: "y"}
26、isValidArrayIndex 是否是可用的数组索引值
数组可用的索引值是--(转换为数值之后是整数数值)0(‘0’)、1(‘1’)、2(‘2’)...
function isValidArrayIndex(val) {
var n = parseFloat(String(val));
return n >= 0 && Math.floor(n) == n && isFinite(val)
}
// isFinite() 判断被传入的参数值是否为一个有限数值(finite number) .参数会先转为一个数值 isFinite(Infinity) // false
isFinite(NaN) // false
isFinite(-Infinity) // false
isFinite(0) // true
isFinite(2e64) // true, isFinite(null) // true , 在Number.isFinite(null)中会得到false isFinite('0') // true ,Number.isFinite('0')--false
27、isPromise判断是否是promise
function isPromise(val) {
return (
isDef(val) && // 判断是否已定义
typeof val.then === 'function' &&
typeof val.catch === 'function'
)
}
const p1 = new Promise(function(resolve, reject) {
resolve('hello')
});
isPromise(p1); // true
28、toString转字符串
数组或者对象的toString方法是Object.prototype.toString,对象或者数据用JSON.stringify转换
function toSring(val) {
return val == null ? '' : Array.isArray(val) ||
(isPlainObject(val) && val.toString === _toString) ?
JSON.stringify(val, null, 2) : String(val)
}
29、toNumber 转数字
如果转换失败会返回原始字符串
function toNumber(val) {
var n = parseFloat(val);
return isNaN(n) ? val : n;
}
toNumber('a') // 'a'
toNumber('1') // 1
toNumber('1a') // 1
toNumber('a1') // a1
30、extend合并
function extend(to, _from) {
for(var key in _from) {
to[key] = _from[key];
}
return to
}
// 示例
const data = { label: 'a'};
const data_2 = extend(data, {label: 'a', name: 'b'})
console.log(data, data_2) // {label: 'a', name: 'b'} {label: 'a', name: 'b'}
console.log(data === data_2) // true
31、noop空函数
function noop(a, b, c){}
32、 no 只返回false
var no = function (a, b, c) {return false;}
33、identity 返回参数本身
var identity = function(_) {return _;}
34、genStaticKeys生成静态属性(获取·模块的静态属性的key值)
function genStaticKeys(modules) {
return modules.reduce(function (keys, m) {
return keys.concat(m.staticKeys || [])
},[]).join(',')
}
35、生命周期
var SSR_ATTR = 'data-server-rendered';
var ASSET_TYPES = [ 'component', 'directive', 'filter' ]
var LIFECYCLE_HOOKS = [
'beforeCreate',
'created', 'beforeMount',
'mounted',
'beforeUpdate',
'update',
'beforeDestroy',
'destroy',
'activated',
'deactivated
'errorCaptured',
'serverPrefetch'
]
不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶。 ——文森特・梵高