一個JavaScript函數fn,被執行有三種途徑:app
JavaScript中定義了一種對象,稱之爲ECMAScript對象,其內部實現包括如下:函數
注意:__prototype__是原型鏈,對全部對象都有的。prototype是原型,是函數纔有的,就是一個普通的對象而已,目的是爲了接受new後給生成的對象提供原型鏈的。this
執行fn就是調用__call__prototype
執行new fn()會進行如下簡化過程:code
fn.call(obj)或者fn.apply(obj)就是將obj做爲this,執行fn。本質是調用__call__,只是傳入了obj做爲this.對象
//定義一個函數,正常函數會具備__call__, __construct__ //同時Parent.__proto__指向Function.prototype function Parent() { this.sayAge = function() { console.log("age is: " + this.age); } } //原型上添加一個方法 Parent.prototype.sayParent = function(){ console.log("this is Parent Method"); } //定義另外一個函數 function Child(firstname){ //這裏就是調用Parent的__call__, 而且傳入this //而這裏的this,是Child接受new時候生成的對象 //所以,這一步會給生成的Child生成的實例添加一個sayAge屬性 Parent.call(this); this.fname = firstname; this.age = 40; this.saySomething = function() { console.log(this.fname); this.sayAge(); } } //這一步就是new的調用,按原理分步來看 //1. 新建了個對象,記做o //2. o.__prototype__ = Parent.prototype, 所以o.sayParent會訪問到o.__prototype__.sayParent(原型鏈查找機制) //3. Parent.call(o), 所以o也會有個sayAge屬性(o.sayAge) //4. Child.prototype = o, 所以 Child.prototype 經過o.__prototype__ 這個原型鏈具備了o.sayParent屬性,同時經過o.sayAge 具備了sayAge屬性(也就是說Child.prototype上具備sayAge屬性,但沒有sayParent屬性,可是經過原型鏈,也能夠訪問到sayParent屬性) Child.prototype = new Parent(); //這也是一步new調用 //1. 新建對象,記做s //2. s.__prototype__ = Child.prototype, 此時s會具備sayAge屬性以及sayParent這個原型鏈上的屬性 //3. Child.call(s), 執行後, 增長了fname, age, saySomething屬性, 同時因爲跑了Parent.call(s), s還具備sayAge屬性, 這個屬性是s身上的, 上面那個sayAge是Child.prototype上的, 即s.__prototype__上的。 //4. child = s var child = new Child("張") //child自己屬性就有,執行 child.saySomething(); //child自己屬性沒有, 去原型鏈上看, child.__prototype__ = s.__prototype__ = Child.prototype = o, 這裏沒找到sayParent, 繼續往上找, o.__prototype__ = Parent.prototype, 這裏找到了, 執行(第二層原型鏈找到) child.sayParent();
原理來看寫得有些繁瑣,自己實際上是比較簡單的東西。
重點是new的過程,原型prototype和原型鏈__prototype__
也正是new的原理,致使了原型鏈的繼承,本質是生成的對象的__prototype__指向了函數的原型prototype繼承
更復雜的調用繼承之類的,均可以經過這個原理來理解。說白了,原型鏈繼承就是複用了prototype而已。ip
本例來看,Child中的Parent.call(this)看似沒有必要,但本質上是有區別的。若是去掉這一句,則Child的實例自己將沒有sayAge屬性,而Child.prototype具備sayAge屬性,所以實例的__prototype__具備sayAge屬性,所以還能夠執行。原型鏈
但目的是爲了繼承,所以屬性是須要對象上自己持有,而非是經過原型鏈上來訪問,因此加上這一句是原理上的嚴謹要求。能夠經過下面的例子來檢驗:原型
function Parent() { this.sayAge = function() { console.log("age is: " + this.age); } } Parent.prototype.sayParent = function(){ console.log("this is Parent Method"); } function Child(firstname){ Parent.call(this); this.fname = firstname; this.age = 40; this.saySomething = function() { console.log(this.fname); this.sayAge(); } } Child.prototype = new Parent(); var child = new Child("張") child.saySomething(); child.sayParent(); console.log(child.hasOwnProperty('sayAge')); // true child.sayAge(); //能調用,此時調用的是自身的屬性 delete child.sayAge; // delete只能刪除自身的屬性,不能刪除原型鏈屬性 console.log(child.hasOwnProperty('sayAge')); // false,自身沒有這個屬性了 child.sayAge(); //還能調用,此時調用的是原型鏈上的方法
若是刪掉Parent.call(this), 上面兩句child.hasOwnProperty('sayAge'), 都將返回false