JS高级

90 阅读4分钟

闭包

1.什么是闭包?

闭包:能够访问另一个函数作用域中变量的函数。

原理:作用域链,当前作用域可以访问上级作用域中的变量

产生闭包的条件

​ 函数套函数

​ 内部函数访问外部函数局部变量,必须要访问了变量才会产生闭包

function outside() {
    var n = 1;
    function inside() {
        console.log(n);  //1
    }
    inside();
}

outside();
console.log(n); //n is not defined

2.闭包的作用

​ 闭包的主要作用:延伸了变量的作用范围

function outside() {
    var n = 1;
    return function() {
        console.log(n); 
    }
}

let get_n = outside();
get_n();  //1  外部访问函数内部的变量

闭包还有个特点就是,一般来说outside函数调用完变量n就会销毁,但是闭包的话延伸了变量的作用范围outside函数调用完n不会立即销毁,等到get_n函数调用完,n才销毁,即函数作用域中的变量在函数执行结束之后不被销毁

但是闭包也有缺点:

由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。

递归

什么是递归?

递归就是函数内部自己调用自己

递归函数的作用和循环差不多,就是无限套娃

递归很容易会发生栈溢出(不断调用不断开辟内存空间,函数放在堆里,函数的调用结果放在栈里,所以是栈溢出不是堆溢出),所以一定要加return才行。

let num = 1;
function fn() {
    console.log('打印6句话');
    if (num === 6) {
        return;
    }
    num++;
    fn();
}
fn();

深拷贝和浅拷贝

​ 浅拷贝只拷贝一层,对象级别只拷贝地址

​ 深拷贝拷贝多层,每一级都会拷贝

浅拷贝

浅拷贝是指将一个对象的属性值复制到另一个新的对象中,新对象和原对象的基本数据类型属性会复制一份新的副本,而引用类型属性则只会复制一份引用,这两份引用指向同一个堆内存地址。因此,如果改变了其中一个对象的引用类型属性,那么另一个对象的引用类型属性也会发生改变。

JavaScript 中常见的浅拷贝方式有:

  1. Object.assign(target, ...sources):该方法会将源对象(source)中的可枚举属性复制到目标对象(target)中,并返回目标对象。当多个源对象具有相同的键名时,后面的源对象的属性值会覆盖前面的源对象的属性值。

  2. 扩展运算符(...):该运算符可以将一个可迭代对象展开为多个元素,可以用于浅拷贝数组或对象。

  3. Array.prototype.slice():该方法可以将一个数组的一部分浅拷贝到一个新的数组中。

    const obj1 = { name: '张三', age: 20 };
    const obj2 = Object.assign({}, obj1); // 将 obj1 浅拷贝给 obj2
    console.log(obj2); // { name: '张三', age: 20 }
    
    // 扩展运算符
    const arr1 = [1, 2, 3];
    const arr2 = [...arr1]; // 将 arr1 浅拷贝给 arr2
    console.log(arr2); // [1, 2, 3]
    
    // Array.prototype.slice()
    const arr3 = [4, 5, 6];
    const arr4 = arr3.slice(); // 将 arr3 浅拷贝给 arr4
    console.log(arr4); // [4, 5, 6]
    

    需要注意的是,浅拷贝只会复制对象自身的可枚举属性,而不会拷贝原型链上的属性

深拷贝

深拷贝是指将一个对象(或数组、函数等)完全复制一份新的副本,并与原对象完全分离,两者互不影响。当源对象发生改变时,新对象不会受到影响。

在 JavaScript 中,由于对象可以嵌套多层,因此需要递归遍历对象的所有属性,将每一个属性都进行复制。常见的深拷贝方式有:

  1. JSON.parse(JSON.stringify(object)):该方法可以将对象转换为 JSON 字符串,然后再将字符串转换回对象,从而实现深拷贝。但是该方法存在一定的限制,比如它无法复制函数和 undefined 类型的值。

  2. 递归实现:可以通过递归遍历对象的每一个属性,对每个属性进行复制,从而实现深拷贝。但是这种方式可能会存在一些问题,例如循环引用问题,即对象中的某个属性引用了该对象本身,会导致死循环。

    // JSON.parse(JSON.stringify())
    const obj1 = { name: '张三', age: 20, hobbies: ['篮球', '足球'] };
    const obj2 = JSON.parse(JSON.stringify(obj1)); // 将 obj1 深拷贝给 obj2
    console.log(obj2); // { name: '张三', age: 20, hobbies: ['篮球', '足球'] }
    
    // 递归实现
    function deepClone(obj) {
      if (obj === null || typeof obj !== 'object') {
        return obj;
      }
    
      let copy = Array.isArray(obj) ? [] : {};
      for (let key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
          copy[key] = deepClone(obj[key]);
        }
      }
      return copy;
    }
    
    const obj3 = { name: '李四', age: 30, address: { city: '上海', street: '中山路' } };
    const obj4 = deepClone(obj3); // 将 obj3 深拷贝给 obj4
    console.log(obj4); // { name: '李四', age: 30, address: { city: '上海', street: '中山路' } }
    
    

    需要注意的是,深拷贝过程中可能会存在一些性能问题和内存泄漏问题,因此在实际应用中需要谨慎使用