对象属性配置 属性的 getter 和 setter(Object property configures the getter and setter of the property)

属性的 getter 和 setter

第一种是 数据属性。我们已经知道如何使用它们了。到目前为止,我们使用过的所有属性都是数据属性。

第二种类型的属性是新东西。它是 访问器属性(accessor properties)。它们本质上是用于获取和设置值的函数,但从外部代码来看就像常规属性。

Getter 和 setter

访问器属性由 “getter” 和 “setter” 方法表示。在对象字面量中,它们用  和  表示:

get
set
let obj = {
  get propName() {
    // 当读取 obj.propName 时,getter 起作用
  },

  set propName(value) {
    // 当执行 obj.propName = value 操作时,setter 起作用
  }
};

当读取  时,getter 起作用,当  被赋值时,setter 起作用。

obj.propName
obj.propName

例如,我们有一个具有  和  属性的对象 :

name
surname
user
let user = {
  name: "John",
  surname: "Smith"
};

现在我们想添加一个  属性,该属性值应该为 。当然,我们不想复制粘贴已有的信息,因此我们可以使用访问器来实现:

fullName
"John Smith"
let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  }
};

alert(user.fullName); // John Smith

从外表看,访问器属性看起来就像一个普通属性。这就是访问器属性的设计思想。我们不以函数的方式 调用 ,我们正常 读取 它:getter 在幕后运行。

user.fullName

截至目前, 只有一个 getter。如果我们尝试赋值操作 ,将会出现错误:

fullName
user.fullName=
let user = {
  get fullName() {
    return `...`;
  }
};

user.fullName = "Test"; // Error(属性只有一个 getter)

让我们通过为  添加一个 setter 来修复它:

user.fullName
let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  },

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }
};

// set fullName 将以给定值执行
user.fullName = "Alice Cooper";

alert(user.name); // Alice
alert(user.surname); // Cooper

现在,我们就有一个“虚拟”属性。它是可读且可写的。

访问器描述符

访问器属性的描述符与数据属性的不同。

对于访问器属性,没有  和 ,但是有  和  函数

value
writable
get
set

所以访问器描述符可能有:

  • get —— 一个没有参数的函数,在读取属性时工作,
  • set —— 带有一个参数的函数,当属性被设置时调用,
  • enumerable —— 与数据属性的相同,
  • configurable —— 与数据属性的相同。

例如,要使用  创建一个  访问器,我们可以使用  和  来传递描述符:

defineProperty
fullName
get
set
let user = {
  name: "John",
  surname: "Smith"
};

Object.defineProperty(user, 'fullName', {
  get() {
    return `${this.name} ${this.surname}`;
  },

  set(value) {
    [this.name, this.surname] = value.split(" ");
  }
});

alert(user.fullName); // John Smith

for(let key in user) alert(key); // name, surname

请注意,一个属性要么是访问器(具有  方法),要么是数据属性(具有 ),但不能两者都是

get/set
value

如果我们试图在同一个描述符中同时提供  和 ,则会出现错误:

get
value
// Error: Invalid property descriptor.
Object.defineProperty({}, 'prop', {
  get() {
    return 1
  },

  value: 2
});

更聪明的 getter/setter

Getter/setter 可以用作“真实”属性值的包装器,以便对它们进行更多的控制。

例如,如果我们想禁止太短的  的 name,我们可以创建一个 setter ,并将值存储在一个单独的属性  中:

user
name
_name
let user = {
  get name() {
    return this._name;
  },

  set name(value) {
    if (value.length < 4) {
      alert("Name is too short, need at least 4 characters");
      return;
    }
    this._name = value;
  }
};

user.name = "Pete";
alert(user.name); // Pete

user.name = ""; // Name 太短了……

所以,name 被存储在  属性中,并通过 getter 和 setter 进行访问。

_name

从技术上讲,外部代码可以使用  直接访问 name。但是,这儿有一个众所周知的约定,即以下划线  开头的属性是内部属性,不应该从对象外部进行访问。

user._name
"_"

兼容性

访问器的一大用途是,它们允许随时通过使用 getter 和 setter 替换“正常的”数据属性,来控制和调整这些属性的行为。

想象一下,我们开始使用数据属性  和  来实现 user 对象:

name
age
function User(name, age) {
  this.name = name;
  this.age = age;
}

let john = new User("John", 25);

alert( john.age ); // 25

……但迟早,情况可能会发生变化。我们可能会决定存储 ,而不是 ,因为它更精确,更方便:

birthday
age
function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;
}

let john = new User("John", new Date(1992, 6, 1));

现在应该如何处理仍使用  属性的旧代码呢?

age

我们可以尝试找到所有这些地方并修改它们,但这会花费很多时间,而且如果其他很多人都在使用该代码,那么可能很难完成所有修改。而且, 中有  是一件好事,对吧?

user
age

那我们就把它保留下来吧。

为  添加一个 getter 来解决这个问题:

age
function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;

  // 年龄是根据当前日期和生日计算得出的
  Object.defineProperty(this, "age", {
    get() {
      let todayYear = new Date().getFullYear();
      return todayYear - this.birthday.getFullYear();
    }
  });
}

let john = new User("John", new Date(1992, 6, 1));

