1. 引言
在 JavaScript 中,当创建一个新对象时,使用 new
关键字,但是 new
背后到底发生了什么呢?在本文中,我们将会从原型链、构造函数、对象属性等多个角度来分析 new
关键字的底层实现。
2. 使用 new 关键字创建对象
在 JavaScript 中,我们常常使用 new
关键字来创建一个新的对象,通常关键字后面跟随一个构造函数:
function Person(name) {
this.name = name;
}
const person1 = new Person('张三');
在上面的代码中,我们使用了 new
关键字来创建一个名为 person1
的 Person 对象。实际上,new Person('张三')
的实现过程可以分为以下几个步骤:
创建一个新的空对象;
将新对象的隐式原型指向构造函数的显式原型(prototype);
使用 this
关键字执行构造函数,并将属性和方法绑定到新对象上;
返回新对象。
上述步骤中的第一步和最后一步都比较容易理解,下面我们来详细介绍第二步和第三步。
3. 构造函数和方法绑定
第二步提到了 prototype
属性。那么 prototype
属性到底是什么?在 JavaScript 中,每个函数都有一个 prototype
属性。它是函数的一个属性,指向一个对象。这个对象被称为原型对象(以下简称为原型)。原型是一个普通对象,它有一些常规属性,最重要的是构造函数的 constructor
属性,该属性指向构造函数本身:
function Person(name) {
this.name = name;
}
console.log(Person.prototype.constructor === Person); // true
创建一个对象时,JavaScript 引擎会将对象的隐式原型指向构造函数的 prototype
属性。因此,使用 new
关键字创建一个对象的时候,对象的隐式原型就指向了构造函数的原型:
function Person(name) {
this.name = name;
}
const person1 = new Person('张三');
console.log(person1.__proto__ === Person.prototype); // true
在第三步中,我们使用 this
关键字将属性和方法绑定到了新对象上。例如,如下代码可以访问到新对象的 name
属性:
function Person(name) {
this.name = name;
}
const person1 = new Person('张三');
console.log(person1.name); // '张三'
使用 this
关键字的另一个作用是在构造函数中使用方法。例如:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, my name is ' + this.name);
}
const person1 = new Person('张三');
person1.sayHello(); // 'Hello, my name is 张三'
在上面的代码中,我们定义了一个 sayHello
方法,并将其添加到了构造函数的原型上。我们使用 this
关键字在构造函数中创建了一个属性 name
。而在 sayHello
方法中,我们同样使用了 this
关键字,来访问构造函数创建的属性。
4. 原型链
在前面的例子中,构造函数的原型提供了可供对象使用的属性和方法,对象通过隐式原型链接到构造函数的原型。这个链接在 JavaScript 中被称为原型链。在这个原型链上,我们可以在指定的连续对象上查找属性。
例如:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, my name is ' + this.name);
}
const person1 = new Person('张三');
console.log(person1.hasOwnProperty('name')); // true
console.log(person1.hasOwnProperty('sayHello')); // false
console.log('sayHello' in Person.prototype); // true
person1.sayHello(); // 'Hello, my name is 张三'
在上面的代码中,我们可以发现:
hasOwnProperty
方法用于判断一个对象是否含有指定的属性,并且该属性是否为对象自身的属性。在 person1 中,name
属性为对象自身的属性,因此返回 true;sayHello
方法是构造函数原型上的一个属性,不是对象自身的属性,因此返回 false。
in
运算符用于检查指定属性是否在指定对象或其原型链中。在上例中,'sayHello' in Person.prototype
返回 true,说明该属性位于对象的原型链上。
在最后一行代码中,通过 person1 访问到了 Person.prototype.sayHello
方法。
5. constructor 属性
很多人可能会疑惑,为什么前面的例子中 Person.prototype.constructor
属性指向了构造函数本身。实际上,这是构造函数默认的原型上的属性。如果我们重新定义了构造函数的原型对象,那么就会改变这个属性:
function Person(name) {
this.name = name;
}
Person.prototype = {
sayHello: function() {
console.log('Hello, my name is ' + this.name);
}
};
const person1 = new Person('张三');
console.log(person1.constructor === Object); // true
在上面的代码中,我们重新定义了 Person.prototype
对象。因此,constructor
属性指向了 Object
构造函数,而不是 Person
构造函数。在重写原型时,一定要记得将原型的 constructor
属性设置正确。
6. 小结
本文分析了 new
关键字的底层实现。使用 new
创建对象的过程可以分为创建新对象、指向原型、绑定属性和方法、返回新对象等多个步骤。在构造函数中,使用 this
可以将属性和方法绑定到新对象上,使用 prototype
可以为对象提供原型链接以及共享相关属性和方法。在重写原型时,一定要记得将原型的 constructor
属性设置正确。