T-JS核心-day06-繼承、多態、自定義繼承、ES5-嚴格模式

五、面向對象

1. 繼承

  1. 自有屬性和共有屬性
    1. 什麼是自有屬性:保存在子對象中,只歸子對象獨有的屬性

    2. 什麼是共有屬性:保存在原型對象中,歸當前類型下所有子對象共有的屬性

    3. 獲取屬性值時:無論獲取自有屬性值,還是獲得共有屬性值,都可用子對象.屬性名,無差別

    4. 修改屬性值時:

      1. 如果修改一個子對象的自有屬性,纔可以子對象.自有屬性=新值

      2. 如果修改多個子對象共有的屬性必須使用原型對象親自修改:
        構造函數.prototype.共有屬性=新值
        在這裏插入圖片描述

      3. 錯誤做法:如果強行用子對象直接修改共有屬性:結果,原型對象中的共有屬性保持不變,而是隻給當前這一個子對象添加一個新的同名的自有屬性。從此,這個子對象,因爲已經有了同名的自有屬性,就不會再使用同名的共有屬性。從此,共有屬性發生變化,當前子對象的這個同名自有屬性也不會跟隨變化。從此,這個子對象和其他子對象,在這個屬性的使用上,分道揚鑣。
        在這裏插入圖片描述

    5. 示例:修改共有屬性

        <script>
          function Student(sname,sage){
            this.sname=sname;
            this.sage=sage;
          }
          // 希望給所有學生添加一個班級名屬性
          Student.prototype.className="初一2班";
      
          var lilei=new Student("Li Lei",11);
          var hmm=new Student("Han Meimei",12);
          console.log(lilei);
          console.log(hmm);
          console.log(lilei.className,hmm.className);
          //            "初一2班"       "初一2班"
          // 修改自有屬性
          lilei.className="初二2班";
          console.log(lilei.className,hmm.className);
          //            "初二2班"       "初一2班"
          // 過了一年,同學們升了一級
          // 修改原型對象中共有屬性:
          Student.prototype.className="初二2班";
          console.log(lilei.className,hmm.className);
          //            "初二2班"       "初二2班"
          // 又過了一年,同學們又升了一級
          // 修改原型對象中共有屬性:
          Student.prototype.className="初三2班";
          console.log(lilei.className,hmm.className);
          //            "初二2班"       "初三2班"
          // 此時lilei的className就跟原型對象中的className屬性分道揚鑣
        </script>
      
  2. 內置類型的原型對象:
    1. 什麼是內置類型/對象:ES標準中已經規定的,瀏覽器已經定義好的,我們可以直接使用的類型/對象

    2. 11種內置類型對象
      String Number Boolean----包裝類型(擴展)
      Array Date RegExp Math(Math不是類型,而是一個對象,不能用new)
      Error
      Function Object
      global(不是類型,是一個對象,不能new,且在瀏覽器中被window代替)
      除了Math和global兩種外,其餘9中都可以new

    3. 今後凡是可以new的,都是構造函數。只要有構造函數,都會牽扯出一個大家庭,每個大家庭中,至少包含2個:

      1. 構造函數(媽媽)
        1. 負責反覆創建多個相同結構的子對象
        2. 構造函數肚子裏的屬性,會成爲將來子對象中的自由屬性
      2. 原型對象(爸爸)
        1. 負責替該類型所有子對象集中保管共有的方法
        2. 原型對象有什麼方法,子對象也就有什麼方法----繼承
          在這裏插入圖片描述
    4. 比如:內置類型Array:包含2部分

      1. function Array(){... 內部代碼 看不見 ...}
      2. 所有數組共用的函數,都放在Array.prototype中
      3. 如果想用的數組函數,原型對象中沒有,可以自己手動想數組的原型對象中添加一個新函數,結果,將來所有數組的子對象,都可用這個自定義的公共函數
      4. 示例:爲數組原型對象中添加求和函數sum
          <script>
            // 需求:爲數組添加一個可對數組內容求和的函數
            Array.prototype.sum=function(){
              // this:指將來調用這個函數的某個數組的子對象
              var result=0;
              for(var i=0;i<this.length;i++){
                result+=this[i];
              }
              return result;
            }
            console.log(Array.prototype);
            //[sum: ƒ, constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, …]
            var arr1=[1,2,3];
            console.log(arr1.sum());//6
            var arr2=[1,2,3,4,5];
            console.log(arr2.sum());//15
          </script>
        
  3. 原型鏈
    1. 什麼是原型鏈:由多級父元素逐級繼承形成的鏈式結構

    2. 保存着一個對象可用的所有屬性和方法

    3. 控制着成員的使用順序:先自有,再共有
      在這裏插入圖片描述

    4. 示例:驗證原型鏈

        <script>
          function Student(sname,sage){
            this.sname=sname;
            this.sage=sage;
          }
          Student.prototype.intr=function(){
            console.log(`I'm ${this.sname},I'm ${this.sage}`)
          }
          var lilei=new Student("Li Lei",11);
          console.log(lilei);//Student {sname: "Li Lei", sage: 11}
          console.log(lilei.__proto__==Student.prototype);//true
          console.log(lilei.__proto__.__proto__==Object.prototype);//true
          console.log(Student.__proto__==Function.prototype);//true
          console.log(Student.__proto__.__proto__==Object.prototype);//true
        </script>
      

