3

延续之前的关于ES6的学习内容整理,该篇主要是整理ES6中关于对象的扩展,希望对大家有帮助。之前已经整理了ES6--字符串扩展ES6--函数扩展,大家有兴趣可以移步了解。

属性简写

允许直接写入变量/函数,作为对象的属性/方法。

let str = 'Clearlove'
let obj = {str}
obj // { str: 'Clearlove' }

// 等同于
let str = 'Clearlove'
let obj = { str: str }

作为方法时的简写:

let obj = {
  method() {
    return 'Hello~'
  }
}

// 等同于
let obj = {
  method: function() {
    return 'Hello~'
  }
}

属性和方法的简写一般作为函数函数的返回值对象属性的赋值器和构造器, 以及CommonJS 模块输出一组变量,就非常合适使用简洁写法。

let Obj = {};

function getItem (key) {
  return key in Obj ? Obj[key] : null;
}

function setItem (key, value) {
  Obj[key] = value;
}

function clear () {
  Obj = {};
}

module.exports = { getItem, setItem, clear }

// 等同于
module.exports = {
  getItem: getItem,
  setItem: setItem,
  clear: clear
}

属性表达式

javascript中定义对象属性,最常见的方式如下:

let obj = {}
obj.iseditable = true

ES6中允许用表达式作为对象的属性,将表达式放在一对中括号中,如下:

let key1 = 'key1'
let obj = {
  [key1]: '123',
  ['key' + '2']: 'abc'
}

表达式还可以定义方法名:

let obj = {
  ['say' + 'hello']() {
    return 'hello'
  }
}

obj.sayhello() // hello

Object.is()

用于比较两个值是否严格相等,与严格比较运算符===基本一致

Object.is('Clearlove', 'Clearlove') // true
Object.is({}, {}) // false

与严格比较运算符===的差异主要有两点:1. +0不等于-0, 2. NaN等于自身

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

ES5可以通过如下方法扩展Object.is方法:

Object.defineProperty(Object, 'is', {
  value: function(x, y) {
    if (x === y) {
      // 针对+0 不等于 -0的情况
      return x !== 0 || 1 / x === 1 / y;
    }
    // 针对NaN的情况
    return x !== x && y !== y;
  },
  configurable: true,
  enumerable: false,
  writable: true
});

Object.assign()

Object.assign方法用于对象合并,将待合并对象的所有可枚举属性,复制到目标对象中。

let target = { name: 'Clearlvoe' }

let age = { age: 18 }
let sex = { sex: '男' }

Object.assign(target, age, sex)
target // {name: 'Clearlvoe', age: 18, sex: '男'}

如果目标对象与待合并对象有同名属性,或多个待合并对象有同名属性,则后面的属性会覆盖前面的属性。

如果只有一个参数,Object.assign会直接返回该参数。

let target = { name: 'Clearlvoe' }
Object.assign(target) //  { name: 'Clearlvoe' }
Object.assign(target) === target // true

如果该参数不是对象,则会先转成对象,然后返回。但undefinednull无法转化为对象,所有以它们为参数时,会报错。

typeof Object.assign(2) // "object"

Object.assign(undefined) // Uncaught TypeError: Cannot convert undefined or null to object
Object.assign(null) // Uncaught TypeError: Cannot convert undefined or null to object

但如果undefinednull是作为带合并数据,则不会报错,因为无法转化为对象,所有跳过。

let target = { name: 'Clearlvoe' }
Object.assign(target, undefined) === obj // true
Object.assign(target, null) === obj // true

若数值、字符串和布尔值做为待合并数据,合并至目标目标对象时,只有字符串会以数组形式,拷贝到目标对象。而数值和布尔值则会被忽略。

let str = 'abc';
let boolean = true;
var num = 10;

let obj = Object.assign({}, str, boolean, num);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

字符串能被拷贝,是因为字符串的包装对象,会产生可枚举属性。

