JS基礎入門篇(三十五)—面向對象(二)

若是沒有面向對象這種抽象概念的小夥伴,建議先看一下我寫的
JS基礎入門篇(三十四)—面向對象(一)👏👏👏👏程序員

1.很是很是重要而又簡單的概念—原型鏈

想要如下爲 f 添加一個say方法,有三種方法。segmentfault

<script>
    function Fn(){};
    var f = new Fn();
</script>

方法一:至關於添加一個自定義屬性,此屬性是一個方法。數組

<script>
    function Fn(){};
    var f = new Fn();
    f.say = function(){
        console.log(1);
    }
    f.say(); //打印 1
</script>

方法二:爲構造函數.prototype添加一個say方法。app

<script>
    function Fn(){};
    var f = new Fn();
    Fn.prototype.say = function(){
        console.log(2);
    }
    f.say(); //打印 2
</script>

方法三:爲Object.prototype添加一個say方法。函數

<script>
    function Fn(){};
    var f = new Fn();
    Object.prototype.say = function(){
        console.log(3);
    }
    f.say(); //打印 3
</script>

疑問🤔️:方法二中掛在構造函數的方法,和方法三中掛在Object的方法, f 爲何能查找的到???post

解析(此解析必定要看懂,沒有看懂多看幾遍或者百度下):
是原型鏈的概念。就是js內部的查找機制。首先要明白:this

1.prototype 原型spa

當一個函數被申明的時候,該函數下默認有一個屬性:prototype,該屬性的值是一個對象。

舉例說明:prototype

<script>
    function Fn(){}
    var f = new Fn();
    console.log( Fn.prototype );
</script>

結果如圖所示:
圖片描述3d

2.__proto__

當一個對象被建立的時候,該對象會自動被添加上一個屬性:__proto__,他的值也是一個對象,而且該屬性 就是 當前這個對象的構造函數的prototype

舉例說明:

<script>
    function Fn(){}
    var f = new Fn();
    console.log( f.__proto__ );
</script>

結果如圖所示:
圖片描述

3.對象.__proto__ === 構造函數.prototype

舉例說明

<script>

    function Fn(){};
    Fn.prototype.say=function () {
        console.log(1);
    };
    var f = new CreatePreson();
    f.say=function () {
        console.log(2);
    };
    console.log( f.__proto__  );
    console.log( Fn.prototype );
    console.log( Fn.prototype ===  f.__proto__ );
</script>

結果如圖所示:
圖片描述

因此查找機制爲:
調用f.say( );時

if( 對象 f上面是否say方法 ){//爲真,執行if內部的代碼
       則調用f上面的say方法
}else if(Fn.prototype是否有say方法){//爲真,執行else if內部的代碼
  第一步:f.__proto__  === Fn.prototype
  由這個查找到f對應的構造函數的原型,即爲 Fn.prototype。
  
  第二步:查看Fn.prototype是否有say方法,有的話,則調用Fn.prototype是上面的say方法。
  
}else if( Object.prototype是否有say方法 ){
  第一步:Fn.prototype.__proto__ === Object.prototype
  由這個查找到Fn.prototype對應的構造函數的原型,即爲 Object.prototype。
  
  第二步:Object.prototype是否有say方法,有的話,則調用Object.prototype是上面的say方法。
   
}else{//若是以上都沒有say方法 
    會報錯。
}

舉例說明

<script>
        function Fn() {
        }
        var f = new Fn();
        f.say = function () {
            console.log(1);
        };
        Fn.prototype.say = function () {
            console.log(2);
        };
        Object.prototype.say = function () {
            console.log(3);
        };

        f.say();//打印1。由於在f上面找到了,就不會往下繼續找了。
</script>

2.hasOwnProperty, constructor, instanceof

1.hasOwnPropert 🌹🌹🌹

做用
        用來判斷某個對象是否含有 指定的 自身屬性
    語法
        boolean object.hasOwnProperty(prop)
    參數
        object
            要檢測的對象
        prop
            要檢測的屬性名稱。
    注意:不會沿着原型鏈查找屬性,只查找自身屬性

若是以上文字都看不懂,能夠先看例子,再看文字。

