T-JS核心-day05-面向對象、封裝、繼承、多態

五、面向對象

  1. 什麼是面向對象:
    程序中先用對象結構保存現實中一個事物的屬性和功能,然後再按需使用事物的屬性和功能,這種編程方法,就叫做面向對象編程
  2. 爲什麼要面向對象:因爲便於大量數據的管理和維護
  3. 何時:大部分的項目開發都用到面向對象的思想
  4. 如何:3步/3大特點:封裝、繼承、多態

1. 封裝

  1. 什麼是:創建一個對象,集中保存現實中一個事物的屬性和功能
  2. 爲什麼:便於大量數據的管理和維護
  3. 何時:只要使用面向對象的編程方式,都要先創建所需的所有對象,備用
  4. 如何使用封裝:3種方法
    1. 用{}創建一個對象(回顧)
      1. 格式:

        var 對象名={
            屬性名:屬性值,
            ... : ....,
            方法名:function(){ ... }
            ... ...
        }
        
      2. 如何訪問對象中的成員:(屬性和方法統稱爲對象的成員)

        1. 訪問對象中的一個屬性:對象.屬性名
        2. 調用對象中的一個方法:對象.方法名()
      3. this

        1. 常出現的問題:對象自己的方法中,想使用對象自己身上的屬性,直接用會報錯,提示屬性名未定義

        2. 原因:任何情況下,不加任何前綴的普通變量,默認只能在函數作用域對象和全局作用域對象window中查找變量使用。無權擅自闖入一個對象中讀取對象的屬性
          在這裏插入圖片描述

        3. 不好的解決方案:在變量前寫死當前對象的對象名:lilei.sname,lilei.sage
          在這裏插入圖片描述

        4. 出現的問題:對象名僅僅是個普通的變量名,很可能隨時發生變化。如果方法中寫死舊對象名,則對象名已發生改變,方法中的執行立即出現錯誤,提示找不到舊對象名。被迫也要修改方法中寫死的對象名-----緊耦合----聯繫過於緊密,不靈活

        5. 好的解決辦法:this關鍵字

          1. 什麼是this:
            每個函數中自帶----不用創建,可直接使用
            自動指向正在調用函數的.前的對象
          2. 何時用:只要在對象的方法中,想用當前對象中的屬性時,就需要用this.屬性名
          3. 優點:鬆耦合。即使對象名發生變化,也不用修改方法中的this。因爲this會在調用方法時,自動獲得.前,正在調用的對象名
            在這裏插入圖片描述
      4. 示例:創建對象,保存一個對象的屬性和方法

          <script>
            // 創建一個對象保存一個學生的屬性和功能
            // 學生姓名:Li Lei
            // 年齡:11
            // 會做自我介紹(intr):I'm Li Lei,I'm 11
            var lilei={
              // 對象的大括號不是作用域。僅僅是new Object()簡寫而已
              sname:"Li Lei",
              sage:11,
              intr:function(){
                console.log(`I'm ${this.sname},I'm ${this.sage}`);
              }
            }
            // 輸出lilei的年齡
            console.log(`年齡:${lilei.sage}`);
            // 請lilei自我介紹
            lilei.intr();
            // 過了一年,lilei漲了一歲
            lilei.sage++;
            // 再輸出lilei的年齡
            console.log(`年齡:${lilei.sage}`);
            // 再請lilei自我介紹
            lilei.intr();
            // 輸出lilei現在在內存中的存儲結構
            console.log(lilei);
          </script>
        

        輸出結果:
        年齡:11
        I’m Li Lei,I’m 11
        年齡:12
        I’m Li Lei,I’m 12
        {sname: “Li Lei”, sage: 12, intr: ƒ}

    2. 用new創建
      1. 創建過程—2步
        1. 先用new創建一個空對象:
          var 對象名=new Object();
          瞭解:非主流寫法:new和()可以省略,但至少保留一個
        2. 在強行給新的空對象賦值新屬性和新方法
          1. 對象名.新屬性名=屬性值;
          2. 對象名.新方法名=function(){...}
      2. 揭示了js中對象底層最本質的祕密:其實js中對象底層也都是關聯數組----列出兩者對比
        1. 存儲結構完全相同:都是名值對兒的集合
        2. 都可以用2種方式訪問成員:
          1. 標準:數組名["下標名"] 對象名["屬性名"]
          2. 簡寫:數組名.下標名 對象名.屬性名
        3. 都可隨時向不存在的下標位置,強行添加屬性,而不會報錯
        4. 都可訪問不存在的下標位置,不會報錯,而是返回undefined
        5. 都可用for in遍歷其中每個成員
        6. 總結:對象底層其實就是關聯數組,對象只不過是管理數組的一種簡化用法而已
      3. 示例:筆試題:克隆一個對象
          <script>
            // 筆試題:需求:克隆一個對象
            var lilei={
              sname:"Li Lei",
              sage:11
            }
            // 錯誤做法:只是把lilei的對象地址給lilei2,不是克隆
            // 一旦lilei2修改對象中的屬性,lilei也發生改變
            // var lilei2=lilei;
            // console.log(lilei2);
            // 正確做法
            function clone(oldObj){
              // 1.創建空對象
              var newObj={};//創建新對象地址,肯定和oldObj不同
              // 2.遍歷舊對象中每個屬性
              for(var key in oldObj){
                // for in會一次遍歷oldObj中每個屬性名,並將屬性名一次賦值給in前的變量key
                // 3.給新對象強行賦值和舊對象同名的屬性,且屬性值也要相同
                // newObj.key=oldObj.key;//錯誤:.key自動翻譯爲["key"]
                // newObj["key"]=oldObj["key"];//錯誤:因爲key是個變量,每循環一次,key的值可能發生變化
                newObj[key]=oldObj[key];//正確
              }
              return newObj;
            }
            var lilei2=clone(lilei);
            console.log(lilei);
            console.log(lilei2);
            console.log(lilei==lilei2);//false
            lilei.money=100;
            console.log(`lilei2的money:${lilei2.money}`);//undefined
          </script>
        
    3. 用構造函數反覆創建多個相同結構的對象
      1. 問題:前兩種創建對象的方式,一次只能創建一個對象。如果需要反覆創建多個相同結構的對象時,代碼就會很繁瑣。

      2. 何時:今後只要反覆創建多個相同結構的對象時,都用構造函數來創建對象

      3. 如何做:2步

        1. 定義一個構造函數,描述多個相同結構的對象的統一結構
          function 類型名(形參變量列表){
              //通常類型名首字母習慣大寫
              this.屬性名=形參;
              ... ...
              this.方法名=function(){...}
              ... ...
          }
          
        2. 用new調用構造函數,反覆創建多個相同結構的對象
          1. 格式:var 新對象=new 構造函數名(屬性值列表)
          2. new做了4件事重點
            1. 第一步:new先創建了一個新的空對象
            2. 第二步:“認爹”----自動設置新對象的__proto__指向構造函數的原型對象-----繼承
            3. 第三步:調用構造函數,new自動將構造函數中this指向正在創建的這個新對象
              1. 所以構造函數中的this都指向要創建的新對象
              2. 構造函數中每個this.屬性名=形參,都在給新對象通過強行賦值新屬性的方式,添加新屬性
              3. 所以,構造函數中所有要加入到新對象中的屬性前都要加this.
              4. 構造函數中所有加this.屬性名的屬性,最終都會添加到新對象中
            4. 第四步:返回新創建的對象地址保存到=左邊的對象名變量中
              在這裏插入圖片描述
      4. 示例:定義學生類型的構造函數,反覆創建兩個學生對象

          <script>
            // 想定義一個構造函數,來描述所有學生類型的對象的統一結構
            // 需求:所有學生都要有學生姓名,學生年齡兩個屬性
            //       所有學生都應該會做自我介紹
            function Student(sname,sage){
              this.sname=sname;
              this.sage=sage;
              // 方法都儘量不寫在構造函數中,寫在原型對象中---節約內存
              this.intr=function(){
                console.log(`I'm ${this.sname},I'm ${this.sage}`);
              }
            }
            // 創建兩個學生對象
            var lilei=new Student("Li Lei",11);
            var hmm=new Student("Han Meimei",12);
            console.log(lilei);
            console.log(hmm);
          </script>
        

