Javascript原型和原型鏈

JavaScript在ES6之前沒有類似class,extend的繼承機制,JavaScript的繼承主要是通過原型和原型鏈實現的。

在說原型和原型鏈之前 ,我們首先要了解以下幾個概念

一.私有變量和私有函數

在函數內部定義的變量和函數,如果不對外提供接口,那么外部是無法訪問到的。這個被定義的變量和函數就叫做該函數的私有變量和私有函數。

function Foo() {

var name = "yiMu";  //私有變量

var fn = function() {   //私有函數

console.log("hello word");

};

}

var bar = new Foo();

console.log(bar.name);  //undefined

console.log(bar.fn);    //undefined

因為變量name和函數fn都是在Foo函數內部定義局部變量,所以在函數外部新創建的實例無法訪問;

二、靜態變量和靜態函數

當定義一個函數后通過"."的方式為其添加屬性和函數,通過對象本身可以訪問到,但是其實例卻無法訪問到,這樣的變量和函數叫做靜態變量和靜態函數。

function Foo(){}

Foo.num = 10;  //靜態變量

Foo.fn = function() {   //靜態函數

console.log("hello word");

};

console.log(Foo.num);  //10

console.log(typeof Foo.fn); //function

var bar = new Foo();

console.log(bar.num);  //undefined

console.log(typeof bar.fn); //undefined
三、實例屬性和實例方法

在面向對象編程中除了一些庫函數,我們還是希望在定義一個對象的時候同時定義一些屬性和方法并在實例化后能夠訪問,這些添加的屬性和方法就叫做實例屬性和實例方法。

function Foo() {

this.num = [];  //實例屬性

this.fn = function() {  //實例方法

console.log("hello word");

};

}

console.log(Foo.num);   //undefined

console.log(typeof Foo.fn); //undefined

var bar = new Foo();

console.log(bar.num);   //[]

console.log(typeof bar.fn); //function

我們也可以為實例屬性和實例方法添加屬性和方法:

function Foo() {

this.num = [];  //實例屬性

this.fn = function() {  //實例方法

console.log("hello word");

}

}

var oneBar = new Foo();

oneBar.num.push(1);

oneBar.fn = {};

console.log(oneBar.num);    //[1]

console.log(typeof oneBar.fn);  //Object

var twoBar = new Foo();

console.log(twoBar.num);   //[]

console.log(typeof twoBar.fn);  //function

從上面的代碼可以看到,當我們在oneBar中修改了num的值和fn的類型,但是在twoBar中卻沒有發生改變,這是由于數組和函數都是對象,屬于引用類型。oneBar和twoBar中的屬性和方法名稱雖然相同但是卻不是同一個引用,它們只是對Foo對象定義的屬性和方法的一個復制。

如果一個構造函數對象有上千的實例方法,那么它的每個實例都要對這個構造函數的上千個實例方法進行復制,這顯然是不科學的,那么這種情況下我們就必須使用prototype了。好,接下來我們正式說一下什么是原型,什么是原型鏈?

首先,什么是原型?

JavaScript 中,萬物皆對象!但對象也是有區別的。分為普通對象和函數對象,Object ,Function 是JS自帶的函數對象。每個對象都有原型(null和undefined除外),你可以把它理解為對象的默認屬性和方法。
普通對象和函數對象
var o1 = {}; 
var o2 =new Object();
var o3 = new f1();

function f1(){}; 
var f2 = function(){};
var f3 = new Function('str','console.log(str)');

console.log(typeof Object); //function 
console.log(typeof Function); //function  

console.log(typeof f1); //function 
console.log(typeof f2); //function 
console.log(typeof f3); //function   

console.log(typeof o1); //object 
console.log(typeof o2); //object 
console.log(typeof o3); //object