<script>
    //建立構造函數
    function CreatPerson(name, age) {
        this.name = name;
        this.age = age;
    }
    CreatPerson.prototype.kind = "人類";
    CreatPerson.prototype.say = function () {
        console.log("我會說話 ");
    };

    //生成對象,實例化的過程
    var p = new CreatPerson("Lily",28);

    //調用hasOwnProperty方法,查看是不是自身的屬性,再也不在原型鏈上面找。
    console.log(p.hasOwnProperty("name"));//true
    console.log(p.hasOwnProperty("age"));//true
    console.log(p.hasOwnProperty("kind"));//false
    console.log(p.hasOwnProperty("say"));//false
</script>

2.constructor 🌹🌹🌹

函數的原型prototype的值是一個對象,初始化會有一個屬性爲constructor,
        對應的值爲擁有這個原型的函數
    注意:prototype的值是能夠修改的,修改了prototype的值,
        要手動將constructor指向函數
<script>
    function Fn() {
        console.log("構造函數");
    }
    console.log(Fn.prototype.constructor); // Fn(){console.log("構造函數");
    


    //由於arr 是經過字面量的方式生成一個數組,可是函數內部仍是會經過new Array 生成arr對象
    //因此Array是arr的構造函數
    //arr沒有constructor,會根據原型鏈查找,找到JS內部的Array.prototype上的constructor方法。
    //Array.prototype.constructor指向Array
    var arr = [1, 2, 3];
    console.log(arr.constructor); // Array() { [native code] }


    //由於obj 是經過字面量的方式生成一個對象,可是函數內部仍是會經過new Object 生成obj對象
    //因此Object是obj的構造函數
    //obj沒有constructor,會根據原型鏈查找,找到JS內部的Object.prototype上的constructor方法。
    //Object.prototype.constructor指向Object
    var obj = {};
    console.log(obj.constructor); //Object() { [native code] }
</script>

3.instanceof🌹🌹🌹

instanceof
    是一個二元運算符,返回布爾值
運算檢測 一個 對象原型 是否 在要檢測的對象的原型鏈上
    使用:object instanceof constructor
<script>
   var arr = [];
    console.log( typeof arr );//"object"
    console.log( arr instanceof Array);//true
    console.log( arr instanceof Object);//true
    
    //str是字面量生成的,是由JS內部的String構造函數new出來的。
    //可是str會馬上"壓扁"本身,讓本身不是對象。
    //因此str都不是對象了,天然instanceof String 的獲得的值爲fasle
    //但str.indexOf(),str仍是能夠調用indexOf()方法的緣由是,當它調用方法的時候,會從新將本身包裝成對象。
    //使用結束後會從新"壓扁"本身,讓本身不是對象。
    var str = "123";
    console.log( str instanceof Array );//false
    console.log( str instanceof String);//false
    console.log( str instanceof Object);//false
    
    var obj = {};
    console.log( obj instanceof Array );//false
    console.log( obj instanceof Object);//true
    
    // Array.prototype -> Object.prototype
</script>

3.this的指向

1.誰調用就指向誰。
2.誰觸發就指向誰。
舉例說明1

<script>
        function  fn() {
            console.log(this);
        }
        fn();//打印結果:Window     解析:至關於 window.fn(); 因此指向window
        document.onclick=fn;//打印結果:document 解析: 由document的觸發,因此指向document
    </script>

舉例說明2

<script>
    var obj={
        n:"k",
        foo:function () {
            console.log(this);
            console.log(this.n);
        }
    };

    obj.foo();
    //運行結果爲:
    //{n: "k", foo: ƒ}
    //k
    //解析:obj.foo();是obj調用foo對應的函數。因此this指向obj。

    var b = obj.foo;
    b();
    //運行結果爲:
    //Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
    //undefined
    //解析:var b = obj.foo; ==== var b=function () { console.log(this); console.log(this.n);}
    //變量b是Window 的自定義屬性,因此b(); === window.b();
    //因此其中的this指向window,this.n === window.n
    //因爲window上面沒有n這個自定義屬性,則打印出來爲 undefined
    
</script>

4.修改this指向的三種方式

1.call

