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

  • new操作符做了什么?
  • 模拟实现JS的new操作符

new操作符做了什么?

new操作符新建了一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象

  1. 创建一个空的对象
  2. 将空对象的原型prototype指向构造函数的原型
  3. 将空对象作为构造函数的上下文(改变this指向)
  4. 确保返回的是对象

构造函数返回值的判断(对第4步的解析)

一般情况下构造函数没有返回值,但是作为函数,是可以有返回值的。
那么在构造函数有返回值的情况下,new操作符做了什么?

先看两个例子:
注意一下两个返回值的差异

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name){
this.name = name;
return 1; // return undefined/NaN/'string'/null
}
let me = new Person('快乐每一天');
console.log(me); // { name:'快乐每一天' }

function Person(name){
this.name = name;
return { age:12 };
}
let me = new Person('快乐每一天');
console.log(me); // { age:12 }

结论:

在new的时候,会对构造函数的返回值做一些判断:

  1. 如果返回值是基础数据类型,则忽略返回值;
  2. 如果返回值是引用数据类型,则使用return 的返回,也就是new操作符无效;

模拟实现JS的new操作符

proto和prototype区别

方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。即:对象具有属性proto,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。
方法这个特殊的对象,除了和其他对象一样有上述_proto_属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。

proto

分析图片

1. 构造函数Foo()构造函数的原型属性Foo.prototype指向了原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是f1,f2)都可以共享这个方法。
2. 原型对象Foo.prototypeFoo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。
3. 实例f1和f2是Foo这个对象的两个实例,这两个对象也有属性proto,指向构造函数的原型对象,这样子就可以像上面1所说的访问原型对象的所有方法啦。

另外:构造函数Foo()构造函数的原型属性Foo.prototype指向了原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是f1,f2)都可以共享这个方法。
原型对象Foo.prototypeFoo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。
实例f1和f2是Foo这个对象的两个实例,这两个对象也有属性proto,指向构造函数的原型对象,这样子就可以像上面1所说的访问原型对象的所有方法啦。
另外:构造函数Foo()除了是方法,也是对象啊,它也有proto属性,指向谁呢?
指向它的构造函数的原型对象。函数的构造函数不就是Function嘛,因此这里的proto指向了Function.prototype。
其实除了Foo(),Function(), Object()也是一样的道理。
原型对象也是对象啊,它的proto属性,又指向谁呢?
同理,指向它的构造函数的原型对象。这里是Object.prototype.最后,Object.prototype的proto属性指向null。

总结

对象有属性proto,指向该对象的构造函数的原型对象。
方法除了有属性proto,还有属性prototype,prototype指向该方法的原型对象。

实现new操作符

1
2
3
4
5
6
function create(Con, ...args) {
let obj = {}
obj.__proto__ = Con.prototype
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}

1. 先创建一个空的新对象
2. 因为 obj 对象需要访问到构造函数原型链上的属性,所以空对象的proto需要指向后遭函数的prototype
3. 将obj绑定到构造函数中,并传入其余参数(改变this指向)
4. 判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj,这样就实现了忽略构造函数返回的原始值

接下来我们来使用下该函数,看看是否和 new 操作符一致

1
2
3
4
5
6
7
8
9
10
11
function Test(name, age) {
this.name = name
this.age = age
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const a = create(Test, 'yck', 26)
console.log(a.name) // 'yck'
console.log(a.age) // 26
a.sayName() // 'yck'