對於JavaScript的繼承和原型鏈,雖然之前自己看了書也聽了session,但還是一直覺得雲裡霧裡,不禁感歎JavaScript真是一門神奇的語言。這次經過Sponsor的一對一輔導和自己回來後反覆思考,總算覺得把其中的精妙領悟一二了。 1. JavaScript建立對象 在物件導向語言中,通常通過定義類然後再進行執行個體化來建立多個具有相同屬性和方法的對象。但是在JavaScript中並沒有類的概念,不過ECMAScript中的建構函式可以用來建立特定類型的對象。因此,在JavaScript中可以建立自訂的建構函式,並且通過new操作符來建立對象。 在JavaScript中並沒有“指定的”建構函式類型,建構函式實質上也是函數,它和一般函數的區別只在於調用方式不同。只有當通過new操作符來調用的時候它才可以作為建構函式來建立對象執行個體,並且把建構函式的範圍賦給這個新對象(將this指向這個新對象)。如果沒有使用new來調用建構函式,那就是普通的函數調用,這個時候this指向的是window對象,這樣做會導致所有的屬性和方法被添加到全域,因此一定要注意命名建構函式時首字母大寫,並且永遠使用new來調用它。 複製代碼function Person(name, gender) { this.name = name; this.gender = gender; this.say = function() { console.log("Hello"); }} var person1 = new Person("Mike", "male");var person2 = new Person("Kate", "female");複製代碼這段代碼就定義了一個建構函式Person, 並且給它添加了name和gender屬性以及say方法。通過調用new操作符來建立了兩個Person的執行個體person1和person2.可以通過代碼來驗證一下: person1 instanceof Person; //true;person2 instanceof Person; //true;並且person1和person2都分別具有了name,gender屬性,並且都被附上了構造對象時傳入的值。同時它們也都具有say方法。 不過通過比較可以看出來,雖然這時person1和person2都具有say方法,但它們其實並不是同一個Function的執行個體,也就是說當使用new來建立建構函式的執行個體時,每個方法都在執行個體上重新被建立了一遍: person1.say == person2.say; //false這樣的重複建立Function是沒有必要的,甚至在執行個體變多的時候造成一種浪費。為此,我們可以使用建構函式的prototype屬性來解決問題。prototype原型對象是用來尋訪繼承特徵的地方,添加到prototype對象中的屬性和方法都會被建構函式建立的執行個體繼承,這時執行個體中的方法就都是指向原型對象中Function的引用了: 複製代碼function Person(name, gender) { this.name = name; this.gender = gender;} Person.prototype.say = function() { console.log("Hello");} var person1 = new Person("Mike", "male");var person2 = new Person("Kate", "female"); person1.say == person2.say //true複製代碼 2. prototype, constructor, 和__proto__ 建構函式,原型對象,執行個體的關係是:JavaScript中,每個函數都有一個prototype屬性,這是一個指標,指向了這個函數的原型對象。而這個原型對象有一個constructor屬性,指向了該建構函式。每個通過該建構函式建立的對象都包含一個指向原型對象的內部指標__proto__。 用代碼錶示它們的關係: Person.prototype.constructor === Person;person1.__proto__ === Person.prototype;person2.__proto__ === Person.prototype; 3. 繼承的實現 JavaScript中使用原型鏈作為實現繼承的主要方法。由於對象執行個體擁有一個指向原型對象的指標,而當這個原型對象又等於另一個類型的執行個體時,它也具有這個指向另一類型原型對象的指標。因此通過指向原型對象的指標__proto__就可以層層遞進的找到原型對象,這就是原型鏈。通過原型鏈來完成繼承: function Teacher(title) { this.title = title;}Teacher.prototype = new Person(); var teacher = new Teacher("professor");這時,我們通過將Teacher的prototype原型對象指向Person的執行個體來完成了Teacher對Person的繼承。可以看到Teacher的執行個體teacher具有了Person的屬性和方法。 但是如果只是將建構函式的prototype原型對象指向另一對象執行個體,發生的事情其實可以歸納為: Teacher.prototype instanceof Person //trueTeacher.prototype.constructor == Person //trueTeacher.prototype.__proto__ === Person.prototype //true問題出現了:這時Teacher的建構函式變成了Person。雖然我們在使用建立的執行個體的屬性和方法的時候constructor的類型並不會產生很大的影響,但是這依然是一個很不合理的地方。因此一般在使用原型鏈實現繼承時,在將prototype指向另一個建構函式的執行個體之後需要再將當前建構函式的constructor改回來: Teacher.prototype = new Person();Teacher.prototype.constructor = Teacher;這樣才是真正的實現了原型鏈繼承並且不改變當前建構函式和原型對象的關係: 到這裡,我們就可以將這個繼承過程封裝成一個extend方法來專門完成繼承的工作了: var extend = function(Child, Parent) { Child.prototype = new Parent(); Child.prototype.constructor = Child; return new Child();};現在這個方法接受兩個參數:Child和Parent,並且在完成繼承之後執行個體化一個Child對象並返回。我們當然可以根據需要來繼續豐富這個函數,包括執行個體化的時候需要傳入的參數什麼的。