深入理解JavaScript中的深浅拷贝、异常处理、this处理和防抖节流
在JavaScript开发中,我们常常会遇到深浅拷贝、异常处理、this处理和防抖节流这些重要概念。掌握这些概念对提升代码质量和开发效率至关重要。本文将详细介绍这些内容,并附上代码实例进行说明。
1. 深浅拷贝
1.1 浅拷贝
浅拷贝是指复制对象的引用,而不是复制对象本身。复制后的新对象与原对象共享同一块内存空间,对新对象的修改会影响原对象。
示例
const original = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, original);
console.log(shallowCopy); // 输出: { a: 1, b: { c: 2 } }
shallowCopy.b.c = 3;
console.log(original.b.c); // 输出: 3,原对象也受到了影响
- 直接赋值和浅拷贝有什么区别?
- 直接赋值的方法,只要是对象,都会相互影响,因为是直接拷贝对
象栈里面的地址 - 浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会
相互影响
- 浅拷贝怎么理解?
- 拷贝对象之后,里面的属性值是简单数据类型直接拷贝值
- 如果属性值是引用数据类型则拷贝的是地址
1.2 深拷贝
深拷贝是指递归复制对象的每一层结构,生成一个完全独立的新对象。深拷贝后的新对象与原对象互不影响。
1.2.1 递归实现深拷贝
通过递归方式实现深拷贝,可以处理嵌套对象。
示例
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
const o = {}
// 拷贝函数
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
// 处理数组的问题 一定先写数组 在写 对象 不能颠倒
if (oldObj[k] instanceof Array) {//检查一个对象是否是另一个对象的实例
newObj[k] = []
// newObj[k] 接收 [] hobby
// oldObj[k] ['乒乓球', '足球']
deepCopy(newObj[k], oldObj[k])
} else if (oldObj[k] instanceof Object) {
newObj[k] = {}
deepCopy(newObj[k], oldObj[k])
}
else {
// k 属性名 uname age oldObj[k] 属性值 18
// newObj[k] === o.uname 给新对象添加属性
newObj[k] = oldObj[k]
}
}
}
deepCopy(o, obj) // 函数调用 两个参数 o 新对象 obj 旧对象
console.log(o)
o.age = 20
o.hobby[0] = '篮球'
o.family.baby = '老pink'
console.log(obj)
console.log([1, 23] instanceof Object)
</script>
1.2.2 lodash
库中的 cloneDeep
lodash
是一个非常流行的 JavaScript 工具库,其中的 cloneDeep
方法可以方便地实现深拷贝。
示例
const _ = require('lodash');
const original = { a: 1, b: { c: 2 } };
const deepCopy = _.cloneDeep(original);
deepCopy.b.c = 3;
console.log(original.b.c); // 输出: 2,原对象未受到影响
1.2.3 JSON序列化
通过 JSON.stringify
和 JSON.parse
可以实现深拷贝,但不能处理函数和 undefined
。
示例
const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.b.c = 3;
console.log(original.b.c); // 输出: 2,原对象未受到影响
小结
- 浅拷贝 只复制对象的引用,适用于非嵌套对象。
- 深拷贝 递归复制对象的每一层,适用于嵌套对象。
- 使用
lodash
的cloneDeep
或 JSON 序列化可以方便地实现深拷贝。
2. 异常处理
2.1 throw
throw
语句用于抛出一个用户自定义的异常。
示例
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero is not allowed.');
}
return a / b;
}
try {
console.log(divide(4, 0));
} catch (e) {
console.error(e.message); // 输出: Division by zero is not allowed.
}
2.2 try...catch
try...catch
语句用于捕获和处理异常。try
代码块中包含可能抛出异常的代码,catch
代码块中包含处理异常的代码。
示例
try {
const result = divide(4, 0);
console.log(result);
} catch (error) {
console.error('An error occurred:', error.message); // 输出: An error occurred: Division by zero is not allowed.
}
2.3 debugger
debugger
语句用于暂停代码执行并打开调试工具。
示例
function debugExample() {
const x = 10;
const y = 20;
debugger; // 代码在这里暂停,可以在浏览器控制台查看变量的值
return x + y;
}
console.log(debugExample()); // 浏览器会暂停在 debugger 处
小结
throw
用于抛出自定义异常。try...catch
用于捕获和处理异常。debugger
用于调试代码。
3. 处理 this
3.1 普通函数
在普通函数中,this
的值取决于函数的调用方式。
示例
const obj = {
value: 42,
getValue: function() {
return this.value;
}
};
console.log(obj.getValue()); // 输出: 42
3.2 箭头函数
箭头函数没有自己的 this
,它继承自外层作用域的 this
。
示例
const obj = {
value: 42,
getValue: () => {
return this.value; // 箭头函数的 this 继承自外层,这里的 this 指向全局对象
}
};
console.log(obj.getValue()); // 输出: undefined
3.3 改变 this
指向
3.3.1 call
call
方法可以调用一个函数,并显式地指定 this
的值。
示例
<script>
// 普通函数
function sayHi() {
console.log(this);
}
let user = {
name: '小明',
age: 18
}
let student = {
name: '小红',
age: 16
}
// 调用函数并指定 this 的值
sayHi.call(user); // this 值为 user
sayHi.call(student); // this 值为 student
// 求和函数
function counter(x, y) {
return x + y;
}
// 调用 counter 函数,并传入参数
let result = counter.call(null, 5, 10);
console.log(result);
</script>
call
方法能够在调用函数的同时指定this
的值- 使用
call
方法调用函数时,第1个参数为this
指定的值 call
方法的其余参数会依次自动传入函数做为函数的参数
3.3.2 apply
apply
方法与 call
类似,但接受参数数组。
示例
function greet(greeting, punctuation) {
console.log(`${greeting}, my name is ${this.name}${punctuation}`);
}
const person = { name: 'Bob' };
greet.apply(person, ['Hi', '!']); // 输出: Hi, my name is Bob!
3.3.3 bind
bind
方法创建一个新的函数,并永久绑定 this
的值。
示例
function greet() {
console.log(`Hello, my name is ${this.name}`);
}
const person = { name: 'Charlie' };
const boundGreet = greet.bind(person);
boundGreet(); // 输出: Hello, my name is Charlie
小结
- 普通函数中的
this
取决于调用方式。 - 箭头函数没有自己的
this
,继承自外层作用域。 call
、apply
和bind
方法可以显式地改变this
的指向。
4. 防抖节流
防抖(Debounce)
防抖技术用于限制函数在一定时间间隔内只执行一次。如果在这个时间间隔内函数被再次调用,则重新计时。
示例
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}
window.addEventListener('resize', debounce(() => {
console.log('Resize event handler called');
}, 300));
节流(Throttle)
节流技术用于限制函数在一定时间间隔内最多执行一次,不管这个时间间隔内函数被调用多少次。
示例
function throttle(func, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
func.apply(this, args);
}
};
}
window.addEventListener('scroll', throttle(() => {
console.log('Scroll event handler called');
}, 300));
小结
- 防抖:在指定时间间隔内多次调用只执行一次,适用于减少高频率事件的调用次数。
- 节流:在指定时间间隔内限制函数调用次数,适用于控制事件处理频率。