本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
本篇是源码共读第24期 | Vue2工具函数,点击了解本期详情
源码:
工具函数
- emptyObject 冻结对象 Object.freeze() 可以冻结一个对象(浅冻结), 不能向这个对象添加新的对象, 不能删除已有属性。 Object.seal() 对象不可删除, 但可以修改 Object.preventExtensions() 对象不可扩展, 不可新增,但可以修改删除
// 浅冻结
var emptyObject = Object.freeze({})
var freezeObj = Object.freeze({name: 'Hello', age: 18, child: {
name: 'zs',age: 1
}})
freezeObj.name = 'world'
freezeObj.child = 'child' // 第一层无法修改
freezeObj.child.name = 'monica' // 里面可以修改
console.log(freezeObj)
// { name: 'Hello', age: 18, child: { name: 'monica', age: 1 } }
// 深冻结 利用递归方法
function deepFreeze(obj){
// 取回定义在 obj 对象上的属性名, 非原型链属性
var propNames = Object.getOwnPropertyNames(obj)
// 在冻结之前冻结属性
propNames.forEach((name) => {
var prop = obj[name]
if(typeof prop === 'object' && prop !== 'null'){
deepFreeze(prop)
}
})
return Object.freeze(obj)
var obj = {name: 'Hello', age: 18, child: {
name: 'zs',age: 1
}}
deepFreeze(obj)
obj.child.name = 'abc'
console.log(obj);
// { name: 'Hello', age: 18, child: { name: 'zs', age: 1 } }
}
- isUndef 是否未定义
function isUndef(v){
return v === null || v === undefined
}
- isDef 是否已定义
function isDef(v){
return v !== null || v !== undefined
}
- isTrue 是否为true
function isTrue(v){
return v === true
}
- isFalse 是否是false
function isFalse(v){
return v === false
}
- isPrimitive 判断值是否是原始值 原始类型有Number、String、Boolean、Symbol(es6)
function isPrimitive(v){
return {
typeof v === 'number' ||
typeof v === 'string' ||
typeof v === 'boolean' ||
typeof v === 'symbol'
}
}
- isObject 判断是否是对象 null 也是 object类型 数组等用该函数判断:
function isObject(obj){
return obj !== null && typeof obj === 'object'
}
isObject([]) // true
- toRawType 转换成原始类型
// Object.prototype.toStrijng()返回描述某个对象数据类型的字符串
var _toString = Object.prototype.toString
function toRawType(value){
return _toString.call(value).sclice(8, -1)
}
// [object String] ---> 第八位开始,倒数第一位结束, 结果为 String
// [object Date] ---> 第八位开始,倒数第一位结束, 结果为 Date
- isPlainObject 是否是纯对象
function isPlainObject(obj){
return _toString.call(obj) === '[object Object]'
}
isPlainObject([]) // false
isPlainObject({}) //true
- isRegExp 是否是正则表达式
function isRegExp(v){
return _toString.call(v) === '[object Regexp]'
}
isRegExp(/test/) // true
- isValidArrayIndex 是否是可用的数组索引值 数组可用的索引值是0('0')、1('1')...
function isValidArrayIndex(value){
var n = parseFloat(String(value))
return n >= 0 && Math.floor(n) === n && isFinite(value)
}
- isPromise 判断是否是promise
function isPromise(val){
return {
isDef(val) &&
typeof val.then === 'function' &&
typeof val.catch === 'function'
}
}
- toString 转字符串
// 将 null 转为字符串时,返回空字符串
// 将 数组或者 普通对象(isPlainObject) 转为字符串时,经常使用 JSON.stringify()
// val.toString === Object.prototype.toString 重写了对象
// 其他情况 使用String() 将变量转为字符串
function toString(val){
return {
val === null ? '' : Array.isArray(val) ||
(isPlainObject(val) && val.toString === Object.prototype.toString) ?
JSON.stringify(val, null, 2) : String(val)
}
}
var obj = {
name: 'hello',
// 重写了 对象的 toString 方法
toString(){
return 'obj'
}
}
- 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'
- makeMap 生成一个map 对象 传入一个以逗号分隔的字符串,生成一个map(键值对),并返回一个函数检测 key 值在不在这个 map 中。第二个参数是小写选项
function makeMap(str, expectLowerCase){
var map = Object.create(null)
var list = str.split(',')
for(var i in list){
map[list[i]] = true
}
return expectLowerCase ? function(val) { return map[val.toLowerCase()]}
: function(val){ return map[val] }
}
// Object.create(null) 没有原型链的空对象
- isBuiltInTag 是否是内置的tag
var isBuildInTag = makeMap('hello,world', true)
isBuildInTag('hello') // true
isBuildInTag('Hello') // true
isBuildInTag('abc') // false
- isReservedAttribute 是否是保留的属性
var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');
isReservedAttribute('key') // true
isReservedAttribute('ref') // true
isReservedAttribute('slot') // true
isReservedAttribute('slot-scope') // true
isReservedAttribute('IS') // undefined
- remove 移除数组中的某一项
function remove(arr, val){
if(arr.length){
var index = arr.indexOf(val)
if(index > -1){
return arr.splice(index, 1)
}
}
}
- hasOwn 检测是否是自己的属性, 不是通过原型链向上查找的
var hasOwnProperty = Object.prototype.hasOwnProperty
function hasOwn(obj, key){
// .call 则是函数里 this 显示指定以为第一个参数,并执行函数
return hasOwnProperty.call(obj, key)
// return obj.hasOwnProperty(key)
}
hasOwn({}, 'toString') // false
hasOwn({a: '123'}, 'a') // true
- cached 缓存
function cached(fn){
var cache = Object.create(null)
return (function cachedFn(str){
var hit = cache[str]
// fn 为匿名函数 用于处理存储的键值
// fn(str) 匿名函数返回的值
return hit || cache[str] = fn(str)
})
}
// 1. cached 方法接收参数fn函数 该函数用于处理存储的键值
// 2. 该方法返回值是函数,形成闭包,返回的函数接收参数str,作为存储数据的键名
// 3. 执行返回值函数时,根据str参从cache这个存储对象中获取对应的键值,如果获取不到,则对该数据进行存储,并返回键值,实现存和取功能一体。
- camelize 连字符转小驼峰
// on-click => onClick
// (\w) 表示一个字符
var regExp = /-(\w)/g
var camelize = cached(function(str){
return str.replace(regExp, function(_, c){
// _: 匹配到的字符串
// c: 匹配到的子字符串
return c ? c.upperCase() : ''
})
})
- capitalize 首字母转大写
var capitalize = cached(function(str){
return str.charAt(0).toUpperCase() + str.slice(1)
})
- hyphenate 小驼峰转连字符
// \B 非单词边界
var hyphenateRE= /\B([A_Z])/g
var hyphenate = cached(function(str) {
return str.replace(hyphenateRE, '-$1').toLowerCase()
})
- polyfillBind bind的垫片
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)
}
var bind = Function.prototype.bind ? nativeBind : polyfillBind
- toArray 把类数组转为真正的数组
function toArray(list, start){
var start = start || 0;
var i = list.length - start;
var res = new Array(i)
while(i--){
res[i] = list[i + start]
}
return res;
}
toArray([1,2,3,4,5], 2) // [3,4,5]
- extend 合并
function extend(to, _from){
for(var key in _from){
to[key] = _from[key]
}
return to;
}
const toData = {name: 'hello', address: '杭州'}
const fromData = {name: 'hhaa', age: 18}
const extentData = extend(toData, fromData)
console.log(extentData);
// { name: 'hhaa', address: 'hz', age: 18 }
// 合并属性, 相同的属性值时后者替换前者
- toObject 数组转对象
function toObject(arr){
var obj = {}
for(var i = 0; i < arr.length; i++){
if(arr[i]){
obj = extend(arr)
}
}
return obj
}
toObject(['hello']) // {0: 'h', 1: 'e', 2: 'l', 3: 'l', 4: 'o'}
- noop 空函数
function noop(a,b,c){}
- no 一直返回false
var no = function (a,b,c){
return false
}
- identify 返回参数本身
var identify = function(_){return _}
- genStaticKeys 生成静态属性
function genStaticKeys(modules){
return modules.reduce(function(keys, m){
return keys.concat(m.staticKeys || [])
}, []).join(',')
}
- looseEqual 宽松相等 由于数组、对象等是引用类型,所以两个内容看起来是相等的,严格相等都是不相等的。
var a = {}
var b = {}
a === b // false
a == b // false
// 该函数是对数组、日期、对象进行递归比对。如果内容完全相等则宽松相等
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(el, i){
return looseEqual(a, el[i])
})
} else if(a instanceof Date && b instanceof Date){
return a.getTime() === b.getTime()
} else if(!isArrayA && !isArrayB){
var keysA = Object.keys(isArrayA)
var keysB = Object.keys(isArrayB)
return keysA.length === keysB.length && keysA.every(function(key){
return looseEqual(a[key], b[key])
})
} else {
return false
}
} catch {
return false
}
} else if(!isObjectA && !isObjectB) {
return String(a) === String(b)
} else {
return false
}
}
looseEqual(a, b) // true
- looseIndexOf 宽松的 indexOf
function looseIndexOf(arr, val){
for(var i = 0; i < arr.length; i++){
if(looseEqual(arr[i], val) {return i}
}
return -1
}
- once 确保函数只执行一次 利用闭包
function once(fn){
var flag = false
return function(){
if(!flag){
flag = true
fn.apply(this, arguments)
}
}
}
var fn1 = once(function(){
console.log('执行once函数')
})
fn1() // 执行once函数
fn1() // 不输出
fn1() // 不输出
总结 昨天把方法都理解了一遍,今天又手敲了一遍。 每个方法都通过用例测试了一遍。收获确实很多,思维确实也提升了不少。今天是情人节,大家都走光了,办公室剩自己一人,指尖在键盘上飞舞~~~~