1. 函數**`會`**馬上執行
2. 函數執行時候,函數**`第一個參數`**是內部的**`this指向`**
3. **`第一個參數以後的參數,都是指 函數執行時候 ,內部的實參`**

直接擼代碼,舉例說明

<script>
        function Fn() {
            console.log(this);
        }
          Fn.call();
        //結果爲:Window
        // 1.函數會當即執行
        // 2.不傳入任何參數,this的指向不變,仍是指向window。


          Fn.call(document);
        //結果爲:#document
        // 1.函數會當即執行
        // 2.括號中的內容 第一個參數 就是 函數執行時候 ,內部的this指向



        function Go(a,b) {
            console.log(this);
            console.log(a,b);
        }
        Go.call(document,2,3);
        //結果爲:
        // #document
        // 2 3

        
        // 1.函數會當即執行
        // 2.括號中的內容 第一個參數 就是 函數執行時候 ,內部的this指向
        // 3.第一個參數以後的參數,都是指 函數執行時候 ,內部的實參
</script>

2.bind

1. 函數**`不會`**馬上執行
2. 函數執行時候,**函數第一個參數是內部的this指向**
3. **第一個參數以後的參數,都是指 函數執行時候 ,內部的實參**
4. **`返回的是 修改了 this指向的新函數`**

與call的區別就是函數不會馬上執行。

舉例說明

<script>
    function foo (a,b) {
        console.log( this );
        console.log( a,b );
    }
    var fn = foo.bind( document,2,3);// 函數 不會 馬上執行,返回的是 修改了 this指向的新函數
    fn();//調用以後纔會執行 this指向的新函數
    
    //運行結果:
    //#document
    //2 3

</script>

3.apply

與call很類似,只是第二個參數值接受數組

舉例說明

<script>
    function foo (a,b) {
        console.log( this );
        console.log( a,b );
    }
    foo.apply( document,[2,3] ); // 和call 類似 直接調用 , 不過第二個參數接受數組
    //運行結果:
    //#document
    //2 3
    
</script>

5.數組的檢測

由於由typeof打印出來,數組和對象的結果都是object。有時候咱們須要判斷變量是不是數組

方法一:

var arr = [1,2,3];
    console.log( arr.toString() );//1,2,3
    Array.prototype.toString = Object.prototype.toString;//從新賦值Array.prototype.toString的方法。可是下次在別的狀況調用Array.prototype.toString,此方法已被從新覆蓋。因此不太好
    console.log( arr.toString() );//[object Array]

var arr = [1,2,3];

console.log( Object.prototype.toString.call(arr) );

// 使用 Object.prototype.toString
// 同時 修改內部的this指向 arr

console.log( arr );//[object Array]

方法二:

6.繼承

繼承
在JavaScript中,繼承就是讓一個對象(子類)擁有另外一個對象(父類)的屬性/方法(還有原型上的屬性和方法)。其中原則就是:

1.子類的修改不能影響父類
2.子類能夠 在 父類 基礎上 添加本身的屬性 和 方法

1.經過prototype 賦值 (行不通,可是仍是要看行不通的緣由)

舉例說明:上代碼

<script>
    function CreatPerson() {}
    CreatPerson.prototype.say = function () {
        console.log("我會說漢語");
    };

    function Coder(){}
    // 此處 子類 的 prototype和父類的 prototype 指的是 同一個對象。
    // 的確是繼承CreatPerson的原型上面的方法,可是當Coder.prototype重寫say方法
    // CreatPerson.prototype中的say方法也會被改寫
    Coder.prototype = CreatPerson.prototype;
    Coder.prototype.say=function () {//
            console.log("我會說漢語,還會碼代碼");
    };

    var person = new CreatPerson();
    person.say();
    var coder = new Coder();
    coder.say();


</script>

2.原型鏈繼承

子類的原型 = 父類的實例
    注意 : 在爲 子類 原型 賦值的時候去修正 constructor
    弊端 : 子類構造函數內的地址的修改會修改其餘子類。
    由於全部子類構造函數的原型共享一個實例。

舉例說明

