延续之前的关于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') // trueObject.is({}, {}) // false
与严格比较运算符===
的差异主要有两点:1. +0
不等于-0
, 2. NaN
等于自身
+0 === -0 //trueNaN === NaN // falseObject.is(+0, -0) // falseObject.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
如果该参数不是对象,则会先转成对象,然后返回。但undefined
和null
无法转化为对象,所有以它们为参数时,会报错。
typeof Object.assign(2) // "object"Object.assign(undefined) // Uncaught TypeError: Cannot convert undefined or null to objectObject.assign(null) // Uncaught TypeError: Cannot convert undefined or null to object
但如果undefined
或null
是作为带合并数据,则不会报错,因为无法转化为对象,所有跳过。
let target = { name: 'Clearlvoe' }Object.assign(target, undefined) === obj // trueObject.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
,就表示某些操作会忽略当前属性。
enumerable
为false
的属性: -
for..in
循环: 只遍历自身和继承的可枚举的属性 -
Object.keys()
: 返回对象所有可枚举的属性的键名 -
JSON.stringify
: 只字符串化可枚举的属性 -
Object.assign()
: 忽略enumerable
为false
的属性,只拷贝可枚举的属性
这四个操作之中,前三个是 ES5 就有的,最后一个Object.assign()
是 ES6 新增的。其中,只有for...in
会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。实际上,引入“可枚举”(enumerable)这个概念的最初目的,就是让某些属性可以规避掉for...in
操作,不然所有内部属性和方法都会被遍历到。
比如,对象原型的toString
方法,以及数组的length
属性,就通过“可枚举性”,从而避免被for...in
遍历到。
Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable// falseObject.getOwnPropertyDescriptor([], 'length').enumerable// false
上面代码中,toString
和length
属性的enumerable
都是false
,因此for...in
不会遍历到这两个继承自原型的属性。
另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable// false