Object(true) // {[[PrimitiveValue]]: true}
Object(10)  //  {[[PrimitiveValue]]: 10}
Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}

上面代码中,布尔值、数值、字符串分别转成对应的包装对象,可以看到它们的原始值都在包装对象的内部属性[[PrimitiveValue]]上面,这个属性是不会被Object.assign拷贝的。只有字符串的包装对象,会产生可枚举的实义属性,那些属性则会被拷贝。

Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false

Object.assign({name: 'Clearlove'},
  Object.defineProperty({}, 'invisible', {
    enumerable: false,
    value: 'hello'
  })
)
// {name: 'Clearlove'}

上面代码中,Object.assign要拷贝的对象只有一个不可枚举属性invisible,这个属性并没有被拷贝进去。

注意点

Object.assign()是浅拷贝,如果源对象的某个属性值是对象,那么目标对象拷贝到的是这个 对象的引用。

let source = {person: { name: 'Clearlove'}}
let target = Object.assign({}, source)

source.person.name = 'Meiko'
target.person.name  // 'Meiko'  

对于这种嵌套的对象,一旦遇到同名属性,Object.assign()的处理方法是替换,而不是添加。

let source = {person: { name: 'Clearlove' }}
let target = {person: { name: 'Meiko', age: 18 }}
Object.assign(target, source) // {person: { name: 'Clearlove' }} 

常见用途

为对象添加属性

class LOL {
  constructor(name, age) {
    Object.assign(this, {name, age})
  } 
}

上面方法通过Object.assign方法,将name属性和age属性添加到LOL类的对象实例。

为对象添加方法

Object.assign(SomeClass.prototype, {
  addClass(classname) {
    ....
  },
  removeClass(class) {
    ....
  }
})

克隆对象

function clone(origin) {
  return Object.assign({}, origin);
}

上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。

不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。

function clone(origin) {
  let originProto = Object.getPrototypeOf(origin)
  return Object.assign(Object.create(originProto), origin) 
}

合并多个对象

将对个对象合并到目标对象中

const merge = (target, ...sources) => Object.assign(target, ...sources)

如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并

const merge = (...sources) => Object.assign({}, ...sources)

为属性制定默认值

const DEAFULT = {
  number: 0,
  template: 'html'
}

funcition processContent(options) {
  options = Object.assigin({}, DEAFULT, options)
  console.log(options)
}

注意,由于存在浅拷贝的问题,DEFAULT对象和options对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULT对象的该属性很可能不起作用。

属性的可枚举性和遍历

可枚举性

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。

let person = { name: 'Clearlove' }
Object.getOwnPropertyDescriptor(person, 'name')

//  {
//    value: Clearlove,
//    writable: true,
//    enumerable: true,
//    configurable: true
//  }

描述对象的enumerable属性,称为”可枚举性“,如果该属性为false,就表示某些操作会忽略当前属性
目前有四个操作会忽略enumerablefalse的属性:

  • for..in循环: 只遍历自身和继承的可枚举的属性
  • Object.keys(): 返回对象所有可枚举的属性的键名
  • JSON.stringify: 只字符串化可枚举的属性
  • Object.assign(): 忽略enumerablefalse的属性,只拷贝可枚举的属性

这四个操作之中,前三个是 ES5 就有的,最后一个Object.assign()是 ES6 新增的。其中,只有for...in会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。实际上,引入“可枚举”(enumerable)这个概念的最初目的,就是让某些属性可以规避掉for...in操作,不然所有内部属性和方法都会被遍历到。

比如,对象原型的toString方法,以及数组的length属性,就通过“可枚举性”,从而避免被for...in遍历到。

Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false

Object.getOwnPropertyDescriptor([], 'length').enumerable
// false

上面代码中,toStringlength属性的enumerable都是false,因此for...in不会遍历到这两个继承自原型的属性。

另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。

Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
// false

未完待续


Clearlove
1.2k 声望53 粉丝

专注做好一件事