在日常使用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、原型移植
这就很简单了,我们有很多办法:
//方案1
result.__proto__ = Person.prototype; //有一定副作用(可枚举)
//方案2
Object.setPrototypeOf(result, Person.prototype);
//方案3
result = 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、被操作的方法的返回值若为引用类型,则会替换原本实例化的结果。
以上如有不当请指出。
本文详细解析了JavaScript中new操作符的工作原理,包括创建对象、this绑定、原型链继承及返回值处理,揭示了new操作符如何实例化对象并正确处理方法的返回值。
394

被折叠的 条评论
为什么被折叠?



