【JavaScript】模拟实现 new

268 阅读3分钟

一、new 的基本内容

1. new 的定义

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

2. new 的语法

new constructor[([arguments])]

参数说明,如下:

  • constructor:一个指定对象实例的类型的类或函数。
  • arguments:一个用于被 constructor 调用的参数列表。

3. new 的使用

function Person(name, age){
    this.name = name;
    this.age = age;
}
Person.prototype.getName = function(){
    console.log(this.name);
}
var p1 = new Person('Tom', 18);

console.log(p1.age); // 18
p1.getName(); // Tom

从上述代码我们可以了解到,new创建的实例具有如下特性:

  • 访问到构造函数里的属性。
  • 访问到原型里的属性。

二、模拟实现

模拟实现之前,先来了解一下使用new操作符这种方式调用构造函数,到底发生了什么事情

具体情况作如下:

  1. 在内存中创建一个新对象。
  2. 这个新对象内部的[[Prototype]]特性被赋值为构造函数的prototype属性。
  3. 构造函数内部的this被赋值为这个新对象(即this指向新对象)。
  4. 执行构造函数内部的代码(给新对象添加属性)。
  5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

先将new调用的构造函数定义为 Foo,然后,让我们逐句分析一下。

第1句,“在内存中创建一个新对象”。用代码实现的话,即是说:

var obj = {};
// 或者
var obj = new Object();
// 或者
var obj = Object.create(null);

第2句,“这个新对象内部的[[Prototype]]特性被赋值为构造函数的prototype属性”。用代码实现的话,即是说:

obj.__proto__ = Foo.prototype;
// 或者
var obj = Object.create(Foo.prototype);

第3、4句,既要“this指向新对象”,又要“执行构造函数内部的代码”。这让我们想到了,改变this指向的两个方法:callapply。那么,代码实现也就很简单了。

Foo.call(obj, 参数1, 参数2...);
// 或者
Foo.apply(obj, 参数集);

第5句,我们可以理解为,根据构造函数返回的内容不同,那么new创建的实例也会不同。情况有两种:

  1. 构造函数返回的是非空对象,实例是构造函数的返回值;
  2. 构造函数返回的是空对象,实例是之前创建的obj对象。(构造函数不返回内容,则看做返回的是undefined

那么关键点就在于:如何判断一个变量是否是非空对象

其实很简单:

变量 && typeof 变量 === 'object'
// 或者
变量 instanceof Object

综上所述,模拟new就很好实现了:

function create(){
    // 获取函数
    var Foo = arguments[0];
    // 创建对象
    var obj = {};
    obj.__proto__ = Foo.prototype;
    // 获取参数
    var args = [];
    for (var i=1; i<arguments.length; i++) {
        args.push(arguments[i]);
    }
    // 改变this指向,调用函数
    var result = Foo.apply(obj, args);
    // 返回值
    return result instanceof Object ? result : obj;
}

用ES6实现的话,也可以这样:

function create(Foo, ...args){
    // 创建对象
    var obj = Object.create(Foo.prototype);
    // 改变this指向,调用函数
    var result = Foo.apply(obj, args);
    // 返回值
    return result instanceof Object ? result : obj;
}

三、参考资料