<script>
        function CreatPerson() {
            this.age=18;
            this.arr=[1,2,3];
        }
        CreatPerson.prototype.say=function () {
            console.log("我會說漢語");
        };
        CreatPerson.prototype.eat=function () {
            console.log("我想吃飯");
        };

        function Coder() {}
        Coder.prototype = new CreatPerson();//子類構造函數內的地址的修改會修改其餘子類。由於全部子類構造函數的原型共享一個實例
        Coder.prototype.constructor=Coder;//在爲 子類 原型 賦值的時候去修正 constructor
        Coder.prototype.say=function () {
            console.log("我會說漢語,還會碼代碼");
        };

        var person=new CreatPerson();
        var coder1=new Coder();
        person.say();//我會說漢語
        coder1.say();//我會說漢語,還會碼代碼
        coder1.eat();//我想吃飯

//----------能夠繼承父類,修改子類也不會影響到父類。可是子類修改會影響到子類-------------------
        var coder2=new Coder();
        coder2.age=10;
        //coder2.age -> Coder.prototype.age === new CreatPerson().age
        // 存儲的是值,Coder.prototype.age的改變,只會影響當前對象的age
        // 別的子類影響不到
        coder2.arr.push(4);
        //coder2.arr -> Coder.prototype.arr === new CreatPerson().arr 
        // 存儲的是地址,Coder.prototype.arr 修改,new CreatPerson().arr 取到的內容就是修改後的內容
        
        console.log(coder1.age);//18
        console.log(coder1.arr);//[1, 2, 3, 4]
        console.log(coder2.age);//10
        console.log(coder2.arr);//[1, 2, 3, 4]
        console.log(person.age);//18
        console.log(person.arr);//[1, 2, 3]
</script>

對原型鏈繼承遇到問題的解決的方案一:

改成:

function Coder() {
    this.arr=[1,2,3];//這樣查找的時候,對象上面就有了,不會查找到上一層,既不會修改到。
}

解析:此方法麻煩,若是父類有不少自定義屬性都是對象或者方法,那麼子類都要從新複製一遍。

對原型鏈繼承遇到問題的解決的方案二:

借用構造函數
    在子類中執行父類的構造函數
        修改子類構造函數中的 this指向

只能繼承父類構造函數中的方法和屬性
    繼承不到父類構造函數原型鏈中的方法和屬性

改成:

function Coder() {
        CreatPerson.call(this);// // 此處的 this 指的 是 Coder 的 實例
    }

總結:經過原型鏈繼承的正確寫法。

<script>
        //父類構造函數
        function CreatPerson( name ) {
            this.age = 18;
            this.arr = [123];
            this.name = name;
        }
        CreatPerson.prototype.say = function () {
            console.log("我會說漢語");
        };

        //子類構造函數
        function Corder(name,job) {
            CreatPerson.call(this,name);//繼承父類上非原型上的屬性和方法。
            this.job=job;//子類擴展的自定義屬性
        }
        Corder.prototype=new CreatPerson();//繼承父類上原型上的屬性和方法。
         Coder.prototype.constructor=Coder;//在爲 子類 原型 賦值的時候去修正 constructor
        Corder.prototype.say=function () {//重寫父類上面的say方法,並不修改父類的say方法
            console.log("我會說漢語,我是程序員!!!");
        };

        //父類的對象實例化
        console.log("------------ 父類1 -----------");
        var person=new CreatPerson("jack");
        console.log(person.age);//18
        console.log(person.name);//jack


        //子類1的對象實例化
        console.log("------------ 子類1 -----------");
        var corder1=new Corder("rose","worker");
        corder1.arr=[234];
        corder1.say();//我會說漢語,我是程序員!!!
        console.log(corder1.age);//18
        console.log(corder1.arr);//[234]
        console.log(corder1.name);//rose
        console.log(corder1.job);//worker


        //子類2的對象實例化
        console.log("------------ 子類2 -----------");
        var corder2=new Corder("Mary","corder");
        console.log(corder2.age);//18
        console.log(corder2.arr);//[123]子類與子類之間的 自定義屬性 沒有受到影響
        console.log(corder2.name);//Mary
        console.log(corder2.job);//corder
        corder2.say();//我會說漢語,我是程序員!!!

        console.log("------------ 父類1 -----------");
        person.say();///我會說漢語     父類原型上面的方法 沒有受到影響
        console.log(person.arr);//[123]   父類自定義屬性 沒有受到影響
 </script>

