【若川视野 x 源码共读】第24期 | vue2工具函数

168 阅读6分钟

前言

本文是参与若川的源码共读活动第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'
   ]

不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶。 ——文森特・梵高