理解JavaScript構(gòu)造函數(shù)(翻譯)

原文:https://css-tricks.com/understanding-javascript-constructors/

理解構(gòu)造函數(shù)對(duì)于真正理解JavaScript語言至關(guān)重要。從技術(shù)上講,JavaScript沒有類,但是它用構(gòu)造函數(shù)和原型來為JavaScript帶來類似的功能。實(shí)際上,在ES2015中引入的類聲明只是作為現(xiàn)有的基于原型繼承的語法糖,并沒有真正為語言添加任何額外的功能。

在本教程中,我們將詳細(xì)討論構(gòu)造函數(shù),并了解JavaScript如何利用它們來創(chuàng)建對(duì)象。

創(chuàng)建和使用構(gòu)造函數(shù)

構(gòu)造函數(shù)與常規(guī)函數(shù)類似,但我們將它們與new關(guān)鍵字一起使用。有兩種類型的構(gòu)造函數(shù):內(nèi)置的構(gòu)造函數(shù),如數(shù)組和對(duì)象,這些構(gòu)造函數(shù)在運(yùn)行時(shí)在執(zhí)行環(huán)境中自動(dòng)可用;以及自定義構(gòu)造函數(shù),它為您自己的對(duì)象類型定義屬性和方法。

當(dāng)您希望創(chuàng)建具有相同屬性和方法的多個(gè)類似對(duì)象時(shí),構(gòu)造函數(shù)非常有用。將構(gòu)造函數(shù)的名稱大寫以區(qū)別于常規(guī)函數(shù)是一種慣例??紤]以下代碼:

function Book() { 
  // unfinished code
} 

var myBook = new Book();

代碼的最后一行創(chuàng)建了Book的一個(gè)實(shí)例并將其賦值給一個(gè)變量。雖然Book構(gòu)造函數(shù)不做任何事情,但myBook仍然是它的一個(gè)實(shí)例。正如您所看到的,這個(gè)函數(shù)與常規(guī)函數(shù)之間沒有區(qū)別,除了它是用new關(guān)鍵字調(diào)用的,而且函數(shù)名是大寫的。

決定實(shí)例的類型

為了查明一個(gè)對(duì)象是否是另一個(gè)對(duì)象的實(shí)例,我們使用instanceof操作符:

myBook instanceof Book    // true
myBook instanceof String  // false

注意,如果instanceof操作符的右邊不是一個(gè)函數(shù),它將拋出一個(gè)錯(cuò)誤:

myBook instanceof {};
// TypeError: invalid 'instanceof' operand ({})

另一種查找實(shí)例類型的方法是使用構(gòu)造函數(shù)屬性??紤]以下代碼片段:

myBook.constructor === Book;   // true

myBook的構(gòu)造函數(shù)屬性指向Book,因此嚴(yán)格的相等運(yùn)算符返回true。JavaScript中的每個(gè)對(duì)象都從其原型繼承一個(gè)構(gòu)造函數(shù)屬性,該屬性指向創(chuàng)建對(duì)象的構(gòu)造函數(shù):

var s = new String("text");
s.constructor === String;      // true

"text".constructor === String; // true

var o = new Object();
o.constructor === Object;      // true

var o = {};
o.constructor === Object;      // true

var a = new Array();
a.constructor === Array;       // true

[].constructor === Array;      // true

但是請(qǐng)注意,使用constructor屬性檢查實(shí)例的類型通常被認(rèn)為是不好的做法,因?yàn)樗赡鼙桓采w。

自定義構(gòu)造函數(shù)

構(gòu)造函數(shù)就像一個(gè)餅干切刀,用來制作具有相同屬性和方法的多個(gè)對(duì)象??紤]以下示例:

function Book(name, year) {
  this.name = name;
  this.year = '(' + year + ')';
}

