第24期:vue.js工具函数源码解读
一、学习前言
1.1 本文参加了由公众号@若川视野发起的每周源码共读活动,点击了解详情一起参与。
1.2 源码阅读辅助文档:juejin.cn/post/702427…
1.3 在线调试地址:github1s vue/vue/src/shared
二、学习目标
2.1 学习vue中的工具函数,学以致用
三、源码解读
3.1 Object.freeze({})
用来表示一个冻结对象(数组也是对象),当前对象的第一层不可变更。对象中有判断对象是否冻结的方法
const obj = Object.freeze({
name: 11,
age: 18
})
console.log(obj.age); // 18
obj.age = 20
console.log(obj) // 18
console.log(Object.isFrozen(obj)) // true
Tips: vue项目中通常会将一些属性/方法挂载在vue原型上,也可以使用Object.freeze(),防止挂载的属性被重写
Object.freeze(Vue.prototype.$apis)
3.2 isUndef isDef isTrue isFalse
// 是否未定义
function isUndef(val) {
return val === undefined || val === null
}
// 是否已定义
function isDef(val) {
return val !== undefined && val !== null
}
// 是否是true
function isTrue(val) {
return val === true
}
// 是否是false
function isFalse(val) {
return val === false
}
3.3 Check if value is primitive. 判断数据是不是原始类型
function isPrimitive(value){
return (typeof value === String || typeof value === Number || typeof value === Boolean || typeof value === Symbol)
}
Tips:
- JS中的值有两种类型:原始类型(Primitive)、对象类型(Object)。
- 原始类型包括:Undefined、Null、Boolean、Number、String、Symbol(es6)等。
3.4 isObject 快速判断是否是对象
function isObject(val) {
return val !== null && typeof val === 'object'
}
typeof null === 'object' // true
isObject([]) // true
isObject({}) // true
3.5 Get the raw type string of a value, e.g., [object Object]. // 获取数据的原始类型字符串
// Object.prototype.toString() 方法返回一个表示该对象的字符串。
var _toString = Object.prototype.toString
function toRawType (value) {
return _toString.call(value).slice(8, -1)
}
// eg:
toRawType(new Date()) // Date
toRawType('') // String
3.6 isPlainObject 判断是否是纯对象
function isPlainObject(value) {
return _toString.call(value) === '[object Object]'
}
// 这个可以用来区分数组和纯对象
isPlainObject([]) // false
isPlainObject({}) // true
3.7 isRegExp 是否是正则表达式
function isRegExp(value) {
return _toString.call(value) === '[object RegExp]'
}
isRegExp(/\D/) // true
3.8 Check if val is a valid array index. 是否是有效的数组索引值
function isValidArrayIndex(val) {
var n = parseFloat(String(val))
return n >= 0 && Math.floor(n) === n && isFinite(val)
}
Tips: parseFloat() 函数 isFinite() 函数
parseFloat(string) // 函数解析字符串并返回浮点数。
parseFloat(111) // 111
parseFloat('222') // 222
parseFloat('333 is number') // 333
parseFloat(' 20 ') // 20
parseFloat('to number 40') // NaN
// tips: 只返回字符串中的第一个数字!允许前导和尾随空格。如果第一个字符不能转换为数字,parseFloat() 返回 NaN。
isFinite(testValue) // 用来检测一个值是否为有限值
isFinite(Infinity); // false
isFinite(NaN); // false
isFinite(-Infinity); // false
isFinite(0); // true
isFinite(2e64); // true, 在更强壮的Number.isFinite(null)中将会得到false
isFinite("0"); // true, 在更强壮的Number.isFinite('0')中将会得到false
// tips: isFinite 方法检测它参数的数值。如果参数是 NaN,正无穷大或者负无穷大,会返回false,其他返回 true。
3.9 isPromise 是否是promise对象
function isPromise(val) {
return isDef(val) && typeof val.then === 'function' && typeof val.catch === 'function'
}
isPromise(new Promise()) // 报错 TypeError: Promise resolver undefined is not a function
isPromise(new Promise(() => {})) // true promise有两个参数,resolve && reject
3.10 Convert a value to a string that is actually rendered. 将值转换为实际呈现的字符串
function toString(val) {
return val == null
? ""
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2)
: String(val);
}
undefined == null // true
Tips: JSON.stringify(val, null, 2) 点击查阅MDN讲解
// 语法: JSON.stringify(value[, replacer [, space]])
// value 将要序列化成 一个 JSON 字符串的值。
// replacer(可选) [Function,Array,Null] 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
// space(可选) 指定缩进用的空白字符串,用于美化输出(pretty-print);如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格;如果该参数没有提供(或者为 null),将没有空格。
var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
var arr = [1,'error',333]
function replacer(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}
JSON.stringify(arr,replacer,2)
// [
// 1,
// null,
// 333
// ]
// 注意: 不能用 replacer 方法,从数组中移除值(values),如若返回 undefined 或者一个函数,将会被 null 取代。
JSON.stringify(foo,replacer,2)
// {
// "week": 45,
// "month": 7
// }
3.11 toNumber
function toNumber(val) {
var n = parseFloat(val) // 将字符串转转化返回浮点数
return isNaN(n) ? val : n // isNaN是否为非数字; 转换失败,返回原值
}
toNumber('23.30') // 23.3
toNumber(' 50 ') // 50
toNumber('1s') // 1
3.12 makeMap
/**
* @description: 生成一个 map(键值对),并且返回一个函数检测 key 值在不在这个 map 中。
* @param {String} str // ,号拼接的字符串
* @param {Boolean} expectsLowerCase // 是否大小写兼容,可选
* @return {Function}
*/
function makeMap(str, expectsLowerCase) {
const map = Object.create(null);
const list = str.split(",");
for (let i = 0; i < list.length; i++) {
map[list[i]] = true;
}
return expectsLowerCase
? function (val) {
return map[val.toLowerCase()];
}
: function (val) {
return map[val];
};
}
makeMap("name,age,sex", true)("Name") // true
makeMap("name,age,sex")("name") // true
Tips: Object.create(null)创建的对象不继承Object原型链上的属性.
使用场景:
1、你需要一个非常干净且高度可定制的对象当作数据字典的时候;
2、减少hasOwnProperty造成的性能损失并且可以偷懒少些一点代码的时候
3、其他的时候,请用{}
3.13 isBuiltInTag: Check if a tag is a built-in tag.(检查标签是否为内置标签)
var isBuiltInTag = makeMap('slot,component', true);
isBuiltInTag('slot') // true
3.14 isReservedAttribute: Check if an attribute is a reserved attribute.(检查属性是否为保留属性)
var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');
isReservedAttribute(ref) // true
3.15 remove: Remove an item from an array.(移除数组中的某一项)
/**
* @description: 移除数组中的某一项,耗费性能,会改变原数组
* @param {Array} arr // 操作的数组数据
* @param {Any} item // 数组的某一项元素
* @return {Array} // 删除的元素数组
*/
function remove(arr, item) {
if (arr.length) {
var index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1);
}
}
}
3.16 hasOwn: Check whether an object has the property.(判断是否是自有属性)
const hasOwnproperty = Object.prototype.hasOwnProperty; // 判断是否是自有属性
function hasOwn(obj, key) {
return hasOwnproperty.call(obj, key);
}
Tips: hasOwnProperty 点击查阅详解
const obj = {
name: "娃哈哈",
};
// 也可以用当前对象的hasOwnProperty判断属性,如果被重写过,结果会不准确
obj.hasOwnProperty("name") // true
obj.hasOwnProperty("age") // false
// hasOwnProperty如果被重写,此处会一直返回false,为了避免此种情况,最好是使用Object 原型上的 hasOwnProperty 属性
obj.hasOwnProperty = function () {
return false;
};
obj.hasOwnProperty("name") // false
hasOwn(obj, "name") // true
3.17 cached: Create a cached version of a pure function. 利用闭包特性,缓存数据
// 利用闭包缓存数据
function cached(fn) {
var cache = Object.create(null); // 定义一个没有原型的对象,比 {} 高效,不需要继承一大堆 Object.prototype 上的属性。
return function cachedFn(str) {
var hit = cache[str];
console.log('hit', hit)
return hit || (cache[str] = fn(str)); // 如果数据已缓存,直接返回。未缓存,将fn 函数返回值缓存
};
}
// 转换成大写操作
const fn = function (str) {
return str.toUpperCase()
}
// 调用 cached 时传入一个 fn 函数,这个函数对某些值进行操作,操作之后会产生返回值
const cachedTest = cached(fn)
// 执行完 cached 会返回一个函数 cachedFn,将来接收需要操作的值。函数 cachedFn 内部调用 fn 函数得到操作后的值,
// 并缓存在对象 cache 中,如果再对同一个值进行操作时,则直接从缓存中取,无需再调用函数计算。
cachedTest('name')
// hit undefined
// NAME
cachedTest('name')
// hit NAME 此处清楚看到是取得缓存数据
// NAME
cachedTest('age')
// hit undefined
// AGE
3.18 短横线连接字符转换成小驼峰
var camelizeRE = /-(\w)/g;
'tab-name'.match(camelizeRE)
console.log(RegExp.$1); // n 正则中的捕获分组
var camelize = cached(function (str) {
// replace(regexp/substr,replacement)方法
return str.replace(camelizeRE, function (_, c,idx,obj) {
// replacement[String,Function] 可以是字符串也可以是函数
console.log(_); // -n 该函数的第一个参数是匹配模式的字符串
console.log(c); // n 接下来的参数是与模式中的子表达式匹配的字符串,可以有 0 个或多个这样的参数
console.log(idx); // 3 声明了匹配在 stringObject 中出现的位置
console.log(obj); // tab-name 最后一个参数是 stringObject 本身
return c ? c.toUpperCase() : '' // c?.toUpperCase() babel编译后也是多元表达式
});
});
camelize('tab-name') // tabName
3.19 首字符大写
var capitalize = cached(function(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
})
capitalize('chat') // Chat
charAt(index) //返回字符串中特定位置的字符。index超出字符串的长度,则返回''
3.20 小驼峰命名转换为连字符
// 小驼峰命名变为连字符
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cached(function (str) {
return str.replace(hyphenateRE, "-$1").toLowerCase();
});
hyphenate('tabName') // tab-name
'tabName'.match(hyphenateRE) // ['N']
Tips: \B 和 \b
// \b 匹配一个单词边界,即字与空格间的位置。
// \B 非单词边界匹配。
const lowercaseRE = /\b([A-Z])/g
const capitalizeRE = /\B([A-Z])/g
'tabName'.match(lowercaseRE) // null
'tabName'.match(capitalizeRE) // ['N']
'tab Name'.match(lowercaseRE) // ['N']
'tab Name'.match(capitalizeRE) // null
3.21 polyfillBind 兼容老版本浏览器不支持原生的 bind 函数
function polyfillBind(fn, ctx) {
function boundFn(a) {
console.log(a);
var l = arguments.length;
return l
? l > 1
? fn.apply(ctx, arguments) //参数多适合用 apply,少用 call 性能更好。
: 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
3.22 类数组转换为数组
// 类数组转换为真实数组
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 sliceToArray(likeArr) {
// slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。
// 原始数组不会被改变。
return Array.prototype.slice.call(likeArr);
}
function fromToArray(likeArr) {
// es6 新增
// Array.from()方法对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
return Array.from(likeArr)
}
function ofToArray(likeArr) {
// es6 新增
// Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
return Array.of(likeArr)
}
function extendToArray(likeArr) {
// es6 新增 扩展运算符
return [...likeArr]
}
(function likeFn(name, age, sex) {
console.log(_toString.call(arguments)); // [object Arguments]
console.log(_toString.call(toArray(arguments, 0))); // [object Array]
console.log(_toString.call(sliceToArray(arguments))); // [object Array]
console.log(_toString.call(fromToArray(arguments))); // [object Array]
console.log(_toString.call(ofToArray(arguments))); // [object Array]
console.log(_toString.call(extendToArray(arguments)));// [object Array]
})("张三", 18, "男");
Tips: 类数组、数组的区别
类数组的定义:
1、拥有length属性,其它属性(索引)为非负整数(对象中的索引会被当做字符串来处理);
2、不具有数组所具有的方法;
3、类数组是一个普通对象,而真实的数组是Array类型。
常见的类数组有:
1、函数的参数arugments
2、DOM对象列表(比如通过 document.querySelectorAll 得到的列表)
3、jQuery 对象 (比如 $("div"))
3.21 extend合并
function extend(to, _from) {
for (var key in _from) {
to[key] = _from[key];
}
return to;
}
let toObj = {
name: "小红",
age: 20,
};
let fromObj = {
name: "小红",
sex: "女",
};
extend(toObj, fromObj) // {name: '小红', age: 20, sex: '女'}
3.22 toObject 数组转对象
function toObject(arr) {
var res = {};
for (let i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i]);
}
}
return res;
}
3.23 noop 空函数
function noop (a, b, c) {}