T-JS核心-day07-ES5-保护对象、创建新对象、call/apply/bind、数组新增函数

ES5

1. 保护对象

  1. 问题:旧的js中的对象毫无自保能力,对象的结构和属性值可以被任意修改

  2. 解决:ES5中,提供了保护对象属性和结构的新方法

  3. 保护对象的属性

    1. 什么是:阻止对象的属性值进行不符合规定的篡改
    2. 如何做:
      1. 其实,ES5中已经把每个对象的属性,变成了一个微缩的小对象
        在这里插入图片描述

      2. 获得对象中一个属性的描述对象
        var 属性的描述对象=Object.getOwnPropertyDescriptor(对象,"属性名")

      3. 如何修改对象属性的开关

        1. 强调:禁止使用.直接访问属性的开关,必须用专门的函数
        2. 如果只修改对象的一个属性的开关:
          Object.defineProperty(对象,"属性名",{
              开关名:truefalse,
              ...   :  ....
          })
          
        3. 问题:defineProperty方法一次只能修改对象中一个属性的开关。如果多个属性都需要修改开关,代码就会很繁琐
        4. 用一个函数批量修改一个对象的多个属性的开关
          Object.defineProperties(对象,{
              属性名:{
                  开关名:truefalse,
                  ...   :   ...
              },
              属性名:{
                  开关名:truefalse,
                  ...   :   ...
              },,
              ...   :   ....
          })
          
        5. 示例:使用开关保护eric对象的属性
          用definePrototy()写
            <script>
              "use strict"
              var eric={
                eid:1001,//只读
                ename:"埃里克",//不能删除
                salary:12000//不能随便用for in遍历
              }
              // 尝试获得eric对象的eid属性的描述对象
              var eid_obj=Object.getOwnPropertyDescriptor(eric,"eid");
              console.log(eid_obj);
              // 让eir的eid只读
              Object.defineProperty(eric,"eid",{
                writable:false,
                configurable:false//不允许再修改writable
              });
              // 让eir的ename不能删除
              Object.defineProperty(eric,"ename",{
                configurable:false//不允许删除ename
              });
              // 让eir的salary属性不能被for in 遍历
              Object.defineProperty(eric,"salary",{
                enumerable:false,
                configurable:false//不允许再修改enumerable开关
              });
              // 测试:
              // 试图打开eid属性的writable开关
              // Object.defineProperty(eric,"eid",{
              //   writable:true,
              //   configurable:true
              // })
              // 报错:提示:不能重定义属性:eid
              // 试图修改eid属性值
              // eric.eid=1002;//报错
              // 报错:提示:不能赋值给只读属性eid
              // 试图删除ename属性
              // delete eric.ename;//报错
              // 报错:提示:不能删除属性ename
              // 试图遍历eric所有属性
              for(var key in eric){
                console.log(`${key}:${eric[key]}`);
              }//仅遍历到eid和ename属性,遍历不到salary属性
              // 试图用.访问salary属性--成功过,可以访问
              console.log(`eric的工资是${eric.salary}`);//12000
            </script>
          
          用defineProterties()写
            <script>
              "use strict"
              var eric={
                eid:1001,//只读
                ename:"埃里克",//不能删除
                salary:12000//不能随便用for in遍历
              }
              // 让eir的eid只读
              // 让eir的ename不能删除
              // 让eir的salary属性不能被for in 遍历
              Object.defineProperty(eric,{
                eid:{
                  writable:false,
                  configurable:false//不允许再修改writable
                },
                ename:{
                  configurable:false//不允许删除ename
                },
                salary:{
                  enumerable:false,
                  configurable:false//不允许再修改enumerable开关
                }
              });
              // 测试:
              // 试图打开eid属性的writable开关
              // Object.defineProperty(eric,"eid",{
              //   writable:true,
              //   configurable:true
              // })
              // 报错:提示:不能重定义属性:eid
              // 试图修改eid属性值
              // eric.eid=1002;//报错
              // 报错:提示:不能赋值给只读属性eid
              // 试图删除ename属性
              // delete eric.ename;//报错
              // 报错:提示:不能删除属性ename
              // 试图遍历eric所有属性
              for(var key in eric){
                console.log(`${key}:${eric[key]}`);
              }//仅遍历到eid和ename属性,遍历不到salary属性
              // 试图用.访问salary属性--成功过,可以访问
              console.log(`eric的工资是${eric.salary}`);//12000
            </script>
          
      4. 问题:开关的保护功能很弱,不灵活,无法使用自定义的规则保护属性值。

      5. 解决:给程序的属性请保镖-----访问器属性

        1. 什么是访问器属性:不实际存储属性值,仅提供对另一个保存数据的属性的保护

        2. 为什么:属性的开关不够灵活,无法用自定义规则灵活保护属性

        3. 何时用:只要用自定义规则,灵活保护属性值时,都用访问器属性

        4. 怎么定义:2步

          1. 第一步:先将要保护的属性,隐姓埋名,半隐藏
          2. 第二步:为受保护的属性,请保镖
            1. 保镖就是访问器属性,但是为对象添加访问器属性,不能直接在对象的{}内添加。只能通过Object.defineProperty()Object.defineProperties()添加
            2. 保镖要茂名顶替原属性名:
              访问器属性的名称,应该和想要保护的那个属性名一致,才能起到替身的作用
            3. 保镖一请就是一对儿:
              get:function(){return this.受保护的变量}
              set:function(value){...}
              其中get和set不能改变,必须怎么写
          3. 怎么用:通过访问器属性来操作受保护的属性
            1. 用访问器属性保护属性不是为了阻止大家使用,而是为了保障大家在合理的范围内使用属性
            2. 希望外界通过访问器属性来操作受保护的属性
            3. 所以,访问器属性的用法和普通的属性完全一样
              1. 当外界想获得属性值时:
                对象.访问器属性
                原理:当外界试图获得属性值时,访问器属性会自动调用自己的get函数
              2. 当外界想修改属性值时:
                对象.访问器属性=新值
                原理:当外界试图修改属性值时,访问器属性会自动调用自己的set函数,同时将等号右边的新值,自动交给set函数的value形参变量,在保存到受保护的属性中
                在这里插入图片描述
        5. 示例:使用访问器属性保护对象eric的eage属性

            <script>
              // 要求年龄可修改,但是年龄必须介于18~65之间
              var eric={
                eid:1001,
                ename:"埃里克",
                _eage:25//隐姓埋名---不想让外人随意使用
              }
              Object.defineProperties(eric,{
                _eage:{
                  // 设置半隐藏
                  enumerable:false,
                  configurable:false
                },
                // 请保镖
                // 冒名顶替
                eage:{
                  // 保镖一请就是一对儿
                  // 专门负责从受保护的属性中,获取属性值
                  get:function(){
                    console.log(`自动调用eage的get()`);
                    // 返回收保护的属性_eage的值
                    return this._eage;
                  },
                  // 专门负责将要修改的新值,结果验证后,保存到受保护的属性中
                  set:function(value){
                    console.log(`自动调用eage的set(${value})`);
                    if(value>18&&value<=65){
                      this._eage=value;
                    }else{
                      throw Error(`年龄必须介于18~65之间!;`)
                    }
                  },
                  enumerable:true,//设置eage替_eage抛头露面
                  configurable:false//不能轻易删除保镖
                  // 因为保镖不实际保存属性值,所以没有value属性
                  // 因为writable开关无法灵活保护属性值,所以保镖也没有writeable开关
                }
              });
              // 测试
              // 试图读取eric的年龄
              console.log(eric.eage);
              // 试图修改eric的年龄为26
              eric.eage=26;
              console.log(eric.eage);
              // 试图修改eric的年龄为-2
              eric.eage=-2;
            </script>
          

          输出结果:
          在这里插入图片描述

      6. 访问器属性中的this
        在这里插入图片描述

  4. 保护对象的机构:3种级别

    1. 防扩展:阻止为对象添加新属性
      1. 旧js中可以随时给对象添加新属性
      2. 如何禁止为对象添加新属性
        Object.preventExtensions(对象)
      3. 示例:阻止为对象添加新属性
          <script>
            "use strict"
            var eric={
              eid:1001,
              ename:"埃里克"
            }
            // 防止对eric添加新属性
            Object.preventExtensions(eric);
            // 试图为对象新的不同名的eid属性
            eric.Eid=1003;//报错
            // 报错提示:不能添加属性Eid,(因为)对象时不可扩展的
            console.log(eric);
          </script>
        
    2. 密封
      1. 什么是:既阻止给对象添加新属性,又阻止删除对象的现有属性
      2. 为什么:因为几乎所有对象中的属性,都应该是禁止删除的,但是每个属性都要写configurable:false太忙
      3. 何时:几乎所有对象都要密封
      4. 如何做:Object.seal(对象)
      5. 原理:seal()做了两件事
        1. 自动调用Object.preventExtensions()阻止对当前对象的扩展
        2. 自动为每个属性都添加了configurable:false
      6. 示例:密封一个对象
          <script>
            "use strict"
            var eric={
              eid:1001,
              ename="埃里克"
            }
            // 密封对象
            Object.seal(eric);
            // 试图添加新属性
            eric.Eid=1003;//报错
            // 试图删除eid属性
            delete eric.eid;//报错
          </script>
        
      7. 大部分一般对象,保护到密封级别(seal)就足够了
    3. 冻结:
      1. 什么是:既不能添加删除现有属性,又不能修改属性值
      2. 何时:如果多个模块共用的对象,就不应该让某一个模块擅自修改对象的属性值,一旦修改,牵一发而动全身
      3. 如何:Object.freeze(对象)
      4. 原理:做了三件事:
        1. 自动调用preventExtensions()阻止添加新属性
        2. 自动为每个属性添加configurable:false
        3. 自动设置每个属性的writable:false
        4. 示例:冻结对象
            <script>
              "use strict"
              var obj={
                host:"192.168.0.100",
                port:3306,
                db:"xz"
              }
              // 希望obj对象中所有属性,禁止修改,禁止删除
              // 且禁止给obj添加新属性
              Object.freeze(obj);
              // 尝试给obj添加新属性
              // obj._host="127.0.0.1";//报错
              // 删除删除obj中现有属性
              // delete obj.host;//报错
              // 尝试修改obj中host属性值
              obj.host="localhost";//报错提示:不能修改只读属性host
            </script>
          

