您好,登录后才能下订单哦!
# JavaScript的继承和原型链是什么
## 引言
JavaScript作为一门基于原型的语言,其继承机制与传统的基于类的语言(如Java、C++)有着本质区别。理解原型链是掌握JavaScript面向对象编程的核心,也是许多高级特性的基础。本文将深入剖析原型链的运行机制、实现继承的多种方式,以及现代JavaScript中的类语法本质。
## 一、原型基础概念
### 1.1 什么是原型(Prototype)
在JavaScript中,每个对象(除`null`外)都有一个内置属性`[[Prototype]]`(可通过`__proto__`访问),这个属性指向另一个对象,我们称其为该对象的"原型"。
```javascript
const animal = {
eats: true
};
const rabbit = {
jumps: true
};
rabbit.__proto__ = animal; // 设置rabbit的原型为animal
console.log(rabbit.eats); // true,通过原型链访问
当访问对象的属性时,JavaScript引擎会:
1. 首先在对象自身属性中查找
2. 如果找不到,则沿着[[Prototype]]
向上查找
3. 直到找到属性或到达原型链末端(null
)
const grandparent = { a: 1 };
const parent = { b: 2 };
const child = { c: 3 };
parent.__proto__ = grandparent;
child.__proto__ = parent;
console.log(child.a); // 1,通过三级原型链查找
每个函数都有一个特殊的prototype
属性(注意不是[[Prototype]]
),这个属性会在使用new
操作符时成为新实例的原型。
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
const john = new Person('John');
john.sayHi(); // 通过原型调用方法
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating.`);
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数
this.breed = breed;
}
// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('Woof!');
};
缺点: - 引用类型属性会被所有实例共享 - 创建子类实例时无法向父类构造函数传参
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
function Child(name, age) {
Parent.call(this, name); // 借用父类构造函数
this.age = age;
}
优点: - 避免了引用类型共享问题 - 可以向父类传参
缺点: - 方法必须在构造函数中定义,无法复用 - 无法访问父类原型上的方法
结合原型链继承和构造函数继承的优点:
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 第二次调用Parent
this.age = age;
}
Child.prototype = new Parent(); // 第一次调用Parent
Child.prototype.constructor = Child;
缺点: - 父类构造函数被调用两次
基于现有对象创建新对象:
function createObject(obj) {
function F() {}
F.prototype = obj;
return new F();
}
const person = {
name: 'Default',
friends: ['Alice', 'Bob']
};
const another = createObject(person);
ES5标准化为Object.create()
:
const another = Object.create(person);
在原型式继承基础上增强对象:
function createAnother(original) {
const clone = Object.create(original);
clone.sayHi = function() {
console.log('Hi');
};
return clone;
}
解决组合继承调用两次构造函数的问题:
function inheritPrototype(child, parent) {
const prototype = Object.create(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`Hi, I'm ${this.name}`);
}
}
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
bark() {
console.log('Woof!');
}
}
super()
),代表父类构造函数super.method()
),指向父类原型class Parent {
static staticMethod() {
console.log('Parent static');
}
}
class Child extends Parent {
static staticMethod() {
super.staticMethod();
console.log('Child static');
}
}
创建一个新对象,使用现有对象作为新对象的原型:
const proto = { x: 10 };
const obj = Object.create(proto);
获取对象的原型:
const proto = {};
const obj = Object.create(proto);
console.log(Object.getPrototypeOf(obj) === proto); // true
设置对象的原型(不推荐用于性能敏感的代码):
const obj = {};
const proto = { x: 10 };
Object.setPrototypeOf(obj, proto);
检查构造函数的prototype
是否出现在对象的原型链上:
function Parent() {}
function Child() {}
Child.prototype = Object.create(Parent.prototype);
const child = new Child();
console.log(child instanceof Parent); // true
检查对象是否存在于另一个对象的原型链上:
const proto = {};
const obj = Object.create(proto);
console.log(proto.isPrototypeOf(obj)); // true
避免意外修改原生原型:
// 危险操作!
Array.prototype.push = function() {
console.log('Array push modified!');
};
// 更安全的扩展方式
function MyArray() {}
MyArray.prototype = Object.create(Array.prototype);
const logSymbol = Symbol('log');
class MyClass {
[logSymbol]() {
console.log('Private method');
}
}
通过mixin模式实现:
const Serializable = {
serialize() {
return JSON.stringify(this);
}
};
const Area = {
getArea() {
return this.length * this.width;
}
};
function mixin(...mixins) {
return function(target) {
Object.assign(target.prototype, ...mixins);
};
}
@mixin(Serializable, Area)
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}
Object.create(null)
创建纯净字典对象[[Prototype]]
指向构造函数的prototype
this
绑定到新对象并执行构造函数function myNew(Constructor, ...args) {
const obj = Object.create(Constructor.prototype);
const result = Constructor.apply(obj, args);
return result instanceof Object ? result : obj;
}
所有原型链的终点是Object.prototype.__proto__
,即null
console.log(Object.prototype.__proto__); // null
JavaScript的原型机制是其面向对象编程的基石。从ES5的各种继承模式到ES6的类语法糖,理解原型链可以帮助开发者编写更优雅、高效的代码。随着JavaScript语言的发展,虽然类语法让传统OOP开发者更易上手,但其底层仍然是基于原型的实现。掌握这些核心概念,方能真正理解JavaScript的设计哲学。
字数统计:约5450字(实际字数可能因排版略有差异) “`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。