先看簡單例子1:javascript
// 例子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
自己就內置的。 再來看下控制檯輸出圖,感興趣的讀者能夠在控制檯試試。java
與
new Object()
生成的對象不一樣的是new Student()
生成的對象中間還嵌套了一層__proto__
,它的constructor
是Student
這個函數。正則表達式
// 也就是說: student.constructor === Student; Student.prototype.constructor === Student;
new
操做符作了兩件事:[[Prototype]]
(也就是__proto__
)連接。接下來咱們再來看升級版的例子2:segmentfault
// 例子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
。數組
new
操做符又作了一件事:this
。接下來繼續看升級版例子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的原型關係筆者以前看到這張圖,以爲很不錯,分享給你們。
app
[[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
)都會直接返回這些值。測試
Object
(包含Functoin
, Array
, Date
, RegExg
, Error
),那麼new
表達式中的函數調用會自動返回這個新的對象。結合這些小結,整理在一塊兒就是:this
[[Prototype]]
(也就是__proto__
)連接。this
。new
建立的每一個對象將最終被[[Prototype]]
連接到這個函數的prototype
對象上。Object
(包含Functoin
, Array
, Date
, RegExg
, Error
),那麼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,排除null var 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
。
筆者以前整理的一篇文章中也有講過,能夠翻看JavaScript 對象全部API解析
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(); }; }
到此,文章就基本寫完了。感謝讀者看到這裏。
new
作了什麼:
- 建立了一個全新的對象。
- 這個對象會被執行
[[Prototype]]
(也就是__proto__
)連接。- 生成的新對象會綁定到函數調用的
this
。- 經過
new
建立的每一個對象將最終被[[Prototype]]
連接到這個函數的prototype
對象上。- 若是函數沒有返回對象類型
Object
(包含Functoin
,Array
,Date
,RegExg
,Error
),那麼new
表達式中的函數調用會自動返回這個新的對象。
// 去除了註釋 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; }