首先須要明確,JavaScript並非傳統意義上的OO語言,它並無class
的概念,
而是包含了另外一套異常強大的原型機制。它的類型體系、繼承體系都創建在原型基礎之上。
爲了迎合傳統的OO開發者,JavaScript語言的設計者經過這套原型體系模擬了傳統面嚮對象語言的編碼風格。javascript
簡單來講,創建一個自定義類型只須要編寫類型的構造函數便可:java
javascriptfunction Person(name, age) { this.name = name; this.age = age; } // 實例化 Person 類型 Person person = new Person("John", 34);
Person
構造函數與通常的函數沒有任何區別,只是調用方式不太同樣,
經過使用new
關鍵字,改變了通常函數調用的行爲,有點相似於下面這樣:
1. Object obj = new Object();
2. Person.call(obj, "John", 34);
3. obj.__proto__ = Person.prototype;
4. return obj;
編程
序號2
中call
函數調用的做用是改變執行Person函數時的this
爲obj
,
序號3
的做用是設置新建實例的原型(一個實例的__proto__
屬性是這個實例的原型)。
記住,全部函數(如這裏的Person)的prototype
屬性默認都是一個Object
實例。
因此序號3
執行後,person.__proto__
正是Person.prototype
,
這解釋了爲何全部的引用類型都派生自Object
。瀏覽器
除此以外,從上面的介紹還應該意識到Person
的全部實例的__proto__
屬性都是Person.prototype
。app
上面定義的屬性都是實例的屬性,也能夠直接爲某個類型添加類型的屬性(記住,方法也是一個對象),
而這個屬性沒法經過類型的實例訪問到,以下面的代碼:函數
javascriptPerson.country = "Canada";
另外一個例子是ECMAScript 5引入的Object
類型的getPrototypeOf
方法,它能夠得到一個實例的原型變量:ui
javascriptObject.getPrototypeOf = function(instance) { // some code.. };
若是想定義同一個類型全部實例共享的屬性(好比方法),能夠定義在類型的原型中:this
javascriptPerson.prototype.logName = function() { console.log(this.name); };
須要注意,經過Person
的實例只能讀取原型中的屬性,而不能重寫;
若是嘗試重寫,其實是在實例中定義了一個同名的屬性,從而屏蔽了原型中的屬性:編碼
javascript// 並無改變 Person.prototype.logName的值 person.logName = function() { // some code.. }
形成屏蔽的緣由是當使用對象.屬性
時,
是從對象
開始查找屬性
,若是沒有找到再在其原型中查找,
若是尚未找到,再查找其原型的原型,以此類推在原型鏈上不斷向上查找,
第一次查找到屬性
後查找過程就結束了。
若是想恢復被屏蔽的原型屬性,可使用delete
操做符:prototype
javascriptdelete person.logName;
最佳的實踐是將實例的屬性定義在構造函數中,將方法定義在原型中。
這樣每一個實例獨享本身的屬性,並和其餘同類型的實例共享方法:
javascript// 構造函數 function Person(name, age) { this.name = name; this.age = age; } // 原型 Person.prototype.logName = function() { console.log(this.name); }
以上這種方式定義的Person
類型,能夠經過instanceof
來判斷一個實例是不是Person
類型的:
javascriptPerson person = new Person("John", 34); console.log(john instanceof Person); // true
實際上instanceof
是經過實例的原型鏈來判斷一個對象是否某個類型的實例的,具體的細節後面會詳細介紹。
這裏首先介紹一下如何得到一個實例的原型對象:
1. isPrototypeOf()
你能夠判斷一個對象是否在另外一個對象的原型鏈上出現:
Person person = new Person("John", 34); console.log(Person.prototype.isPrototypeOf(person)); // true
2. Object.getPrototypeOf()
你能夠獲得一個對象的原型。
這個方法是ECMAScript 5引入的,某些IE瀏覽器並不支持:
// get the prototype of person instance Object.getPrototypeOf(person);
3. __proto__
JavaScript中每一個對象都有一個指向其原型的內部屬性,
在某些瀏覽器(如Chrome)中可使用它們。
既然能夠將屬性定義在實例自己或它的原型鏈中,那麼可不能夠判斷某個屬性具體是在哪裏定義呢?固然能夠:
1. hasOwnProperty()
若是屬性在實例自己出現,則返回true
:
console.log(person.hasOwnProperty("name")); // true console.log(person.hasOwnProperty("logName")); // false
2. in
操做符,若是屬性在實例或其原型鏈中出現,則返回true
alert("logName" in person); // true
利用in
操做符,咱們還能夠枚舉出一個實例和它原型鏈中全部可枚舉的屬性:
for (var propertyName in person) { // log all property name and its value console.log(propertyName + "\t\t" + person[propertyName]); }
最後來介紹一下instanceof
的原理,假設執行下面的代碼:
javascriptfunction Person(name, age) { this.name = name; this.age = age; } function Student(name, age, school) { Person.call(this, name, age); this.school = school; } // Student 繼承了 Person Student.prototype = new Person(); Student student = new Student("Adam", 30, " School");
關於繼承的細節以後再詳細討論,這裏只須要明確咱們將Student
類型的原型賦值爲一個Person
實例。
此時student
的原型鏈能夠表示爲:
student.__proto__
==>Person
實例(假設爲person
)person.__proto__
==>Object
實例(假設爲object
)object.__proto__
==>null
(到頂了)
也許會有人疑惑person
的原型爲何是Object
實例,
這是由於全部的方法(好比這裏的Person
、Student
)的prototype屬性默認都是一個Object類型的實例,
這也證實了爲何全部的內置類型和自定義類型無一例外所有都派生自Object
類型。
當執行下面的代碼時:
javascriptconsole.log(student instanceof Student); // true
其實是判斷Student.prototype是否在student的原型鏈中出現,若是出現了則返回true。
這裏Student.prototype
是person
,是student的原型,因此返回true。
再看:
javascriptconsole.log(student instanceof Person); // true
同理由於存在Person.prototype === student.__proto__.__proto__
,因此返回true。
看到這裏相信你應該對prototype
和__proto__
的關係有了比較清楚的理解了。
它們之間的關係能夠總結爲:
__proto__
:__proto__
is the actual object that is used in the lookup chain to resolve methods.
It is a property that all objects have.
This is the property which is used by the JavaScript engine for inheritance.
According to ECMA specifications it is supposed to be an internal property,
however most vendors allow it to be accessed and modified.prototype
:prototype
is a property belonging only to functions.
It is used to build__proto__
when the function
happens to be used as a constructor with the new keyword.
能夠說JavaScript是Python的另外一個極端
——There's always more than one way to do it.
實現繼承也不例外,不一樣的實現模式有不一樣的使用場景,
各有優點和不足,這裏只介紹一個最常被使用的模式——組合繼承模式,直接看例子:
javascript/* * 基類,定義屬性 */ function Person(name, age) { this.name = name; this.age = age; } /* * 基類,定義方法 */ Person.prototype.selfIntroduce = function () { console.log("name: " + this.name); console.log("age: " + this.age); } /* * 子類,定義子類的屬性 */ function Student(name, age, school) { // 調用基類的構造函數 Person.call(this, name, age); this.school = school; } // 使子類繼承基類 Student.prototype = new Person(); /* * 定義子類的方法 */ Student.prototype.goToSchool = function() { // some code.. } /* * 擴展並調用了超類的方法 */ Student.prototype.selfIntroduce = function () { Student.prototype.__proto__.selfIntroduce.call(this); console.log("school: " + this.school); } var student = new Student("John", 22, "My School"); student.selfIntroduce();