2. Object.create()

  1. 什么是:基于一个现有对象,创建新的子对象,来继承这个父对象
  2. 简单说:没有构造函数,也能创建子对象,继承父对象
  3. 如何
    1. 只创建子对象
      1. 格式:var 子对象=Object.create(父对象)
      2. 原理:
        1. 创建一个新的空对象
        2. 自动设置新的空对象的__proto__继承父对象
    2. 既创建子对象,又为子对象添加自有属性
      1. 格式
        var 子对象=Object.create(父对象, {
            //必须采用defineProperties函数同样的格式添加自有属性
            自有属性名:{
                value:属性值,
                writable:true,
                enumerable:true,
                configurable:false
            },
            自有属性名:{
                value:属性值,
                writable:true,
                enumerable:true,
                configurable:false
            }
          })
        
      2. 原理:做了3件事
        1. 创建一个空对象
        2. 自动设置新的空对象的__proto__继承父对象
        3. 可以为新对象添加自有属性
  4. 示例:基于父对象,创建一个子对象,并未子对象添加自有属性
      <script>
        "use strict"
        var father={
          bal:1000000,
          car:"infiniti"
        }
        // 创建一个子对象hmm,继承父对象father
        var hmm=Object.create(father,{
          // 并为子对象hmm,添加两个自有属性
          bao:{
            value:"LV",
            writable:true,
            enumerable:true,
            configurable:false
          },
          phone:{
            value:"华为",
            writable:true,
            enumerable:true,
            configurable:false
          }
        });
        console.log(hmm);
        console.log(hmm.bal,hmm.car);
        console.log(hmm.bao,hmm.phone);
      </script>
    
    输出结果:
    在这里插入图片描述

