歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> JavaScript原型,原型鏈

JavaScript原型,原型鏈

日期:2017/3/1 9:20:35   编辑:Linux編程

js原型

問題:什麼是js原型?

js每聲明一個function,都有prototype原型,prototype原型是函數的一個默認屬性,在函數的創建過程中由js編譯器自動添加。

也就是說:當生產一個function對象的時候,就有一個原型prototype。

舉個例子:

是不是還看到了一個_proto_的屬性?!騷年,你的眼睛不錯~待會在解釋prototype和_proto_的基友關系!

prototype的屬性值是一個對象,是屬性的集合,是屬性的集合,是屬性的集合,重要事情說三遍!

為什麼要說他的屬性的集合呢?我再來舉個例子~:

function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.sayHello=function(){
alert("使用原型得到Name:"+this.name);
}
var per=new Person("alin",21);
per.sayHello(); //輸出:使用原型得到Name:alin

在函數Person裡面自定義了屬性name和age,而prototype是我們的屬性集合,也就是說,我要添加sayHello這個屬性到Person,則要這樣寫:Person.prototype.sayHello,就能添加Person的屬性。

(我們可以簡單的把prototype看做是一個模板,新創建的自定義對象都是這個模板prototype的一個拷貝,其實准確來說,不應該說是一個拷貝,而是一個連接,只不過這種鏈接是不可見,新實例化的對象內部有一個看不見的_Proto_指針,指向原型對象)。

使用原型來優化代碼:

普通code:

function add(x,y){
return x+y;
}
function subtract(x,y){
return x-y;
}
console.log(add(1,3));

第一種方式用原型優化後:

var Calculator = function(){

};
Calculator.prototype = {
add:function(x,y){
return x+y;
},
subtract:function(x,y){
return x-y;
}
};
console.log((new Calculator()).add(1,3));

第二種方式用原型優化後:

var Calculator = function () {};
Calculator.prototype = function(){
add = function(x,y){
return x+y;
},
subtract = function(x,y){
return x-y;
}
return{
add:add,
subtract:subtract
}
}();

console.log((new Calculator()).add(1,3));   

它目的:封裝私有的function,通過return的形式暴露出簡單的使用名稱,以達到public/private的效果。

------------------------------------華麗的分割線------------------------------------

js原型鏈
問題:什麼是原型鏈?
根據《JavaScript高級程序設計》P162頁可以作出回答:原型鏈是實現繼承的主要方法。其基本思想是:利用原型讓一個引用類型繼承另一個應用類型的屬性和方法。
簡單回顧一下構造函數、原型和實例的關系:每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。

JavaScript高級程序設計(第3版)高清完整PDF中文+英文+源碼 下載 http://www.linuxidc.com/Linux/2014-09/107426.htm

首先,我覺得有必要先解釋一下prototype 和_proto_之間的關系。
每一個基本對象都有自己的_proto_屬性,而每一個函數對象都有自己的prototype原型(函數對象也屬於基本對象,所以也有_proto_),每當去定義一個prototype的時候,就相當於把該實例的__proto__指向一個結構體,那麼這個被指向結構體就稱為該實例的原型。 我們還是來看圖吧~比較清晰:

var foo = {
x: 10,
y: 20
};

解析:當你定義一個函數對象的時候,其內部就有這樣一個鏈表關系。聲明foo對象,自帶了_proto_的屬性,而這個屬性指向了prototype,從而實現對象的擴展(例如繼承等操作)。

再看一個例子:

var a = {
x: 10,
calculate: function (z) {
return this.x + this.y + z
}
};
var b = {
y: 20,
__proto__: a
};

var c = {
y: 30,
__proto__: a
};


b.calculate(30); // 60

附上另外說明:

1、一個沒有繼承操作的函數的_proto_都會指向Object.prototype,而Object.prototype都會指向null。

2、所以,也可以很明顯知道,為何null是原型鏈的終端。

理解了__proto__這個屬性鏈接指針的本質。。再來理解constructor。

prototype默認的有一個叫做constructor的屬性,指向這個函數本身。
一般construtor就是我們平時對函數設置的實例化對象

如上圖:SuperType是一個函數,下面包括他的原型對象prototype,原型對象指向了構造函數的指針,而構造函數指回像了原型對象的內部指針,這樣就形成了鏈式關系了。
就是說,當一個函數對象被創建時候,Function構造器產生的函數對象會運行類似這樣的一行代碼:
this.prototype = {constructor:this};
這個prototype對象是存放繼承特征的地方。因為js沒有提供一個方法去確定哪個函數是打算用來做構造器,所以每個函數都會得到一個prototype對象。constructor屬性沒有什麼用,重要的是prototype對象。

