十七、数据劫持、闭包、递归、拷贝、Promise

286 阅读7分钟

1. 数据劫持(数据代理)

1.1 数据劫持(数据代理):

  • 在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果

1.2 数据劫持(数据代理)的2种方式:

  • ① Object.defineProperty()
  • ② proxy

1.3 Object.defineProperty(obj,key,{属性描述符}) ---> ES5 ---> 只能代理对象的某个属性

  1. 作用:给对象定义新属性或修改对象的现有属性,并返回对象

  2. 参数:

    • 参数1:obj,操作的对象

    • 参数2:key,属性名

    • 参数3:{ },属性描述符。可配置项包括:

      • value:50; 设置属性值。默认值undefined
      • configurable:true; 属性能否被删除。默认是false
      • writable:true; 是否能设置属性值。默认是false
      • enumerable:true; 属性是否能遍历。默认是false
      • 以上4个属性不能和getter,setter一起使用
      • get ---> 属性的 getter 函数,获取会调用此函数。默认值undefined
      • set ---> 属性的 setter 函数,修改会调用此函数。默认值undefined
  3. 返回值:返回修改后的对象

QQ图片20230422194431.png

【补充】Object.defineProperty()

1.4 new Proxy(target,handler) ---> ES6 ---> 代理整个对象

  1. 作用:代理整个对象
  2. 参数:
    • 参数1:target,被代理的对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
    • 参数2:handler,一个通常以函数作为属性的对象,各属性中的函数定义了代理的行为
    QQ图片20230422193807.png

【补充】数据劫持

2. Object.prototype.hasOwnProperty() 过滤原型上的属性和方法

QQ图片20230422194834.png

3. 闭包

3.1 什么是闭包

  • 函数嵌套函数,内部函数可以使用外部函数的参数和变量,并且参数和变量不会被js的垃圾回收机制(gc)回收。

    function fn(){
        var num = 10;
        return num;
    }
    fn();
    console.log(num); // 报错,num未定义
    // 报错的原因:
    // 1. 作用范围超出。函数内部的局部变量,不能超出作用范围访问
    // 2. 函数执行完毕之后,函数内部的局部变量被销毁
    
    
    // 解决问题:闭包
    function fn1(){
        var num = 20;
        return function(){
            num++;
            return num;
        }
    }
    var a = fn1();
    console.log(a()); // 21
    console.log(a()); // 22
    var b = fn1();
    console.log(b()); // 21
    

3.2 闭包的优点

  • ① 让一个变量长期驻扎在内存当中不被释放
  • ② 避免变量全局污染

3.3 闭包的缺点

  1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
  2. 闭包会在父函数外部,改变父函数内部变量的值。

3.4 闭包的应用

  1. 实现缓存
  2. 存储值与避免变量全局污染
  3. 函数的柯里化
  4. 节流和防抖

3.5 防抖debounce

防抖:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行

QQ图片20230422220023.png

3.6 节流throttle

节流:指定时间间隔内只会执行一次任务

QQ图片20230424175225.png

3.7 函数的柯里化(了解)

4. 递归

4.1 递归

函数自己调用自己,要有临界点

4.2 递归的特点

循环能做的事,递归都能做。但是性能不太好,不推荐使用

4.3 递归的应用

  • ① 快速排序
  • ② nodejs磁盘文件的遍历
  • ③ 管理系统的权限菜单栏
  • ④ 对象的深拷贝

5. 拷贝

仅存在于引用类型中。

5.1 2种拷贝:深拷贝和浅拷贝

  • 浅拷贝:拷贝的对象(包括子对象),除了第一层对象没有任何关联,有可能第二层对象存在关联。只有第一层引用地址不共享,第二层引用地址共享。
  • 深拷贝:拷贝的对象(包括子对象),与原来的对象没有任何关联。所有的引用地址都不共享。

5.2 浅拷贝的方式

5.2.1 数组的浅拷贝:
  • arr.slice(0)
  • arr.concat([])
  • ③ 扩展运算符 [...arr]
  • ④ 使用第三方js库lodash _.clone(arr)
5.2.2 对象的浅拷贝:
  • Object.assign({},obj)
  • ② 扩展运算符 {...obj}
  • ③ 使用第三方js库lodash _.clone(obj)

5.2 深拷贝的方式

  • JSON.stringify() + JSON.parse()

    缺点:会丢失方法

  • ② 使用第三方js库lodash _.cloneDeep(obj)

  • ③ 利用递归手写深拷贝

      function cloneDeep(a){
          // 先判断是数组还是对象,再创建新的数组或者对象
          let b = Array.isArray(a) ? [] : {};
          for(let key in a){
              if(typeof a[key] == 'object'){
                  b[key] = cloneDeep(a[key]);
              }else{
                  b[key] = a[key];
              }
          }
          return b;
      }
    

【补充:区分数组和对象的一些方法】

1. Array.isArray() ---> 可靠

   let obj = {};
   let arr = [];
   console.log(Array.isArray(obj)); // false
   console.log(Array.isArray(arr)); // true