3. call/apply/bind

  1. 相同点:都能替换函数中不想要的this为想要的对象
  2. 何时:如歌一个函数中的this不是你想要的,就可以用call/apply/bind将this替换为想要的对象
  3. 如何:3种情况
    1. 只在调用函数时,临时替换一次this为指定的对象-----call
      1. 如何做:要调用的函数.call(替换this的对象,实参值列表)
      2. 原理:call做了2件事
        1. 会调用一次该函数,并将实参值列表传递给形参变量,用于函数内执行
        2. 会在本次调用时,临时将函数中的this替换为一个指定的对象(call的第一个实参)
        3. 示例:使用call替换一次函数中的this
            <script>
              // 一个公共计算薪资的函数
              function jisuan(base,bonus1,bonus2){
                console.log(`${this.ename}的总工资是:${base+bonus1+bonus2}`);
              }
              // 创建两个员工对象
              var lilei={ename:"Li Lei"};
              var hmm={ename:"Han Meimei"};
              // lilei想计算自己的薪资
              // 错误写法一:此时的this指向window
              // jisuan(10000,2000,3000);
              // 错误写法二:报错,因为lilei的原型链上没有jisuan函数
              // lilei.jisuan(10000,2000,3000);
              // 用call写----成功:输出结果:Li Lei的总工资是:15000
              jisuan.call(lilei,10000,2000,3000);
              // call会调用jisuan函数执行
              // 并将call从第二个实参值开始的的实参值传递给jisuan()的形参变量
              // 同时call会将jisuan()中的this,临时替换为lilei,调用后,恢复原样
            </script>
          
    2. 既可替换this,又能打散数组参数-----apply
      1. 为什么:如果要调用的函数有多个实参值,但是多个实参值却是放在一个数组中给的
      2. 如何做:要调用的函数.apply(替换this的对象,数组)
      3. 原理:
        1. apply拥有和call相同的功能,都能调用函数,并替换其中的this为指定对象
        2. 但apply比call多一个功能,apply能先打散数组为多个值,再传参
      4. 示例:用apply替换this,并打散数组参数
          <script>
            // 一个公共计算薪资的函数
            function jisuan(base,bonus1,bonus2){
              console.log(`${this.ename}的总工资是:${base+bonus1+bonus2}`);
            }
            // 创建两个员工对象
            var lilei={ename:"Li Lei"};
            var hmm={ename:"Han Meimei"};
            // lilei想计算自己的薪资,但是lilei的工资条是一个数组
            var arr=[10000,2000,3000];
            // 用apply写----输出结果:Li Lei的总工资是:15000
            jisuan.apply(lilei,arr);
          </script>
        
    3. 基于原函数创建一个新函数副本,并永久替换this
      1. 问题:如果需要反复使用替换this后的函数,那么每次.call(对象名)就很繁琐
      2. 如何做:var 新函数名=原函数.bind(替换this的对象)
      3. 原理:
        1. .bind()会创建一个和原函数一模一样的新函数副本,原函数保持不变
        2. 将新函数副本中的this,永久替换为指定的对象
      4. 结果:将来反复调用新函数,即使不传入替换this的对象,也可以保证this为指定对象
      5. 如果部分实参值也永久固定不变,也可以用.bind()提前绑定到形参变量上
        var 新函数名=原函数.bind(替换this的对象,固定不变的一个或多个实参值);
      6. 强调:已经被.bind()永久绑定的实参值,将来调用函数时,无需重复传入。只要从后续未绑定的实参值继续传入即可
      7. 示例:使用bind永久绑定函数的this
          <script>
            // 一个公共计算薪资的函数
            function jisuan(base,bonus1,bonus2){
              console.log(`${this.ename}的总工资是:${base+bonus1+bonus2}`);
            }
            // 创建两个员工对象
            var lilei={ename:"Li Lei"};
            var hmm={ename:"Han Meimei"};
        
            // lilei不香总是call(lilei)这么麻烦
            // 想自己有一个专属的jisuan()函数
            var js_lilei=jisuan.bind(lilei,10000);
            // 只是赋值jisuan函数的副本,不改变原函数
            js_lilei(2000,3000);//输出Li Lei的总工资是:15000
            // hmm依然可以使用元函数jisuan(),不受影响
            jisuan.call(hmm,3000,4000,5000);
            //输出:Han Meimei的总工资是:9000
          </script>
        