實現原型鏈有一種基本模式,其代碼大致如下:

function A(){
this.Aproperty = "111";
}

A.prototype.getA = function(){
return this.Aproperty;
};

function B(){
this.Bproperty = "222";
}

B.prototype = new A();//B繼承A
B.prototype.getB = function(){
return this.Bproperty;
};

var C = new B();
console.log(C.getA());//111

以上定義了兩個類型A和B。每個類型分別有一個屬性和一個方法。它們的主要區別是B繼承了A,而繼承是通過創建A的實例,並將實例賦給B.prototype實現的。實現的本質是重寫原型的對象,代之以一個新的類型的實例。換句話說,原來存在於A的實例中的所有屬性和方法,現在也存在於B.prototype中了。在確立了繼承關系之後,我們給B.prototype添加了一個方法,這樣就繼承A的屬性和方法的基礎上又添加了一個新方法。
如圖:

另外一個很重要的鏈式繼承模式:

function A(x){
  this.x = x;
}
A.prototype.a = "a";
function B(x,y){
  this.y = y;
  A.call(this,x);
}
B.prototype.b1 = function(){
  alert("b1");
}
B.prototype = new A();
B.prototype.b2 = function(){
  alert("b2");
}
B.prototype.constructor = B;
var obj = new B(1,3);

就是說把B的原型指向了A的1個實例對象,這個實例對象具有x屬性,為undefined,還具有a屬性,值為"a"。所以B原型也具有了這2個屬性(或者說,B和A建立了原型鏈,B是A的下級)。而因為方才的類繼承,B的實例對象也具有了x屬性,也就是說obj對象有2個同名的x屬性,此時原型屬性x要讓位於實例對象屬性x,所以obj.x是1,而非undefined。第13行又定義了原型方法b2,所以B原型也具有了b2。雖然第9~11行設置了原型方法b1,但是你會發現第12行執行後,B原型不再具有b1方法,也就是obj.b1是undefined。因為第12行使得B原型指向改變,原來具有b1的原型對象被拋棄,自然就沒有b1了。


  第12行執行完後,B原型(B.prototype)指向了A的實例對象,而A的實例對象的構造器是構造函數A,所以B.prototype.constructor就是構造對象A了(換句話說,A構造了B的原型)。

alert(B.prototype.constructor)出來後就是"function A(x){...}" 。同樣地,obj.constructor也是A構造對象,alert(obj.constructor)出來後就是"function A(x){...}" ,也就是說B.prototype.constructor===obj.constructor(true),但是B.prototype===obj.constructor.prototype(false),因為前者是B的原型,具有成員:x,a,b2,後者是A的原型,具有成員:a。如何修正這個問題呢,就在第16行,將B原型的構造器重新指向了B構造函數,那麼B.prototype===obj.constructor.prototype(true),都具有成員:x,a,b2。

  如果沒有第16行,那是不是obj = new B(1,3)會去調用A構造函數實例化呢?答案是否定的,你會發現obj.y=3,所以仍然是調用的B構造函數實例化的。雖然obj.constructor===A(true),但是對於new B()的行為來說,執行了上面所說的通過構造函數創建實例對象的3個步驟,第一步,創建空對象;第二步,obj.__proto__ === B.prototype,B.prototype是具有x,a,b2成員的,obj.constructor指向了B.prototype.constructor,即構造函數A;第三步,調用的構造函數B去設置和初始化成員,具有了屬性x,y。雖然不加16行不影響obj的屬性,但如上一段說,卻影響obj.constructor和obj.constructor.prototype。所以在使用了原型繼承後,要進行修正的操作。

關於第12、16行,總言之,第12行使得B原型繼承了A的原型對象的所有成員,但是也使得B的實例對象的構造器的原型指向了A原型,所以要通過第16行修正這個缺陷。

文章說明:個人查看各種資料,原創所得,觀點不一定准確,歡迎各路大牛勘誤,小女子感激不盡。

大話設計模式(帶目錄完整版) PDF+源代碼 http://www.linuxidc.com/Linux/2014-08/105152.htm

JavaScript設計模式 中文清晰掃描版PDF http://www.linuxidc.com/Linux/2015-09/122725.htm

淺談JavaScript中的對象 http://www.linuxidc.com/Linux/2015-10/124067.htm

Copyright © Linux教程網 All Rights Reserved