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