4. 数组新增函数

  1. 判断:2个
    1. 判断数组中是否包含符合条件的元素
      1. 格式:
        var bool=arr.some(function(val){
            return 判断条件
        })
        
      2. 原理:
        1. some中封装了for循环,自动遍历数组中的每个元素
        2. 每遍历一个元素,就自动调用一次回调函数,在调用回调函数时,自动将当前正在遍历的元素值传递给回调函数的形参val,回调函数内判断当前元素值是否符合要求,并且返回判断结果
        3. some函数会根据回调函数返回的判断结果,决定有没有必要继续向后执行,如果染回结果为true,循环结束,整体返回true,说明找到符合要求的元素;当前元素的判断结果返回false,则循环继续,直到循环结束,若都为找到符合要求的元素,则整体返回false,说明没有找到符合条件的元素
      3. 示例:判断数组中是否包含符合条件的元素
          <script>
            var arr1=[1,3,5,3,1];
            var arr2=[2,4,6,4,2];
            // 判断哪个数组包含偶数
            var result1=arr1.some(function(val){
              // 返回当前元素是不是偶数的判断结果
              return val%2==0;
            });
            var result2=arr2.some(function(val){
              return val%2==0;
            });
            console.log(result1,result2);//false true
          </script>
        
    2. 判断数组中是否所有元素符合条件
      1. 格式:
        var bool=arr.every(function(val){
            return 判断条件
        })
        
      2. 原理:同some只是判断数组内所有元素都满足判断条件返回true,否则返回false
      3. 示例:判断数组是否全由偶数组成
          <script>
            var arr1=[2,4,5,4,2];
            var arr2=[2,4,6,4,2];
            // 判断数组是否全是偶数
            var result1=arr1.every(function(val){
              // 返回当前元素是不是偶数的判断结果
              return val%2==0;
            });
            var result2=arr2.every(function(val){
              return val%2==0;
            });
            console.log(result1,result2);//false true
          </script>
        

