揭開js之constructor屬性的神祕面紗
轉載自 https://blog.csdn.net/zengyonglan/article/details/53465505
揭開 constructor
在 Javascript 語言中,constructor 屬性是專門為 function 而設計的,它存在於每一個 function 的prototype 屬性中。這個 constructor 儲存了指向 function 的一個引用。
在定義一個函式(程式碼如下所示)時,
function F() {
// some code
}
JavaScript 內部會執行如下幾個動作:
1.為該函式新增一個原形(即 prototype)屬性
2. 為 prototype 物件額外新增一個 constructor 屬性,並且該屬性儲存指向函式F 的一個引用
這樣當我們把函式 F 作為自定義建構函式來建立物件的時候,物件例項內部會自動儲存一個指向其建構函式(這裡就是我們的自定義建構函式 F)的 prototype 物件的一個屬性proto,
所以我們在每一個物件例項中就可以訪問建構函式的 prototype 所有擁有的全部屬性和方法,就好像它們是例項自己的一樣。當然該例項也有一個 constructor屬性了(從 prototype 那裡獲得的),每一個物件例項都可以通過 constrcutor 物件訪問它的建構函式,請看下面程式碼:
var f = new F();
alert(f.constructor === F);// output true
alert(f.constructor === F.prototype.constructor);// output true
我們可以利用這個特性來完成下面的事情:
物件型別判斷,如
if(f.constructor === F) {
// do sth with F
}
其實 constructor 的出現原本就是用來進行物件型別判斷的,但是 constructor 屬性易變,不可信賴。我們有一種更加安全可靠的判定方法:instanceof 操作符。下面程式碼
仍然返回 true
if(f instanceof F) {
// do sth with F
}
原型鏈繼承,由於 constructor 存在於 prototype 物件上,因此我們可以結合
constructor 沿著原型鏈找到最原始的建構函式,如下面程式碼:
function Base() {}
// Sub1 inherited from Base through prototype chain
function Sub1(){}
Sub1.prototype = new Base();
Sub1.prototype.constructor = Sub1;
Sub1.superclass = Base.prototype;
// Sub2 inherited from Sub1 through prototype chain
function Sub2(){}
Sub2.prototype = new Sub1();
Sub2.prototype.constructor = Sub2;
Sub2.superclass = Sub1.prototype;
// Test prototype chain
alert(Sub2.prototype.constructor);// function Sub2(){}
alert(Sub2.superclass.constructor);// function Sub1(){}
alert(Sub2.superclass.constructor.superclass.constructor);// function Base(){}
上面的例子只是為了說明 constructor 在原型鏈中的作用,更實際一點的意義在於:一個子類物件可以獲得其父類的所有屬性和方法,稱之為繼承。
之前提到 constructor 易變,那是因為函式的 prototype 屬性容易被更改,我們用時下很流行的編碼方式來說明問題,請看下面的示例程式碼:
function F() {}
F.prototype = {
_name: 'Eric',
getName: function() {
return this._name;
}
};
初看這種方式並無問題,但是你會發現下面的程式碼失效了:
var f = new F();
alert(f.constructor === F); // output false
怎麼回事?F 不是例項物件 f 的構造函數了嗎?當然是!只不過建構函式 F 的原型被開發者重寫了,這種方式將原有的 prototype 物件用一個物件的字面量{}來代替。而新建的物件{}只是 Object 的一個例項,系統(或者說瀏覽器)在解析的時候並不會在{}上自動新增一個 constructor 屬性,因為這是 function 建立時的專屬操作,僅當你宣告函式的時候解析器才會做此動作。然而你會發現 constructor 並不是不存在的,下面程式碼可以
測試它的存在性:
alert(typeof f.constructor == 'undefined');// output false
既然存在,那這個 constructor 是從哪兒冒出來的呢?我們要回頭分析這個物件字面量
{}。因為{}是建立物件的一種簡寫,所以{}相當於是 new Object()。那既然{}是 Object
的例項,自然而然他獲得一個指向建構函式 Object()的 prototype 屬性的一個引用proto,又因為 Object.prototype 上有一個指向 Object 本身的 constructor屬性。所以可以看出這個constructor其實就是Object.prototype的constructor,
下面程式碼可以驗證其結論:
alert(f.constructor === Object.prototype.constructor);//output true
alert(f.constructor === Object);// also output true
一個解決辦法就是手動恢復他的 constructor,下面程式碼非常好地解決了這個問題:
function F() {}
F.prototype = {
constructor: F, /* reset constructor */
_name: 'Eric',
getName: function() {
return this._name;
}
};
之後一切恢復正常,constructor 重新獲得的建構函式的引用,我們可以再一次測試上面的程式碼,這次返回 true
var f = new F();
alert(f.constructor === F); // output true this time ^^
- 1
- 2
解惑:建構函式上怎麼還有一個 constructor ?它又是哪兒來的?
細心的會發現,像 JavaScript 內建的建構函式,如 Array, RegExp, String,Number, Object, Function 等等居然自己也有一個 constructor:
alert(typeof Array.constructor != ‘undefined’);// output true
經過測試發現,此物非彼物它和 prototype 上 constructor 不是同一個物件,他們是共存的:
alert(typeof Array.constructor != 'undefined');// output true
alert(typeof Array.prototype.constructor === Array); // output true
- 1
- 2
不過這件事情也是好理解的,因為 建構函式也是函式。是函式說明它就是 Function 建構函式的例項物件,自然他內部也有一個指向 Function.prototype 的內部引用proto啦。因此我們很容易得出結論,這個 constructor(建構函式上的constructor 不是 prototype 上的)其實就是 Function 建構函式的引用:
alert(Array.constructor === Function);// output true
alert(Function.constructor === Function); // output true
- 1
- 2
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-a47e74522c.css" rel="stylesheet">
</div>
<div class="hide-article-box text-center">
<a class="btn" id="btn-readmore" data-track-view="{"mod":"popu_376","con":",https://blog.csdn.net/zengyonglan/article/details/53465505,"}" data-track-click="{"mod":"popu_376","con":",https://blog.csdn.net/zengyonglan/article/details/53465505,"}">閱讀更多</a>
</div>
<script>
(function(){
function setArticleH(btnReadmore,posi){
var winH = $(window).height();
var articleBox = $("div.article_content");
var artH = articleBox.height();
if(artH > winH*posi){
articleBox.css({
'height':winH*posi+'px',
'overflow':'hidden'
})
btnReadmore.click(function(){
if(typeof window.localStorage === "object" && typeof window.csdn.anonymousUserLimit === "object"){
if(!window.csdn.anonymousUserLimit.judgment()){
window.csdn.anonymousUserLimit.Jumplogin();
return false;
}else if(!currentUserName){
window.csdn.anonymousUserLimit.updata();
}
}
articleBox.removeAttr("style");
$(this).parent().remove();
})
}else{
btnReadmore.parent().remove();
}
}
var btnReadmore = $("#btn-readmore");
if(btnReadmore.length>0){
if(currentUserName){
setArticleH(btnReadmore,3);
}else{
setArticleH(btnReadmore,1.2);
}
}
})()
</script>
</article>