对象

ECMA把对象定义为:无序的集合,其属性可以包含基本值、对象或者函数。
严格来说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。
对象是基于一个引用类型创建的。

创建一个对象最简单的方式就是创建一个Object实例,例如:

  1. let obj = new Object();

或者使用构造函数来创建,例如:

  1. function Person(){
  2. }
  3. let p = new Person()

或者也可以用对象字面量的方式来创建,例如:

  1. let person = {
  2. name: 'tom',
  3. age: 18,
  4. sex: 'male',
  5. say: function(){
  6. console.log('I can speak Engilsh')
  7. }
  8. }

属性类型

Javascript中有两种属性:数据属性和访问器属性。确切的说这两种属性都是用来描述对象的各种特征,因为这些特性是内部值,js不能直接访问,所以规范把它们放在两对儿方括号中 [[Enumerable]]。
**

数据属性

数据属性包含一个数据值的位置,这个位置可读、可写。

  • [[Configurable]]: 表示能否通过delete删除属性从而定义新的属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为true.
  • [[Enumerable]]: 表示能否通过for-in循环出属性,默认值为true.
  • [[Writable]]: 表示能否修改属相的值。直接在对象上定义属性,这个特性的默认值为true.
  • [[Value]]: 包含这个属性的数据值,访问器属性里面是没有这个特性的。默认为undefined;比如let person = {name:’tom’},这里设置了一个名叫name的属性,它的值是”tom”, [[Value]]特性将被设置为”tom”,其余的是三个特性都是默认值true。

下面就用具体的实例来理解上面的特性。首先这里要先谈的是Object.defineProperty()方法,这个方法接收三个参数:属性所在的对象、属性的名字、和一个描述符对象(也就是上面的四个数据属性,用来描述对象属性的特性),这里要注意了,采用Object.defineProperty方法创建属性时候,数据属性[[Configurable]]、[[Writable]]、[[Enumerable]]默认为false,这要和字面量直接声明属性时默认值相反, 但是如果只是Object.defineProperty改变原来已有属性的值则没有此限制,示例如下:

  1. let person = {};
  2. Object.defineProperty(person,'name',{
  3. value: 'james'
  4. })
  5. console.log(person.name) // james
  6. person.name = 'kobe';
  7. console.log(person.name) // 依然还是james

这里我们看到[[Writable]]特性的默认属性是false, 这里属相的值是不可写的。

  1. let person = {};
  2. Object.defineProperty(person,'name',{
  3. writable: true,
  4. value: 'james'
  5. })
  6. console.log(person.name) // james
  7. person.name = 'kobe';
  8. console.log(person.name) // 这里改为了kobe

将[[Writable]]特性的值改为true, 这里是属性值变为可写入。

  1. let person = {};
  2. Object.defineProperty(person,'name',{
  3. value: 'james'
  4. })
  5. console.log(person.name) // james
  6. delete person.name
  7. console.log(person.name) // 依然还是james,没有删除

[[Configurable]]特性的默认属性是false, 这里属相的值是不可删除的。

  1. let person = {};
  2. Object.defineProperty(person,'name',{
  3. configurable: true,
  4. value: 'james'
  5. })
  6. console.log(person.name) // james
  7. delete person.name
  8. console.log(person.name) // undefined,这里删除了