总结:ES5:

  1. 严格模式: 4个新要求:
    1. 禁止给未声明的变量赋值
    2. 静默失败升级为错误
    3. 匿名函数自调和普通函数调用中的this不再指window,而是undefined
    4. 禁用了arguments.callee,不推荐使用递归。
  2. 保护对象:
    1. 保护对象的属性:
      1. 使用开关对对象的属性提供简单的保护:
        1. 每个属性包括三个开关:
          1. writable: 控制是否可修改属性值
          2. enumerable: 控制是否可被for in遍历到。但是只是半隐藏,只防for in,不防.
          3. configurable: 控制2件事:
            1. 是否可删除该属性
            2. 是否可修改前两个特性:因为一旦改为false不可逆,所以凡是修改前两个特性时,都要带上configurable:false,作为双保险。
        2. 如果只修改一个属性的开关:
          Object.defineProperty(对象, "属性名", {
                          开关: truefalse,
                            ... : ...
                      })
          
        3. 如果想同时修改一个对象的多个属性的开关:
          Object.defineProperties(对象,{
                  属性名: {
                      开关: truefalse,
                        ... : ...
                  },
                  属性名: {
                      ... : ...,
                  }
                  ... : { ... }
              })
          
      2. 用访问器属性,保护对象的属性:
        1. 定义访问器属性: 2步:
          1. 将被保护的数据属性隐姓埋名,并且半隐藏:
            var eric={
                _eage:25
            }
            Object.defineProperties(eric,{
                _eage:{
                    enumerable:false,
                    configurable:false
                },
            
          2. 为受保护的数据属性请保镖:
            eage:{//保镖应该冒名顶替原数据属性的值
                        //保镖一请就是一对儿
                        get:function(){ //专门负责从受保护的数据属性中获取值
                            return this._eage
                        },
                        set:function(value){//专门负责将新值经过验证后,再保存回受保护的数据属性中
                            if(value符合要求){
                                this._eage=value
                            }else{
                                throw Error(`错误提示`)
                            }
                        },
                        enumerable:true, //访问器属性顶替受保护的属性抛头露面
                        configurable:false, //访问器属性不能被轻易删除
                    }
                })
            
        2. 如何使用访问器属性: 访问器属性的用法和普通属性完全一样
          1. 获取属性值时: 对象.访问器属性 自动调用访问器属性的get
          2. 修改属性值时: 对象.访问器属性=新值 自动调用访问属性的set,同时自动将=右侧的新值传给set的value形参变量。经过验证value后,再赋值。
    2. 保护对象的结构: 3个级别:
      1. 防扩展: Object.preventExtensions(对象)
        只禁止给对象添加新属性
      2. 密封: Object.seal(对象)
        既禁止给对象添加新属性,又禁止删除现有属性(自动设置configurable:false)
      3. 冻结: Object.freeze(对象)
        既禁止给对象添加新属性
        又禁止删除现有属性(自动设置configurable:false)
        同时还禁止修改属性值(自动设置writable:false)
  3. Object.create()
    1. 何时: 当没有构造函数时,也想为一个父对象创建子对象
    2. 如何:
          var 子对象=Object.create(父对象,{
              自有属性名:{
                  value:属性值,
                  writable:true,
                  enumerable:true,
                  configurable:false
              },
              ... : { ... }
          })
      
    3. 做了三件事:
      1. 创建一个新的空对象
      2. 让新对象继承父对象
      3. 为新对象添加自有属性
  4. 替换this:
    1. 调用一次函数,同时临时替换一次this:
      1. 默认call: 要调用的函数.call(替换this的对象, 实参值列表)
      2. 如果实参值列表是放在一个数组中给的,和要调用的函数发生不一致,应该用apply打散数组,再传参
        要调用的函数.apply(替换this的对象, 数组)
    2. 反复调用函数,永久替换this,应该用bind:2步
      1. 先用bind创建函数副本:
        var 新函数=原函数.bind(替换this的对象, 固定不变的实参值)
      2. 再反复调用新函数: 不用再传入替换this的对象和部分已经固定的实参值
        新函数(除已经固定的实参值之外其他实参值列表)
  5. 数组新增函数:
    1. 判断:
      1. 判断数组中是否包含符合条件的元素
                var bool=arr.some(function(val){
                    return val是否符合条件
                })
        
      2. 判断数组中是否所有元素都符合要求
                var bool=arr.every(function(val){
                    return val是否符合条件
                })
        
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章