JavaScript 中的属性描述符(Descriptors)继承:为什么原型链上的 setter 会影响子类赋值?

技术讲座:JavaScript 属性描述符继承与原型链 setter 影响

引言

JavaScript 作为一种高级的、解释型的编程语言,以其灵活性和简洁性受到了广泛的应用。在 JavaScript 中,对象和原型链是核心概念之一。属性描述符(Descriptors)是控制对象属性行为的关键,而原型链则是实现继承的重要机制。本文将深入探讨 JavaScript 中的属性描述符继承以及原型链上的 setter 如何影响子类的赋值。

属性描述符概述

在 JavaScript 中,每个属性都可以被定义为一个描述符对象,它包含了一系列属性来描述该属性的行为。属性描述符主要分为两种类型:数据描述符(Data Descriptors)和访问器描述符(Accessor Descriptors)。

数据描述符

数据描述符描述了一个数据属性的行为,它具有以下属性:

  • value: 属性的值。
  • writable: 是否可以修改属性的值。
  • enumerable: 是否可以被枚举。
  • configurable: 是否可以被删除或重新定义。

访问器描述符

访问器描述符描述了一个访问器属性的行为,它具有以下属性:

  • get: 获取属性值的函数。
  • set: 设置属性值的函数。
  • configurable: 是否可以被删除或重新定义。

原型链与继承

JavaScript 中的对象继承是通过原型链实现的。每个对象都有一个原型(prototype)属性,该属性指向其构造函数的原型对象。如果查找一个对象没有找到属性,那么会继续在原型链上查找,直到找到为止。

原型链上的 setter 影响

当原型链上的一个属性定义了 setter 函数时,任何试图修改该属性的赋值操作都会执行这个 setter 函数。这种机制对于子类赋值的影响如下:

示例 1:数据描述符

function Parent() {
  this.parentValue = 10;
}

Parent.prototype = {
  set value(newValue) {
    this.parentValue = newValue * 2;
  },
  get value() {
    return this.parentValue;
  }
};

function Child() {}

Child.prototype = new Parent();

let child = new Child();
console.log(child.value); // 20
child.value = 5;
console.log(child.value); // 10

在这个例子中,即使 Child 类没有定义 value 属性,它仍然可以访问和修改 value,因为 value 属性被定义在 Parent 的原型上,并且有一个 setter。

示例 2:访问器描述符

function Parent() {
  this.parentValue = 10;
}

Parent.prototype = {
  set value(newValue) {
    this.parentValue = newValue * 2;
  },
  get value() {
    return this.parentValue;
  }
};

function Child() {}

Child.prototype = new Parent();

let child = new Child();
console.log(child.value); // 20
child.value = 5;
console.log(child.value); // 10

在这个例子中,value 是一个访问器属性,因此即使 Child 类没有定义 value 属性,它仍然可以访问和修改 value

总结

原型链上的 setter 函数可以影响子类的赋值,因为它们定义了属性的行为。当子类试图访问或修改一个在原型链上定义了 setter 的属性时,setter 函数会被调用,从而影响了子类的赋值行为。

附录:工程级代码示例

以下是一些使用不同语言的工程级代码示例,展示了如何处理属性描述符和原型链。

PHP 示例

class ParentClass {
  private $parentValue = 10;

  public function setValue($newValue) {
    $this->parentValue = $newValue * 2;
  }

  public function getValue() {
    return $this->parentValue;
  }
}

class ChildClass extends ParentClass {
}

$child = new ChildClass();
echo $child->getValue(); // 20
$child->setValue(5);
echo $child->getValue(); // 10

Python 示例

class ParentClass:
    def __init__(self):
        self.parent_value = 10

    @property
    def value(self):
        return self.parent_value

    @value.setter
    def value(self, new_value):
        self.parent_value = new_value * 2

class ChildClass(ParentClass):
    pass

child = ChildClass()
print(child.value) # 20
child.value = 5
print(child.value) # 10

Shell 示例

#!/bin/bash

declare -A parent_vars=(
  [parent_value]="10"
)

function set_value() {
  parent_vars[parent_value]=$1*2
}

function get_value() {
  echo "${parent_vars[parent_value]}"
}

set_value 5
echo $(get_value) # 10

SQL 示例

CREATE TABLE ParentTable (
  parent_value INT
);

INSERT INTO ParentTable (parent_value) VALUES (10);

CREATE TABLE ChildTable AS SELECT * FROM ParentTable;

UPDATE ChildTable SET parent_value = parent_value * 2 WHERE parent_value = 10;

SELECT parent_value FROM ChildTable; -- 输出 20

这些示例展示了在 PHP、Python、Shell 和 SQL 中如何处理属性描述符和原型链的概念。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注