new 運算符創建一個用户定義的對象類型的實例或具有構造函數的內置對象的實例。
這是MDN上對new操作符的定義,從這句話中可以看出new返回的其實就是一個實例,那麼問題來了實例又是個啥東西?
先看個例子:
function Cat(name, gender) {
this.name = name;
this.gender = gender;
}
Cat.prototype.say = () => {
console.log('miao, miao, miao');
}
const cat = new Cat('Tom', 'male');
console.log(cat.name); /* Tom */
console.log(cat.gender); /* male */
cat.say(); /* miao, miao, miao */
console.log(typeof cat); /* object */
從例子中可以看到,new操作符返回的其實是一個對象,這個對象可以訪問到構造函數中屬性和方法。那麼就很明顯了,在js中,所有對象都包含一個__proto__屬性指向構造函數的原型,在對象上查找屬性時會順着__proto__一直向上查找。
相關:簡單説説原型和原型鏈
那new的實現就簡單了啊。
/* 因為new是關鍵詞,所以用方法替代 */
function newObj() {
const obj = {};
const Constructor = Array.prototype.shift.call(arguments);
obj.__proto__ = Constructor.prototype;
Constructor.apply(obj, arguments);
return obj;
}
對apply不熟悉的童鞋可能會對Constructor.apply(obj, arguments);這句代碼有疑問。
function Cat(name, gender) {
this.name = name;
this.gender = gender;
}
const obj = {};
Cat.apply(obj, 'Tom', 'Mole'); // Constructor.apply(obj, arguments);
// 就相當於
obj.name = 'Tom';
obj.gender = 'Mole';
使用newObj來替換new會發現貌似沒有問題,可以正常運行。可是當我們稍稍修改一下上述代碼就會發現運行結果會不一樣。
function Cat(name, gender) {
this.name = name;
this.gender = gender;
return { name: 'Jerry', skin: 'block' };
}
Cat.prototype.say = () => {
console.log('miao, miao, miao');
}
const cat = new Cat('Tom', 'male');
console.log(cat.name); /* Jerry */
console.log(cat.gender); /* undefined */
console.log(typeof cat); /* object */
cat.say(); /* TypeError: cat.say is not a function */
可以發現,當構造函數裏面有返回值時,明顯new操作符應該返回的對象是構造函數裏面的返回值(僅當返回值是對象時有效)。
修改之前的代碼。
function newObj() {
const obj = {};
const Constructor = Array.prototype.shift.call(arguments);
obj.__proto__ = Constructor.prototype;
const ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj;
}
以上就是關於new的模擬實現。最後再貼上MDN上的描述:
- 創建一個空的簡單JavaScript對象(即{});
- 鏈接該對象(即設置該對象的構造函數)到另一個對象 ;
- 將步驟1新創建的對象作為this的上下文 ;
- 如果該函數沒有返回對象,則返回this。