Javascript面向对象(二)——setter、getter属性

Javascript面向对象(二)——setter、getter属性

Javascript对象有两种属性,一种是数据属性,我们经常使用比较熟悉;第二种是访问器属性,本质就是获取和设置值的函数,但从代码上好像是正常属性。

Getters 和 setters

访问器属性通过”getter”和”setter”方法表示,在对象中使用get和set文字标识。

let obj = {
  get propName() {
    // getter, the code executed on getting obj.propName
  },

  set propName(value) {
    // setter, the code executed on setting obj.propName = value
  }
};

obj.proName调用是,getter方法读取属性,赋值时setter方法调用。示例,我们有user对象,有namesurname两个属性。
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

从外面看,访问器属性好像正常属性,这是访问器属性的思想。我们没有作为一个函数调用user.fullName,只是正常读取:getter在后台运行。
现在,fullName仅有一个getter,如果我们打算赋值user.fullName=,则会报错。让我们来增加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 is executed with the given value.
user.fullName = "Alice Cooper";

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

现在我们有了一个虚拟的属性,可读可写,实际上并不存在。

访问器属性只能使用get/set来访问

属性可以是数据属性或访问器属性,但不能都是。
一旦属性被定义了get prop()或在set prop(),则为访问器属性。所以必须通过getter读取、setter赋值。
有时仅有setter或getter,这时正常的,只是这时不能读取或赋值。

访问器描述符

访问器描述符是不同的。对访问器属性,没有valuewritable,代替他们的是getset函数。
访问器描述可能有:

  • get – 无参函数,读取属性时调用,
  • set – 无参函数, 给属性赋值时调用,
  • enumerable – 与数据属性一致,
  • configurable – 与数据属性一致.

示例,使用defineProperty创建fullName访问器时,可以getset传递描述符。

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);

再次提醒,属性只能是访问器或数据属性,不能都是。如果我们视图给描述符同时提供getvalue,会报错:

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

  value: 2
});

智能的getter/setter

getter/setter能用作“真实”属性的包装器,实现更多的控制。例如,如果我们想避免user的名称太短,可以存储name在一个特殊属性_name中,然后使用setter过滤赋值。

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 is too short...

技术上,外部代码可以直接访问user._name,但有个普遍的共识,使用“_”开头的属性,仅在内部使用,外部对象不要直接访问。

兼容性用途
getter和setter的一个好处是,他们可以控制正常数据属性,并在任何时候调整。举例,我们开始实现user对象使用数据属性nameage

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

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

alert( john.age ); // 25

但是很快或后来,需求变了,代替age,我们可能决定去存储birthday,因为更严格、方便:
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
}

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

现在,如何出来原来的代码,他们仍然在使用age属性?
我们可以尝试找到所有age的代码并修改他们,但这太费时,且如果代码是别人写的,很难去做。另外name属性对user对象来说并不多余,在有些地方正是我们需要的,给age增加一个getter,解决这个问题。

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

  // age is calculated from the current date and 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 is available
alert( john.age );  // ...as well as the age

现在我们多了一个属性,而且原来的代码也正常工作。

你可能感兴趣的:(深入理解Javascript)