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是否符合條件
                })
        
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章