ES6 Class 之读写属性

1,030 阅读2分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

1. ES5 中读写属性

ES5 中通过“this.属性”的方式读写一个属性。

2. ES6 中读写属性

ES6 中可以轻松地实现对属性的读写操作,同时可以对属性进行保护和只读。

如果设置对象的属性为只读,可以这样写:

class Animal {
  constructor (type) {
    this.type = type
  }
  // age 属性只提供 get,就相当于设置为了只读
  get age () { // 函数前加 get 或 set 就变成了属性,ES6 中允许把属性放在类的最顶层(没有被嵌套到构造函数里)
    return 4
  }
  eat () {
    console.log('I am eating food...')
  }
}
let dog = new Animal('dog')
console.log(dog.age)
dog.age = 5 // 修改 age 属性的值
console.log(dog.age)

/* 运行结果:
4
4
*/

可以看到,age 属性设置为只读后,我们尝试重新赋值,age 的值并没有改变。

如果我们还想让 age 可写,可以这样做:

class Animal {
  constructor (type) {
    this.type = type
  }
  get age () { 
    return 4
  }
  set age (val) {
    this.realAge = val // 注意:这里不能写成 this.age 否则就导致 this.age 和 set age (val) 形成死循环了。
  }
  // 注意:上面的两个 age 可以理解为对外的出入口名称,而不是实例对象的属性名。
  eat () {
    console.log('I am eating food...')
  }
}
let dog = new Animal('dog')
console.log(dog.age)
dog.age = 5
console.log(dog.realAge)

/* 运行结果:
4
5
*/

可以看到,dog.age 赋值为 5 后,realAge 也被成功修改了。

注意,在写 set 的时候,不能写成这样:

set age (val) {
  this.age = val
}

为什么呢?因为上述代码中,当我们给 this.age 赋值时,会再次进入 set age (val) 中,这不就形成死循环了吗?所以不能这么写。

我们还可以结合私有属性的概念,做出如下修改:

let _age = 4
class Animal {
  constructor (type) {
    this.type = type
  }
  // 利用闭包实现对 _age 这一私有属性的保护
  get age () { // 函数前加 get 或 set 就变成了属性,ES6 中允许把属性放在类的最顶层
    return _age // 注意:返回值和“出入口”的名字不能一样(比如这里如果写成 return age 就会导致死循环了)
  }
  set age (val) {
    if (val > 4 && val < 7) {
      _age = val
    }
  }
  // 注意:上面的两个 age 可以理解为访问时的出入口,而不是实例对象的属性名。
  eat () { // 定义实例对象的方法
    console.log('I am eating food...')
  }
}
let dog = new Animal('dog')
console.log(dog.age)
dog.age = 5
dog.age = 8
console.log(dog.age) // set 和 get 本身是函数,但是调用它们时是按属性调用的,所以这里的 age 后面不加括号
console.log(dog._age)

/* 运行结果:
4
5
undefined
*/

上面的代码中,为什么把对 _age 的声明放在 class 外面,而不是 class 里面,比如说放在 constructor 中呢?这是因为现在的语法还不支持私有属性,我们只能用闭包1的方式去做。_age 通过 dog 这个实例是取不到的,因为我们暴露的是 Animal 这个类的实例,而 _age 这个变量不在这个实例中,所以拿不到,因此上面的代码最后打印 dog._age 的结果是 undefined。但是,在 getset 中可以拿到 _age,因为它们在相同的顶层作用域中。

总结:ES5 中通过“this.属性”的方式读写一个属性,但是做不到有条件地读写;而 ES6 中通过 setget 的方式能让我们在属性的读写上有更大的操作权,实现有条件的读写。

Footnotes

  1. 闭包是指能够访问另一个函数作用域中的变量的函数。在 JavaScript 中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数”。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。