2. 多態

  1. 什麼是多態:同一個函數,在不同情況下,表現出不同的狀態

  2. 包括2種情況:重載和重寫(override)

  3. 什麼是重寫:子對象中定義了和父對象中重名的成員

  4. 爲什麼重寫:因爲從父對象中繼承來的屬性或方法並不總是好用的

  5. 何時重寫:只要從父對象繼承來的某個成員不好用,就可以重寫

  6. 如何:只要在子對象中,定義和父對象中名稱相同的一個成員,從此子對象再使用這個成員時,都會優先使用自己定義的成員,而不再使用父對象的成員
    在這裏插入圖片描述

  7. 示例:在自定義類型和對象中重寫Object原型對象裏的toString()方法

      <script>
        function Student(sname,sage){
          this.sname=sname;
          this.sage=sage;
        }
        // 希望所有學生類型的對象,都有toString()可用,可輸出學生的屬性值
        // 在Student的原型對象中重寫和父對象中同名的toString()方法
        Student.prototype.toString=function(){
          return `{ sname:${this.sname},sage:${this.sage} }`
        }
        var lilei=new Student("Li Lei",11);
        var obj={
          x:1,
          y:2,
          // 希望obj也有好用的toString()
          toString:function(){
            return `{x:${this.x},y:${this.y}}`
          }
        }
        var arr=[1,2,3];
        var now=new Date();
        console.log(lilei.toString());//{ sname:Li Lei,sage:11 }
        console.log(lilei);//Student {sname: "Li Lei", sage: 11}
        console.log(obj.toString());//{x:1,y:2}
        console.log(arr.toString());//1,2,3
        console.log(now.toString());//Tue May 05 2020 20:38:23 GMT+0800 (中國標準時間)
      </script>
    

3. 自定義繼承

如果子對象覺得整個父對象都不好用,可以換父對象:2種方法

  1. 只修改一個對象的父對象
    1. 不推薦寫法:子對象.__proto__=新父對象
      問題:不是所有瀏覽器都開放__proto__讓我們隨意使用

    2. 推薦的等效做法:Object.setPrototypeOf(子對象,父對象)
      在這裏插入圖片描述

    3. 示例:僅修改hmm的父對象

        <script>
          function Student(sname,sage){
            this.sname=sname;
            this.sage=sage;
          }
          Student.prototype.bal=9.9;
          Student.prototype.car="none";
          var lilei=new Student("Li Lei",11);
          var hmm=new Student("Han Meimei",12);
          var father={
            bal:100000000000000,
            car:"infiniti"
          }
          // hmm想用father中的成員,可讓hmm繼承father
          // hmm.__proto__=father;
          Object.setPrototypeOf(hmm,father);
          console.log(hmm.bal,hmm.car);//100000000000000 "infiniti"
          console.log(lilei.bal,lilei.car);//9.9 "none"
        </script>
      
  2. 同時修改多個子對象的父對象
    1. 只要更換構造函數的prototype屬性,指向新的原型對象即可
      構造函數.prototype=新原型對象;

    2. 時機:應該在創建子對象之前就要更換
      好處:之後再創建的子對象都自動繼承新的父對象
      而已經創建的子對象所繼承的父對象不變,仍是舊父對象
      在這裏插入圖片描述

    3. 示例:同時更換lilei和hmm的父對象

        <script>
          var father={
            bal:100000000,
            car:"infiniti"
          }
          function Student(sname,sage){
            this.sname=sname;
            this.sage=sage;
          }
          Student.prototype.bal=9.9;
          Student.prototype.car="none";
          // 創建更換原型對象之前的子對象
          var xiaoming=new Student("Xiao Ming",13);
          // 將構造函數的原型對象更換
          Student.prototype=father;
          // 新原型對象的構造函數更換爲Student
          father.constructor=Student;
          // 創建子對象
          var lilei=new Student("Li Lei",11);
          var hmm=new Student("Han Meimei",12);
          console.log(xiaoming.bal,xiaoming.car);//9.9 "none"
          console.log(lilei.bal,lilei.car);//100000000 "infiniti"
          console.log(hmm.bal,hmm.car);//100000000 "infiniti"
        </script>
      

