本文参加了由公众号@若川视野发起的每周源码共读活动,点击了解详情一起参与。
这是源码共读的第24期,链接:juejin.cn/post/707976…。
工具函数
1.1 emptyObject
var emptyObject = Object.freeze({});
Object.freeze()
Object.freeze()
方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze()
返回和传入的参数相同的对象。
"use strict"
const obj = {name: '张三', age: 23}
console.log(Object.getOwnPropertyDescriptors(obj)) // { name: { value: '张三', writable: true, enumerable: true, configurable: true }, age: { value: 23, writable: true, enumerable: true, configurable: true } }
const freeObj = Object.freeze(obj)
console.log(Object.getOwnPropertyDescriptors(freeObj)) // { name: { value: '张三', writable: false, enumerable: true, configurable: false }, age: { value: 23, writable: false, enumerable: true, configurable: false } }
// 严格模式下以下操作都会报错
// 添加属性
freeObj.gender = '男'
// 删除属性
delete freeObj.name
// 修改属性值
freeObj.name = '李四'
// 设置原型
Object.setPrototypeOf(freeObj, {sayHello() {console.log('hello')}})
const obj = {name: '张三', age: 23}
const freeObj = Object.freeze(obj)
freeObj.name = '李四'
// 非严格模式下 虽然没有报错 但是对象不会被修改
console.log(freeObj.name) // 张三
PS:
不明白这个空对象用来做什么。
1.2 isUndef
function isUndef(v) {
return v === undefined || v === null
}
1.3 isDef
function isDef(v) {
return v !== undefined && v !== null
}
1.4 isTrue
function isTrue(v) {
return v === true
}
1.5 isFalse
function isFalse(v) {
return v === false
}
1.6 isPrimitive
// 判断值是否是原始值
function isPrimitive(value) {
return (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}
console.log(isPrimitive('1')) // true
console.log(isPrimitive(1)) // true
console.log(isPrimitive(Symbol('1'))) // true
console.log(isPrimitive(true)) // true
console.log(isPrimitive({})) // false
console.log(isPrimitive([])) // false
console.log(isPrimitive(null)) // false
console.log(isPrimitive(undefined)) // false
console.log(isPrimitive(function() {})) // false
console.log(isPrimitive(1n)) // false
在 JavaScript
中有 7
种原始类型:string,number,bigint,boolean,symbol,null
和 undefined
。
1.7 isObject
function isObject(obj) {
return obj !== null && typeof obj === 'object'
}
1.8 _toString
var _toString = Object.prototype.toString;
Object.prototype.toString()
语法:
obj.toString()
返回值:
一个表示该对象的字符串。
描述:
每个对象都有一个 toString()
方法,默认情况下,toString()
方法被每个 Object
对象继承。如果此方法在自定义对象中未被覆盖,toString()
返回 "[object type]"
,其中 type
是对象的类型。
const _toString = Object.prototype.toString;
console.log(_toString.call(1)) // [object Number]
console.log(_toString.call('1')) // [object String]
console.log(_toString.call(true)) // [object Boolean]
console.log(_toString.call(undefined)) // [object Undefined]
console.log(_toString.call(null)) // [object Null]
console.log(_toString.call({})) // [object Object]
console.log(_toString.call([])) // [object Array]
console.log(_toString.call(function(){})) // [object Function]
console.log(_toString.call(new Date())) // [object Date]
console.log(_toString.call(new RegExp())) // [object RegExp]
console.log(_toString.call(new Error())) // [object Error]
console.log(_toString.call(Symbol())) // [object Symbol]
从 JavaScript 1.8.5
开始,toString()
调用 null
返回 [object Null]
,undefined
返回 [object Undefined]
。
覆盖默认的 toString
方法:
可以自定义一个方法,来取代默认的 toString()
方法。该 toString()
方法不能传入参数,并且必须返回一个字符串。
function Student(name, age) {
this.name = name;
this.age = age;
}
const s = new Student("John", 20);
// 默认的 toString() 方法
console.log(s.toString()); // [object Object]
// 覆盖默认的 toString() 方法
s.toString = function() {
return 'name: ' + this.name + ', age: ' + this.age;
}
console.log(s.toString()); // name: John, age: 20
// 也可以这样写
Student.prototype.toString = function() {
return 'name: ' + this.name + ', age: ' + this.age;
}
console.log(s.toString()); // name: John, age: 20
1.9 toRawType
// 转换成原始类型
function toRawType(value) {
return _toString.call(value).slice(8, -1)
}
console.log(toRawType(1)); // Number
console.log(toRawType('1')); // String
console.log(toRawType(true)) // Boolean
console.log(toRawType(null)) // Null
console.log(toRawType(undefined)) // Undefined
console.log(toRawType({})) // Object
console.log(toRawType(Symbol())) // Symbol
String.prototype.slice()
slice()
方法提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串。
语法:
str.slice(beginIndex[, endIndex])
参数:
beginIndex
从该索引(以 0 为基数)处开始提取原字符串中的字符。如果值为负数,会被当做 strLength + beginIndex
看待,这里的 strLength
是字符串的长度(例如, 如果 beginIndex
是 -3
则看作是:strLength - 3
)
endIndex
可选。在该索引(以 0 为基数)处结束提取字符串。如果省略该参数,slice()
会一直提取到字符串末尾。如果该参数为负数,则被看作是 strLength + endIndex
,这里的 strLength
就是字符串的长度 (例如,如果 endIndex
是 -3
,则是,strLength - 3
)。
返回值:
返回一个从原字符串中提取出来的新字符串
描述:
slice()
提取的新字符串包括 beginIndex
但不包括 endIndex
。
const str = 'welcome to javascript!' // length = 23
console.log(str.slice(8)); // to javascript!
console.log(str.slice(0, 7)) // welcome
console.log(str.slice(11, -1)) // javascript
1.10 isPlainObject
// 是否是纯对象
function isPlainObject(obj) {
return _toString.call(obj) === '[object Object]'
}
console.log(isPlainObject({})) // true
console.log(isPlainObject([])) // false
console.log(isPlainObject(new Date())) // false
1.11 isRegExp
// 是否是正则表达式
function isRegExp(v) {
return _toString.call(v) === '[object RegExp]'
}
1.12 isValidArrayIndex
// 是否是可用的数组索引值
function isValidArrayIndex(val) {
var n = parseFloat(String(val));
return n >= 0 && Math.floor(n) === n && isFinite(val)
}
console.log(isValidArrayIndex(1)) // true
console.log(isValidArrayIndex(1.1)) // false
console.log(isValidArrayIndex(-1)) // false
parseFloat
语法:
parseFloat(string)
参数:
string
需要被解析成为浮点数的值。
返回值:
给定值被解析成浮点数。如果给定值不能被转换成数值,则会返回 NaN
。
描述:
parseFloat
是个全局函数,不属于任何对象。
-
如果
parseFloat
在解析过程中遇到了正号(+
)、负号(-
)、数字(0-9
)、小数点(.
)、或者科学记数法中的指数(e 或 E
)以外的字符,则它会忽略该字符以及之后的所有字符,返回当前已经解析到的浮点数。 -
第二个小数点的出现也会使解析停止(在这之前的字符都会被解析)。
-
参数首位和末位的空白符会被忽略。
-
如果参数字符串的第一个字符不能被解析成为数字,则
parseFloat
返回NaN
。 -
parseFloat
也可以解析并返回Infinity
。 -
parseFloat
解析BigInt
为Numbers
, 丢失精度。因为末位n
字符被丢弃。
console.log(parseFloat('1.2.3')); // 1.2
console.log(parseFloat(' 1.2.3 ')); // 1.2
console.log(parseFloat('A')); // NaN
console.log(parseFloat('1n')); // 1
console.log(parseFloat('Infinity')); // Infinity
Math.floor()
语法:
Math.floor(x)
参数:
x
一个数字。
返回值:
一个表示小于或等于指定数字的最大整数的数字。
描述:
由于 floor
是 Math
的一个静态方法,你总是应该像这样使用它 Math.floor()
。
示例:
console.log(Math.floor(1.2)); // 1
console.log(Math.floor(1.9)); // 1
console.log(Math.floor(-1.2)); // -2
console.log(Math.floor(-1.9)); // -2
isFinite
语法:
isFinite(testValue)
参数:
testValue
用于检测有限性的值。
描述:
isFinite
是全局的方法,不与任何对象有关系。
你可以用这个方法来判定一个数字是否是有限数字。isFinite
方法检测它参数的数值。如果参数是 NaN
,正无穷大 或者 负无穷大,会返回 false
,其他返回 true
。
示例:
console.log(isFinite(Infinity)); // false
console.log(isFinite(-Infinity)); // false
console.log(isFinite(NaN)); // false
console.log(isFinite(undefined)); // false
console.log(isFinite(null)); // true
console.log(isFinite(0)); // true
console.log(isFinite(2e64)); // true
console.log(isFinite('0')); // true
1.13 isPromise
// 判断是否是Promise
function isPromise(val) {
return (
isDef(val) &&
typeof val.then === 'function' &&
typeof val.catch === 'function'
)
}
console.log(isPromise(new Promise((resolve, reject) => {
resolve(1)
}))); // true
1.14 toString
// 转换成字符串。如果 「val是数组」或者 「val是对象并且对象的 toString 方法是 Object.prototype.toString」,用 JSON.stringify 转换。
function toString(val) {
return val == null
? ''
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2)
: String(val)
}
console.log(toString(1)); // 1
console.log(toString('1')); // 1
console.log(toString(null)); //
console.log(toString(undefined)); //
console.log(toString({ a: 1 })); // { "a": 1 }
console.log(toString([1, 2, 3])); // [ 1, 2, 3 ]
console.log(toString([1, 2, { a: 1 }])); // [ 1, 2, { "a": 1 } ]
JSON.stringify()
语法:
JSON.stringify(value[, replacer [, space]])
参数:
value
将要序列化成 一个 JSON
字符串的值。
replacer
可选。如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON
字符串中;如果该参数为 null
或者未提供,则对象所有的属性都会被序列化。
space
可选。指定缩进用的空白字符串,用于美化输出;如果参数是个数字,它代表有多少的空格;上限为 10
。该值若小于 1
,则意味着没有空格;如果该参数为字符串(当字符串长度超过 10
个字母,取其前 10
个字母),该字符串将被作为空格;如果该参数没有提供(或者为 null
),将没有空格。
返回值:
一个表示给定值的 JSON
字符串。
异常:
当在循环引用时会抛出异常 TypeError ("cyclic object value")
(循环对象值)
当尝试去转换 BigInt
类型的值会抛出 TypeError ("BigInt value can't be serialized in JSON")
(BigInt
值不能 JSON
序列化)。
1.15 toNumber
// 转换成数字。如果转换失败返回val。
function toNumber(val) {
var n = parseFloat(val);
return isNaN(n) ? val : n
}
console.log(toNumber('1.2')); // 1.2
console.log(toNumber('A')); // A
1.16 makeMap
/**
* 把字符串转换成对象,返回一个验证函数
* @param {string} str
* @param {boolean} expectsLowerCase
* @returns value => boolean
*/
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]; }
}
console.log(makeMap('hello,world')('Hello')); // undefined
console.log(makeMap('hello,world', true)('Hello')); // true
1.17 isBuiltInTag
// 检查是否是 slot | component
var isBuiltInTag = makeMap('slot,component', true);
console.log(isBuiltInTag('slot')); // true
console.log(isBuiltInTag('component')); // true
console.log(isBuiltInTag('Slot')); // true
console.log(isBuiltInTag('Component')); // true
1.18 isReservedAttribute
// 检查是否是 key | ref | slot | slot-scope | is
var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');
console.log(isReservedAttribute('key')); // true
console.log(isReservedAttribute('Key')); // undefined
console.log(isReservedAttribute('ref')); // true
console.log(isReservedAttribute('slot')); // true
console.log(isReservedAttribute('slot-scope')); // true
console.log(isReservedAttribute('is')); // true
1.19 remove
// 移除数组中的一项
function remove(arr, item) {
if (arr.length) {
var index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1)
}
}
}
const arr = [1, 2, 3, 4, 5];
console.log(remove(arr, 2)); // [2]
console.log(arr); // [1, 3, 4, 5]
console.log(remove(arr, 2)); // undefined
console.log(arr); // [1, 3, 4, 5]
Array.prototype.indexOf()
语法:
indexOf(searchElement, fromIndex)
参数:
searchElement
要查找的元素。
fromIndex
可选,开始查找的位置。如果该索引值大于或等于数组长度,意味着不会在数组里查找,返回 -1
。如果参数中提供的索引值是一个负值,则将其作为数组末尾的一个抵消,即 -1
表示从最后一个元素开始查找,-2
表示从倒数第二个元素开始查找 ,以此类推。注意:如果参数中提供的索引值是一个负值,并不改变其查找顺序,查找顺序仍然是从前向后查询数组。
返回值:
首个被找到的元素在数组中的索引位置; 若没有找到则返回 -1
。
描述:
indexOf
使用全等运算 ===
判断 searchElement
与数组中包含的元素之间的关系。
示例:
const arr = [1, 2, 3, 4, 5];
console.log(arr.indexOf(2)); // 1
console.log(arr.indexOf(2, 2)); // -1
console.log(arr.indexOf(1, -1)); // -1
console.log(arr.indexOf(1, -2)); // -1
console.log(arr.indexOf(5, -1)); // 4
console.log(arr.indexOf(5, -2)); // 4
console.log(arr.indexOf(4, -1)); // -1
console.log(arr.indexOf(4, -2)); // 3
1.20 hasOwn
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key)
}
console.log(hasOwn({ a: 1 }, 'a')); // true
console.log(hasOwn({ a: 1 }, 'b')); // false
console.log(hasOwn({ a: 1 }, 'toString')); // false
console.log(hasOwn({ a: 1, toString: () => {} }, 'toString')); // true
console.log(hasOwn({ a: 1, toString: () => {} }, 'hasOwnProperty')); // false
console.log(hasOwn({ a: 1, hasOwnProperty: () => {} }, 'hasOwnProperty')); // true
Object.prototype.hasOwnProperty()
语法:
obj.hasOwnProperty(prop)
参数:
prop
要检测的属性的名称,String
或者 Symbol
。
返回值:
布尔值 Boolean
。
描述:
所有继承了 Object
的对象都会继承到 hasOwnProperty
方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和 in
运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
备注:
即使属性的值是 null
或 undefined
,只要属性存在,hasOwnProperty
依旧会返回 true
。
示例:
const obj = {name: null, age: undefined}
console.log(obj.hasOwnProperty('name')); // true
console.log(obj.hasOwnProperty('age')); // true
JavaScript
并没有保护 hasOwnProperty
这个属性名,因此,当某个对象可能自有一个占用该属性名的属性时,就需要使用外部的 hasOwnProperty
获得正确的结果:
const obj = {
name: null,
age: undefined,
hasOwnProperty: () => {
return false
}
};
console.log(obj.hasOwnProperty('name')); // false
console.log(obj.hasOwnProperty('age')); // false
console.log(obj.hasOwnProperty('hasOwnProperty')); // false
Object.prototype.hasOwnProperty.call(obj, 'age'); // true
1.21 cached
function cached(fn) {
var cache = Object.create(null);
return (function cachedFn(str) {
var hit = cache[str];
return hit || (cache[str] = fn(str))
})
}
function upper(str) {
return str.toUpperCase()
}
// cached 会缓存 upper 函数的计算结果
console.log(cached(upper)('hello')) // HELLO
// 从缓存中取值
console.log(cached(upper)('hello')) // HELLO
1.22 camelize
// 连字符转驼峰
var camelizeRE = /-(\w)/g;
var camelize = cached(function (str) {
return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
});
console.log(camelize('a-b-c')); // aBC
console.log(camelize('hello-world')); // helloWorld
1.23 capitalize
// 首字母转大写
var capitalize = cached(function (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
});
console.log(capitalize('hello')) // Hello
1.24 hyphenate
// 驼峰转连字符
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cached(function (str) {
return str.replace(hyphenateRE, '-$1').toLowerCase()
});
console.log(hyphenate('helloWorld')); // hello-world
1.25 toArray
// 把类数组转换成真数组
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 sum() {
const args = toArray(arguments);
return args.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
1.26 extend
// 合并对象
function extend(to, _from) {
for (var key in _from) {
to[key] = _from[key];
}
return to
}
const obj_1 = {name: 'zhangsan'};
const obj_2 = {age: 18};
const obj_3 = extend(obj_1, obj_2);
console.log(obj_1); // { name: 'zhangsan', age: 18 }
console.log(obj_2); // { age: 18 }
console.log(obj_3); // { name: 'zhangsan', age: 18 }