Vue 2 源码中的工具函数

326 阅读3分钟

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

源码篇:

笔记篇:

一、判断型函数

1. emptyObject
  • Object.freeze({}); 冻结对象,即对象的第一层不能修改。
var emptyObject = Object.freeze({});
2. isUndef 判断变量是 未定义
function isUndef (v) {
    return v === undefined || v === null
}
​
eg:
isUndef(undefined)   
// true
isUndef(null)        
// true
isUndef(123)         
//false
isUndef('hello')     
// false
3. isDef 判断变量是 已定义
  • 和上述判断正好相反。
 function isDef (v) {
    return v !== undefined && v !== null
 }
4. isTrue 判断变量是否未true。
function isTrue (v) {
    return v === true
}
5. isFalse 判断变量是否未false。
function isFalse (v) {
    return v === false
}
6. isPrimitive 判断变量试是否是 原始数据类型
  • primitive: 原始,初始;

原始数据类型: string number symbol boolean

  function isPrimitive (value) {
    return (
      typeof value === 'string' ||
      typeof value === 'number' ||
      // $flow-disable-line
      typeof value === 'symbol' ||
      typeof value === 'boolean'
    )
  }
​
eg:
isPrimitive('12')
// true
isPrimitive(12)
// true
isPrimitive(false)
// true
isPrimitive([])
// false
isPrimitive({})
// false
7. isObject 判断是否是一个对象 || 数组 类型
  • 不是严格判断 变量是否为object。
function isObject (obj) {
  return obj !== null && typeof obj === 'object'
}
​
eg:
isObject({x: 123})
// true
isObject([1,2,3])
// true
isObject(function() {})
// false
isObject('')
// false
isObject(123)
// false
8. isPlainObject 判断是否为 纯对象
function isPlainObject (obj) {
  return _toString.call(obj) === '[object Object]'
}
​
eg:
isPlainObject({})
// true
isPlainObject([])
// false
isPlainObject('')
// false
9. toRawType 获取原始数据类型。

Tip :Object.prototype.toString(), 表示返回一个表示该对象的字符串

  • 每个object都会有一个toString()方法,默认返回的是 [object type],type 是当前obj的类型。

    let obj = { name: 123 };
    ​
    obj.toString()  
    // [object Object]
    
  • 如果想重写obj上的默认toString()方法,可以

    obj.prototype.toString = function resetToString () {
        return hello ${this.name}
    }
    
  • 使用 Object.prototype.toString 结合 call 或 apply 进行类型判断

    Object.prototype.toString.call(new Date)  
    // '[object Date]'
    Object.prototype.toString.call(Math)  
    // '[object Math]'
    Object.prototype.toString.call(undefined)  
    // '[object Undefined]'
    Object.prototype.toString.call(null)  
    // '[object Null]'
    

参考:Object.prototype.toString() mdn

  • toRawType 得到val的类型

    Object.prototype.toString.call(val).slice(8, -1)
    
10. isRegExp 判断是否是正则表达式
function isRegExp (v) {
  return _toString.call(v) === '[object RegExp]'
}
​
eg:
isRegExp(/hello/)
// true
11. isValidArrayIndex 判断是否是可用的数组索引值
function isValidArrayIndex (val) {
  var n = parseFloat(String(val));
  return n >= 0 && Math.floor(n) === n && isFinite(val)
}

parseFloat : 解析一个参数(必要时先转换为字符串)并返回一个浮点数。parseFloat mdn

isFinite : 判断被传入的参数值是否为一个有限数值。 isFinite mdn

parseFloat('hello')
// NaN
parseFloat(12.34)
// 12.34
isFinite(100)
// true
isFinite(10/3)
// true
isFinite('0')
// true
isFinite(2e12)
// true
isFinite(Infinity)
// false
12. isPromise 判断是否是 promise
function isPromise (val) {
  return (
    isDef(val) &&
    typeof val.then === 'function' &&
    typeof val.catch === 'function'
  )
}
​
eg:
const test = new Promise((resolve, reject) => {
    resolve('hello world')
})
​
isPromise(test)
// true
13. makeMap
  • 输入一个以 , 分割的字符串,生成一个map对象;

  • 输出一个检测key值不在map中的函数;

  • expectsLowerCase: 是否要转化为小写

    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]; }
    }
    
14. isBuiltInTag 是否是内置的tag
var isBuiltInTag = makeMap('slot,component', true);
​
eg:
isBuiltInTag('slot') // true
isBuiltInTag('component') // true
isBuiltInTag('Slot') // true
isBuiltInTag('Component') // true
15. 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') // true
isReservedAttribute('IS') // undefined  大小写

二、转化类函数

1. toString 转字符串
function toString (val) {
  return val == null
    ? ''
    : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
      ? JSON.stringify(val, null, 2)
      : String(val)
}

用 Array.isArray 判断变量是否为数组类型 或者 是Object类型,是则用 JSON.stringify 转换;

若为 其他 类型,用 String() 转换 。