六、ES5

ECMAScript語言(js語言的核心語法)標準的第五個升級版本.

1. 嚴格模式

  1. 什麼是嚴格模式:比普通js運行機制要求更嚴格的模式
  2. 爲什麼:舊的js語言存在很多廣受詬病的缺陷
  3. 何時:所有的js程序,都需要運行在嚴格模式下
  4. 如何開啓嚴格模式:在當前代碼段的頂部添加字符串:
    "use strict"
  5. 新要求:4個
    1. 禁止給未聲明的變量賦值
      1. 舊js中:強行給未聲明的變量賦值,會自動在全局創建該變量。-----極容易造成全局污染
      2. 示例:舊js中,非嚴格模式下給未聲明的變量賦值
          <script>
            // "use strict"//啓用嚴格模式
            function send(){
              var gf;
              // 假設不小心把gf寫成了qgf
              qgf="今晚308,w84u";
              console.log(gf);
            }
            send();
            console.log(qgf);
            // 如果未啓用嚴格模式
            // 輸出undefined和今晚308,w84u
            // 如果啓用嚴格模式
            // 報錯:qgf未定義
          </script>
        
    2. 靜默失敗升級爲錯誤
      1. 靜默失敗:執行不成功,但是還不報錯
      2. 缺點:靜默失敗極其不便於調試
      3. 嚴格模式下:將所有的靜默失敗都升級爲錯誤
      4. 優點:極其便於調試,避免歧義
      5. 示例:對比嚴格、非嚴格模式下執行錯誤的操作
          <script>
            "use strict"//啓用嚴格模式
            var eric={
              eid:1001,//只讀
              ename:"埃裏克"
            }
            // 設置eid爲只讀
            Object.defineProperty(eric,"eid",{
              writable:false,//設置eric的eid屬性爲不可修改--只讀
              configurable:false//設置不允許再修改writable
            })
            // 嘗試修改eric的eid
            eric.eid=1002;
            console.log(eric);
            // 不啓用嚴格模式
            // 不報錯 輸出{eid: 1001, ename: "埃裏克"}
            // 啓用嚴格模式
            // 報錯:提示不可修改只讀屬性eid
          </script>
        
    3. 普通函數調用和匿名函數自調中的this默認值undefined,而不再指window
      1. 舊js:普通函數調用和匿名函數自調中的this默認指window
      2. 問題:容易導致全局污染
      3. 嚴格模式下:普通函數調用和匿名函數自調中的this指undefined,而不再指window
      4. 好處:大大減少了因爲this導致的全局污染
      5. 示例:對比嚴格、非嚴格模式下的錯誤使用構造函數
          <script>
            "use strict"//啓用嚴格模式
            function Student(sname,sage){
              this.sname=sname;
              this.sage=sage;
            }
            // 正確使用構造函數的用法
            // var lilei=new Student("Li Lei",11);
            // console.log(lilei);
            // 錯誤的使用構造函數的用法
            var hmm=Student("Han Meimei",12);
            // Student前沒有.也沒有new,所以Student中的this暫時指向window
            // 相當於:window.sname="Han Meimei";
            // 相當於:window.sage=12;
            console.log(hmm);
            console.log(window.sname);
            console.log(window.sage);
            // 非嚴格模式下輸出:
            // undefined
            // Han Meimei
            // 12
            // 嚴格模式下輸出:
            // 報錯:無法設置Student中未定義的屬性sname
          </script>
        
    4. 禁止使用arguments.callee
      1. 什麼是arguments.callee:是在函數內部,獲得當前函數本身的一種關鍵詞
      2. 何時:遞歸
      3. 問題:如果在函數內遞歸調用時,寫死當前函數的函數名,則一旦當前函數名改變,就必須同時修改函數體中寫死的函數名,一旦漏寫就報錯----緊耦合
      4. 解決:在函數內用arguments.callee自動獲得當前函數對象本身,直接用當前函數對象進行遞歸調用,與函數名無關
      5. 爲什麼嚴格模式要禁用arguments.callee:因爲遞歸調用效率極低(重複計算量太大)
      6. 所以嚴格模式強力不建議使用遞歸調用arguments.callee,使用就會報錯
      7. 解決:多數遞歸調用都可以用循環來解決----但是難度較大
      8. 總結:但是改用遞歸還是首先選擇遞歸----因爲簡單
        除非遞歸在項目中確實影響效率了,才被迫找循環的方法替代
      9. 示例:使用遞歸實現斐波那契數列
          <script>
            "use strict"//啓用嚴格模式 
            // 斐波那契數列
            // 1 1 2 3 5 8 13 21 34 55
            // 1 2 3 4 5 6  7  8  9 10
            // 數學公式:f(1)=f(2)=1,n>2是f(n)=f(n-1)+f(n-2)
            function f(n){
              if(n<3){
                return 1;
              }else{
                var fun=arguments.callee;//自動獲得當前函數本身
                return fun(n-1)+fun(n-2);
              }
            }
            // 測試
            console.log(f(10));
            // 非嚴格模式下輸出:55
            // 嚴格模式下:報錯
          </script>
        

