關於原型,原型鏈和繼承的討論
阿新 • • 發佈:2018-11-11
寫部落格也有不短的時間了,經常做一些自己的總結和技術的分享。但是之前的由於之前的備用號碼丟失,也正好準備換個備用卡,所以直接登出了,沒有找回。所以就開了個新的部落格。以後,我會經常在這裡分享一下心得,和和大家做一些技術探討。今天就原型鏈做一個總結。
在剛接觸到js原型和原型鏈的時候,可能很多人都會有很多的困惑和疑問。這是很正常,因為當你有了這種狀態的時候,說明你已經到了王國維先生說的學習的三重境界中的第一重了,昨夜西風凋碧樹,獨上高樓,望斷天涯路。就是說你已經有了想要學的更透徹,追尋執行原理的想法了。是所謂獨上高樓,望斷天涯路。一定是有著窮其理的好奇心與幹勁。
一,基本釋義
所有型別(包括基本型別與函式,陣列,物件)都擁有__proto__屬性(隱式原型)
所有函式擁有prototype屬性(顯式原型)最底層的Object也有prototype屬性。這也驗證了萬物皆物件,所有的型別都是從它中來。 函式的prototype是個指標,它指向自己的原型物件, function Ball(words){ this.words = words; } Ball.prototype = { a:10, play:function(){ console.log( this.a); } } var ball=new Ball(); 這個 Ball.prototype就是建構函式function Ball的原型物件; 通過這個建構函式new 出來的新的物件ball中就有__proto__這個屬性,並且__proto__指向這個建構函式function Ball的原型物件 Ball.prototype
二,建構函式例項解析
//建立例項 function Ball(words){ this.words = words;//當前this 指向window,new出新的ball物件後,指向ball(在面向物件中就用這裡為原型物件傳引數) } Ball.prototype = { a:10, play:function(){ console.log( this.a);//這裡this指向原型物件 Ball.prototype,在new 出新的例項後, this指向例項即 ball; } } var ball=new Ball(); ball.play();// a=10; play()方法是ball例項本身具有的方法,所以ball.play()列印a=10;ball.play()不屬於ball例項的方法,屬於建構函式的方法,因為例項繼承建構函式的方法。 例項w的隱式原型指向它建構函式的顯式原型,指向的意思是恆等於 ball.__proto__ === Ball.prototype 當呼叫某種方法或查詢某種屬性時,首先會在自身呼叫和查詢,如果自身並沒有該屬性或方法,則會去它的__proto__屬性中呼叫查詢,也就是它建構函式的prototype中呼叫查詢。所以很好理解例項繼承建構函式的方法和屬性: ball本身沒有play()方法,所以會去Ball()的顯式原型中呼叫play(),即例項繼承建構函式的方法。
三,原型和原型鏈
Object.prototype.play= “play”;
function Ball(){}
console.log(Ball); //function Ball()
let ball= new Ball();
console.log(ball); //Ball{} 物件
console.log(ball.play); //play
解析:
ball是Ball()的例項,是一個Ball物件,它擁有一個屬性值__proto__,並且__proto__是一個物件,包含兩個屬性值constructor和__proto__
console.log(ball.__proto__.constructor); //function Ball(){}
console.log(ball.__proto__.__proto__); //物件{},擁有很多屬性值
我們會發現ball.__proto__.constructor返回的結果為建構函式本身,ball.__proto__.__proto__有很多引數
原型鏈總結:
1.查詢屬性,如果本身沒有,則會去__proto__中查詢,也就是建構函式的顯式原型中查詢,如果建構函式中也沒有該屬性,因為建構函式也是物件,也有__proto__,那麼會去它的顯式原型中查詢,一直到null,如果沒有則返回undefined
2.ball.__proto__.constructor == function Ball(){}
3.ball.___proto__.__proto__== Object.prototype
4.ball.___proto__.__proto__.__proto__== Object.prototype.__proto__ == null
5.通過__proto__形成原型鏈而非protrotype
console.log(ball.__proto__.__proto__.__proto__); //null
console.log(Object.prototype.__proto__); //null
通過上述我們發現prototype下面是有constructor和__proto__的,也就是說建構函式也是有__proto__指向的,它就指向Object.prototype,而且Object.prototype下也有__proto__指向null;
所以利用這個原理,我們就能完成繼承。在繼承的類中,例如下列;
//原來的的類
function Ball(words){
this.words = words;//當前this 指向window,new出新的ball物件後,指向ball(在面向物件中就用這裡為原型物件傳引數)
}
Ball.prototype = {
a:10,
play:function(){
this.a===10;//這裡this指向原型物件 Ball.prototype,在new 出新的例項後, this指向例項即 ball;
}
};
//寫一個繼承函式
//subClass想要繼承的新類,supClass是原來的類
function extend(subClass,supClass) {
// 建立一個臨時類
function F() {}
// 設定這個臨時的類的原型是父類的原型
F.prototype=supClass.prototype;
// 子類的原型是例項化的這個臨時類
subClass.prototype=new F();
// 設定子類原型中該子類的指標指向自己這個建構函式
subClass.prototype.constructor=subClass;
// 設定子類的屬性superClass是父類的原型
subClass.superClass=supClass.prototype;
// 如果父類原型中指標沒有指向父類的建構函式,仍然指向Object的指標時,
// 重設父類原型中指標指向父類的建構函式
if(supClass.prototype.constructor===Object.prototype.constructor){
supClass.prototype.constructor=supClass;
}
}
//繼承得到的新類
function Box() {
// 使用冒充將父類的物件屬性加入到子類的物件屬性中
Ball.call(this);
}
extend(Box,Ball);
// console.log(Box);
Box.prototype.play=function () {
// Box.superClass.play.call(this);//類似es6中super作用的語句
console.log(this.a);
};
var box=new Box();
box.play(); //列印的結果為1;(如果上面的super語句不註解的話那麼結果就為10;)
這就是es5當中我們常用的繼承方法,是基於原型和原型鏈基礎之上來的。可以看到Box.prototype=new new F(); 而 F.prototype=supClass.prototype;其中是通過new改變了Box中的__proto__的指向,讓其指向supClass.prototype;所以我們在新類中可以用到原來類的屬性和方法,也可以對Box.prototype.play進行原來類下某個方法和屬性進行覆蓋。(原理是物件屬性和方法的呼叫順序先找自身,再去原型鏈上找)但是注意不能對Box.prototype.__proto__進行修改,因為Box.prototype.__proto__指向Ball.prototype。