理解 JavaScript 原型和继承:从原型链到类的演变

在 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 操作符背后做了几件事情:

  1. 创建一个新的空对象。
  2. 该对象的 __proto__ 指向构造函数的 prototype。
  3. this 被绑定到该新对象上,构造函数内部的代码执行。
  4. 如果构造函数没有显式返回对象,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) 简化了继承的语法,使得对象的创建和继承更加直观和易用。
相关推荐
怕浪猫6 小时前
LLM 面试必问的 8 个问题,答不上来直接淘汰
人工智能·python·面试
谢亮_vipxieliang6 小时前
泛型擦除、通配符与 PECS 一次讲透
java·面试
星栈7 小时前
pnpm 12 升级实测
前端·javascript
Ai-_Man8 小时前
您您这可以把Microsofat Copilot的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭
javascript·人工智能·ai·小程序·电脑·copilot
Bruce_Liuxiaowei11 小时前
网页版 DeepSeek “harness desktop“ WebGL 上下文创建失败排查实录
开发语言·javascript·webgl
小溪学编程11 小时前
从 C++ 的规范变迁看语言发展
jvm·c++·面试
小凯在掘金12 小时前
为什么箭头函数不能作为构造函数?
前端·javascript
可乐鸡翅yeah_12 小时前
网页端 M3U8 播放器性能监控,怎么看播放卡顿指标
javascript·ffmpeg·音视频·safari·m3u8
可乐鸡翅yeah_12 小时前
M3U8 防盗链 URL‑Token 签名,新手开发实操避坑
开发语言·javascript·ios·音视频·safari
ShineWinsu12 小时前
对于Redis:string类型的解析
java·c++·redis·分布式·缓存·面试·string