js对象特性学习

93 阅读7分钟

对象简介:ECMA-262将对象定义为一组属性的无序集合。严格来说,这意味着对象就是一组没有特定顺序的值。对象的每个属性或方法都由一个名称来标识,这个名称映射到一个值。

理解对象

早期创建对象的方式是通过new Object()构造函数。现在都是使用更加简便的对象字面量方式创建对象。

属性的类型

ECMA-262使用一些内部特性来描述属性的特征。这些特性是由为JavaScript实现引擎的规范定义的。开发者不能直接在JavaScript中直接访问这些特性。对象属性分为两种:数据属性和访问器属性。
1-数据属性:
数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置,数据属性有四个特性描述他们的行为。

  1. [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
  2. [[Enumberable]]:表示属性是否可以通过for-in循环返回。默认情况下,这个特性是true。
  3. [[Writable]]:表示属性的值是否可以被修改。默认是true。
  4. [[Value]]:包含属性实际的值,这就是前面提到的那个读取和写入属性值的位置,这个特性默认为undefined。
let person = {
  name: 'jury'
}
// 这里我创建了一个name属性,并给它赋予一个值。这意味着[[Value]]特性会被设置为jury。另外的三个特性的值都是true。

如果要修改属性的默认特性,就必须使用Object.defineProperty()方法。这个方法接受三个参数,对象,属性名称,一个描述符对象。

let per = {}
Object.defineProperty(per, 'name', {
  writable: false,
  value: 'kangkang'
})
per.name = 'abc';
console.log(per.name); // kangkang
// 不可再修改name值。

// 注意: 一个属性一旦被定义为不可配置之后,就不能再变回可配置的了,再次调用Object.defineProperty()会  
//报错。在调用Object.defineProperty()的时候,如果configurable,enumerable,writable的值如果不指定,则都默认为false。

2-访问器属性:
访问器属性不包含数据值,它们包含一个获取函数(getter)和一个设置函数(setter)。不过这两个函数不是必须的。在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。在写入访问器属性时,会调用设置函数并传入新值,这个函数必须决定对数据作出什么修改。访问器属性有四个特性描述它们的行为。

  1. [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
  2. [[Enumberable]]:表示属性是否可以通过for-in循环返回。默认情况下,这个特性是true。
  3. [[Get]]: 获取函数,在读取属性时调用,默认值为undefined。
  4. [[Set]]: 设置函数,在写入属性时调用,默认值为undefined。

访问器属性是不能直接定义的,必须使用Object.defineProperty()。

let book = {
  year_: 2017,
  edition: 1
}
Object.defineProperty(book, 'year', {
  get() {
    return this.year_;
  }
  set() {
    if (newValue > 2017) {
      this.year_ = newValue;
      this.edition += newValue - 2017;
    }
  }
})
// 注意:获取函数和设置函数不一定都要定义,但是如果没有定义其中一个,则该属性就不具备相应的功能

同时定义多个属性

Object.defineProperties(),它接受两个参数,对象和一个描述符对象。

let book = {}
Object.defineProperties(book, {
  year_: {
    value: 2017
  },
  edition: {
    value: 1,
  },
  year: {
    get() {
      return this.year_;
    }
    set() {
      if (newValue > 2017) {
        this.year_ = newValue;
        this.edition += newValue - 2017;
      }
    }
  }
})

读取属性的特性

Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。该方法接受两个参数,第一个是属性所在的对象,第二个是要取得属性描述符的属性。返回值是一个对象。对于访问器属性,包含configurable、enumerable、get和set属性。对于数据属性包含configurable、enumerable、writable和value属性。比如:

let book = {};
Object.defineProperties(book, {
  year_: {
    value: 2017
  },
  edition: {
    value: 1
  },
  year: {
    get() {
      return this.year_
    },
    set() {
      if (newValue>2017) {
        this.year_ = newValue;
        this.edition += newValue -2017;
      }
    }
  }
});
let desc = Object.getOwnPropertyDescriptor(book, "year_");
console.log(desc.value) // 2017
console.log(desc.configurable) // false
console.log(desc.get) // undefined
let desc2 = Object.getOwnPropertyDescriptor(book, "year");
console.log(desc2.value) // undefined
console.log(desc2.enumerable) // false
console.log(typeof desc.get) // function

ECMAScript 2017新增了Object.getOwnPropertyDescriptors()静态方法。这个方法会获取整个对象所有属性的属性描述符。相当于在每个属性上调用Object.getOwnPropertyDescriptor方法,然后在一个对象中返回所有的属性描述符。比如上面的例子会像以下这样:

let book = {};
Object.defineProperties(book, {
  year_: {
    value: 2017
  },
  edition: {
    value: 1
  },
  year: {
    get() {
      return this.year_
    },
    set() {
      if (newValue>2017) {
        this.year_ = newValue;
        this.edition += newValue -2017;
      }
    }
  }
});
console.log(Object.getOwnPropertyDescriptors(book));
{
  year_: {
    configurable: false,
    enumerable: false,
    writable: false,
    value: 2017
  },
  year: {
    configurable: false,
    enumerable: false,
    get: f(),
    set: f(newValue);
  },
  edition: {
    configurable: false,
    enumerable: false,
    writable: false,
    value: 1
  }
}

合并对象

合并对象,顾名思义就是将多个对象的属性合并到一个对象里面去,ES6专门提供了一个方法叫Object.assign()方法。该方法接受一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举和自有属性复制到目标对象。对每个符合条件的属性,这个方法会使用源对象上的[[get]]取得属性的值,然后使用目标对象上的[[set]]设置属性的值。

      let dest = {};
      let src = {
        id: 'src'
      },
      result = Object.assign(dest, src);
      console.log(dest === result); // true
      console.log(dest !== src); // true
      //Object.assign会修改目标对象,然后返回目标对象。
      // 当有对个源对象的时候,如果这些源对象中的属性名有重复的,那么会按照参数的位置,就是拥有该属性的最后一个源对象参数的值为准。  
      //Object.assign方法执行的是浅复制,而且不能在对象之间转移获取函数和设置函数。  
      let obj = { a: {} };
      Object.assign(dest, obj);
      console.log(dest.a === src.a); // true
      //如果赋值之间出错了,则操作会终止并退出,同时抛出错误,Object.assign没有回滚之前
      //赋值的概念,它是一个尽力而为,可能只会完成部分复制的方法。

对象标识及相等判定

Object.is()方法大部分情况下和===操作符的效果是一样的,不同的是在一些边界情况下会有不同的结果。如+0,-0的判断,两个NaN之间的判断。

console.log(+0 === -0); // true
console.log(+0 === 0); // true
console.log(0 === -0); // true
console.log(Object.is(+0,-0)); // false
console.log(Object.is(+0,0)); // true
console.log(Object.is(-0,0)); // false
console.log(Object.is(NaN,NaN)); // true

增强的对象语法

属性值简写:

当给对象添加变量的时候,属性名和变量名是一样的,那样就可以只写一个属性名就能达到同样的效果。

let name = 'a'
let obj = {
  name: name
}
// 下面和上面是等价的
let obj = {
  name
}

可计算属性

const namekey = 'name';
let person = {
  [nameky]: 'john'
}
console.log(person); //{ name: 'john' }

对象解构赋值

      let obj = {
        a: '123',
        b: '234',
      }
      let { a : o1, b: o2} = obj; // 此步骤既声明o1,o2变量,又执行了赋值操作。
      console.log(o1,o2) // 123,234
      
      // 如果想让变量直接使用属性的名称,可以使用简洁方法:
      let obj = {
        a: '123',
        b: '234',
      }
      let { a, b} = obj;
      console.log(a,b) // 123,234
      
      // 赋值的时候如果引用的属性不存在,则该变量的值就是undefined。
      let obj = {
        a: '123',
        b: '234',
      }
      let { a, c} = obj;
      console.log(a,c) // 123 undefined
      // 也可以在解构赋值的同时定义默认值,以防上面不存在属性的情况
      let obj = {
        a: '123',
        b: '234',
      }
      let { a, c = '456'} = obj;
      console.log(a,c) // 123 456
      // 注意:解构在内部使用ToObject()(不能在运行时环境直接访问)把源数据结构转换为对象。
      //这意味着在对象解构的上下文中,原始值也会被当成对象。但是null和undefined不能被解
      //构,否则会抛出错误。
      let { length } = 'abc'
      console.log(lenght); // 3
      let { constructor:c } = 2;
      console.log(c === Number); // true

嵌套解构

     let person = {
        name: 'a',
        age: 12,
        job: {
          title: 'doctor'
        }
      }
      let obj = {};
      ({name:obj.name, age:obj.age, job: obj.job} = person);
      person.job.title = 'police';
      console.log(person.job); // police
      console.log(obj.job); // police 由此可知,两个对象中的job是一个引用
      
      let { job : { title }} = person;
      console.log(title); // doctor
      
      // 在外层属性没有定义的情况下不能使用嵌套解构,无论源对象还是目标对象都一样。
      let person = {
        name: 'a',
        age: 12,
      }
      let obj = {};
      ({foo:{obj.age}} = person); // foo是undefined,会报错,不可解构它
      ({job: {title: obj.a.job }}= person) // obj上不存在a属性,报错。