Book構(gòu)造函數(shù)需要兩個(gè)參數(shù):name和year。使用new關(guān)鍵字調(diào)用構(gòu)造函數(shù)時(shí),它將接收到的參數(shù)分配給當(dāng)前實(shí)例的name和year屬性,如下所示:

var firstBook = new Book("Pro AngularJS", 2014);
var secondBook = new Book("Secrets Of The JavaScript Ninja", 2013); 
var thirdBook = new Book("JavaScript Patterns", 2010);
 
console.log(firstBook.name, firstBook.year);           
console.log(secondBook.name, secondBook.year);           
console.log(thirdBook.name, thirdBook.year);  

此代碼將以下內(nèi)容記錄到控制臺(tái):

image.png

可以看到,通過使用不同的參數(shù)調(diào)用book構(gòu)造函數(shù),我們可以快速構(gòu)建大量不同的book對(duì)象。這與JavaScript在其內(nèi)置構(gòu)造函數(shù)(如Array()和Date()中使用的模式完全相同。

Object.defineProperty() 方法

可以在構(gòu)造函數(shù)中使用Object.defineProperty()方法來幫助執(zhí)行所有必要的屬性設(shè)置??紤]以下構(gòu)造函數(shù):

function Book(name) { 
  Object.defineProperty(this, "name", { 
      get: function() { 
        return "Book: " + name;       
      },        
      set: function(newName) {            
        name = newName;        
      },               
      configurable: false     
   }); 
}

var myBook = new Book("Single Page Web Applications");
console.log(myBook.name);    // Book: Single Page Web Applications

// we cannot delete the name property because "configurable" is set to false
delete myBook.name;    
console.log(myBook.name);    // Book: Single Page Web Applications

// but we can change the value of the name property
myBook.name = "Testable JavaScript";
console.log(myBook.name);    // Book: Testable JavaScript

這段代碼使用Object.defineProperty()來定義訪問器屬性。訪問器屬性不包括任何屬性或方法,但它們定義了在讀取屬性時(shí)要調(diào)用的getter和在寫入屬性時(shí)要調(diào)用的setter。

getter將返回一個(gè)值,而setter將接收賦給屬性的值作為參數(shù)。上面的構(gòu)造函數(shù)返回一個(gè)實(shí)例,該實(shí)例的name屬性可以設(shè)置或更改,但不能刪除。當(dāng)我們獲得name的值時(shí),getter將字符串Book:添加到名稱并返回它。

對(duì)象字面量(Object literal)優(yōu)于構(gòu)造函數(shù)

JavaScript語言有9個(gè)內(nèi)置的構(gòu)造函數(shù):Object()、Array()、String()、Number()、Boolean()、Date()、Function()、Error()和RegExp()。在創(chuàng)建值時(shí),我們可以自由地使用對(duì)象字面量(Object literal)或構(gòu)造函數(shù)。但是,對(duì)象字面量不僅更容易讀取,而且運(yùn)行速度更快,因?yàn)樗鼈兛梢栽诮馕鰰r(shí)進(jìn)行優(yōu)化。因此,對(duì)于簡單的對(duì)象,最好使用對(duì)象字面量。

// a number object
// numbers have a toFixed() method
var obj = new Object(5);
obj.toFixed(2);     // 5.00

// we can achieve the same result using literals
var num = 5;
num.toFixed(2);     // 5.00

// a string object
// strings have a slice() method 
var obj = new String("text");
obj.slice(0,2);     // "te"

// same as above
var string = "text";
string.slice(0,2);  // "te"

正如您所看到的,對(duì)象字面量和構(gòu)造函數(shù)之間幾乎沒有任何區(qū)別。更有趣的是,仍然可以在字面量上調(diào)用方法。當(dāng)對(duì)字面量調(diào)用方法時(shí),JavaScript會(huì)自動(dòng)將文字轉(zhuǎn)換為臨時(shí)對(duì)象,以便該方法可以執(zhí)行操作。一旦不再需要臨時(shí)對(duì)象,JavaScript就會(huì)丟棄它。