在上面的例子中 o1 o2 o3 為普通對象,f1 f2 f3 為函數對象。怎么區分,其實很簡單,凡是通過 new Function() 創建的對象都是函數對象,其他的都是普通對象。f1,f2,歸根結底都是通過 new Function()的方式進行創建的。Function Object 也都是通過 New Function()創建的。

  • Object:Object是一個函數對象,Object的原型就是一個Object對象,它里面存在著一些對象的方法和屬性,例如最常見的toString方法。
  • 新建對象:用new Object或者{}建的對象是普通對象,它沒有prototype屬性,只有proto屬性,它指向Object.prototype。
  • Array Array也是一個函數對象,它的原型就是Array.prototype,它里面存在著一些數組的方法和屬性,例如常見的push,pop等方法。
  • Function:Function也是一個函數對象,但它有點特殊,它的原型就是一個function空函數。
  • 自定義函數:它的原型就是你給它指定的那個東西。如果你不指定,那它的原型就是一個Object.prototype。

實例就是通過構造函數創建的。實例一創建出來就具有constructor屬性(指向構造函數)和proto屬性(指向原型對象)。構造函數中有一個prototype屬性,這個屬性是一個指針,指向它的原型對象。原型對象內部也有一個指針(constructor屬性),指向構造函數Person.prototype.constructor = Person。實例可以訪問原型對象上定義的屬性和方法。

普通對象的proto指向這個對象(this)的構造函數的prototype;
函數對象的proto全部都是指向Function的prototype。這是一個空函數
然后無論是Function的prototype還是構造器的prototype的proto都指向object.prototype,然后最終object.prototype指向null,原型鏈結束;

總的來說:

在Javascript中,萬物皆對象,然而對象又分為普通對象和函數對象,至于怎么區分普通對象和函數對象,很簡單,沒有通過new Function()創建的都為普通對象,Object ,Function 是JS自帶的函數對象,每個對象都具有原型屬性prototype指向它的原型對象,每個實例都是通過構造函數創建的,一旦創建就擁有constructor屬性,指向它的構造函數,和_ proto _屬性,指向原型對象,也就是說

實例對象._ proto _屬性===構造函數.prototype

所有普通對象的_ proto 屬性等于它構造器的prototype;
所有函數對象的
proto 屬性等于Function的prototype;
無論是FUnction還是構造器,構造函數的prototype的
proto _都等于object.prototype;

Function.prototype._ proto _ === Object.prototype
Object.prototype._ proto _===null
則原型鏈完成

function Person(name) {
    this.name = name
}
// 重寫原型
Person.prototype = {
    getName: function() {}
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
這里直接重寫了 Person.prototype。輸出結果可以看出p._ proto 仍然指向的是Person.prototype,而不是p.constructor.prototype。這也很好理解,給Person.prototype賦值的是一個對象直接量{getName: function(){}},使用對象直接量方式定義的對象其構造器(constructor)指向的是根構造器Object_,Object.prototype是一個空對象{},{}自然與{getName: function(){}}不等。

什么是原型鏈

在JavaScript 中,每個對象都有一個指向它的原型(prototype)對象的內部鏈接。這個原型對象又有自己的原型,直到某個對象的原型為 null 為止(也就是不再有原型指向),組成這條鏈的最后一環。這種一級一級的鏈結構就稱為原型鏈(prototype chain)。
JavaScript 對象是動態的屬性“包”(指其自己的屬性)。JavaScript 對象有一個指向一個原型對象的鏈。當試圖訪問一個對象的屬性時,它不僅僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依此層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。


原型鏈.png
var A = function(){};

var a = new A();

console.log(a.__proto__);   //A{}(即構造器function A 的原型對象)

console.log(a.__proto__.__proto__); //Object{}(即構造器function Object 的原型對象)

console.log(a.__proto__.__proto__.__proto__);   //null
參考:

作者:Yi罐可樂
最詳盡的 JS 原型與原型鏈終極詳解,沒有「可能是」。(一)
最詳盡的 JS 原型與原型鏈終極詳解,沒有「可能是」。(二)
最詳盡的 JS 原型與原型鏈終極詳解,沒有「可能是」。(三)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容