1. 程式人生 > >揭開js之constructor屬性的神祕面紗

揭開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="{&quot;mod&quot;:&quot;popu_376&quot;,&quot;con&quot;:&quot;,https://blog.csdn.net/zengyonglan/article/details/53465505,&quot;}" data-track-click="{&quot;mod&quot;:&quot;popu_376&quot;,&quot;con&quot;:&quot;,https://blog.csdn.net/zengyonglan/article/details/53465505,&quot;}">閱讀更多</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>