使用new關(guān)鍵字是必須的

一定要記住在所有構(gòu)造函數(shù)之前使用new關(guān)鍵字。如果您不小心忘記了new,您將修改全局對(duì)象而不是新創(chuàng)建的對(duì)象??紤]以下示例:

function Book(name, year) {
  console.log(this);
  this.name = name;
  this.year = year;
}

var myBook = Book("js book", 2014);  
console.log(myBook instanceof Book);  
console.log(window.name, window.year);

var myBook = new Book("js book", 2014);  
console.log(myBook instanceof Book);  
console.log(myBook.name, myBook.year);

下面是這段代碼記錄到控制臺(tái)的內(nèi)容:

image.png

當(dāng)我們調(diào)用沒有new的Book構(gòu)造函數(shù)時(shí),實(shí)際上是在調(diào)用沒有返回語句的函數(shù)。因此,在構(gòu)造函數(shù)中,它指向window(而不是myBook),并創(chuàng)建兩個(gè)全局變量。但是,當(dāng)我們使用new調(diào)用函數(shù)時(shí),上下文將從全局(窗口)切換到實(shí)例。這個(gè)正確地指向myBook。

注意,在嚴(yán)格模式下,這段代碼會(huì)拋出一個(gè)錯(cuò)誤,因?yàn)閲?yán)格模式的設(shè)計(jì)是為了防止程序員不小心調(diào)用沒有new關(guān)鍵字的構(gòu)造函數(shù)。

范圍安全的構(gòu)造函數(shù)

正如我們所看到的,構(gòu)造函數(shù)只是一個(gè)函數(shù),因此可以在不使用new關(guān)鍵字的情況下調(diào)用它。但是,對(duì)于沒有經(jīng)驗(yàn)的程序員來說,這可能是bug的來源。作用域安全的構(gòu)造函數(shù)被設(shè)計(jì)成返回相同的結(jié)果,而不管調(diào)用它時(shí)是否帶有new,因此它不會(huì)遇到這些問題。

大多數(shù)內(nèi)置構(gòu)造函數(shù)(如Object、Regex和Array)都是作用域安全的。它們使用一種特殊的模式來確定如何調(diào)用構(gòu)造函數(shù)。如果沒有使用new,它們將通過使用new再次調(diào)用構(gòu)造函數(shù)來返回對(duì)象的適當(dāng)實(shí)例??紤]以下代碼:

function Fn(argument) { 

  // if "this" is not an instance of the constructor
  // it means it was called without new  
  if (!(this instanceof Fn)) { 

    // call the constructor again with new
    return new Fn(argument);
  } 
}

因此,我們的構(gòu)造函數(shù)的作用域安全版本應(yīng)該是這樣的:

function Book(name, year) { 
  if (!(this instanceof Book)) { 
    return new Book(name, year);
  }
  this.name = name;
  this.year = year;
}

var person1 = new Book("js book", 2014);
var person2 = Book("js book", 2014);

console.log(person1 instanceof Book);    // true
console.log(person2 instanceof Book);    // true

結(jié)論

重要的是要理解ES2015中引入的類聲明只是作為現(xiàn)有的基于原型的繼承的語法糖,并沒有向JavaScript添加任何新東西。構(gòu)造函數(shù)和原型是JavaScript定義相似和相關(guān)對(duì)象的主要方式。

在本文中,我們已經(jīng)很好地了解了JavaScript構(gòu)造函數(shù)的工作方式。我們了解到構(gòu)造函數(shù)與常規(guī)函數(shù)類似,但是它們與new關(guān)鍵字一起使用。我們了解了構(gòu)造函數(shù)如何使我們能夠快速創(chuàng)建具有相同屬性和方法的多個(gè)類似對(duì)象,以及為什么instanceof操作符是確定實(shí)例類型的最安全方法。最后,我們討論了作用域安全的構(gòu)造函數(shù),可以使用new或不使用new來調(diào)用它們。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容