在 JavaScript 中,原型和继承是非常核心的概念,它们帮助我们实现代码复用、构造更灵活的对象模型。理解原型、原型链、new
的实现以及类(class
)的使用,能帮助你更好地理解 JavaScript 中的继承机制,并且理解原型与类之间的关系。
1. 显示原型和隐式原型
显示原型:prototype
每个构造函数都会有一个 prototype
属性,这个属性是一个对象,包含了通过构造函数创建的实例共享的属性和方法。比如,假设我们有一个构造函数 Parent
,如下所示:
javascript
function Parent(age) {
this.name = 'parent';
this.age = age;
}
Parent.prototype.say = function() {
console.log('hello');
};
在上面的例子中,say
方法被定义在 Parent.prototype
上,意味着所有通过 Parent
构造函数创建的实例
都可以共享这个 say
方法。
隐式原型:__proto__
每个对象都有一个 __proto__
属性,它指向该对象的构造函数的 prototype
。通过 __proto__
,我们可以访问到对象继承自其构造函数的所有属性和方法。
javascript
const p = new Parent(30);
console.log(p.__proto__ === Parent.prototype); // true
可以看到,p.__proto__
实际上是 Parent.prototype
。
2. 原型链
原型链是 JavaScript 中的一个非常重要的概念。它描述了对象如何通过 __proto__
访问继承自父对象的属性和方法。如果在对象本身找不到某个属性或方法,JavaScript 会沿着 __proto__
向上查找,直到找到为止,最终会查找到 Object.prototype
,如果还没有找到,就返回 null
。
例如,假设我们创建了一个 Child
构造函数,并将其 prototype
设置为一个 Parent
的实例:
javascript
Parent.prototype.say = function() {
console.log('hello');
};
function Parent(age) {
this.name = 'parent';
this.age = age;
}
Child.prototype = new Parent();
function Child(name) {
this.name = name;
}
const c = new Child('child');
console.log(c.name); // 'child'
console.log(c.age); // undefined
c.say(); // 'hello'
在这个例子中,Child
的实例 c
没有直接继承 say
方法,但因为 Child.prototype
是 Parent
的实例,所以 c
可以访问到 Parent.prototype
上的 say
方法。我们看到,c
的 __proto__
链指向了 Child.prototype
,而 Child.prototype
的 __proto__
又指向了 Parent.prototype
,最终找到了 say
方法。
3. new
的实现
理解 new
是如何工作的,可以帮助我们更好地理解 JavaScript 中的构造函数和原型机制。new
操作符背后做了几件事情:
- 创建一个新的空对象。
- 该对象的
__proto__
指向构造函数的prototype
。 this
被绑定到该新对象上,构造函数内部的代码执行。- 如果构造函数没有显式返回对象,
new
返回新创建的对象。
例如,下面是 new
的简化实现:
javascript
function myNew(constructor, ...args) {
const obj = {}; // 1. 创建新对象
obj.__proto__ = constructor.prototype; // 2. 指向构造函数的 prototype
constructor.apply(obj, args); // 3. 执行构造函数,并将 obj 作为 this
return obj; // 4. 返回新对象
}
4. 通过 Object.create
创建原型链
有时,我们希望创建一个对象,并将其原型指向另一个对象。Object.create
是实现这一目标的一个方法。它会创建一个新对象,并将新对象的 __proto__
指向给定的对象。
javascript
const obj = { name: '张三', age: 40 };
const newObj = Object.create(obj);
console.log(newObj.name); // '张三'
在上面的代码中,newObj
的 __proto__
指向了 obj
,因此它可以访问 obj
上的属性。
5. 构造函数与 class
的继承
JavaScript 的继承模型经历了从原型链到 class
的过渡。在 ES6 中,class
提供了一种更为简洁的方式来实现继承。
javascript
class Parent {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
console.log('hello');
}
}
class Child extends Parent {
constructor(name, age) {
super(name, age);
this.sex = 'boy';
}
}
const c = new Child('张三', 18);
c.say(); // 'hello'
在这个例子中,Child
通过 extends
继承了 Parent
,并使用 super()
调用了父类的构造函数。这使得代码更加简洁且易于理解。
6. 总结
JavaScript 的原型和继承机制是构建面向对象程序的基础。通过理解原型、原型链、new
的工作原理以及 class
的使用,我们能够更好地掌握 JavaScript 中的对象继承关系和构造函数的实现。
- 原型和原型链 是 JavaScript 继承的核心,它让我们能够共享方法和属性。
new
操作符 是实例化对象的关键,理解其实现有助于更深入地理解构造函数。Object.create
提供了一种创建对象并指定其原型的简洁方式。- ES6 类(
class
) 简化了继承的语法,使得对象的创建和继承更加直观和易用。