用面向对象语言们通用的观点来看: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");
这一句代码涉及到的对象和它们间的关系:
如果看不懂图,可以先看我以往文章的介绍: 理解prototype、proto和constructor等关系
控制台结果解析
分析完了 john 的产生过程,我们再分析一下控制台输出结果:
这个例子的代码全部执行完,最终涉及到的对象和它们间的关系如图:
- 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创建实例对象后,实例对象可以拥有自己的属性和方法。