提醒本身: Coder.prototype.constructor=Coder;//在爲 子類 原型 賦值的時候去修正 constructor。 不要忘記修正子類的constructor。

3.拷貝式繼承

1. 完成拷貝式繼承首先要知道如何拷貝對象。因此先來拷貝對象

<script>

        //拷貝對象的內容
        function cloneFn( sourse ) {
            var obj= (Object.prototype.toString.call(sourse).
            indexOf("Array")!==-1)?[]:{};//若是對象是數組,就應該建立數組。若是是非數組的對象,就應該建立對象。
            for(var attr in sourse){
                if(typeof sourse[attr] ==="object"&& sourse[attr] !==null){//若是對象內的鍵值仍是對象,進行更深一步的拷貝
                    obj[attr]=cloneFn( sourse[attr] );
                }else{
                    obj[attr]=sourse[attr];
                }
            }
            return obj;
        }

        var a={
            abc:1,
            abc2:2,
            arr:[1,23,4]
        };

        var clone=cloneFn( a );
        clone.abc=9;
        clone.arr.push(5);//不會影響a中的arr
        console.log(clone);//{abc: 9, abc2: 2, arr: Array(4)}
        console.log(a.abc);//1
        console.log(a.arr);//[1, 23, 4] 
</script>

2.拷貝繼承

<script>
    //拷貝對象的內容
    function cloneFn( sourse ) {
        var obj= (Object.prototype.toString.call(sourse).
        indexOf("Array")!==-1)?[]:{};//若是對象是數組,就應該建立數組。若是是非數組的對象,就應該建立對象。
        for(var attr in sourse){
            if(typeof sourse[attr] ==="object"&& sourse[attr] !==null){//若是對象內的鍵值仍是對象,進行更深一步的拷貝
                obj[attr]=cloneFn( sourse[attr] );
            }else{
                obj[attr]=sourse[attr];
            }
        }
        return obj;
    }
        //父類構造函數
        function CreatPerson( name ) {
            this.age = 18;
            this.arr = [123];
            this.name = name;
        }
        CreatPerson.prototype.say = function () {
            console.log("我會說漢語");
        };

        //子類構造函數
        function Corder(name,job) {
            CreatPerson.call(this,name);//繼承父類上非原型上的屬性和方法。
            this.job=job;//子類擴展的自定義屬性
        }
                       
     
   Corder.prototype=cloneFn(CreatPerson.prototype);//拷貝父類上原型上的屬性和方法。
         Coder.prototype.constructor=Coder;//在爲 子類 原型 賦值的時候去修正 constructor
        Corder.prototype.say=function () {//重寫父類上面的say方法,並不修改父類的say方法
            console.log("我會說漢語,我是程序員!!!");
        };

        //父類的對象實例化
        console.log("------------ 父類1 -----------");
        var person=new CreatPerson("jack");
        console.log(person.age);//18
        console.log(person.name);//jack


        //子類1的對象實例化
        console.log("------------ 子類1 -----------");
        var corder1=new Corder("rose","worker");
        corder1.arr=[234];
        corder1.say();//我會說漢語,我是程序員!!!
        console.log(corder1.age);//18
        console.log(corder1.arr);//[234]
        console.log(corder1.name);//rose
        console.log(corder1.job);//worker


        //子類2的對象實例化
        console.log("------------ 子類2 -----------");
        var corder2=new Corder("Mary","corder");
        console.log(corder2.age);//18
        console.log(corder2.arr);//[123]子類與子類之間的 自定義屬性 沒有受到影響
        console.log(corder2.name);//Mary
        console.log(corder2.job);//corder
        corder2.say();//我會說漢語,我是程序員!!!

        console.log("------------ 父類1 -----------");
        person.say();///我會說漢語     父類原型上面的方法 沒有受到影響
        console.log(person.arr);//[123]   父類自定義屬性 沒有受到影響
</script>
相關文章
相關標籤/搜索