模拟new

定义

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一

原始功能

// Otaku 御宅族,简称宅
function Otaku (name, age) {
    this.name = name;
    this.age = age;

    this.habit = 'Games';
}

// 因为缺乏锻炼的缘故,身体强度让人担忧
Otaku.prototype.strength = 60;

Otaku.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}

var person = new Otaku('Kevin', '18');

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60

person.sayYourName(); // I am Kevin

从这个例子中,我们可以看到,实例 person 可以:

  1. 访问到 Otaku 构造函数里的属性

  2. 访问到 Otaku.prototype 中的属性

模拟思路

  1. new 返回是一个Object

  2. 需要设置__proto__指向

模拟实现

第一版

// 第一版代码
function objectFactory() {
    var obj = new Object(),
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    Constructor.apply(obj, arguments);
    return obj;
};

在这一版中,我们:

  1. 用new Object() 的方式新建了一个对象 obj

  2. 取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数

  3. 将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性

  4. 使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性

  5. 返回 obj

返回值效果实现

接下来我们再来看一种情况,假如构造函数有返回值,举个例子:

function Otaku (name, age) {
    this.strength = 60;
    this.age = age;

    return {
        name: name,
        habit: 'Games'
    }
}

var person = new Otaku('Kevin', '18');

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // undefined
console.log(person.age) // undefined

在这个例子中,构造函数返回了一个对象,在实例 person 中只能访问返回的对象中的属性。

而且还要注意一点,在这里我们是返回了一个对象,假如我们只是返回一个基本类型的值呢?

再举个例子:

function Otaku (name, age) {
    this.strength = 60;
    this.age = age;

    return 'handsome boy';
}

var person = new Otaku('Kevin', '18');

console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18

结果完全颠倒过来,这次尽管有返回值,但是相当于没有返回值进行处理。

所以我们还需要判断返回的值是不是一个对象,如果是一个对象,我们就返回这个对象,如果没有,我们该返回什么就返回什么。

第二版 解决构造函数返回对象的问题

// 第二版的代码
function objectFactory() {
    var obj = new Object(), //从Object.prototype上克隆一个对象
    Constructor = [].shift.call(arguments); //取得外部传入的构造器
    obj.__proto__ = Constructor.prototype; //指向正确的原型
    var ret = Constructor.apply(obj, arguments); //借用外部传入的构造器给obj设置属性
    return typeof ret === 'object' ? ret : obj; //确保构造器总是返回一个对象
};

最后更新于