详解JS原型链与继承

本文详细阐述了JavaScript中的原型和原型链概念,介绍了如何基于原型链实现继承,包括属性和方法的继承,以及构造函数与[[prototype]]的区别。同时讨论了构造函数.prototype修改对已有实例的影响,提醒开发者注意可能引发的问题。
摘要由CSDN通过智能技术生成

JavaScript 是动态的且没有静态类型,在谈到继承的时候,JavaScript 只有一种结构:对象。每个对象(object)都有一个私有属性指向另一个名为原型(prototype)的对象。原型对象也有一个自己的原型,层层向上直到一个对象的原型为 null。根据定义,null 没有原型,并作为这个原型链(prototype chain)中的最后一个环节。可以改变原型链中的任何成员,甚至可以在运行时换出原型,因此 JavaScript 中不存在静态分派的概念。尽管这种混杂通常被认为是 JavaScript 的弱点之一,但是原型继承模型本身实际上比类式模型更强大。

原型和原型链区分

原型(Prototype)和原型链(Prototype Chain)是两个相关但不同的概念。
它们是 JavaScript 实现继承和对象之间关系的核心机制之一。

原型(Prototype)

  • 实例对象与原型:每个函数都有一个 prototype 属性,它指向一个对象,这个对象就是该构造函数的原型对象。通过原型对象,我们可以将属性和方法共享给由该构造函数创建的实例对象。
  • 实例对象与原型:当通过构造函数创建对象实例时,该实例对象会继承构造函数的原型对象上的属性和方法。实例对象可以通过原型链访问原型对象的属性和方法。
function Person(name) {
  this.name = name;
}
// 构造函数的原型对象
console.log(Person.prototype);

var person = new Person('John');
// 实例对象继承原型对象的属性和方法
console.log(person.name);

原型链(Prototype Chain)

  • 对象之间的链接: 在 JavaScript 中,对象之间通过原型链进行链接。每个对象都有一个指向其原型的内部链接,这个链接组成了原型链。
  • 原型链的构建: 当试图访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到对应的属性或方法,或者到达原型链的顶端(null)。
    在这里插入图片描述

浏览器中[[prototype]] 与prototype字段的区别

[[prototype]](对象的内部属性)

  • 存在于所有对象实例: 每个对象实例都有一个 [[prototype]] 属性,它指向该对象的原型。

  • 可通过 Object.getPrototypeOf(obj) 获取 obj 对象的 [[prototype]]

  • [[prototype]] 在有的浏览器也显示为__proto__,即也叫__proto__

prototype(构造函数的原型属性)

  • 只存在函数对象上:prototype 是函数对象的一个属性,它并不是对象实例上的属性。

  • 用于创建对象实例的原型: 当通过构造函数创建对象实例时,该实例的 [[prototype]] 将指向构造函数的 prototype
    在这里插入图片描述

基于原型链的继承

继承属性

当访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。总之就是先搜寻当前对象自身属性,再搜寻当前对象的原型,再到原型的原型,直到原型的顶端null

const o = {
    a: 1,
    b: 2,
    // __proto__ 设置了 [[Prototype]]
    __proto__: { 
        b: 3,
        c: 4,
    },
};
// 完整的原型链:{ a: 1, b: 2 } ---> { b: 3, c: 4 } ---> Object.prototype ---> null
console.log(o.b); // 2
console.log(o.c); // 4
console.log(o.d); // undefined

继承"方法"

当继承的函数被调用时,this 值指向的是当前继承的对象,而不是拥有该函数属性的原型对象。

const parent = {
  value: 2,
  method() {
    ++this.value
    return this.value;
  },
};
console.log(parent.method()); // 3  
// 当调用 parent.method 时,“this”指向了 parent
const child = {
  __proto__: parent,
};
console.log(child.value); // 3
// 首先在 child 上寻找“value”属性。
// 但由于 child 本身没有名为“value”的自有属性,该属性会在
// [[Prototype]] 上被找到,即 parent.value
console.log(child.method()); // 4
// “this”指向了 child,又因为 child 继承的是 parent 的方法
child.value = 5;  // 这会遮蔽 parent 上的“value”属性。
// child 对象现在看起来是这样的 
// { value: 4, __proto__: { value: 2, method: [Function] } }
console.log(child.method()); // 6

基于原型链的构造函数

假设我们要创建多个盒子,其中每一个盒子都是一个对象,包含一个可以通过 getValue 函数访问的值。一个简单的实现可能是:

const boxes = [
  { value: 1, getValue() { return this.value; } },
  { value: 2, getValue() { return this.value; } },
  { value: 3, getValue() { return this.value; } },
];

但每一个实例都有自己的,做相同事情的函数属性,这是冗余且不必要的。我们可以将 getValue 移动到所有盒子的 [[Prototype]] 上,使用构造函数,自动为每个构造的对象设置[[Prototype]]

// 一个构造函数
function Box(value) {
  this.value = value;
}
// 使用 Box() 构造函数创建的所有盒子都将具有的属性
Box.prototype.getValue = function () {
  return this.value;
};

const boxes = [new Box(1), new Box(2), new Box(3)];

这样所有盒子的 getValue 方法都会引用相同的函数,降低了内存使用率。

Constructor.prototypeConstructor.prototype = ...)存在一些问题:

  • 当你重新赋值 Constructor.prototype 时,之前通过该构造函数创建的实例的 [[Prototype]] 不再指向原来的原型对象。这意味着在重新赋值之前和之后创建的实例现在具有不同的原型链。这可能导致一些意外行为,因为它们可能具有不同的方法和属性。
  • 每个对象都有一个特殊的内部属性 [[Prototype]],它引用对象的原型。通过 instance.constructor,你可以访问对象的构造函数。但是,如果你重新赋值了构造函数的 Constructor.prototype,这个链接可能会被破坏。
  • 一些 JavaScript 内置操作(例如某些序列化操作或某些对象方法)依赖于 constructor 属性的正确设置。如果 constructor 属性没有被正确设置,这些操作可能无法按预期工作,破坏了语言的一些默认行为。

下面是这种负面情况的例子:

function Person(name) {
  this.name = name;
}
var person1 = new Person("Alice");
Person.prototype = { age: 25 };
var person2 = new Person("Bob");
console.log(person1);  // Person { name: 'Alice' }
console.log(person2);  // { age: 25 }
console.log(person1.constructor);  
console.log(person2.constructor);

在这里插入图片描述

参考文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值