從問題入手,深入了解JavaScript中原型與原型鏈
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|? _Fatman
來源 |? urlify.cn/VjUJ3e
前言
開篇之前,我想提出3個問題:
新建一個不添加任何屬性的對象為何能調(diào)用toString方法?
如何讓擁有相同構(gòu)造函數(shù)的不同對象都具備相同的行為?
instanceof關(guān)鍵字判斷對象類型的依據(jù)是什么?
要是這3個問題都能回答上來,那么接下來的內(nèi)容不看也罷。但若是對這些問題還存在疑慮和不解,相信我,下面的內(nèi)容將正是你所需要的。
正文
新建一個不添加任何屬性的對象為何能調(diào)用toString方法?
我在深入了解JavaScript中基于原型(prototype)的繼承機制一文中提到過,JavaScript使用的是基于原型的繼承機制,它的引用類型與其對應(yīng)的值將都存在著__proto__[1]屬性,指向繼承的原型對象[2]。當訪問對象屬性無果時,便會在其原型對象中繼續(xù)查找,倘若其原型對象中還是查詢無果,那便接著去其原型對象的原型中去查找,直到查找成功或原型為null時[3]才會停止查找。
let?obj?=?{
}
obj.toString();//"[object?Object]"
這段代碼就是在obj對象中查找toString方法,查詢無果,繼而在其原型[4]中查找toString方法,正好其原型中含有toString方法,故而得以輸出"[object Object]"。
如何讓擁有相同構(gòu)造函數(shù)的不同對象都具備相同的行為?
下面是一段實現(xiàn)了發(fā)布訂閱模式的代碼:
let?_indexOf?=?Array.prototype.indexOf;
let?_push?=?Array.prototype.push;
let?_slice?=?Array.prototype.slice;
let?_concat?=?Array.prototype.concat;
let?_forEach?=?Array.prototype.forEach;
function?Publish(){
????this.subList;
????
????this.indexOf?=?function(sub){
????????let?index?=?-1;
????????if(typeof?this.subList?===?'undefined'?||?this.subList?===?null){
????????????this.subList?=?[];
????????}
????????if(typeof?sub?!==?'undefined'?&&?sub?!==?null){
????????????index?=?_indexOf.call(this.subList,sub);
????????}
????????return?index;
????}
????this.addSub?=?function(sub){
????????let?index?=?this.indexOf(sub);
????????index?>?-1??
????????????''?:?
????????????_push.call(this.subList,sub);
????};
????this.removeSub?=?function(sub){
????????let?index?=?this.indexOf(sub);
????????index?>?-1??
????????????index?===?0????
????????????????this.subList?=?_slice.call(this.subList,1)?:
????????????????this.subList?=?_concat.call(_slice.call(this.subList,0,index),_slice.call(this.subList,index?+?1))?:??
????????????'';
????};
????this.notifySingle?=?function(sub,msg){
????????let?index?=?this.indexOf(sub);
????????index?>?-1??
????????????(typeof?sub.onReceive?===?'function'???
????????????????sub.onReceive(msg)?:?
????????????????'')?:?
????????????'';
????};
????this.notifyAll?=?function(msg){
????????if(typeof?this.subList?!==?'undefined'?&&?this.subList?!==?null){
????????????_forEach.call(this.subList,(sub)=>{
????????????????if(typeof?sub?!==?'undefined'?&&?sub?!==?null){
????????????????????typeof?sub.onReceive?===?'function'???
????????????????????????sub.onReceive(msg)?:?
????????????????????????'';
????????????????}
????????????})
????????}
????};
}
function?Subscription(name){
????this.name?=?name;
????this.onReceive?=?function(msg){
????????console.log(this.name?+?'?收到消息?:?'?+?msg);
????};
}
let?pub?=?new?Publish();
let?sub1?=?new?Subscription('sub1');
let?sub2?=?new?Subscription('sub2');
let?sub3?=?new?Subscription('sub3');
let?sub4?=?new?Subscription('sub4');
pub.addSub(sub1);
pub.addSub(sub1);
pub.addSub(sub2);
pub.addSub(sub3);
pub.addSub(sub4);
pub.notifyAll('這是一條全部推送的消息');
//?sub1?收到消息?:?這是一條全部推送的消息
//?sub2?收到消息?:?這是一條全部推送的消息
//?sub3?收到消息?:?這是一條全部推送的消息
//?sub4?收到消息?:?這是一條全部推送的消息
pub.notifySingle(sub2,"這是一條單獨推送的消息");
//?sub2?收到消息?:?這是一條單獨推送的消息
pub.removeSub(sub3);
pub.notifyAll('這是一條全部推送的消息');
//?sub1?收到消息?:?這是一條全部推送的消息
//?sub2?收到消息?:?這是一條全部推送的消息
//?sub4?收到消息?:?這是一條全部推送的消息
此代碼中擁有同一構(gòu)造函數(shù)的所有對象都含有不同的方法。
sub1.onReceive?===?sub2.onReceive;//false
sub1.onReceive?===?sub3.onReceive;//false
sub1.onReceive?===?sub4.onReceive;//false
sub2.onReceive?===?sub3.onReceive;//false
sub2.onReceive?===?sub4.onReceive;//false
sub3.onReceive?===?sub4.onReceive;//false
這樣會導(dǎo)致:
1.浪費內(nèi)存;
2.不易于對方法進行批量操作。
接下來是改進版本,使用原型達到代碼復(fù)用的效果:
let?_indexOf?=?Array.prototype.indexOf;
let?_push?=?Array.prototype.push;
let?_slice?=?Array.prototype.slice;
let?_concat?=?Array.prototype.concat;
let?_forEach?=?Array.prototype.forEach;
function?Publish(){
????this.subList;
}
Publish.prototype.indexOf?=?function(sub){
????let?index?=?-1;
????if(typeof?this.subList?===?'undefined'?||?this.subList?===?null){
????????this.subList?=?[];
????}
????if(typeof?sub?!==?'undefined'?&&?sub?!==?null){
????????index?=?_indexOf.call(this.subList,sub);
????}
????return?index;
}
Publish.prototype.addSub?=?function(sub){
????let?index?=?this.indexOf(sub);
????index?>?-1??
????????''?:?
????????_push.call(this.subList,sub);
};
Publish.prototype.removeSub?=?function(sub){
????let?index?=?this.indexOf(sub);
????index?>?-1??
????????index?===?0????
????????????this.subList?=?_slice.call(this.subList,1)?:
????????????this.subList?=?_concat.call(_slice.call(this.subList,0,index),_slice.call(this.subList,index?+?1))?:??
????????'';
};
Publish.prototype.notifySingle?=?function(sub,msg){
????let?index?=?this.indexOf(sub);
????index?>?-1??
????????(typeof?sub.onReceive?===?'function'???
????????????sub.onReceive(msg)?:?
????????????'')?:?
????????'';
};
Publish.prototype.notifyAll?=?function(msg){
????if(typeof?this.subList?!==?'undefined'?&&?this.subList?!==?null){
????????_forEach.call(this.subList,(sub)=>{
????????????if(typeof?sub?!==?'undefined'?&&?sub?!==?null){
????????????????typeof?sub.onReceive?===?'function'???
????????????????????sub.onReceive(msg)?:?
????????????????????'';
????????????}
????????})
????}
};
function?Subscription(name){
????this.name?=?name;
????
}
Subscription.prototype.onReceive?=?function(msg){
????console.log(this.name?+?'?收到消息?:?'?+?msg);
};
let?pub?=?new?Publish();
let?sub1?=?new?Subscription('sub1');
let?sub2?=?new?Subscription('sub2');
let?sub3?=?new?Subscription('sub3');
let?sub4?=?new?Subscription('sub4');
pub.addSub(sub1);
pub.addSub(sub1);
pub.addSub(sub2);
pub.addSub(sub3);
pub.addSub(sub4);
pub.notifyAll('這是一條全部推送的消息');
//?sub1?收到消息?:?這是一條全部推送的消息
//?sub2?收到消息?:?這是一條全部推送的消息
//?sub3?收到消息?:?這是一條全部推送的消息
//?sub4?收到消息?:?這是一條全部推送的消息
pub.notifySingle(sub2,"這是一條單獨推送的消息");
//?sub2?收到消息?:?這是一條單獨推送的消息
pub.removeSub(sub3);
pub.notifyAll('這是一條全部推送的消息');
//?sub1?收到消息?:?這是一條全部推送的消息
//?sub2?收到消息?:?這是一條全部推送的消息
//?sub4?收到消息?:?這是一條全部推送的消息
sub1.onReceive?===?sub2.onReceive;//true
sub1.onReceive?===?sub3.onReceive;//true
sub1.onReceive?===?sub4.onReceive;//true
sub2.onReceive?===?sub3.onReceive;//true
sub2.onReceive?===?sub4.onReceive;//true
sub3.onReceive?===?sub4.onReceive;//true
改進版本與之前的版本相比有一個特點:擁有同一構(gòu)造函數(shù)的對象,屬性是唯一的,行為是一致的[5]。所有對象都擁有獨立于其它對象的屬性,卻存在相同的行為。這正是因為在改進版本中,方法存在于構(gòu)造函數(shù)的prototype屬性值上,其將被其創(chuàng)建的對象所繼承。也正是因為如此,盡管此時的sub1、sub2、sub3、sub4中都不包含onReceive方法,但也可以通過繼承的原型對象Subscription.prototype去達到調(diào)用onReceive的目的。而且修改Subscription.prototype上的onReceive方法是可以馬上作用到sub1、sub2、sub3、sub4上的。將方法定義到構(gòu)造函數(shù)的prototype屬性值上,就可以讓擁有相同構(gòu)造函數(shù)的不同對象都具備相同的行為以達到代碼復(fù)用目的。
instanceof關(guān)鍵字判斷對象類型的依據(jù)是什么?
我在深入了解JavaScript中基于原型(prototype)的繼承機制中聲明了函數(shù)Person,并以它為構(gòu)造函數(shù)創(chuàng)建了person對象。
function?Person(){
?
}
let?person?=?new?Person();
person對象的繼承Person函數(shù)的prototype屬性值,而Person函數(shù)的prototype屬性值又繼承Object函數(shù)的prototype屬性值,這種一層一層繼承的關(guān)系構(gòu)成了原型鏈。
instanceof關(guān)鍵字判斷對象類型的依據(jù)便是判斷函數(shù)的prototype屬性值是否存在于對象的原型鏈上。
正如Person函數(shù)的prototype屬性值和Object函數(shù)的prototype屬性值都存在于person對象的原型鏈上,所以使用instanceof判斷兩者都為true。
person?instanceof?Person;//true
person?instanceof?Object;//true
而Function函數(shù)的prototype屬性值不存在于person對象的原型鏈上,所以使用instanceof判斷Function函數(shù)為false。
person?instanceof?Function;//false
最后,完成一個instanceof。
/**
*?obj?變量
*?fn?構(gòu)造函數(shù)
*/
function?myInstanceof(obj,fn){
????let?_prototype?=?Object.getPrototypeOf(obj);
????if(null?===?_prototype){
????????return?false;
????}
????let?_constructor?=?_prototype.constructor;
????if(_constructor?===?fn){
????????return?true;
????}
????return?myInstanceof(_prototype,fn);
}
//測試代碼
myInstanceof({},Object);//true
myInstanceof([],Array);//true
myInstanceof(window,Window);//true
myInstanceof(new?Map(),Map);//true
myInstanceof({},Array);//false
myInstanceof({},Function);//false
大功告成。
結(jié)尾
這3個問題的解答分別對原型和原型鏈的含義以及它們在JavaScript中起到了什么作用進行了闡述。不過由于本人才疏學(xué)淺,難免會遇到一些我個人理解亦或是表達存在錯誤的地方,還望各位遇到之時,能不吝指出。
雖然__proto__已經(jīng)被不推薦使用,但是為了更直觀,我在此文中獲取對象原型的方法都將通過對象的__proto__屬性,還望悉知。???
Object.prototype繼承的原型指向null。???
Object.prototype的原型為null,它是原型鏈的頂點,查到Object.prototype的原型時還找不到便會報找不到了。???
對象obj的原型為obj的構(gòu)造函數(shù)的prototype屬性,也就是Object.prototype。???
這里的屬性意指除方法外的屬性,行為意指方法。???
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長按上方微信二維碼?2 秒
感謝點贊支持下哈?
