在 JavaScript 中,new 運算子建立一個使用者定義的物件型別的例項或具有建構函式的內建物件的例項。建立一個物件很簡單,為什麼我們還要多此一舉使用 new 運算子呢?它到底有什麼樣的魔力?
認識 new 運算子
通過下面的例子理解 new 運算子:
function Person (name) {
this.name = name
}
Person.prototype.getName = function () {
console.log(this.name)
}
var joe = new Person('joe')
joe.sayHello = function () {
console.log('Hello!')
}
joe.getName() // joe
joe.sayHello() // Hello!
Person.sayHello() // Uncaught TypeError: Person.sayHello is not a function
Person 是一個普通的函式,當它與 new 運算子一起使用時,Person 就是一個建構函式。通過 new Person('joe') 得到的新物件 joe 繼承了 Person 的屬性,同時,this 也指向 joe 例項。為 joe 新增的屬性 sayHello 不會影響 Person,即 joe 是區別與 Person 的一個新物件。
因此,通過 new 建立的例項物件和建構函式之間建立了一條原型鏈,並通過原型鏈賦予例項物件繼承屬性的能力。
new 的原理和實現
通過上面的分析,new 運算子內部做了如下四個操作:
- 建立一個空的簡單 JavaScript 物件(即{});
- 連結新物件(即設定該新物件的建構函式)到函式物件;
- 將新建立的物件作為 this 的上下文;
- 如果該函式沒有返回物件,返回新建立的物件。
new 的實現如下:
function newOperator (ctor, ...args) {
var obj = {};
obj.__proto__ = ctor.prototype
var res = ctor.apply(obj, args)
return res || obj;
}
優化一下程式碼:
function newOperator (ctor, ...args) {
var o = Object.create(ctor.prototype) // 合併第一和第二步:建立一個空的簡單 JavaScript 物件(即{}),連結新物件(即設定該新物件的建構函式)到函式物件
return fn.apply(o, args) || o
}
使用 newOperator 函式測試上面 Person 的例子:
function Person(name) {
this.name = name
}
Person.prototype.getName = function () {
console.log(this.name)
}
var joe = newOperator(Person, 'joe')
joe.sayHello = function () {
console.log('Hello!')
}
joe.getName() // joe
joe.sayHello() // Hello!
Person.sayHello() // Uncaught TypeError: Person.sayHello is not a function
結果是一致的。
更好的檢查方式是:
function Person(name) {
this.name = name
}
console.log(new Person('joe')) // @1
console.log(newOperator(Person, 'joe')) // @2
@1 和 @2 在控制檯的顯示資訊是一模一樣的。