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

假如沒有面向對象這類抽象觀點的小夥伴,發起先看一下我寫的
JS基本入門篇(三十四)—面向對象(一)👏👏👏👏

1.異常異常重要而又簡樸的觀點—原型鏈

想要以下為 f 增加一個say要領,有三種要領。

<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要領。

<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 為何能查找的到???

剖析(此剖析一定要看懂,沒有看懂多看幾遍或許百度下):
是原型鏈的觀點。就是js內部的查找機制。起首要邃曉:

1.prototype 原型

    當一個函數被說明的時刻,該函數下默許有一個屬性:prototype,該屬性的值是一個對象。

舉例說明:

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

效果如圖所示:
《JS基本入門篇(三十五)—面向對象(二)》

2.__proto__

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

舉例說明:

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

效果如圖所示:
《JS基本入門篇(三十五)—面向對象(二)》

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>

效果如圖所示:
《JS基本入門篇(三十五)—面向對象(二)》

所以查找機製為:
挪用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>
    原文作者:梁志芳
    原文地址: https://segmentfault.com/a/1190000015419419
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