JavaScript學習系列之原型、原型鏈

原型是Javascript中的繼承的基礎,JavaScript的繼承主要依靠原型鏈來實現的。html

1.原型

在JavaScript中,咱們建立一個函數A(就是聲明一個函數), 就會爲該函數建立一個prototype屬性。並且也會在內存中建立一個對象B,A函數的屬性 prototype 指向這個對象B( 即:prototype的屬性的值是這個對象 )。這個對象B就是函數A的原型對象,簡稱函數的原型。這個原型對象B 默認會有一個屬性 constructor, constructor屬性指向函數A ( 意思就是說:constructor屬性的值是函數A )。java

/*
    		聲明一個函數,則這個函數默認會有一個屬性叫 prototype 。並且瀏覽器會自動按照必定的規則
    		建立一個對象,這個對象就是這個函數的原型對象,prototype屬性指向這個原型對象。這個原型對象
    		有一個屬性叫constructor 執行了這個函數
	
	      注意:原型對象默認只有屬性:constructor。其餘都是從Object繼承而來,暫且不用考慮。
	   */
	    function Person () {
	    	
	    }	
複製代碼

下面的圖描述了聲明一個函數以後發生的事情:瀏覽器

當把一個函數做爲構造函數 (理論上任何函數均可以做爲構造函數) 使用new建立對象的時候,那麼這個對象就會存在一個默認的不可見的屬性,來指向了構造函數的原型對象。 這個不可見的屬性咱們通常用 [[prototype]] 來表示,只是這個屬性沒有辦法直接訪問到。bash

function Person () {  }	
        /*
        	利用構造函數建立一個對象,則這個對象會自動添加一個不可見的屬性 [[prototype]], 並且這個屬性
        	指向了構造函數的原型對象。
        */
      	var p1 = new Person();
複製代碼

觀察下面的示意圖:函數

1.1 小結

__proto__ 是對象實例纔有的屬性,指向對象的原型。
prototype 是構造函數纔有的屬性,該屬性指向了一個對象,這個對象正是調用該構造函數而建立的實例的原型
實例的__proto__屬性 和 構造函數的 prototype 都指向該對象原型
複製代碼

這幾句話能解釋一切關於原型方面的問題:優化

當 new 一個函數的時候會建立一個對象,『函數.prototype』 等於 『被建立對象.__proto__』
一切函數都是由 Function 這個函數建立的,因此『Function.prototype === 被建立的函數.__proto__』
一切函數的原型對象都是由 Object 這個函數建立的,因此『Object.prototype === 一切函數.prototype.__proto__』
複製代碼

2.原型鏈

原型鏈基本思路:利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。this

每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數想指針(constructor),而實例對象都包含一個指向原型對象的內部指針(__proto__)。
若是讓原型對象等於另外一個類型的實例,此時的原型對象將包含一個指向另外一個原型的指針(__proto__),另外一個原型也包含着一個指向另外一個構造函數的指針(constructor)。
假如另外一個原型又是另外一個類型的實例……這就構成了實例與原型的鏈條。
複製代碼

原型鏈基本思路(圖解):spa

'__proto__'是對象的屬性、'prototype'是函數的屬性
null是對象原型鏈的終點,其值既有(是一個對象)又無(不引用任何對象),
表明着對象本源的一種混沌、虛無的狀態,正與老子《道德經》中的「道」,有着同等的意義
(心中一萬隻艹尼瑪奔騰而過,仍是寫java爽啊)。

在JS中,undefined是全局對象的一個屬性,它的初始值就是原始數據類型undefined,而且沒法被配置,也沒法被改變。
undefined從字面意思上理解爲「未定義」,即表示一個變量沒有定義其值。

而null是一個JS字面量,表示空值,即沒有對象。
與undefined相比,null被認爲是「指望一個對象,可是不引用任何對象的值」,而undefined是純粹的「沒有值」。

複製代碼

從一張圖看懂原型對象、構造函數、實例對象之間的關係prototype

prototype:構造函數中的屬性,指向該構造函數的原型對象。

constructor:原型對象中的屬性,指向該原型對象的構造函數

