new创建对象的过程发生了什么

用面向对象语言们通用的观点来看:new 是用来实例化一个类,从而在内存中分配一个实例对象。

我们通过一个例子来讨论new的过程中发生了什么

function Person(name) {
    this.name = name;
}
Person.hairColor = "black";
Person.prototype.say = function() {
    console.log("My name is " + this.name);
};
var john = new Person("John");

console.log(
	john.name, // "John",
  	john.hairColor, // undefined
  	john.height // undefined
);
john.say(); // "My name is John"

console.log(
	Person.name, // "Person"
  	Person.hairColor // "black"
);
Person.say(); // Person.say is not a function

重点解析

第8行代码是关键:

var john = new Person("John");

Person本身是一个普通函数,但当通过new来创建对象时,Person就是构造函数了。

JS引擎执行这句代码时,在内部做了很多工作,用伪代码模拟其工作流程如下:

new Person("John") = {
  var obj = {};
	obj.__proto__ = Person.prototype;
	// 此时便建立了obj对象的原型链:
	// obj->Person.prototype->Object.prototype->null

	var result = Person.call(obj,"John"); // 相当于obj.Person("John")

	// 如果无返回值或者返回一个非对象值,则将obj返回作为新对象:
	return typeof result === 'object' ? result || obj : obj;
}

可以看到,这里的obj经返回后会被赋给var john变量:

obj.name是在Person.call(obj,"John")这句话执行之后就有了“John”这个值;

等到obj经返回被赋给john之后,john.name就是“John”了。

画出最终var john = new Person("John");这一句代码涉及到的对象和它们间的关系:

js-new-process-1

如果看不懂图,可以先看我以往文章的介绍: 理解prototype、proto和constructor等关系

控制台结果解析

分析完了 john 的产生过程,我们再分析一下控制台输出结果:

这个例子的代码全部执行完,最终涉及到的对象和它们间的关系如图:

js-new-process-2

  • john.name: 临时变量obj有name,obj经返回被赋给john,john的一些属性由此而来。
  • john.hairColor:john实例对象 先查找自身的 hairColor,没有找到便会沿着原型链查找,在上述例子中,我们仅在 Person 对象上定义了 hairColor,并没有在其原型链上定义,因此找不到。
  • john.height:john实例对象 先查找自身的 height,没有找到便会沿着原型链查找,原型链上也没有,因此找不到。
  • john.say:john会先查找自身的 say 方法,没有找到便会沿着原型链查找,在上述例子中,我们在 Person.prototype上定义了say,因此在原型链上找到了say 方法。

另外,在 say 方法中还访问this.name,这里的 this 指的是其调用者。如果john调用say,john就是调用者,因此输出john.name的值。


对于Person来说,它本身也是一个对象,因此它在访问属性和方法时也遵守上述查找规则,所以:

  • Person.name —> Person // 这是特殊情况
  • Person.hairColor —> “black”
  • Person.say() —> Person.say is not a function

需要注意的是,Person 先查找自身的 name,找到了 name,但这个 name 并不是我们定义的 name,而是函数对象内置的属性,一般情况下,函数对象在产生时会内置 name 属性并将函数名作为赋值(仅函数对象)。

另外,Person 在自身没有找到 say() 方法,也会沿着其原型链查找:

Person 的原型链: Person->Function.prototype->Object.prototype->null ,这原型链在 往期文章 里面我有介绍到。

由于 Person 的原型链上也没有定义 say 方法,因此返回异常提示。

总结new的过程中发生了什么

[1] 导致john继承了Person.prototype:

令john的__proto__属性指向Person.prototype,确立了这条原型链, 导致 john 能通过原型链继承Person.prototype 中的部分属性,可以简单地视john和Person.prototype是继承关系。

[2] john是 Person构造函数 的实例:

// instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上:
john instanceof Person; //true

这个过程中Person构造函数被调用并发挥了作用。其结果是,经过new创建实例对象后,实例对象可以拥有自己的属性和方法。

文章目录
  1. 重点解析
  2. 控制台结果解析
  3. 总结new的过程中发生了什么