2. instanceof ---> 不太可靠

原理:通过原型链查找是否有该类型的原型

   let obj = {};
   let arr = [];
   console.log(obj instanceof Object); // true
   console.log(arr instanceof Object); // true
   console.log(obj instanceof Array); // false
   console.log(arr instanceof Array); // true

3. Object.prototype.toString.call() ---> 最可靠

   let obj = {};
   let arr = [];
   console.log(Object.prototype.toString.call(obj));  // [object Object]
   console.log(Object.prototype.toString.call(arr));  // [object Array]
   
   // 封装一个判断引用类型具体类型的方法
   function checkType(o){
       let str1 = Object.prototype.toString.call(o);
       let str2 = str1.split(" ")[1]; 
       return str2.slice(0,str2.length-1);
   }

6. promise

【知识补充1】

  • 回调函数:一个函数作为另一个函数的参数,在另一个函数内部被执行和传递参数。
  • 回调函数优点:① 解决异步; ② 扩展函数的功能。
  • 缺点:容易造成回调地狱,不方便维护和理解

【知识补充2】

  • 同步:按顺序一步一步执行,容易造成阻塞,js是单线程。阻塞:第一步未完成,就不能进行第二步,等待的过程就是阻塞
  • 异步:和同步相反

【知识补充3】解决异步的发展史:

  • ① 回调函数
  • ② 函数生成器 ---> ES5 (已淘汰)
  • ③ promise ---> ES6
  • ④ promise + async + await ---> ES7

6.1 Promise

是一个构造函数/类,需要被实例化,是一个微任务。

  • ① promise在实例化的时候需要传递一个参数,这个参数是一个函数。

  • ② new Promise(function(){}) 本身是同步的

  • ③ 语法:let p1 = new Promise((resolve,reject)=>{})

    【注】resolve,reject都是函数

6.2 promise的3个状态

  • ① pending 等待
  • ② fulfilled 完成
  • ③ rejected 拒绝
  • 顺序不可逆,只能pending --> fulfilled,或者pending --> rejected

6.3 promise的3个原型方法

6.3.1 then()
  • ① 作用:可以接收resolve和reject的结果
  • ② 参数:2个函数,第1个函数接收resolve的结果,第2个函数接收reject的结果。如果第2个函数不写,后面要调用catch()方法捕获reject的结果。
  • ③ 返回值:返回一个promise对象
6.3.2 catch()
  • ① 作用:捕获reject的结果
  • ② 参数:1个函数,捕获reject的结果。
  • ③ 返回值:返回一个promise对象
6.3.3 finally()
  • ① 作用:执行resolve或reject后,都会执行finally

  • ② 参数:无

  • ③ 返回值:返回一个promise对象

    function getData(){
        return new Promise(function(resolve,reject){
            resolve('resolve被执行了'); // resolve执行reject就不会执行
            reject('reject被执行了'); // reject执行resolve就不会执行
        })
    }
    
    getData().then(function(res){
        console.log(res);
    }).catch(function(res){
        console.log(res);
    }).finally(console.log('无论成功或者失败都会执行finally'))
    

6.4 promise的4个静态方法

6.4.1 Promise.all() 并发执行
  • ① 作用:将多个promise包装成一个promise,并发执行,返回所有的结果
  • ② 参数:参数是一个数组,数组成员是promise对象
  • ③ 返回值:返回一个promise对象
6.4.2 Promise.race() 返回先完成的结果
  • ① 作用:将多个promise包装成一个promise,谁先完成返回谁
  • ② 参数:参数是一个数组,数组成员是promise对象
  • ③ 返回值:返回一个promise对象
6.4.3 Promise.resolve()

作用:返回状态为fulfilled的promise对象

6.4.4 Promise.reject()

作用:返回状态为rejected的promise对象

6.5 promise解决异步(ES6)

// 1. 回调函数解决异步
function getData(callback){
    setTimeout(()=>{
        var o = {id:1,name:"小A"};
        callback(o);
    },5000)
}
getData(function(res){console.log(res)}); // {id: 1, name: '小A'}
// 如果要多次调用,容易造成回调地狱,不方便维护和理解
getData(function(res){
    getData(function(res){
        getData(function(res){
            console.log(res)
        })
    })
})


// 2. promise解决异步:用promise把异步包裹起来
function getData(){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            var o = {id:1,name:"小A"};
            resolve(o);
        },5000)
    })
}
getData().then((res)=>{console.log(res)},(res)=>{console.log(res)}); // {id: 1, name: '小A'}
// 如果要多次调用,代码冗余
getData().then(function(res){
    return getData();
}).then(function(res){
    return getData();
}).then(function(res){
    console.log(res);
})

6.6 promise + async + await 解决异步(ES7)

  • await 必须放在 异步函数 中。函数前加async就是异步函数

  • await 后面必须是一个 promise对象

【注】onsubmit事件不能和 async await 使用

function getData(){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            var o = {id:1,name:"小A"};
            resolve(o);
        },5000)
    })
}
async function getA(){
    var res = await getData();
    
}