JS笔记
不定时更新说明
本篇文章记录自己在 js 上面容易忘记的知识点或者很有用的知识,将会不定时添加新的内容。
几种深拷贝的方法
1. JSON.parse(JSON.stringify(obj))
一般情况下对普通对象需要进行深拷贝,可以使用这种方法,这是最简单且代码量最少的方法
let obj = {
a: 1,
b: 2,
};
let cloneObj = JSON.parse(JSON.stringify(obj));
cloneObj.a = 5;
console.log(cloneObj); // {a: 5, b: 2}
console.log(obj); // {a: 1, b: 2}
缺陷:
① 取不到值为 undefined 的 key
② 如果对象里面有函数,函数无法被拷贝下来
③ 无法拷贝对象原型链上的属性和方法
④ 如果值是 new Date() 这种类型的,拷贝出来的值是一个当前时间的一个字符串
let a = {
name: "Jack",
age: 18,
hobbit: ["sing", { type: "sports", value: "run" }],
score: {
math: "A",
},
run: function () {},
walk: undefined,
fly: NaN,
cy: null,
date: new Date(),
};
let b = JSON.parse(JSON.stringify(a));
console.log(b);
// {
// age: 18,
// cy: null,
// date: "2022-05-15T08:04:06.808Z"
// fly: null,
// hobbit: (3) ["dance", "sing", {…}],
// name: "Jack",
// score: {math: "A"},
// }
2. 普通递归函数实现深拷贝
function deepClone(source) {
if (typeof source !== "object" || source == null) {
return source;
}
const target = Array.isArray(source) ? [] : {};
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (typeof source[key] === "object" && source[key] !== null) {
target[key] = deepClone(source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
缺陷:该方法遇到循环引用会出现栈溢出的情况,所以只适合一般的对象深拷贝,不适合嵌套循环引用的对象;
如果值是 new Date() 这种类型的,拷贝出来的值是一个空对象 {}
解决循环引用以及 Symbol 类型 ↓
function cloneDeep(source, hash = new WeakMap()) {
if (typeof source !== 'object' || source === null) {
return source;
}
if (hash.has(source)) {
return hash.get(source);
} else {
hash.set(source, source);
}
const target = Array.isArray(source) ? [] : {};
Reflect.ownKeys(source).forEach(key => {
const val = source[key];
if (typeof val === 'object' && val != null) {
target[key] = cloneDeep(val, hash);
} else {
target[key] = val;
}
})
return target;
}
缺陷: 如果值是 new Date() 这种类型的,拷贝出来的值是一个空对象
3. 兼容多种数据类型
const deepClone = (source, cache) => {
if(!cache){
cache = new Map()
}
if(source instanceof Object) { // 不考虑跨 iframe
if(cache.get(source)) { return cache.get(source) }
let result
if(source instanceof Function) {
if(source.prototype) { // 有 prototype 就是普通函数
result = function(){ return source.apply(this, arguments) }
} else {
result = (...args) => { return source.call(undefined, ...args) }
}
} else if(source instanceof Array) {
result = []
} else if(source instanceof Date) {
result = new Date(source - 0)
} else if(source instanceof RegExp) {
result = new RegExp(source.source, source.flags)
} else {
result = {}
}
cache.set(source, result)
for(let key in source) {
if(source.hasOwnProperty(key)){
result[key] = deepClone(source[key], cache)
}
}
return result
} else {
return source
}
}
判断对象是否具有属性的5种方法
1. in
如果属性在指定对象或其原型链中,则返回 true
let obj = {
name: "cty",
};
console.log("name" in obj); // true
console.log("toString" in obj); // true
2. Reflect.has()
该方法效果跟第一种方法 in 是一样的,在指定对象或者其原型链中都会返回 true
let obj = {
name: "cty",
};
console.log(Reflect.has(obj, "name")); // true
console.log(Reflect.has(obj, "toString")); // true
3. hasOwnProperty()
返回一个布尔值,只是对象是否具有指定的属性作为它自己的属性,而不是继承属性,它可以正确区分对象本身属性和其原型属性
let obj = {
name: "cty",
};
console.log(obj.hasOwnProperty("name")); // true
console.log(obj.hasOwnProperty("toString")); // false
但是这个方法有个缺陷,如果对象是用 Object.create(null) 创建的话,这个方法就不能使用了,要报错
let obj = Object.create(null);
obj.name = "cty";
console.log(obj.hasOwnProperty("name")); // 报错
4. Object.prototype.hasOwnProperty.call()
解决第三种方法的缺陷很简单,就是使用 Object.prototype.hasOwnProperty.call() 方法,该方法直接调用内置的有效用函数,跳过原型链
let obj = Object.create(null);
obj.name = "cty";
console.log(Object.prototype.hasOwnProperty.call(obj, "name")); // true
console.log(Object.prototype.hasOwnProperty.call(obj, "toString")); // false
5. Object.hasOwn()
由于前面几种方式都不太优雅,ES2022有一个新的提议:Object.hasOwn()
如果指定的对象具有指定的属性作为自己的属性,则返回 true,如果属性是继承而来或者不存在,则返回 false
let obj = Object.create(null);
obj.name = "cty";
console.log(Object.hasOwn(obj, "name")); // true
console.log(Object.hasOwn(obj, "toString")); // false
JS 数组 reduce() 方法
reduce() 方法实现的东西有时候可以用 for 循环或者 forEach() 方法实现,但是为什么要用 reduce() 方法呢?
答案是:有时候用 reduce() 方法可以简化代码,亦或许是显得逼格更高?
语法:
reduce 为数组中每个元素依次执行回调函数,不包括数组中被删除或者从未被赋值的元素
接受两个参数:
1. 回调函数:回调函数接受四个参数
2. 初始值:可有可无,如果指定初始值,第一次调用回调函数的时候 pre 的值为该初始值,cur 从数组第一个元素开始,即从数组索引为0开始执行回调函数;如果不指定初始值,则从索引为1开始执行回调函数,数组第一个元素的值赋给 pre 作为初始值。回调函数执行返回的值作为下一次执行回调函数的 pre 值
arr.reduce((pre, cur, index, arr)=>{}, [initialValue])
// pre:开始执行回调函数的初始值(指定的初始值或者上一次执行回调函数的返回值,
// 第一次执行回调函数时,若欸没有指定初始值,则初始值为数组第一个元素的值)
// cur:当前数组元素的值(第一次执行回调函数时,若没有指定初始值,则cur为数组中第二个元素的值)
// index:当前执行回调函数时的数组索引值
// arr:调用reduce方法的数组
注意: 如果数组为空,没有指定初始值的情况下会报错,制定了初始值就不会报错,所以一般来说我们指定一个初始值会更加安全。
reduce 的简单用法:
数组求和、数组求乘积
let arr = [1,2,3,4,5]
let sum = arr.reduce((pre, cur)=>{return pre + cur})
let mul = arr.reduce((pre, cur)=>{return pre*cur})
console.log(sum, mul) // 15 120
reduce 的高级用法:
- 计算数组中每个元素出现的次数
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']
let result = names.reduce((pre, cur)=>{
if (cur in pre) pre[cur]++
else pre[cur] = 1
return pre
}, {})
console.log(result) // {Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
- 数组去重
let arr = [1,2,3,4,4,1]
arr = arr.reduce((pre, cur)=>{
if(!pre.includes(cur)) pre.push(cur)
return pre
}, [])
console.log(arr) // [1, 2, 3, 4]
- 将二维数组转换成一维数组
let arr = [[0, 1], [2, 3], [4, 5]]
arr = arr.reduce((pre, cur)=>{
return pre.concat(cur)
}, [])
console.log(arr) // [0, 1, 2, 3, 4, 5]
- 将多维数组转换成一维数组
let arr = [[0, 1], [2, 3], [4,[5,6,7]]]
const newArr = function(arr) {
return arr.reduce((pre, cur)=>{
return pre.concat(Array.isArray(cur)?newArr(cur):cur)
}, [])
}
console.log(newArr(arr)) // [0, 1, 2, 3, 4, 5, 6, 7]
- 对象里的属性求和
let result = [
{
subject: 'math',
score: 10
},
{
subject: 'chinese',
score: 20
},
{
subject: 'english',
score: 30
}
]
let sumScore = result.reduce((pre, cur)=>{
return pre + cur.score
}, 0)
console.log(sumScore) // 60
JS 数组 Array.flat() 扁平化处理
flat() 方法创建一个新数组,其中所有子数组元素都以递归的方式连接到该数组中,直到达到指定的深度,如果指定的深度为 Infinity,则不用考虑嵌套的深度,该方法会递归处理完所有深度的子数组。
flat() 方法不支持 IE 浏览器
const arr1 = [1, 2, [3, 4, [5, 6], 7, 8]]
console.log(arr1.flat()) // [ 1, 2, 3, 4, [ 5, 6 ], 7, 8 ]
console.log(arr1.flat(2)) // [ 1, 2, 3, 4, 5, 6, 7, 8 ]
console.log(arr1.flat(Infinity)) // [ 1, 2, 3, 4, 5, 6, 7, 8 ]
内存管理机制
JavaScript 解释器有自己的内存管理机制,可以自动对内存进行垃圾回收。这意味着程序可以按需创建对象,程序员则不必担心这些对象的销毁和内存回收。当不再有任何引用指向一个对象,解释器就知道这个对象没用了,然后自动回收它所占的内存资源。
Math类常用的的方法
Math.pow(2, 53) // 2 的 53 次幂
Math.round(0.6) // 四舍五入 1
Math.ceil(0.6) // 向上取整 1
Math.floor(0.6) // 向下取整 0
Math.abs(-5) // 求绝对值 5
Math.max(x, y, z) // 返回 x, y, z 中的最大值
Math.min(x, y, z) // 返回 x, y, z 中的最小值
Math.random() // 生成 [0, 1) 的随机数
Math.PI // Π,圆周率
Math.E // e,自然对数的底数
Math.sqrt(3) // 3 的平方根
Math.pow(3, 1/3) // 3 的立方根
Math.sin(0) // 三角函数,还有 Math.cos, Math.atan 等
Math.log(10) // 10 的自然对数
Math.log(100)/Math.LN10 // 以 10 为底 100 的对数,这里值为 2
Math.log(512)/Math.LN2 // 以 2 为底 512 的对数,这里值为 9
Math.exp(3) // e 的 3 次幂
JS排序sort函数
array.sort(fun):参数fun可选,规定排序顺序,必须是函数。
如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序,而不是按照数值大小排序。
如果想按照其他规则进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:
若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
若 a 等于b,则返回 0。
若 a 大于 b,则返回一个大于 0 的值。
简单点就是:比较函数两个参数a和b,返回a-b 升序,返回b-a 降序
1. 数组元素排序
2. 根据数组中对象的某个属性排序
3. 根据数组中对象的多个属性值排序
?? 与 || 的区别
使用 | | 时,将前面的值转换成布尔类型,若为 true 则返回前面的值,若为 false 则返回后面的值。
使用 ?? 时,只有前面的值为 null 或者 undefined 时,才会返回后面的值。
举例:
let [a, b, c, d] = [0, 1, null, undefined]
console.log(a ?? 'cty')
console.log(b ?? 'cty')
console.log(c ?? 'cty')
console.log(d ?? 'cty')
console.log('****************')
console.log(a || 'cty')
console.log(b || 'cty')
console.log(c || 'cty')
console.log(d || 'cty')
测试结果:
字符串大小写转换
字符串转换成小写: str.toLowerCase()
字符串转换成大写: str.toUpperCase()
这两个方法只返回转换过后的字符串,并不会修改原字符串的值。
字符串替换
常规替换: str.replace(‘要替换的字符串’, ‘用于替换的字符串’)
正则替换: str.replace(/要替换的字符串/g, ‘用于替换的字符串’)
注意:第一种常规替换只能替换字符串中第一次出现的替换项,第二种是加了全局标识的正则替换则会替换字符串中出现的全部替换项