ECMAScript
ES,即 ECMAScript,是 js 的规范。ES3 1999 年发布,ES5 2009 年发布,ES6 2015 年发布,为什么没有 ES4?这是因为在 2007 年商讨发布 ES4 的时候,由于 ES4 相对 ES3改动太大,导致各大浏览器公司分歧很大,最后也就搁浅了,没有发布 ES4。其中的一些规范加到了后来的 ES5 和 ES6 中。
现在每一年也会有新的 ES2016,ES2017,ES2018。每一年都有新的规范提出,而我们学 js 很多规范都还是出自 ES3。一些教程有时候也会出现不同的术语,也可能就是因为说的是不同规范中的内容。这篇文章讲 ES5 中对属性的定义。
ES5 相对 ES3,改变了一些数据特性的名字,同时也提供了统一的方法来操作数据属性。
数据属性
我们看一下定义对象属性的方法。
ES6 之前
1 | var person = { |
ES6 语法
1 | var person = { |
可以看到函数属性更加简洁了,但这不重要。我们使用 ES5 新的函数 Object.getOwnPropertyDescriptor 把属性特性输出看一下。
1 | Object.getOwnPropertyDescriptor(person,'name'); |
可以看到不管是函数,还是字符串或者其他基本类型,它们都有四个属性特性描述。
[[Value]]: 表示属性的数据值。默认值: undefined
[[Writable]]: 表示能否修改属性的值。默认值: true
[[Enumerable]]: 表示能否通过 for-in,Object.keys ( ) 迭代。默认值:true
[[Configurable]]: 表示能否通过 delete 删除属性,能否修改属性的特性,能否将数据属性和访问器属性互转。
如果为 false,只可以把 [[Writable]] 从 true 变为 false,[[Enumerable]] 和 [[Configurable]] 的值都不能再改变,[[Value]] 只取决于 [[Writable]] ,数据属性不能变成访问器属性,访问器属性也不能变成数据属性,也不能通过 delete 删除。默认值: true
ES5 提供了 Object.defineProperty 方法,既可以用来定义属性,也可以用来修改属性的特性。
1 | Object.defineProperty(person, 'name', { |
然后再对 person.name 赋值的话就无效了。
也可以定义新的属性,不同于直接定义的属性默认值都为 true。这里如果没有定义,默认值是 false。
1 | Object.defineProperty(person, 'id', { |
访问器属性
除了数据属性,还多了访问器属性。
1 | var book = { |
没有了 [[Value]] 和 [[Writable]],取而代之的是 get 和 set 函数。如果 set 属性没有定义,那么就无法修改 book 的值。[[Enumerable]] 和 [[Configurable]] 和之前是一样的。
如果你对 C++ 或者 JAVA 了解,那么对 get 和 set 一定不陌生,但是你有没有过疑问,为什么要有访问器属性呢?
JAVA 里边有 Private 变量,然后提供 public 的 get,set 方法来访问这些变量,那么 js 为什么要有呢?直接访问变量不好吗?
你可能会说,像上边的例子,我们可以控制设置的值呀,大于 2004 我们才进行赋值,如果是数据属性就做不到呀。那么问题来了,为什么不直接定义一个函数呢,非新增个访问器属性呢?
函数还是访问器属性?
如果对象有三个属性,firstName,lastName,fullName,很明显 fullName = firstName + lastName。
1 | var person = { |
这样的话,当我想改变 firstName 的话,我还得同时去改变 fullName,当属性间有关联的时候,维护它们之前的关系太麻烦了。我们可以把 fullName 定义成一个函数,这样的话,它就可以自动改变了。
1 | var person = { |
这样问题解决了,但不够优雅,让我们看看访问器属性可以怎么做。
1 | var person = { |
访问器属性的优点很明显了。
语法上更简洁,将函数和属性值语法统一了起来。访问器属性虽然调用了函数,但在使用上和属性的语法是一样的。也更符合逻辑,如果想得到 FullName,很明显这应该是对象的一个属性,而不应该是方法,如果去调用函数得到它,就太不优雅了。
访问器属性其他优点
说了这么多,其实用函数和访问器属性可以实现同样的功能,但既然 ES5 中提供了访问器属性的语法,我们当然会优先是用访问器属性,而不是定义一个函数了。那么除了当属性间有关联可以使用它,还有些什么优点呢?
就是最开始说的,有了 get 和 set 我们就可以在返回和设置值的时候进行一些额外的操作。
1 | var obj = { |
除了上边的有点外,我们还提到 js 里边没有私有变量,所以在外边可以直接访问变量而不经过访问器属性。
1 | var obj = { |
我们没有通过访问器而改变了内部的值,结合访问器属性,我们可以实现数据的私有化。
- 利用块级作用域。
1 | /* BLOCK SCOPE, leave the braces alone! */ |
- 利用函数作用域
1 | function myobj(){ |
最后,访问器属性相比于函数还有一个更大的优点。比如,一个已经写过的项目,里边用了一个 date 变量。
1 | var obj = { |
我们在 100 个文件里用了 date 变量,并且进行了赋值操作。
a.js 有下边的语句
1 | obj.date = "2019.8.8" |
b.js 有下边的语句
1 | obj.date = "2019.7.8" |
c.js 有下边的语句
1 | obj.date = "2018.9.9" |
d.js 有下边的语句
1 | obj.date = "2017.2.8" |
e.js 有下边的语句
1 | obj.date = "2019.2.23" |
…. 还有很多文件也都对 obj.date 进行了赋值操作。
有一天,我们突然想要变更数据从 “XXXX.YY.ZZ” 到 “XXXX-YY-ZZ” 的类型。
一种方法就是找到之前所有赋值的地方,然后进行修改。
改 a.js
1 | obj.date = "2019-8-8" |
改 b.js
1 | obj.date = "2019-7-8" |
改 c.js
1 | obj.date = "2018-9-9" |
… …
另一种办法就是把 date 改成访问器属性,找到对象定义的地方改一次就行了,这样在赋值的时候 date 就会自动改变了。
1 | var obj = { |
由此也可以看出访问器属性的好处,可以对数据进行更好的控制,合法性判断,格式之类的,以及关联多个属性,所以最好用访问器属性,可以为未来的扩展留下一个接口。
以上都是自己的理解,如果发现问题可以告诉我,感谢。
参考了下边的链接:
https://zcfy.cc/article/ecma-262-5-in-detail-chapter-1-properties-and-property-descriptors
https://javascriptplayground.com/es5-getters-setters
https://stackoverflow.com/questions/42342623/why-use-getters-and-setters-in-javascript
https://www.hongkiat.com/blog/getters-setters-javascript/