JavaScript是一門面向對象的語言。在JavaScript中有一句很經典的話,萬物皆對象。既然是面向對象的,那就有面向對象的三大特徵:封裝、繼承、多態。這裏講的是JavaScript的繼承,其餘兩個容後再講。javascript
JavaScript的繼承和C++的繼承不大同樣,C++的繼承是基於類的,而JavaScript的繼承是基於原型的。java
如今問題來了。瀏覽器
function Animal(name) { this.name = name; } Animal.prototype.setName = function(name) { this.name = name; } var animal = new Animal("wangwang");
function Animal(name) { this.name = name; } var animal = new Animal("wangwang");
Animal.prototype.setName = function(name) { this.name = name; }這時animal也會有setName方法。
var animal = Animal("wangwang");
function Animal(name) { this.name = name; return this; }猜猜如今animal是什麼?
function Animal(name) { if(!(this instanceof Animal)) { return new Animal(name); } this.name = name; }這樣就萬無一失了。
console.log(Animal.prototype.constructor === Animal); // true咱們能夠換種思惟:prototype在函數初始時根本是無值的,實現上多是下面的邏輯
// 設定__proto__是函數內置的成員,get_prototyoe()是它的方法 var __proto__ = null; function get_prototype() { if(!__proto__) { __proto__ = new Object(); __proto__.constructor = this; } return __proto__; }這樣的好處是避免了每聲明一個函數都建立一個對象實例,節省了開銷。
function Animal(name) { this.name = name; } function Dog(age) { this.age = age; } var dog = new Dog(2);
Dog.prototype = new Animal("wangwang");這時,dog就將有兩個屬性,name和age。而若是對dog使用instanceof操做符
console.log(dog instanceof Animal); // true console.log(dog instanceof Dog); // false這樣就實現了繼承,可是有個小問題
console.log(Dog.prototype.constructor === Animal); // true console.log(Dog.prototype.constructor === Dog); // false能夠看到構造器指向的對象更改了,這樣就不符合咱們的目的了,咱們沒法判斷咱們new出來的實例屬於誰。所以,咱們能夠加一句話:
Dog.prototype.constructor = Dog;再來看一下:
console.log(dog instanceof Animal); // false console.log(dog instanceof Dog); // truedone。這種方法是屬於原型鏈的維護中的一環,下文將詳細闡述。
<pre name="code" class="javascript">function Animal(name) { this.name = name; } Animal.prototype.setName = function(name) { this.name = name; } function Dog(age) { this.age = age; } Dog.prototype = Animal.prototype;這樣就實現了prototype的拷貝。
function Animal(name) { this.name = name; } function Dog(age) { this.age = age; } var animal = new Animal("wangwang"); Dog.prototype = animal; var dog = new Dog(2);
咱們能夠看到,子對象的prototype指向父對象的實例,構成了構造器原型鏈。子實例的內部proto對象也是指向父對象的實例,構成了內部原型鏈。當咱們須要尋找某個屬性的時候,代碼相似於安全
function getAttrFromObj(attr, obj) { if(typeof(obj) === "object") { var proto = obj; while(proto) { if(proto.hasOwnProperty(attr)) { return proto[attr]; } proto = proto.__proto__; } } return undefined; }
在這個例子中,咱們若是在dog中查找name屬性,它將在dog中的成員列表中尋找,固然,會找不到,由於如今dog的成員列表只有age這一項。接着它會順着原型鏈,即.proto指向的實例繼續尋找,即animal中,找到了name屬性,並將之返回。假如尋找的是一個不存在的屬性,在animal中尋找不到時,它會繼續順着.proto尋找,找到了空的對象,找不到以後繼續順着.proto尋找,而空的對象的.proto指向null,尋找退出。ide
(new obj()).prototype.constructor === obj;而後,當咱們重寫了原型屬性以後,子對象產生的實例的constructor不是指向自己!這樣就和構造器的初衷背道而馳了。
Dog.prototype = new Animal("wangwang"); Dog.prototype.constructor = Dog;看起來沒有什麼問題了。但實際上,這又帶來了一個新的問題,由於咱們會發現,咱們無法回溯原型鏈了,由於咱們無法尋找到父對象,而內部原型鏈的.proto屬性是沒法訪問的。
function Dog(age) { this.constructor = arguments.callee; this.age = age; } Dog.prototype = new Animal("wangwang");這樣,全部子對象的實例的constructor都正確的指向該對象,而原型的constructor則指向父對象。雖然這種方法的效率比較低,由於每次構造實例都要重寫constructor屬性,但毫無疑問這種方法能有效解決以前的矛盾。
function getAttrFromObj(attr, obj) { if(typeof(obj) === "object") { do { var proto = Object.getPrototypeOf(dog); if(proto[attr]) { return proto[attr]; } } while(proto); } return undefined; }固然,這種方法只能在支持ES5的瀏覽器中使用。爲了向後兼容,咱們仍是須要考慮上一種方法的。更合適的方法是將這兩種方法整合封裝起來,這個相信讀者們都很是擅長,這裏就不獻醜了。