JS高級——原型鏈

構造函數數組

  1. 構造函數是特殊的函數,裏面沒有returen返回值
  2. new的過程當中,會自動將對象返回,不須要return
  3. new的過程當中,會執行函數中的代碼,會將建立的對象賦值給構造函數中的this

基本概念瀏覽器

  1. 經過同一個構造函數建立的對象都會關聯一個神祕的對象,能夠經過構造函數.prototype進行訪問,這個神祕對象被稱爲原型對象
  2. 這個原型對象能夠被用來作繼承用,js中的繼承有好幾種,包括混入繼承,經典繼承,還有原型繼承
  3. 經過構造函數建立出來的對象,不只擁有構造函數中的屬性,還擁有原型對象中建立出來的屬性
  4. 實例化後的對象也能夠經過__proto__進行訪問原型對象,可是隻是調試時使用,不推薦正式代碼中使用

繼承方式安全

原型基本

<script>
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    //不只擁有構造函數中的屬性,還擁有原型中建立出來的屬性
    Person.prototype.gender = 'male';
    var p = new Person('qx', 18);
    console.log(p.name);//qx
    console.log(p.age);//18
    console.log(p.gender);//male
</script>

混入繼承

<script>
    var o = {}
    var obj = {
        name: "張三",
        age: 18,
        sayHello: function () {
            console.log("Hello world");
        }
    }
    //混入式繼承
    for (var k  in obj) {
        o[k] = obj[k];
    }
    console.log(o);
</script>

經典繼承

一、最先的原理函數

<script>
    //經過替換原型,使得被建立出來對象也擁有傳入對象的屬性
    function jicheng(obj) {
        var o = {};
        o.__proto__ = obj;
        return o;
    }

    var o = jicheng({name: "張三"});
    console.log(o);
</script>

二、create方法this

<script>
    var o = {
        name: "週三"
    };

    var obj = Object.create(o);
    console.log(obj.name);
</script>

三、create方法存在兼容性問題spa

<script>
    var obj = {
        name:"週三"
    };

    //檢測瀏覽器的能力,若是沒有Object.create方法就給他添加一個(不推薦使用)
    if(Object.create){
        var o = Object.create(obj);
    }else{
        Object.create = function () {
            function F() {
            }
            F.prototype = obj;
            var o = new F();
        }
        var o = Object.create(obj);
    }
</script>
<script>
    //本身定義個函數
    function create(obj) {
        if (Object.create) {
            return Object.create(obj);
        } else {
            function F() {
            }

            F.prototype = obj;
            return new F();
        }
    }
</script>

原型對象prototype

  • 原型對象能夠經過構造函數.prototype得到
  • 原型對象中的屬性和方法,能夠提供給那些經過此構造函數建立的對象使用,達到了全局使用的做用
  • 原型對象被替換,若是替換的屬性與原有構造函數的屬性相沖突,那麼被建立的對象,依然首先訪問的是構造函數中的屬性
<script>
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.say=function () {
        console.log('chi');
    }
    var p=new Person('wx',18);
    p.say();//chi
</script>
  • 原型對象添加屬性和方法還能夠經過如下方式,用{}來追加,{}這是字面量,同時表明了一個字面量對象,Person.prototype能夠直接點出{}裏面的屬性和方法
<script>
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype={
        say:function () {
            console.log('chi');
        },
        play:function () {
            console.log('wan');
        }
    };
    var p=new Person('wx',18);
    p.say();//chi
    p.play();//wan
</script>
  • 原型對象在沒有被替換前會輸出一個constructor,其指向就是本身的構造函數,可是一旦被替換就會變成失去本來的構造函數,確實有,可是其指向的是Object的構造函數,最好最好的方式是在原型對象被替換以後,再次添加一個constructor屬性,而且指向本身原先的構造函數
  • 補充下,爲何原型對象被替換後,其構造函數屬性指向的是Object的構造函數?由於Person.protype,被稱爲原型對象,既然是對象,確定也是經過某一個構造函數建立出來的,結果就是Object構造函數,因此Person.prototype有Object的構造函數

<script>
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }

    console.dir(Person.prototype);
    Person.prototype = {
        say: function () {
            console.log('chi');
        },
        play: function () {
            console.log('wan');
        }
    };
    Person.prototype.constructor = Person;
    console.dir(Person.prototype);
</script>

原型繼承3d

將animal這個對象賦值給Person.prototype,與上面的經過字面量{}道理是同樣的,畢竟animal也是一個對象,裏面的屬性也是被{}包裹的調試

<script>
    function Animal() {
        this.eat = 'chifan';
    }

    var animal = new Animal();

    function Person() {
        this.read = 'dushu';
    }

    var p1 = new Person();

    //person顯示是具備animal的屬性,畢竟人也是動物,具體方法是讓person的原型被替換成animal
    //替換後,經過person構造函數建立出來的對象,不只具備person的屬性,還具備animal的屬性

    Person.prototype = animal;//Person.prototype = new Animal()這樣也行

    var p2 = new Person();
    console.log(p1);
    console.log(p2);
</script>

 

輸出結果:code

  1. p1與p2輸出的都是Person類,因此都具備read屬性
  2. p1與p2輸出的原型__proto__中的constructor構造函數屬性不同,p1指向的是Person構造函數,p2指向的是Animal構造函數並且具備eat屬性
  3. p1與p2輸出的原型__proto__中的原型proto__指向都是一致的,都爲Object類

