【源码共读】vue2工具函数

110 阅读6分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动点击了解详情一起参与。

本篇是源码共读第24期 | Vue2工具函数,点击了解本期详情

源码:

github.com/vuejs/vue/b…

工具函数

  1. 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 } }
}
  1. isUndef 是否未定义
function isUndef(v){
    return v === null || v === undefined
}
  1. isDef 是否已定义
function isDef(v){
    return v !== null || v !== undefined
}
  1. isTrue 是否为true
function isTrue(v){
    return v === true
}
  1. isFalse 是否是false
function isFalse(v){
    return v === false
}
  1. isPrimitive 判断值是否是原始值 原始类型有Number、String、Boolean、Symbol(es6)
function isPrimitive(v){
    return {
        typeof v === 'number' ||
        typeof v === 'string' ||
        typeof v === 'boolean' ||
        typeof v === 'symbol'
    }
}
  1. isObject 判断是否是对象 null 也是 object类型 数组等用该函数判断:
function isObject(obj){
    return obj !== null && typeof obj === 'object'
}

isObject([])  // true
  1. 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
  1. isPlainObject 是否是纯对象
function isPlainObject(obj){
    return _toString.call(obj) === '[object Object]'
}
isPlainObject([])  // false
isPlainObject({})  //true
  1. isRegExp 是否是正则表达式
function isRegExp(v){
    return _toString.call(v) === '[object Regexp]'
}

isRegExp(/test/)  // true
  1. isValidArrayIndex 是否是可用的数组索引值 数组可用的索引值是0('0')、1('1')...
function isValidArrayIndex(value){
    var n = parseFloat(String(value))
    return n >= 0 && Math.floor(n) === n && isFinite(value)
}
  1. isPromise 判断是否是promise
function isPromise(val){
    return {
        isDef(val) &&
       typeof val.then === 'function' &&
       typeof val.catch === 'function'
    }
}
  1. 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'
    }
}
  1. 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'
  1. 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)  没有原型链的空对象
  1. isBuiltInTag 是否是内置的tag
var isBuildInTag = makeMap('hello,world', true)
isBuildInTag('hello')  // true
isBuildInTag('Hello')  // true
isBuildInTag('abc')  // false
  1. 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
  1. remove 移除数组中的某一项
function remove(arr, val){
    if(arr.length){
        var index = arr.indexOf(val)
        if(index > -1){
            return arr.splice(index, 1)
        }
    }
}
  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
  1. 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这个存储对象中获取对应的键值,如果获取不到,则对该数据进行存储,并返回键值,实现存和取功能一体。

  1. 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() : ''
        })
    })
  1. capitalize 首字母转大写
var capitalize = cached(function(str){
    return str.charAt(0).toUpperCase() + str.slice(1)
}) 
  1. hyphenate 小驼峰转连字符
// \B 非单词边界
var hyphenateRE= /\B([A_Z])/g
var hyphenate = cached(function(str) {
    return str.replace(hyphenateRE, '-$1').toLowerCase()
})
  1. 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
  1. 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]
  1. 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 }
// 合并属性, 相同的属性值时后者替换前者
  1. 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'}
  1. noop 空函数
function noop(a,b,c){}
  1. no 一直返回false
var no = function (a,b,c){
    return false
}
  1. identify 返回参数本身
var identify = function(_){return _}
  1. genStaticKeys 生成静态属性
function genStaticKeys(modules){
    return modules.reduce(function(keys, m){
        return keys.concat(m.staticKeys || [])
    }, []).join(',')
}
  1. 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

  1. looseIndexOf 宽松的 indexOf
function looseIndexOf(arr, val){
    for(var i = 0; i < arr.length; i++){
        if(looseEqual(arr[i], val) {return i}
    }
    return -1
}
  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()  // 不输出

总结 昨天把方法都理解了一遍,今天又手敲了一遍。 每个方法都通过用例测试了一遍。收获确实很多,思维确实也提升了不少。今天是情人节,大家都走光了,办公室剩自己一人,指尖在键盘上飞舞~~~~