2. toNumber 转数字
  • 通过parseFloat 转数字

    • 转换成功,将成功的部分返回;
    • 转换失败,返回原来的值。
    function toNumber (val) {
        var n = parseFloat(val);
        return isNaN(n) ? val : n
    }
    ​
    eg:
    toNumber(123)
    // 123
    toNumber('123')
    // 123
    toNumber('a')
    // 'a'
    toNumber('123a')
    // 123
    toNumber('a123')
    // 'a123'
    
3. camelize 连字符 转 小驼峰
var camelizeRE = /-(\w)/g;
var camelize = cached(function (str) {
    return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
});
​
eg:
camelize('on-click')   // onClick
4. capitalize 首字母大写
var capitalize = cached(function (str) {
    return str.charAt(0).toUpperCase() + str.slice(1)
});
​
eg:
capitalize('hello')   // 'Hello'
5. hyphenate 小驼峰 转 连字符
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cached(function (str) {
return str.replace(hyphenateRE, '-$1').toLowerCase()
});
​
eg: 
hyphenate('onClick'); // 'on-click'
6. 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
}
​
eg:
function fn(){
  var arr1 = toArray(arguments);
  console.log(arr1); // [1, 2, 3, 4, 5]
  var arr2 = toArray(arguments, 2);
  console.log(arr2); // [3, 4, 5]
}
​
fn(1,2,3,4,5); 
​
​
toArray('1,2,3')
// ['1', ',', '2', ',', '3']
7. toObject 数组转对象
function toObject (arr) {
    var res = {};
    for (var i = 0; i < arr.length; i++) {
        if (arr[i]) {
            extend(res, arr[i]); // 合并
        }
    }
    return res
}
​
eg:
toObject(['hello', 'world'])
// {0: 'w', 1: 'o', 2: 'r', 3: 'l', 4: 'd'}

三、操作类函数

1. remove 从数组中移除某项
  • 移除使用 splice (start,deleteCount) , 返回被删除的元素组成的数组,如果没有删除元素,则返回空数组。

  • 注意splice会修改原数组的值。

  • splice mdn

    function remove (arr, item) {
        if (arr.length) {
            var index = arr.indexOf(item);
            if (index > -1) {
                return arr.splice(index, 1)
            }
        }
    }
    ​
    eg:
    arr = [1,2,3,4];
    // [1, 2, 3, 4]
    arr.splice(1,1);
    // [2]
    console.log(arr);
    // [1, 3, 4]
    
2.hasOwn 检查变量本身是否拥有这个属性
  • 是自身拥有,不是原型链上含有
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn (obj, key) {
    return hasOwnProperty.call(obj, key)
}
​
eg:
Object.prototype.hasOwnProperty.call({}, 'a');
// false
Object.prototype.hasOwnProperty.call({a: 123}, 'a');
// true
Object.prototype.hasOwnProperty.call({}, 'toString');
// false
3.cached 缓存
  • 利用闭包特性,缓存数据
function cached (fn) {
    var cache = Object.create(null);
    return (function cachedFn (str) {
        var hit = cache[str];
        return hit || (cache[str] = fn(str))
    })
}
4. 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;

目的是兼容了老版本浏览器不支持原生的 bind 函数。

5. extend 合并

把第二个变量中的属性_from 合并到第一个变量to 中

function extend (to, _from) {
    for (var key in _from) {
        to[key] = _from[key];
    }
    return to
}
​
eg:
a = {name: 'hello', age: 10};
// {name: 'hello', age: 10}
b = extend(a, {love: 'fe'});
// {name: 'hello', age: 10, love: 'fe'}
合并过后的 a === b  // true
6. noop 空函数
  • 永远返回 false

     function noop (a, b, c) {}
    
7. no 始终返回false
var no = function (a, b, c) { return false; };
8. identity 返回输入的传参
var identity = function (_) { return _; };
9. genStaticKeys 生成静态属性
function genStaticKeys (modules) {
   return modules.reduce(function (keys, m) {
     return keys.concat(m.staticKeys || [])
   }, []).join(',')
}
  • reduce 逐个遍历数组元素,每一步都将当前元素的值与上一步的计算结果相加 reduce mdn
10. looseEqual 判断是否在内容上相等(宽松相等)
  • 对比 array, object, Date的类型,非严格模式下的内容是否相等
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) {
                    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)
    } else {
        return false
    }
}
11. looseIndexOf 宽松的indexOf
  • 返回arr中第一个与val 宽松相等的索引值, 否则返回 -1
function looseIndexOf (arr, val) {
    for (var i = 0; i < arr.length; i++) {
        if (looseEqual(arr[i], val)) { return i }
    }
    return -1
}
12. once 函数只执行一次
function once (fn) {
    var called = false;
    return function () {
        if (!called) {
            called = true;
            fn.apply(this, arguments);
        }
    }
}
​
eg: 
 const func = once(() => console.log('hello world'))
 
 func()  // hello world
 func() // undefined
 func() // undefined