2. 繼承

  1. 問題:構造函數的優點雖然是可重用結構代碼,便於維護,但是構造函數卻無法節約內存,反而浪費內存。因爲,放在構造函數中的方法,會爲每個子對象都反覆創建一個函數對象的副本。
    所以:在構造函數中就不應該包含方法定義
    解決辦法:用繼承來解決

  2. 什麼是繼承:父對象中的成員,子對象無需重複創建,就可直接使用,像使用自己的成員一樣使用

  3. 何時:只要多個子對象,希望共用同一個方法或屬性,就可用繼承的方式來定義一次,反覆使用----節約內存

  4. 如何:js中的繼承都是藉助於原型對選哪個實現的

  5. 原型對象:

    1. 其實在創建每個構造函數時,都會自動附贈一個對象,名爲原型對象(prototype)
    2. 通過構造函數.prototype屬性,可獲得這個構造函數對應的一個原型對象
      例如:想獲得Student類型的原型對象:Student.prototype
    3. 當構造函數創建對象時,new的第二步自動爲新對象添加"_proto_“屬性,將”_proto_"屬性指向當前構造函數的原型對象
      例如:如果var lilei=new Student("Li Lei",11)
      則new會自動:lilei.__proto__=Student.prototype;
    4. 結果
      1. 凡是這個構造函數創建出的新對象,都是原型對象的孩子(子對象)
      2. 放在原型對象中的屬性或方法,所有子對象無需重複創建,就可直接使用
        在這裏插入圖片描述
  6. 如何爲空的原型對象添加共有的屬性或方法:強行賦值

    構造函數.prototype.方法名=function(){
        ... ...
    }
    
  7. 原型對象中的方法如何使用:子對象可以直接調用,像調用自己的方法一樣

    1. 子對象.原型對象中的方法名()
    2. 原理:js引擎會先在當前子對象中查找是否包含該方法
      1. 如果找到,就優先使用當前子對象自己的方法
      2. 如果沒找到,就會自動沿着"_proto_"去父對象(原型對象)中查找方法使用
  8. 總結:

    1. 構造函數中應該只包含屬性結構的定義
    2. 所有的方法,都應該強行添加到原型對象中,所有子對象共用一份----節約內存
  9. 示例:爲Student的原型對象中添加共有方法

      <script>
        function Student(sname,sage){
          this.sname=sname;
          this.sage=sage;
          // 構造函數中不再包含任何方法的定義
        }
        console.log(Student.prototype);
        // 爲原型對象添加方法:希望所有的學生可以叢自我介紹intr
        Student.prototype.intr=function(){
          console.log(`I'm ${this.sname},I'm ${this.sage}`);
        }
        // 通過構造函數創建2個對象
        var lilei=new Student("Li Lei",11);
        var hmm=new Student("Han Meimei",12);
        lilei.intr();
        hmm.intr();
        console.log(lilei);
        console.log(hmm);
        // 驗證lilei的父對象是構造函數的原型對象
        console.log(lilei.__proto__==Student.prototype);
      </script>
    

    輸出結果:
    {constructor: ƒ}intr: ƒ ()constructor: ƒ Student(sname,sage)_proto_: Object
    I’m Li Lei,I’m 11
    I’m Han Meimei,I’m 12
    Student {sname: “Li Lei”, sage: 11}
    Student {sname: “Han Meimei”, sage: 12}
    true

  10. “原型對象"就是"父對象”,"父對象"就是"原型對象"都是同一個東西,只是稱呼不同
    在這裏插入圖片描述

3. 多態

此處內容在下一篇

總結: 面向對象: 三大特點: 封裝 繼承 多態

  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. (自動)每定義一個構造函數,都會自動附贈一個共的原型對象(prototype)
    2. (自動)當用new創建子對象時,第二步new會自動設置子對象的_ proto _指向構造函數的原型對象(prototype)。結果,子對象繼承父對象
    3. (自動)從此,父對象中的成員,子對象無需重複創建,就可直接使用
    4. (需要我們做的) 向原型對象中爲所有子對象添加共有的方法
      構造函數.prototype.共有方法=function(){
              ... this.屬性名 ... 
          }
      
  3. 多態:
    重寫: 如果從父對象繼承來的個別成員不好用!就可在子對象中定義同名成員。結果,子對象再使用同名成員時,優先使用自己自有的同名成員。不再使用父對象不好用的同名成員。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章