結果分析:

  1. p1與p2都是經過構造函數Person建立出來的,因此都具備read屬性
  2. p1與p2建立的中間,執行了 Person.prototype= new Animal() 代碼,因此p2的原型被替換了,致使p2的原型__proto__中指向的是Animal類,其構造函數固然也就是Animal,因此也具備eat屬性
  3. p1與p2追蹤到最後其實都是屬於Object類,再追蹤的話,Objec類的原型就是null
  4. 須要注意的是在執行 Person.prototype= new Animal() 代碼以後,全部被建立出來Person對象的原型對象的構造函數constructor將再也不指向本來的Person,而是Animal,因此最好將改過來,Person.prototype.constructor=Person

複雜原型繼承

<script>
    function Yuanzi() {
        this.des = '進化';
    }

    function Animal() {
        this.skill = '捕獵';
    }

    function Human() {
        this.say = '說話';
    }

    Animal.prototype = new Yuanzi();
    Animal.prototype.constructor = Animal;
    Human.prototype = new Animal();
    Human.prototype.constructor = Human;


    var h = new Human();
    console.log(h);
</script>

 

打印對象h:

  1. h是一個Human類,原型對象指向Animal類,原型對象的構造函數又指向Human
  2. Animal類的原型對象指向的是Yuanzi,其構造函數又指向Animal
  3. h因此擁有了Human類,Animal類的全部屬性

構造器的做用

  • 構造器其實就是構造函數,實例化後的對象是能夠經過對象.constructor的方式訪問本身的構造函數的,這個屬性實際上是實例化後的對象自己沒有,可是原型對象上有,那麼它爲何被建立出來呢?
  • 其實很簡單的,由於構造函數能夠被看做是類,在簡單類型中,var a=‘hello’ 或者 var b=1 他們的typeof類型都是能夠找到的對應的內置對象(類),那麼複雜類型呢?好比一個經過Person構造函數建立的對象經過typeof打印結果是object,經過Animal構造函數建立出來的對象經過typeof打印結果也是object,那麼這些對象的類究竟是什麼呢?答案是constructor,有了它就可讓對象知道建立本身的構造函數是什麼,等同於知道了本身屬於什麼類

原型鏈基本概念

一、每一個構造函數都有原型對象

二、每一個對象都會有構造函數

三、每一個構造函數的原型都是一個對象

四、那麼這個原型對象也會有構造函數

五、那麼這個原型對象的構造函數也會有原型對象

六、這樣就會造成一個鏈式的結構,稱爲原型鏈

七、經過修改原型鏈結構實現的繼承,就叫作原型繼承 

八、三角關係到成立的必要性之一是 實例化對象p原型的原型與Object構造函數都是指向同一個對象(在內存中的內容與地址都是同樣的)

consolo.log (p.__proto__.__proto__===Object.prototype)//true

屬性搜索基本原則

  1. 當訪問一個對象的成員的時候,會如今自身找有沒有,若是找到直接使用,
  2. 若是沒有找到,則去當前對象的原型對象中去查找,若是找到了直接使用
  3. 若是沒有找到,繼續找原型對象的原型對象,若是找到了,直接使用
  4. 若是沒有找到,則繼續向上查找,直到Object.prototype,若是仍是沒有,就報錯

 原型對象的替換

上面負責原型繼承的模式是進行的原型的替換,這樣雖然方便了,可是也是有問題的

(1)給原型對象替換賦值,原型對象就會失去constructor屬性

<script>
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }

    var p = new Person('qx', 18);
    console.log(Person.prototype);
    Person.prototype = {
        say: function () {
            console.log(this.name);
        }
    };
    console.log(Person.prototype);
</script>

(2)固然即便替換了原型對象,可是實例化後的對象依然具備constructor屬性,而這個屬性屬於對象的原型對象的原型對象,從上圖能夠看出,以及下圖也看出,替換先後打印的結果是不同的。替換後,訪問的是對象的原型對象的原型的構造函數

<script>
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }

    var p = new Person('qx', 18);
    console.log(Person.prototype.constructor);
    Person.prototype = {
        say: function () {
            console.log(this.name);
        }
    };
    console.log(Person.prototype.constructor);
</script>

(3)對原型對象進行替換,其實能夠看做是將一個對象賦值給了一個普通對象,相似var a={},咱們打印a,consolo.dir(a),結果是a並無構造函數的屬性

(4)解決辦法就是,替換了原型對象,必須在替換的對象上面加上constructor屬性,而且賦值指向的是自身構造函數,和上面的複雜原型繼承是同樣的

<script>
    function Person() {

    }
    Person.prototype = {
        constructor: Person;
    }
</script>

擴展內置對象

  • 確實能夠經過修改內置對象的原型對象添加一些方法,可是直接添加是不安全的,安全的方式是經過一箇中介,理由是內置對象是系統默認的,直接修改它是不安全的,讓一箇中介擁有內置對象的全部屬性,再經過這個對象的原型對象添加方法是保險的
<script>
    function MyArray() {
        // this.name = '我是一個數組';
    }

    MyArray.prototype = new Array();
    var arr1 = new MyArray();
    arr1.push(4)
    console.log(arr1);
    MyArray.prototype.say = function () {
        console.log('添加一個說的方法');
    }
    var arr2=new MyArray();
    arr2.say();
</script>
相關文章
相關標籤/搜索