JS 进阶(part3)
深浅拷贝
- 只针对引用类型
浅拷贝
- 浅拷贝只拷贝外面一层的属性, 如果对象里面还有对象, 那么这个对象的引用会被拷贝过去, 所以修改其中一个属性会影响到另一个对象
javascript
const obj = {
name: "zhangsan",
age: 18,
child: {
name: "xiaoming",
age: 10,
},
};
const obj2 = { ...obj };
console.log(obj2); // {name: "zhangsan", age: 18, child: {name: "xiaoming", age: 10}}
obj2.name = "lisi";
obj2.child.age = 12;
console.log(obj); // {name: "zhangsan", age: 18, child: {name: "xiaoming", age: 12}}
const obj3 = {};
Object.assign(obj3, obj);
console.log(obj3); // {name: "zhangsan", age: 18, child: {name: "xiaoming", age: 10}}
obj3.name = "wangwu";
obj3.child.age = 15;
console.log(obj); // {name: "zhangsan", age: 18, child: {name: "xiaoming", age: 15}}
深拷贝
-
拷贝的是对象, 即拷贝的是对象本身, 内部的属性也会被拷贝, 所以修改其中一个属性不会影响到另一个对象
-
常用方法:
-
通过递归实现深拷贝: 递归遍历对象的所有属性, 如果属性是对象, 则递归调用深拷贝函数, 否则直接赋值;
-
通过 JSON.parse(JSON.stringify())实现深拷贝: 先将对象序列化成 JSON 字符串, 再将 JSON 字符串反序列化成对象, 这样就实现了深拷贝;
-
通过 lodash.cloneDeep()实现深拷贝: 这个方法内部也是通过 JSON.parse(JSON.stringify())实现的;
-
递归实现深拷贝
javascript
function deepClone(newObj, oldObj) {
for (let key in oldObj) {
if (oldObj[key] instanceof Array) {
// 如果属性是数组, 则递归调用深拷贝函数
newObj[key] = [];
deepClone(newObj[key], oldObj[key]);
} else if (oldObj[key] instanceof Object) {
// 如果属性是对象, 则递归调用深拷贝函数
newObj[key] = {};
deepClone(newObj[key], oldObj[key]);
} else {
// 如果属性是基本类型, 则直接赋值
newObj[key] = oldObj[key];
}
}
}
// 先判断数组, 再判断对象, 最后判断基本类型
JSON.parse(JSON.stringify())实现深拷贝
javascript
const obj = {
name: "zhangsan",
age: 18,
child: {
name: "xiaoming",
age: 10,
},
};
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "zhangsan", age: 18, child: {name: "xiaoming", age: 10}}
obj2.name = "lisi";
obj2.child.age = 12;
console.log(obj); // {name: "zhangsan", age: 18, child: {name: "xiaoming", age: 10}}
lodash.cloneDeep()实现深拷贝
javascript
const obj = {
name: "zhangsan",
age: 18,
child: {
name: "xiaoming",
age: 10,
},
};
const obj2 = _.cloneDeep(obj);
console.log(obj2); // {name: "zhangsan", age: 18, child: {name: "xiaoming", age: 10}}
obj2.name = "lisi";
obj2.child.age = 12;
console.log(obj); // {name: "zhangsan", age: 18, child: {name: "xiaoming", age: 10}}
异常处理
throw 关键字
- throw 关键字用于抛出一个异常, 并将其传递到调用者, 调用者可以选择捕获这个异常并处理, 也可以继续抛出这个异常, 直到被捕获为止
javascript
function add(a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw new Error("参数必须是数字");
}
return a + b;
}
try...catch 语句
- try...catch 语句用于捕获异常, 如果 try 代码块中的代码抛出了异常, 则 catch 代码块中的代码将被执行, 并将异常对象作为参数传入
javascript
try {
add(1, 2);
} catch (error) {
console.log(error); // Error: 参数必须是数字
} finally {
console.log("finally");
} // 一定会执行的代码
自定义异常
- 自定义异常需要继承 Error 类, 并实现构造函数, 并将错误信息作为参数传入
javascript
class MyError extends Error {
constructor(message) {
super(message);
this.name = "MyError";
}
}
try {
throw new MyError("自定义异常");
} catch (error) {
console.log(error); // MyError: 自定义异常
}
this 关键字
-
this 关键字在函数执行的时候, 绑定的是函数执行时所在的作用域, 而不是函数定义时所在的作用域, 谁调用函数, this 就指向谁
-
在全局作用域中, this 指向 window
-
在函数中, this 指向调用函数的对象
-
在方法中, this 指向调用方法的对象
-
在构造函数中, this 指向新创建的对象
-
在箭头函数中, this 指向定义箭头函数时所在的作用域
-
在事件处理函数中, this 指向绑定事件的对象
-
在 setTimeout、setInterval 中, this 指向全局对象 window
-
在 Promise 中, this 指向 undefined
-
在 class 中, this 指向实例对象
-
在模块中, this 指向模块对象
-
在自定义对象中, this 指向自定义对象本身
改变 this 指向
-
在函数中, 可以使用 apply、call、bind 方法改变 this 指向
-
apply、call、bind 方法的第一个参数是 this 要指向的对象, 第二个参数是函数参数数组
javascript
function add(a, b) {
return a + b;
}
const obj = {
name: "zhangsan",
age: 18,
};
const result1 = add.apply(obj, [1, 2]); // this 指向 obj, 参数为 [1, 2]
const result2 = add.call(obj, 1, 2); // this 指向 obj, 参数为 1, 2
const result3 = add.bind(obj)(1, 2); // this 指向 obj, 参数为 1, 2
防抖
- 单位时间内, 频繁触发事件, 但是只执行(最后一次)一次, 防止函数被频繁调用
通过 _.debounce 实现防抖
javascript
const box = document.getElementById("box");
box.addEventListener(
"mousemove",
_.debounce(() => {
console.log("mousemove");
}, 1000)
); // 1000 毫秒内只执行一次
通过 setTimeout 实现防抖
javascript
function debounce(func, wait) {
let timeout;
return function () {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, arguments);
}, wait);
};
}
const box = document.getElementById("box");
box.addEventListener(
"mousemove",
debounce(() => {
console.log("mousemove");
}, 1000)
); // 1000 毫秒内只执行一次
节流
- 单位时间内, 频繁触发事件, 但是只执行(等待第一次实现完成)一次, 防止函数被频繁调用
通过 _.throttle 实现节流
javascript
const box = document.getElementById("box");
box.addEventListener(
"mousemove",
_.throttle(() => {
console.log("mousemove");
}, 1000)
); // 1000 毫秒内只执行一次
通过 setInterval 实现节流
javascript
function throttle(func, wait) {
let timeout;
return function () {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(this, arguments);
}, wait);
}
};
}
const box = document.getElementById("box");
box.addEventListener(
"mousemove",
throttle(() => {
console.log("mousemove");
}, 1000)
); // 1000 毫秒内只执行一次
记录视频播放进度
javascript
const video = document.getElementById("video");
video.ontimeupdate = _.throttle(() => {
localStorage.setItem("videoProgress", video.currentTime);
}, 1000);
video.onloadedmetadata = () => {
video.currentTime = localStorage.getItem("videoProgress") || 0;
};