把[[Configurable]]设置为false,表示不能删除对象属性,对这个这个属性调用delete方法,非严格模式下什么也不会发生,严格模式下则会抛出错误,而且一旦把属性从configurable设置为false,以后就不能把它设置为true,此时在调用Object.defineProperty方法修改除writable、value之外的特性,都会导致错误。

  1. let person = {};
  2. Object.defineProperty(person,'name',{
  3. configurable: false,
  4. value: 'james'
  5. })
  6. Object.defineProperty(person,'name',{
  7. configurable: true,
  8. value: 'james'
  9. }
  10. //这样就会报错了

我们可以这样理解,多次调用Object.defineProperty()方法修改同一个属性,但是在configurable特性设置为false之后就会有限制了。

访问器属性

访问器属性不包含数值,它包含一对函数(getter和setter), 在读取访问器属性时,会调用getter函数,这个函数负责返回有效的值;在写入访问器属性时,会调用setter函数并传入新值,访问器属性有如下四个特性:

  • [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,能否把属性修改为数据属性,默认为true;
  • [[Enumerable]]:用来修饰对象属性的枚举特性,表示能否通过用for in循环返回属性,默认为true;
  • [[Get]]:在读取属性时候调用的函数,默认为undefined;
  • [[Set]]:在写入属性时候调用的函数,默认为undefined;

访问器属性不能直接定义,必须使用Object.defineProperty()来定义。示例如下:

  1. let person = {
  2. _age : 18,
  3. edition : 1
  4. }
  5. Object.defineProperty(person,'age',{
  6. get: function(){
  7. return this._age;
  8. },
  9. set: function(newVal){
  10. if(newVal > 19){
  11. this._age = newVal;
  12. this.edition += newVal - 19
  13. }
  14. }
  15. })
  16. console.log(person.edition) // 1
  17. person.age = 20
  18. console.log(person.edition) // 2

以上例子中,person对象有两个默认属性:_age 和 edition ,其中_age前面加了下划线,用于表示只能通过对象方法去访问这个属性,即通过get方法返回属性值。如果直接访问person.edition的值,则不会调用get方法返回。person.age=20即是修改person的age属性值,会调用set方法,参数newValue就是设置的20这个值,在上面例子中set函数里还改变了属性edition的值。
这是使用访问器属性的常见方式,即设置一个属相的值会导致其它属性发生变化。

定义多个属性

采用Object.defineProperties()我们也可以为一个对象定义多个属性,这个方法有两个参数:第一个要定义属性的对象,第二个也是对象,表示要添加的多个属性和其对应的属性描述符,示例如下:

  1. var person = {};
  2. Object.defineProperties(person,{
  3. _age:{
  4. writable:true,
  5. value:18
  6. },
  7. edition:{
  8. writable:true,
  9. value:1
  10. },
  11. age:{
  12. get:function(){
  13. return this._age
  14. },
  15. set:function(newVal){
  16. if(newVal > 18){
  17. this._age = newVal
  18. this.edition += newVal - 18
  19. }
  20. }
  21. }
  22. })

以上代码在person对象上定义了两个数据属性(_age和edition)和一个访问器属性(age)。

读取属性的特性

上面我们一直是在讨论如何设置属性的描述特性,那么对于数据属性和访问器属性里面的具体特性我们怎么读取呢?这里介绍另一个Object的方法:getOwnPropertyDescriptor方法,该方法接收两个参数:属性所在的对象和要读取其描述符的属性名称,它的返回是一个对象,示例如下:

  1. var book = {};
  2. Object.defineProperties(book, {
  3. _year: {
  4. writable: true,
  5. value: 2004,
  6. },
  7. edition: {
  8. writable: true,
  9. value: 1,
  10. },
  11. year: {
  12. get: function () {
  13. return this._year;
  14. },
  15. set: function (newValue) {
  16. if (newValue > 2004) {
  17. this._year = newValue;
  18. this.edition += newValue - 2004;
  19. }
  20. }
  21. }
  22. });
  23. book.year = 2005;
  24. console.log(book.edition); //2
  25. var descriptor1 = Object.getOwnPropertyDescriptor(book, '_year');
  26. console.log(descriptor1.value); //2005
  27. console.log(descriptor1.configurable);//false
  28. console.log(typeof descriptor1.get); //undefine
  29. var descriptor2 = Object.getOwnPropertyDescriptor(book, 'year');
  30. console.log(descriptor2.value); //2005
  31. console.log(descriptor2.configurable);//false
  32. console.log(typeof descriptor2.get); //function;

对于数据属性_year, value等于最初的值, configurable是false, 而get是undefined;
对于访问器属性year, value等于undefined, configurable是false, 而get是一个指向getter函数的指针。