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