可否模擬實現JS的new操做符

new 作了什麼

先看簡單例子1javascript

// 例子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操做符來調用後,生成了一個全新的對象。並且StudentObject都是函數,只不過Student是咱們自定義的,ObjectJS自己就內置的。 再來看下控制檯輸出圖,感興趣的讀者能夠在控制檯試試。java

例子1 控制檯輸出圖new Object()生成的對象不一樣的是new Student()生成的對象中間還嵌套了一層__proto__,它的constructorStudent這個函數。正則表達式

 

// 也就是說:
student.constructor === Student;
Student.prototype.constructor === Student;

小結1:從這個簡單例子來看,new操做符作了兩件事:

  1. 建立了一個全新的對象。
  2. 這個對象會被執行[[Prototype]](也就是__proto__)連接。

接下來咱們再來看升級版的例子2segmentfault

// 例子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操做符又作了一件事:

  1. 生成的新對象會綁定到函數調用的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

 

例子3 控制檯輸出圖關於JS的原型關係筆者以前看到這張圖,以爲很不錯,分享給你們。JavaScript原型關係圖app

 

小結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(包含FunctoinArrayDateRegExgError)都會直接返回這些值。測試

由此得出 小結4:

  1. 若是函數沒有返回對象類型Object(包含FunctoinArrayDateRegExgError),那麼new表達式中的函數調用會自動返回這個新的對象。

結合這些小結,整理在一塊兒就是:this

  1. 建立了一個全新的對象。
  2. 這個對象會被執行[[Prototype]](也就是__proto__)連接。
  3. 生成的新對象會綁定到函數調用的this
  4. 經過new建立的每一個對象將最終被[[Prototype]]連接到這個函數的prototype對象上。
  5. 若是函數沒有返回對象類型Object(包含FunctoinArrayDateRegExgError),那麼new表達式中的函數調用會自動返回這個新的對象。

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

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();
    };
}

到此,文章就基本寫完了。感謝讀者看到這裏。

最後總結一下:

  1. new作了什麼:
  1. 建立了一個全新的對象。
  2. 這個對象會被執行[[Prototype]](也就是__proto__)連接。
  3. 生成的新對象會綁定到函數調用的this
  4. 經過new建立的每一個對象將最終被[[Prototype]]連接到這個函數的prototype對象上。
  5. 若是函數沒有返回對象類型Object(包含FunctoinArrayDateRegExgError),那麼new表達式中的函數調用會自動返回這個新的對象。
  1. 怎麼模擬實現
// 去除了註釋
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;
}
相關文章
相關標籤/搜索