在日常使用new时,我们很清楚它的作用。
准备工作
我们先创建一个Person
类,他接受两个参数name
姓名和age
年龄:
function Person(name, age){ this.name = name; this.age = age;}Person.prototype.sayHello = function(){ console.log('我叫' + this.name + ', 今年' + this.age + '岁了');}复制代码
new 的使用
我们先用new实例化一个person,并打印出来,看看结构。
var person = new Person('小明', 20);console.log(person); // Person {name: "小明", age: 20}person.sayHello(); // 我叫小明, 今年20岁了复制代码
最终person的结果是一个object
:
模拟过程
结合Person方法,我们容易发现,仿佛有一个object替代了this的位置,执行了赋值操作,输出了最后的结果。
1、替代this赋值
思考替代过程:
// 1、创建了一个对象 var result = {}; // 2、对象代替了this的位置,执行了赋值 { result.name = "小明"; result.age = 20; } // 3、输出 {name: "小明", age: 20} return result; 复制代码
那么问题来了,我该如何将这个result替代this的位置呢?
这就用到了call
或者apply
:
var result = {}; Person.call(result, '小明', 20); result; // {name: "小明", age: 20}复制代码
这样我们就完成了第一步,你可以在console控制台中尝试一下!
接下来就是处理原型部分了。
2、原型移植
这就很简单了,我们有很多办法:
//方案1result.__proto__ = Person.prototype; //有一定副作用(可枚举)//方案2Object.setPrototypeOf(result, Person.prototype);//方案3result = Object.create(Person.prototype);复制代码
3、初步结果
综上我们容易整理出这样的结果:
function likeNew(fn){ //我们先完成原型移植,以免构造函数中调用了原型方法。 var result = Object.create(fn.prototype); var args = [].slice.call(arguments,1); fn.apply(result, args); return result } var person = likeNew(Person, '小明', 20); console.log(person); // Person {name: "小明", age: 20} person.sayHello(); // 我叫小明, 今年20岁了复制代码
虽然我们按照思路是先创建对象->执行->处理原型。
但是实际上正确的顺序是 创建包含对应原型的对象->执行。问题
如果被实例化的方法如果本身包含返回值,new的结果会是什么呢?
function Person(name, age){ this.name = name; this.age = age; return name } var person = new Person('张三', 20); console.log(person); // ? var person1 = new Person(['张三'], 20); console.log(person); // ?复制代码
通过尝试,输出结果分别为Person {name: '张三', age: 20}
、['张三']
。
猜想:方法返回值的类型决定实例化后的结果
。 1、基本类型
js中的基本类型有number、string、boolean、undefined、null、symbol(es6)
共6种。
function Test(value){ this.value = value; return value; } //number var number = new Test(123); console.log(number); // Test {value: 123} //string var string = new Test('abc'); console.log(string); // Test {value: 'abc'} //boolean var boolean = new Test(true); console.log(boolean); // Test {value: true} //undefined var Undefined = new Test(); console.log(Undefined); // Test {value: undefined} //null var Null = new Test(null); console.log(Null); // Test {value: null} //symbol var symbol = new Test(Symbol('key')); console.log(symbol); // Test {value: Symbol(key)}复制代码
上述例子所有返回值均为实例化后的对象,由此可见,所有基本类型返回的都是正常的。
2、引用类型
js中的引用类型有 object、array、function
。我们接着上面的Test类继续创建对象:
//object var object = new Test({}}); console.log(object); // {} //array var array = new Test([]); console.log(array); // [] //function var functions = new Test(function(){}); console.log(functions); // function(){} //特殊的number var number = new Test(new Number(1)); console.log(number); // Number {1} console.log(typeof number); // object。复制代码
可见,方法的返回值若为引用类型,new操作符就“失效”了。
那他真的失效了吗?让我们看看方法内部执行的过程:function Test(value){ this.value = value; console.log(this); return [1,2,3]; } var result = new Test(1); // Test {value: 1} console.log(result);// [1, 2, 3]复制代码
由此可见,this在Test的实例化过程中,确实被创建了,只不过由于Test本身的返回值为引用类型,所以实例化后的结果被其替换
了。
最后的整理
根据以上的推论,再次完善了likeNew:
function likeNew(fn){ var result = Object.create(fn.prototype); var args = [].slice.call(arguments,1); var fnResult = fn.apply(result, args); if(typeof fnResult === 'object' || typeof fnResult === 'function' && fnResult !== null){ return fnResult } return result } var person = likeNew(Person, '小明', 20); console.log(person); // Person {name: "小明", age: 20} person.sayHello(); // 我叫小明, 今年20岁了 //原始类型 number var number = likeNew(Test, 1); console.log(number); // Test {value: 1} var Null = likeNew(Test, null); console.log(Null); // Test {value: null} //引用类型 var object = likeNew(Test, {}); console.log(object); // {} var numberObject = likeNew(Test, new Number(1)); console.log(numberObject); // Number{1}复制代码
总结:
1、new操作符在进行实例化时,首先会创建一个包含指定__proto__的对象,再带入方法中执行,并选择性输出此对象。 2、被操作的方法的返回值若为引用类型,则会替换原本实例化的结果。以上如有不当请指出。