前言
用過(guò)Vuejs的同學(xué)都知道,需要用new操作符來(lái)實(shí)例化。
new Vue({
el: '#app',
mounted(){},
});
那么面試官可能會(huì)問(wèn)是否想過(guò)new到底做了什么,怎么模擬實(shí)現(xiàn)呢。
附上之前寫(xiě)文章寫(xiě)過(guò)的一段話:已經(jīng)有很多模擬實(shí)現(xiàn)
new操作符的文章,為什么自己還要寫(xiě)一遍呢。學(xué)習(xí)就好比是座大山,人們沿著不同的路登山,分享著自己看到的風(fēng)景。你不一定能看到別人看到的風(fēng)景,體會(huì)到別人的心情。只有自己去登山,才能看到不一樣的風(fēng)景,體會(huì)才更加深刻。
new 做了什么
先看簡(jiǎn)單例子1:
// 例子1
function Student(){
}
var student = new Student();
console.log(student); // {}
// student 是一個(gè)對(duì)象。
console.log(Object.prototype.toString.call(student)); // [object Object]
// 我們知道平時(shí)聲明對(duì)象也可以用new Object(); 只是看起來(lái)更復(fù)雜
// 順便提一下 `new Object`(不推薦)和Object()也是一樣的效果
// 可以猜測(cè)內(nèi)部做了一次判斷,用new調(diào)用
/** if (!(this instanceof Object)) {
* return new Object();
* }
*/
var obj = new Object();
console.log(obj) // {}
console.log(Object.prototype.toString.call(student)); // [object Object]
typeof Student === 'function' // true
typeof Object === 'function' // true
從這里例子中,我們可以看出:一個(gè)函數(shù)用new操作符來(lái)調(diào)用后,生成了一個(gè)全新的對(duì)象。而且Student和Object都是函數(shù),只不過(guò)Student是我們自定義的,Object是JS本身就內(nèi)置的。
再來(lái)看下控制臺(tái)輸出圖,感興趣的讀者可以在控制臺(tái)試試。
與
new Object() 生成的對(duì)象不同的是new Student()生成的對(duì)象中間還嵌套了一層__proto__,它的constructor是Student這個(gè)函數(shù)。
// 也就是說(shuō):
student.constructor === Student;
Student.prototype.constructor === Student;
小結(jié)1:從這個(gè)簡(jiǎn)單例子來(lái)看,new操作符做了兩件事:
- 創(chuàng)建了一個(gè)全新的對(duì)象。
- 這個(gè)對(duì)象會(huì)被執(zhí)行
[[Prototype]](也就是__proto__)鏈接。
接下來(lái)我們?cè)賮?lái)看升級(jí)版的例子2:
// 例子2
function Student(name){
console.log('賦值前-this', this); // {}
this.name = name;
console.log('賦值后-this', this); // {name: '軒轅Rowboat'}
}
var student = new Student('軒轅Rowboat');
console.log(student); // {name: '軒轅Rowboat'}
由此可以看出:這里Student函數(shù)中的this指向new Student()生成的對(duì)象student。
小結(jié)2:從這個(gè)例子來(lái)看,new操作符又做了一件事:
- 生成的新對(duì)象會(huì)綁定到函數(shù)調(diào)用的
this。
接下來(lái)繼續(xù)看升級(jí)版例子3:
// 例子3
function Student(name){
this.name = name;
// this.doSth();
}
Student.prototype.doSth = function() {
console.log(this.name);
};
var student1 = new Student('軒轅');
var student2 = new Student('Rowboat');
console.log(student1, student1.doSth()); // {name: '軒轅'} '軒轅'
console.log(student2, student2.doSth()); // {name: 'Rowboat'} 'Rowboat'
student1.__proto__ === Student.prototype; // true
student2.__proto__ === Student.prototype; // true
// __proto__ 是瀏覽器實(shí)現(xiàn)的查看原型方案。
// 用ES5 則是:
Object.getPrototypeOf(student1) === Student.prototype; // true
Object.getPrototypeOf(student2) === Student.prototype; // true
關(guān)于JS的原型關(guān)系筆者之前看到這張圖,覺(jué)得很不錯(cuò),分享給大家。
小結(jié)3:這個(gè)例子3再一次驗(yàn)證了小結(jié)1中的第2點(diǎn)。
也就是這個(gè)對(duì)象會(huì)被執(zhí)行[[Prototype]](也就是__proto__)鏈接。并且通過(guò)new Student()創(chuàng)建的每個(gè)對(duì)象將最終被[[Prototype]]鏈接到這個(gè)Student.protytype對(duì)象上。
細(xì)心的同學(xué)可能會(huì)發(fā)現(xiàn)這三個(gè)例子中的函數(shù)都沒(méi)有返回值。那么有返回值會(huì)是怎樣的情形呢。
那么接下來(lái)請(qǐng)看例子4
// 例子4
function Student(name){
this.name = name;
// Null(空) null
// Undefined(未定義) undefined
// Number(數(shù)字) 1
// String(字符串)'1'
// Boolean(布爾) true
// Symbol(符號(hào))(第六版新增) symbol
// Object(對(duì)象) {}
// Function(函數(shù)) function(){}
// Array(數(shù)組) []
// Date(日期) new Date()
// RegExp(正則表達(dá)式)/a/
// Error (錯(cuò)誤) new Error()
// return /a/;
}
var student = new Student('軒轅Rowboat');
console.log(student); {name: '軒轅Rowboat'}
筆者測(cè)試這七種類(lèi)型后MDN JavaScript類(lèi)型,得出的結(jié)果是:前面六種基本類(lèi)型都會(huì)正常返回{name: '軒轅Rowboat'},后面的Object(包含Functoin, Array, Date, RegExg, Error)都會(huì)直接返回這些值。
由此得出 小結(jié)4:
- 如果函數(shù)沒(méi)有返回對(duì)象類(lèi)型
Object(包含Functoin,Array,Date,RegExg,Error),那么new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新的對(duì)象。
結(jié)合這些小結(jié),整理在一起就是:
- 創(chuàng)建了一個(gè)全新的對(duì)象。
- 這個(gè)對(duì)象會(huì)被執(zhí)行
[[Prototype]](也就是__proto__)鏈接。 - 生成的新對(duì)象會(huì)綁定到函數(shù)調(diào)用的
this。 - 通過(guò)
new創(chuàng)建的每個(gè)對(duì)象將最終被[[Prototype]]鏈接到這個(gè)函數(shù)的prototype對(duì)象上。 - 如果函數(shù)沒(méi)有返回對(duì)象類(lèi)型
Object(包含Functoin,Array,Date,RegExg,Error),那么new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新的對(duì)象。
new 模擬實(shí)現(xiàn)
知道了這些現(xiàn)象,我們就可以模擬實(shí)現(xiàn)new操作符。直接貼出代碼和注釋
/**
* 模擬實(shí)現(xiàn) new 操作符
* @param {Function} ctor [構(gòu)造函數(shù)]
* @return {Object|Function|Regex|Date|Error} [返回結(jié)果]
*/
function newOperator(ctor){
if(typeof ctor !== 'function'){
throw 'newOperator function the first param must be a function';
}
// ES6 new.target 是指向構(gòu)造函數(shù)
newOperator.target = ctor;
// 1.創(chuàng)建一個(gè)全新的對(duì)象,
// 2.并且執(zhí)行[[Prototype]]鏈接
// 4.通過(guò)`new`創(chuàng)建的每個(gè)對(duì)象將最終被`[[Prototype]]`鏈接到這個(gè)函數(shù)的`prototype`對(duì)象上。
var newObj = Object.create(ctor.prototype);
// ES5 arguments轉(zhuǎn)成數(shù)組 當(dāng)然也可以用ES6 [...arguments], Aarry.from(arguments);
// 除去ctor構(gòu)造函數(shù)的其余參數(shù)
var argsArr = [].slice.call(arguments, 1);
// 3.生成的新對(duì)象會(huì)綁定到函數(shù)調(diào)用的`this`。
// 獲取到ctor函數(shù)返回結(jié)果
var ctorReturnResult = ctor.apply(newObj, argsArr);
// 小結(jié)4 中這些類(lèi)型中合并起來(lái)只有Object和Function兩種類(lèi)型 typeof null 也是'object'所以要不等于null,排除null
var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
var isFunction = typeof ctorReturnResult === 'function';
if(isObject || isFunction){
return ctorReturnResult;
}
// 5.如果函數(shù)沒(méi)有返回對(duì)象類(lèi)型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),那么`new`表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新的對(duì)象。
return newObj;
}
最后用模擬實(shí)現(xiàn)的newOperator函數(shù)驗(yàn)證下之前的例子3:
// 例子3 多加一個(gè)參數(shù)
function Student(name, age){
this.name = name;
this.age = age;
// this.doSth();
// return Error();
}
Student.prototype.doSth = function() {
console.log(this.name);
};
var student1 = newOperator(Student, '軒轅', 18);
var student2 = newOperator(Student, 'Rowboat', 18);
// var student1 = new Student('軒轅');
// var student2 = new Student('Rowboat');
console.log(student1, student1.doSth()); // {name: '軒轅'} '軒轅'
console.log(student2, student2.doSth()); // {name: 'Rowboat'} 'Rowboat'
student1.__proto__ === Student.prototype; // true
student2.__proto__ === Student.prototype; // true
// __proto__ 是瀏覽器實(shí)現(xiàn)的查看原型方案。
// 用ES5 則是:
Object.getPrototypeOf(student1) === Student.prototype; // true
Object.getPrototypeOf(student2) === Student.prototype; // true
可以看出,很符合new操作符。讀者發(fā)現(xiàn)有不妥或可改善之處,歡迎指出。
回顧這個(gè)模擬new函數(shù)newOperator實(shí)現(xiàn),最大的功臣當(dāng)屬于Object.create()這個(gè)ES5提供的API。
Object.create() 用法舉例
筆者之前整理的一篇文章中也有講過(guò),可以翻看JavaScript 對(duì)象所有API解析
Object.create(proto, [propertiesObject])
方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來(lái)提供新創(chuàng)建的對(duì)象的proto。
它接收兩個(gè)參數(shù),不過(guò)第二個(gè)可選參數(shù)是屬性描述符(不常用,默認(rèn)是undefined)。
var anotherObject = {
name: '軒轅Rowboat'
};
var myObject = Object.create(anotherObject, {
age: {
value:18,
},
});
// 獲得它的原型
Object.getPrototypeOf(anotherObject) === Object.prototype; // true 說(shuō)明anotherObject的原型是Object.prototype
Object.getPrototypeOf(myObject); // {name: "軒轅Rowboat"} // 說(shuō)明myObject的原型是{name: "軒轅Rowboat"}
myObject.hasOwnProperty('name'); // false; 說(shuō)明name是原型上的。
myObject.hasOwnProperty('age'); // true 說(shuō)明age是自身的
myObject.name; // '軒轅Rowboat'
myObject.age; // 18;
對(duì)于不支持ES5的瀏覽器,MDN上提供了ployfill方案。
if (typeof Object.create !== "function") {
Object.create = function (proto, propertiesObject) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object: ' + proto);
} else if (proto === null) {
throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
}
if (typeof propertiesObject != 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");
function F() {}
F.prototype = proto;
return new F();
};
}
到此,文章就基本寫(xiě)完了。感謝讀者看到這里。
最后總結(jié)一下:
1.new做了什么:
- 創(chuàng)建了一個(gè)全新的對(duì)象。
- 這個(gè)對(duì)象會(huì)被執(zhí)行
[[Prototype]](也就是__proto__)鏈接。- 生成的新對(duì)象會(huì)綁定到函數(shù)調(diào)用的
this。- 通過(guò)
new創(chuàng)建的每個(gè)對(duì)象將最終被[[Prototype]]鏈接到這個(gè)函數(shù)的prototype對(duì)象上。- 如果函數(shù)沒(méi)有返回對(duì)象類(lèi)型
Object(包含Functoin,Array,Date,RegExg,Error),那么new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新的對(duì)象。
2.怎么模擬實(shí)現(xiàn):
// 去除了注釋
function newOperator(ctor){
if(typeof ctor !== 'function'){
throw 'newOperator function the first param must be a function';
}
newOperator.target = ctor;
var newObj = Object.create(ctor.prototype);
var argsArr = [].slice.call(arguments, 1);
var ctorReturnResult = ctor.apply(newObj, argsArr);
var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
var isFunction = typeof ctorReturnResult === 'function';
if(isObject || isFunction){
return ctorReturnResult;
}
return newObj;
}
讀者發(fā)現(xiàn)有不妥或可改善之處,歡迎指出。另外覺(jué)得寫(xiě)得不錯(cuò),可以點(diǎn)個(gè)贊,也是對(duì)筆者的一種支持。
關(guān)于
作者:常以軒轅Rowboat為名混跡于江湖。前端路上 | PPT愛(ài)好者 | 所知甚少,唯善學(xué)。
個(gè)人博客
segmentfault個(gè)人主頁(yè)
掘金個(gè)人主頁(yè)
知乎
github