_proto_:實例中的屬性,指向new這個實例的構造函數的原型對象
複製代碼

3.利用原型實現繼承

3.1 利用 call 借用構造函數繼承

優勢:實現了繼承屬性,但值都不相同指針

缺點: 沒法繼承父級類別中原型上的方法

function Person(name,age,sex,weight){
    this.name=name;
    this.age=age;
    this.sex=sex;
    this.weight=weight;
}
Person.prototype.sayHi=function(){
    console.log("您好")
}
 
function Student(name,age,sex,weight,score){
    //將當前實例對象傳入Person 借過來使用一次來達到繼承效果
    Person.call(this,name,age,sex,weight);
    this.score=score;
}
 
var stu1=new Student("小明",10,"男","10kg","100")
複製代碼

3.2 prototype 實現繼承

利用prototype,將Student 的prototype 指向 Person 來達到繼承效果,

優勢:繼承了父級原型上的方法

缺點: 實例化多個Student 都必須共用相同的name 和 age

// 此處
Student.prototype.constructor=Student
複製代碼
function Person(name,age){
        this.name=name;
        this.age=age;
     }
 
     Person.prototype.eat=function(){
        console.log("Person 吃飯")
     }
 
     function Student(num,score){
        this.num=num
        this.score=score
     }
     //繼承
    Student.prototype=new Person("小紅",10)
    Student.prototype.constructor=Student
 
    var stu =new Student(2016002288,80)
 
    stu.eat()//Person 吃飯
複製代碼

3.3 組合繼承

組合繼承其實就是結合了上述的兩種方法來實現繼承,擁有兩種方法的優勢

function Person(name,age,sex){
        this.name=name;
        this.age=age;
        this.sex=sex;
     }
     Person.prototype.sayHi=function(){
        console.log("你好")
     }
 
     function  Student(name,age,sex,score){
        //借用構造函數
        Person.call(this,name,age,sex)
        this.score=score
     }
 
     // 改變了原型指向
     Student.prototype=new Person();//不傳值
     Student.prototype.eat=function(){
        console.log("吃東西");
     }
 
     var stu=new Student("小黑",20,"男","100分")
     console.log(stu.name,stu.age,stu.sex,stu.score);
     stu.sayHi()//你好
     stu.eat()//吃東西
複製代碼

3.4 拷貝繼承

相似於複製,把一個對象中的屬性和方法直接複製到另外一個對象中

function Person(){
    }
 
    Person.prototype.name="小紅"
    Person.prototype.age=18
 
    function Student(){
    }
    
    var p=Person.prototype;
    var s=Student.prototype;
 
    for(key in p){
        s[key]=p[key]
    }
 
    console.dir(Student)
複製代碼

每次都要for in 好累 , 能夠進行優化封裝一下

function extend(Child,Parent) {
 
    var p = Parent.prototype;
    var c = Child.prototype;
 
    for (var i in p) {
      c[i] = p[i];
      }
        
        //這個屬性直接指向父對象的prototype屬性,能夠直接調用父對象的方法,爲了實現繼承的完備性,純屬備用性質
    c.par = p;
 
  }
複製代碼

3.5 直接繼承prototype

優勢 : 效率比較高

缺點 : 由於至關因而個傳址過程 因此修改Student的屬性 Person 的也會被更改

function Person(){};
 
    Person.prototype.name="小紅";
    Person.prototype.age=18;
 
    function Student(){};
 
    Student.prototype=Person.prototype;
 
    console.dir(Student);
    console.dir(Person);
    Student.prototype.age=25;
複製代碼

3.6 利用空對象做中介實現繼承

用這種方式修改 Student 的prototype 不會影響到 Person的prototype

function Person(){};
    Person.prototype.name="小紅";
    Person.prototype.age=11;
 
    function Student(){};
    var F=function(){};
    F.prototype=Person.prototype;
 
    Student.prototype=new F();
    Student.prototype.constructor=Student;
 
    Student.prototype.age=25;
 
    console.dir(Person)
    console.dir(Student)
複製代碼

參考文獻

www.cnblogs.com/wangfupeng1…

相關文章
相關標籤/搜索