rwson

rwson

一个前端开发

实现一个new函数

在日常javaScript开发中,我们多多少少都会用到new,最常见的比如new Date等等,在一些js面向对象中,用到new的地方更多了,比如我们通过function来模拟声明一个类,需要实例化的时候就需要用new xxx():

function Class() {
    //	...
}

var inst = new Class();

MDN上对在执行了new之后的介绍如下:

  • 一个新对象被创建。它继承自*foo*.prototype
  • 构造函数 *foo* 被执行。执行的时候,相应的传参会被传入,同时上下文会被指定为这个新实例。new *foo* 等同于 new *foo*(), 只能用在不传递任何参数的情况。
  • 如果构造函数返回了一个“对象”,那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象。(一般情况下构造函数不返回任何值,不过用户如果想覆盖这个返回值,可以自己选择返回一个普通对象来覆盖。当然,返回数组也会覆盖,因为数组也是对象。)

在知道了new之后发生的事情,我们的_new函数就可以按照上面的几个步骤来:

function _new() {
  		//	获取到所有参数
	var args = [].slice.call(arguments),
        //	创建一个空对象
		obj = Object.create({}),
        //	把第一个参数作为构造器
		Constructor = args[0],
		res;
  	//	继承构造器下的原型
	obj.__proto__ = args[0].prototype;
  	//	执行构造器,并传入相关参数
	res = Constructor.apply(obj, args.slice(1));
    return obj;
}

上面是第一版,我们把构造器的执行结果作为返回值返回出去,下面我们一起写个模拟类测试下:

function Class(name, age) {
	this.name = name;
	this.age = age;
}

Class.prototype = {
	constructor: Class,
	method: function () {
		console.log("I'm method under Class.prototype");
	}
};

var inst = _new(Class, "rwson", 24);
inst.method();
console.log(inst);

可以看到在控制台打出的类似下图的东西

new-1

我们现在实现了一个版本,只是把在_new方法中的obj对象返回出来了,刚才上面第三步骤说到,如果构造函数返回了一个“对象”,那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象。(一般情况下构造函数不返回任何值,不过用户如果想覆盖这个返回值,可以自己选择返回一个普通对象来覆盖。当然,返回数组也会覆盖,因为数组也是对象。),针对构造函数有返回值的情况,我们需要把该返回值返回,下面我们把之前的代码改改,以适配有返回值的情况:

function _new() {
	var args = [].slice.call(arguments),
		obj = Object.create({}),
		Constructor = args[0],
		res;
	obj.__proto__ = args[0].prototype;
	res = Constructor.apply(obj, args.slice(1));
  
  	//	判断Constructor的返回值类型
    return typeof res === "object" ? res : obj;
}

同样我们写个类做测试

function Class(name, age) {
	this.name = name;
	this.age = age;
    return {
       name: name,
       age: age
	};
}

var inst = _new(Class, "rwson", 24);
console.log(inst);

观察在控制台的输出应该和下面的类似:

new-2

到这里我们的_new方法就实现了,但是由于Object.create的原因,在IE9-的版本中可能不太支持,所以我们需要把Object.create改成new Object或者对象字面量的形式

function _new() {
	var args = [].slice.call(arguments),
		obj = {},
		Constructor = args[0],
		res;
	obj.__proto__ = args[0].prototype;
	res = Constructor.apply(obj, args.slice(1));
  
  	//	判断Constructor的返回值类型
    return typeof res === "object" ? res : obj;
}