alert( john.birthday ); // birthday 是可访问的
alert( john.age );      // ……age 也是可访问的

现在旧的代码也可以工作,而且我们还拥有了一个不错的附加属性。

————————

属性的 getter 和 setter

The first is  < Strong > data attribute < / strong >. We already know how to use them. So far, all the attributes we have used are data attributes.

The second type of attribute is something new. It is  < Strong > accessor properties < / strong >. They are essentially < strong > functions used to get and set values < / strong >, but from the perspective of external code, they are like general attributes.

Getter 和 setter

Accessor properties are represented by “getter” and “setter” methods. In object literals, they use    and    express:

get
set
let obj = {
  get propName() {
    // 当读取 obj.propName 时,getter 起作用
  },

  set propName(value) {
    // 当执行 obj.propName = value 操作时,setter 起作用
  }
};

When reading    Getter works when    When assigned, the setter works.

obj.propName
obj.propName

For example, we have one with    and    Property object  :

name
surname
user
let user = {
  name: "John",
  surname: "Smith"
};

Now we want to add one    Property, the property value should be  。 Of course, we don’t want to copy and paste existing information, so we can use accessors to achieve:

fullName
"John Smith"
let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  }
};

alert(user.fullName); // John Smith

From the outside, the accessor property looks like a normal property. This is the design idea of accessor properties. We don’t use functions  < Strong > call < / strong >  , We’re normal  < Strong > read < / strong >   It: getters run behind the scenes.

user.fullName

So far,   There is only one getter. If we try the assignment operation  , An error will appear:

fullName
user.fullName=
let user = {
  get fullName() {
    return `...`;
  }
};

user.fullName = "Test"; // Error(属性只有一个 getter)

Let’s pass for    Add a setter to fix it:

user.fullName
let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  },

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }
};

// set fullName 将以给定值执行
user.fullName = "Alice Cooper";

alert(user.name); // Alice
alert(user.surname); // Cooper

Now we have a “virtual” attribute. It is readable and writable.

Accessor descriptor

The descriptor of accessor property is different from that of data property.

For < strong > accessor attributes, no    and  , But yes    and    Function < / strong >.

value
writable
get
set

Therefore, the accessor descriptor may include:

  • get  —— A function without parameters works when reading properties,
  • set  —— The function with one parameter is called when the property is set,
  • enumerable  —— Same as data attributes,
  • configurable —— 与数据属性的相同。

For example, to use    Create a    Accessor, we can use    and    To pass the descriptor:

defineProperty
fullName
get
set
let user = {
  name: "John",
  surname: "Smith"
};

Object.defineProperty(user, 'fullName', {
  get() {
    return `${this.name} ${this.surname}`;
  },

  set(value) {
    [this.name, this.surname] = value.split(" ");
  }
});

alert(user.fullName); // John Smith

for(let key in user) alert(key); // name, surname

Note that < strong > an attribute is either an accessor < / strong > (with    Method), < strong > or data attribute < / strong > (with  ), But < strong > not both < / strong >.

get/set
value

If we try to provide both in the same descriptor    and  , An error occurs:

get
value
// Error: Invalid property descriptor.
Object.defineProperty({}, 'prop', {
  get() {
    return 1
  },

  value: 2
});

更聪明的 getter/setter

Getters / setters can be used as wrappers for “real” attribute values to give them more control.

For example, if we want to ban too short    Name, we can create a setter  , And store the value in a separate attribute    Medium:

user
name
_name
let user = {
  get name() {
    return this._name;
  },

  set name(value) {
    if (value.length < 4) {
      alert("Name is too short, need at least 4 characters");
      return;
    }
    this._name = value;
  }
};

user.name = "Pete";
alert(user.name); // Pete

user.name = ""; // Name 太短了……

So, name is stored in    Property and accessed through getters and setters.

_name

Technically, external code can be used    Access name directly. However, there is a well-known convention here, that is, underline    The initial attribute is an internal attribute and should not be accessed from outside the object.

user._name
"_"

compatibility

A major use of accessors is that they allow you to control and adjust the behavior of “normal” data properties at any time by replacing them with getters and setters.

Imagine we start using data attributes    and    To implement the user object:

name
age
function User(name, age) {
  this.name = name;
  this.age = age;
}

let john = new User("John", 25);

alert( john.age ); // 25

… but sooner or later, things may change. We may decide to store  , instead of  , Because it is more accurate and convenient:

birthday
age
function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;
}

let john = new User("John", new Date(1992, 6, 1));

What should I do now to still use    What about the old code of the property?

age

We can try to find all these places and modify them, but it will take a lot of time, and if many others are using the code, it may be difficult to complete all the modifications. and,   There are    It’s a good thing, isn’t it?

user
age

Then let’s keep it.

by    Add a getter to solve this problem:

age
function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;

  // 年龄是根据当前日期和生日计算得出的
  Object.defineProperty(this, "age", {
    get() {
      let todayYear = new Date().getFullYear();
      return todayYear - this.birthday.getFullYear();
    }
  });
}

let john = new User("John", new Date(1992, 6, 1));

alert( john.birthday ); // birthday 是可访问的
alert( john.age );      // ……age 也是可访问的

Now < strong > old code can also work < / strong >, and we have a good additional attribute.