延续之前的关于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
如果该参数不是对象,则会先转成对象,然后返回。但undefined
和null
无法转化为对象,所有以它们为参数时,会报错。
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
但如果undefined
或null
是作为带合并数据,则不会报错,因为无法转化为对象,所有跳过。
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
,就表示某些操作会忽略当前属性。
目前有四个操作会忽略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
// false
Object.getOwnPropertyDescriptor([], 'length').enumerable
// false
上面代码中,toString
和length
属性的enumerable
都是false
,因此for...in
不会遍历到这两个继承自原型的属性。
另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
// false
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。