深入理解:JavaScript原型與繼承 看過不少書籍,不少文章,對於原型與繼承的說明基本上讓人不明覺厲,特別是對於習慣了面向對象編程的人來說更難理解,這裡我就給大家說說我的理解。 首先JavaScript是一門基於原型編程的語言,它遵守原型編程的基本原則: 1. 所有的數據都是對象(javascr ...
深入理解:JavaScript原型與繼承
看過不少書籍,不少文章,對於原型與繼承的說明基本上讓人不明覺厲,特別是對於習慣了面向對象編程的人來說更難理解,這裡我就給大家說說我的理解。
首先JavaScript是一門基於原型編程的語言,它遵守原型編程的基本原則:
- 所有的數據都是對象(javascript中除了字元串字面量、數字字面量、true、false、null、undefined之外,其他值都是對象!)
- 要得到一個對象,不是通過實例化類,而是找到一個對象作為原型並克隆它
- 對象會記住它的原型
- 如果對象無法響應某個請求,它會把該請求委托給它自己的原型
這麼說來,JavaScript一定有一個根對象,所有的對象的最終原型都將是它,它就是Object.prototype
,Object.prototype
也是一個對象,它是一個空的對象。(記住一點:所有的原型都是對象,但不是函數,雖然函數也是對象,Object對象其實就是一個函數)
以下代碼創建空對象:
var obj1 = new Object();
var obj2 = {};
Object.getPrototypeOf(obj1) === Object.prototype; //true
Object.getPrototypeOf(obj2) === Object.prototype; //true
我們再來看下以下代碼:
function Book(name){
this.name = name;
}
Book.prototype.getName = function(){
return this.name;
}
Book.num = 5;
var book1 = new Book('javascript權威指南');
book1.getName(); //javascript權威指南
Object.getPrototypeOf(book1) === Book.prototype; //true
我們通常說,使用了new Book()來創建了Book的實例book1,但是JavaScript並沒有類的概念,這裡的Book本質上只是一個函數而已,如果用new則把它當著構造函數對待,那麼var book1 = new Book('javascript權威指南')
是怎麼個執行過程呢?在這之前,我們來先看下Function與Object的關係
這裡有張圖:
Function與Object的關係
console.log(Function); //[Function: Function]
console.log(Function.constructor); //[Function: Function]
console.log(Function.__proto__); //[Function]
console.log(Function.prototype); //[Function]
console.log(Function.constructor.prototype); //[Function]
console.log(Object.__proto__); //[Function]
console.log(Object.prototype); //{}
console.log(Object.constructor); //[Function: Function]
console.log(Object.constructor.prototype); //[Function]
在JavaScript中:Function和Object就像是女媧和伏羲,Object提供種子(Object.prototype),Function負責繁衍。Object是由Object.prototype提供的種子經過Function打造出來的。Object.prototype會被一直繼承下去,並一代一代增強。
- 每一個函數都有一個原型對象(prototype)和隱藏的__proto__屬性,函數的__proto__屬性指向Function.prototype,而原型對象(prototype)是一個對象,符合以下第2點(也有構造函數constructor和隱藏的__proto__屬性);
- 每一個非函數對象(實例對象)都有一個構造函數(constructor)和隱藏的__proto__屬性,constructor自然指的是它的構造函數,而__proto__指向的是它的構造函數的原型對象prototype
- 通過__proto__屬性,每個對象和函數都會記住它的原型,這樣就形成了原型鏈;
console.log(Book); //{ [Function: Book] num: 5 }
console.log(Book.__proto__); //[Function]
console.log(Book.prototype.__proto__); //{} 等於Object.prototype
console.log(Book.prototype); //Book { getName: [Function] }
console.log(Book.constructor); //[Function: Function]
console.log(Book.prototype.constructor); //{ [Function: Book] num: 5 }
console.log(Book.constructor.prototype); //[Function]
console.log(Book.__proto__.__proto__); //{} 等於Object.prototype
每一個函數都是通過Function構造出來的,函數的原型屬性__proto__指向Function.prototype,而函數的原型對象prototype是代表著自身的函數對象,它的__proto__屬性指向Object.prototype,它的constructor屬性預設指向它自己構造函數(也可改為別的函數,如:Book.prototype.constructor = Person;)。
console.log(book1); //Book { name: 'javascript權威指南' }
console.log(book1.__proto__); //Book { getName: [Function] }
console.log(book1.prototype); //undefined
console.log(book1.constructor); //{ [Function: Book] num: 5 }
console.log(book1.constructor.prototype); //Book { getName: [Function] }
所以,我們通常說‘一個對象的原型’其實是不准確的,應該是‘一個對象的構造器的原型’,且對象是把它無法響應的請求委托給它的構造器的原型順著原型鏈往上傳遞的。
現在來講解一下var book1 = new Book('javascript權威指南')
的執行過程,是這樣:new先從Object.prototype克隆一個空對象,先將空對象的構造函數指定為Book,然後將空對象的__proto__指向它的構造函數的原型Book.prototype,之後通過Book構造函數對這個空對象進行賦值操作,並將這個對象返回給變數book1。
我們再看如下代碼:
var obj = {name: 'Sufu'};
var Person = function(){};
Person.prototype = obj;
var a = new Person();
console.log(a.name); //Sufu
這段是這樣執行的:
- 首先嘗試查找對象a的name屬性,找不到,執行第2步
- 將查找name屬性的這個請求委托給a的構造器的原型,a.__proto__記錄的是Person.prototype,Person.prototype為對象obj
- 在對象obj中查找name,找到並返回它的值給a,假如還找不到,它就通過obj.__proto__找到Object.prototype,找不到就返回undefined,因為Object.prototype的原型不存在了,為null
好了,就介紹到這裡了,以上是個人的理解,有不對的地方歡迎指出!
參考文獻:《javascript權威指南》