常規的 {...}
語法允許創建壹個對象。但是我們經常需要創建很多類似的對象,例如多個用戶或菜單項等。
這可以使用構造函數和 "new"
操作符來實現。
構造函數在技術上是常規函數。不過有兩個約定:
它們的命名以大寫字母開頭。
它們只能由 "new"
操作符來執行。
例如:
function User(name) { this.name = name; this.isAdmin = false; } let user = new User("Jack"); alert(user.name); // Jack alert(user.isAdmin); // false
當壹個函數被使用 new
操作符執行時,它按照以下步驟:
壹個新的空對象被創建並分配給 this
。
函數體執行。通常它會修改 this
,爲其添加新的屬性。
返回 this
的值。
換句話說,new User(...)
做的就是類似的事情:
function User(name) { // this = {};(隱式創建) // 添加屬性到 this this.name = name; this.isAdmin = false; // return this;(隱式返回) }
所以 new User("Jack")
的結果和以下操作的結果相同:
let user = { name: "Jack", isAdmin: false };
現在,如果我們想創建其他用戶,我們可以調用 new User("Ann")
,new User("Alice")
等。比每次都使用字面量創建要短得多,而且更易于閱讀。
這是構造器的主要目的 —— 實現可重用的對象創建代碼。
讓我們再強調壹遍 —— 從技術上講,任何函數(除了箭頭函數,它沒有自己的 this
)都可以用作構造器。即可以通過 new
來運行,它會執行上面的算法。“首字母大寫”是壹個共同的約定,以明確表示壹個函數將被使用 new
來運行。
new function() { … }
如果我們有許多行用于創建單個複雜對象的代碼,我們可以將它們封裝在壹個立即調用的構造函數中,像這樣:
// 創建壹個函數並立即使用 new 調用它 let user = new function() { this.name = "John"; this.isAdmin = false; // ……用于用戶創建的其他代碼 // 也許是複雜的邏輯和語句 // 局部變量等 };
這個構造函數不能被再次調用,因爲它不保存在任何地方,只是被創建和調用。因此,這個技巧旨在封裝構建單個對象的代碼,而無需將來重用。
進階內容
本節涉及的語法內容很少使用,除非妳想了解所有內容,否則妳可以直接跳過該語法。
在壹個函數內部,我們可以使用 new.target
屬性來檢查它是否被使用 new
進行調用了。
對于常規調用,它爲 undefined,對于使用 new
的調用,則等于該函數:
function User() { alert(new.target); } // 不帶 "new": User(); // undefined // 帶 "new": new User(); // function User { ... }
它可以被用在函數內部,來判斷該函數是被通過 new
調用的“構造器模式”,還是沒被通過 new
調用的“常規模式”。
我們也可以讓 new
調用和常規調用做相同的工作,像這樣:
function User(name) { if (!new.target) { // 如果妳沒有通過 new 運行我 return new User(name); // ……我會給妳添加 new } this.name = name; } let john = User("John"); // 將調用重定向到新用戶 alert(john.name); // John
這種方法有時被用在庫中以使語法更加靈活。這樣人們在調用函數時,無論是否使用了 new
,程序都能工作。
不過,到處都使用它並不是壹件好事,因爲省略了 new
使得很難觀察到代碼中正在發生什麽。而通過 new
我們都可以知道這創建了壹個新對象。
通常,構造器沒有 return
語句。它們的任務是將所有必要的東西寫入 this
,並自動轉換爲結果。
但是,如果這有壹個 return
語句,那麽規則就簡單了:
如果 return
返回的是壹個對象,則返回這個對象,而不是 this
。
如果 return
返回的是壹個原始類型,則忽略。
換句話說,帶有對象的 return
返回該對象,在所有其他情況下返回 this
。
例如,這裏 return
通過返回壹個對象覆蓋 this
:
function BigUser() { this.name = "John"; return { name: "Godzilla" }; // <-- 返回這個對象 } alert( new BigUser().name ); // Godzilla,得到了那個對象
這裏有壹個 return
爲空的例子(或者我們可以在它之後放置壹個原始類型,沒有什麽影響):
function SmallUser() { this.name = "John"; return; // <-- 返回 this } alert( new SmallUser().name ); // John
通常構造器沒有 return
語句。這裏我們主要爲了完整性而提及返回對象的特殊行爲。
省略括號
順便說壹下,如果沒有參數,我們可以省略 new
後的括號:
let user = new User; // <-- 沒有參數 // 等同于 let user = new User();
這裏省略括號不被認爲是壹種“好風格”,但是規範允許使用該語法。
使用構造函數來創建對象會帶來很大的靈活性。構造函數可能有壹些參數,這些參數定義了如何構造對象以及要放入什麽。
當然,我們不僅可以將屬性添加到 this
中,還可以添加方法。
例如,下面的 new User(name)
用給定的 name
和方法 sayHi
創建了壹個對象:
function User(name) { this.name = name; this.sayHi = function() { alert( "My name is: " + this.name ); }; } let john = new User("John"); john.sayHi(); // My name is: John /* john = { name: "John", sayHi: function() { ... } } */
類 是用于創建複雜對象的壹個更高級的語法,我們稍後會講到。
構造函數,或簡稱構造器,就是常規函數,但大家對于構造器有個共同的約定,就是其命名首字母要大寫。
構造函數只能使用 new
來調用。這樣的調用意味著在開始時創建了空的 this
,並在最後返回填充了值的 this
。
我們可以使用構造函數來創建多個類似的對象。
JavaScript 爲許多內建的對象提供了構造函數:比如日期 Date
、集合 Set
以及其他我們計劃學習的內容。
對象,我們還會回來哒!
在本章中,我們只介紹了關于對象和構造器的基礎知識。它們對于我們在下壹章中,學習更多關于數據類型和函數的相關知識非常重要。
在我們學習了那些之後,我們將回到對象,在 原型,繼承 和 類 章節中深入介紹它們。
重要程度: 2
是否可以創建像 new A() == new B()
這樣的函數 A
和 B
?
function A() { ... } function B() { ... } let a = new A; let b = new B; alert( a == b ); // true
如果可以,請提供壹個它們的代碼示例。
是的,這是可以的。
如果壹個函數返回壹個對象,那麽 new
返回那個對象而不是 this
。
所以它們可以,例如,返回相同的外部定義的對象 obj
:
let obj = {}; function A() { return obj; } function B() { return obj; } alert( new A() == new B() ); // true
重要程度: 5
創建壹個構造函數 Calculator
,它創建的對象中有三個方法:
read()
使用 prompt
請求兩個值並把它們記錄在對象的屬性中。
sum()
返回這些屬性的總和。
mul()
返回這些屬性的乘積。
例如:
let calculator = new Calculator(); calculator.read(); alert( "Sum=" + calculator.sum() ); alert( "Mul=" + calculator.mul() );
運行 demo
打開帶有測試的沙箱。
function Calculator() { this.read = function() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); }; this.sum = function() { return this.a + this.b; }; this.mul = function() { return this.a * this.b; }; } let calculator = new Calculator(); calculator.read(); alert( "Sum=" + calculator.sum() ); alert( "Mul=" + calculator.mul() );
使用沙箱的測試功能打開解決方案。
重要程度: 5
創建壹個構造函數 Accumulator(startingValue)
。
它創建的對象應該:
將“當前 value”存儲在屬性 value
中。起始值被設置到構造器 startingValue
的參數。
read()
方法應該使用 prompt
來讀取壹個新的數字,並將其添加到 value
中。
換句話說,value
屬性是所有用戶輸入值與初始值 startingValue
的總和。
下面是示例代碼:
let accumulator = new Accumulator(1); // 初始值 1 accumulator.read(); // 添加用戶輸入的 value accumulator.read(); // 添加用戶輸入的 value alert(accumulator.value); // 顯示這些值的總和
運行 demo
打開帶有測試的沙箱。
function Accumulator(startingValue) { this.value = startingValue; this.read = function() { this.value += +prompt('How much to add?', 0); }; } let accumulator = new Accumulator(1); accumulator.read(); accumulator.read(); alert(accumulator.value);
使用沙箱的測試功能打開解決方案。