總結:

一、面向對象

  1. 封裝: 創建對象 3種方法:

    1. 只創建一個對象,且已經知道對象的成員是什麼:
      var 對象名={
              屬性名: 屬性值, 	
                  ... : ... ,
              方法名: function(){
                  ... this.屬性名 ...
              }
          }
      
    2. 只創建一個對象,但是暫時不知道對象的成員: 2步
      1. 先創建一個空對象等着:
        var 對象名={} //new Object()的簡寫
      2. 等知道對象的成員之後,再爲對象強行添加新屬性和方法:
        對象.屬性名=屬性值
        對象.方法=function(){ ... this.屬性名 ...}
    3. 反覆創建多個相同結構的對象時: 2步:
      1. 先定義構造函數:
        //因爲構造函數是爲了描述同一類型的多個對象統一的屬性結構
        //所以,構造函數名通常是一種類型的名稱,比如學生,商品,訂單,用戶...
                function 類型名(形參變量列表){
        
                    this.屬性名=形參變量;
                        ... = ...
                    //今後構造函數中不要包含方法定義
                }
        
      2. 用new調用構造函數創建對象
        var 對象名=new 類型名(屬性值列表);

      new 做了4件事:
      1. 創建一個新的空對象
      2. 讓子對象繼承構造函數的原型對象(自動設置子對象的_ proto _指向構造函數的prototype對象)
      3. 調用構造函數時,同時將構造函數中的this臨時替換爲new正在創建的這個新對象
        結果構造函數中的每一句話,都會強行給新對象添加構造函數早就規定好的統一的新屬性
      4. 返回新對象地址,保存到等號左邊的變量中

  2. 繼承:

    1. 何時: 今後只要多個子對象共用的方法或屬性值,只要集中放在原型對象中一份,所有子對象即可共用。
    2. 向原型對象中爲所有子對象添加共有的方法
      構造函數.prototype.共有方法名=function(){
              ... this.屬性名 ... 
          }
      
  3. 多態:
    重寫: 如果從父對象繼承來的個別成員不好用!就可在子對象中定義同名成員。結果,子對象再使用同名成員時,優先使用自己自有的同名成員。不再使用父對象不好用的同名成員。

  4. 自定義繼承: 如果子對象覺得整個父對象都不好用,可認別的父對象當爹

    1. 只修改一個子對象的父對象:
      1. 不推薦: 子對象.__proto__=新父對象
      2. 推薦: Object.setPrototypeOf(子對象, 新父對象)
    2. 如果更換該類型下所有子對象的父對象
      1. 構造函數.prototype=新父對象
      2. 時機: 最好在創建子對象之前,就要替換!

      this的指向: 4種:
      注意:判斷this一定不要看定義在哪兒!!只看調用時.前是誰。
      1. obj.fun() fun中的this->.前的obj對象
      2. new Fun() Fun中的this-> new正在創建的新對象
      3. 構造函數.prototype.fun=function(){ … } fun中的this->將來調用這個fun()函數的當前類型的子對象
      4. (function(){ … })() 或 普通函數調用fun() this默認->window

二、ES5

  1. 嚴格模式: 4個新要求:
    (1). 禁止給未聲明的變量賦值
    (2). 靜默失敗升級爲錯誤
    (3). 匿名函數自調和普通函數調用中的this不再指window,而是undefined
    (4). 禁用了arguments.callee,不推薦使用遞歸,但該寫還是要寫。

補充: 函數也是一個對象,既保存函數體,同時也有自己的屬性
console.log(Student) 僅輸出函數內容
console.dir(Student) 僅輸出函數對象在內存中的屬性
以上兩個輸出的內容合起來,纔是一個完整的函數對象。
在這裏插入圖片描述

補充: 不要用for in來遍歷索引數組
因爲in不僅遍歷當前對象的所有自有成員,而且會延_ _proto_ _繼續遍歷原型對象中深紫色的共有成員(忽略淺紫色的成員)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章