1. 对象的简介表示法
ES6 允许直接写入变量和函数, 作为对象的属性和方法. 这样的书写更加简洁.
- const foo = 'bar';
- const baz = {
- foo,
- hello(){console.log(this.foo);
- }
- };
- baz.hello();//bar
2. 属性名表达式
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
方法的 name 属性
函数的 name 属性, 返回函数名. 对象方法也是函数, 因此也有 name 属性.
- const person = {
- sayName() {
- console.log('hello!');
- },
- };
- person.sayName.name // "sayName"
- 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, 这个属性并没有被拷贝进去.
注意点
1. 浅拷贝
Object.assign 方法实行的是浅拷贝, 而不是深拷贝. 也就是说, 如果源对象某个属性的值是对象, 那么目标对象拷贝得到的是这个对象的引用.
- const obj1 = {a: {b: 1}};
- const obj2 = Object.assign({}, obj1);
- obj1.a.b = 2;
- obj2.a.b // 2
上面代码中, 源对象 obj1 的 a 属性的值是一个对象, Object.assign 拷贝得到的是这个对象的引用. 这个对象的任何变化, 都会反映到目标对象上面.
2. 同名属性的替换
对于这种嵌套的对象, 一旦遇到同名属性, Object.assign 的处理方法是替换, 而不是添加.
- const target = { a: { b: 'c', d: 'e' } }
- const source = { a: { b: 'hello' } }
- Object.assign(target, source)
- // { a: { b: 'hello' } }
上面代码中, target 对象的 a 属性被 source 对象的 a 属性整个替换掉了, 而不会得到 { a: { b: 'hello', d: 'e' } } 的结果. 这通常不是开发者想要的, 需要特别小心.
一些函数库提供 Object.assign 的定制版本(比如 Lodash 的_.defaultsDeep 方法), 可以得到深拷贝的合并.
3. 数组的处理
Object.assign 可以用来处理数组, 但是会把数组视为对象.
- Object.assign([1, 2, 3], [4, 5])
- // [4, 5, 3]
上面代码中, Object.assign 把数组视为属性名为 0,1,2 的对象, 因此源数组的 0 号属性 4 覆盖了目标数组的 0 号属性 1.
4. 取值函数的处理
Object.assign 只能进行值的复制, 如果要复制的值是一个取值函数, 那么将求值后再复制.
- const source = {
- get foo() { return 1 }
- };
- const target = {};
- Object.assign(target, source)
- // { foo: 1 }
上面代码中, source 对象的 foo 属性是一个取值函数, Object.assign 不会复制这个取值函数, 只会拿到值以后, 将这个值复制过去.
常见用途
1. 为对象添加属性
- class Point {
- constructor(x, y) {
- Object.assign(this, {x, y});
- }
- }
上面方法通过 Object.assign 方法, 将 x 属性和 y 属性添加到 Point 类的对象实例.
2. 为对象添加方法
- Object.assign(SomeClass.prototype, {
- someMethod(arg1, arg2) {
- ...
- },
- anotherMethod() {
- ...
- }
- });
- // 等同于下面的写法
- SomeClass.prototype.someMethod = function (arg1, arg2) {
- ...
- };
- SomeClass.prototype.anotherMethod = function () {
- ...
- };
上面代码使用了对象属性的简洁表示法, 直接将两个函数放在大括号中, 再使用 assign 方法添加到 SomeClass.prototype 之中.
3. 克隆对象
- function clone(origin) {
- return Object.assign({}, origin);
- }
上面代码将原始对象拷贝到一个空对象, 就得到了原始对象的克隆.
不过, 采用这种方法克隆, 只能克隆原始对象自身的值, 不能克隆它继承的值. 如果想要保持继承链, 可以采用下面的代码.
- function clone(origin) {
- let originProto = Object.getPrototypeOf(origin);
- return Object.assign(Object.create(originProto), origin);
- }
4. 合并多个对象
将多个对象合并到某个对象.
- const merge =
- (target, ...sources) => Object.assign(target, ...sources);
如果希望合并后返回一个新对象, 可以改写上面函数, 对一个空对象合并.
- const merge =
- (...sources) => Object.assign({}, ...sources);
5. 为属性指定默认值
- const DEFAULTS = {
- logLevel: 0,
- outputFormat: 'html'
- };
- function processContent(options) {
- options = Object.assign({}, DEFAULTS, options);
- console.log(options);
- // ...
- }
上面代码中, DEFAULTS 对象是默认值, options 对象是用户提供的参数. Object.assign 方法将 DEFAULTS 和 options 合并成一个新对象, 如果两者有同名属性, 则 option 的属性值会覆盖 DEFAULTS 的属性值.
注意, 由于存在浅拷贝的问题, DEFAULTS 对象和 options 对象的所有属性的值, 最好都是简单类型, 不要指向另一个对象. 否则, DEFAULTS 对象的该属性很可能不起作用.
- const DEFAULTS = {
- url: {
- host: 'example.com',
- port: 7070
- },
- };
- processContent({ url: {port: 8000} })
- // {
- // url: {port: 8000}
- // }
上面代码的原意是将 url.port 改成 8000,url.host 不变. 实际结果却是 options.url 覆盖掉 DEFAULTS.url, 所以 url.host 就不存在了.
属性的可枚举型和遍历
可枚举性
对象的每个属性都有一个描述对象(Descriptor), 用来控制该属性的行为. Object.getOwnPropertyDescriptor 方法可以获取该属性的描述对象.
- let obj = { foo: 123 };
- Object.getOwnPropertyDescriptor(obj, 'foo')
- // {
- // value: 123,
- // writable: true,
- // enumerable: true,
- // configurable: true
- // }
描述对象的 enumerable 属性, 称为 "可枚举性", 如果该属性为 false, 就表示某些操作会忽略当前属性.
目前, 有四个操作会忽略 enumerable 为 false 的属性.
for...in 循环: 只遍历对象自身的和继承的可枚举的属性.
Object.keys(): 返回对象自身的所有可枚举的属性的键名.
JSON.stringify(): 只串行化对象自身的可枚举的属性.
Object.assign(): 忽略 enumerable 为 false 的属性, 只拷贝对象自身的可枚举的属性.
属性的遍历
(1)for...in
for...in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性).
(2)Object.keys(obj)
Object.keys 返回一个数组, 包括对象自身的 (不含继承的) 所有可枚举属性 (不含 Symbol 属性) 的键名.
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames 返回一个数组, 包含对象自身的所有属性 (不含 Symbol 属性, 但是包括不可枚举属性) 的键名.
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols 返回一个数组, 包含对象自身的所有 Symbol 属性的键名.
(5)Reflect.ownKeys(obj)
Reflect.ownKeys 返回一个数组, 包含对象自身的所有键名, 不管键名是 Symbol 或字符串, 也不管是否可枚举.
- Object.keys(),Object.values(),Object.entries()
- 1.Object.keys()
ES5 引入了 Object.keys 方法, 返回一个数组, 成员是参数对象自身的 (不含继承的) 所有可遍历 (enumerable) 属性的键名.
- var obj = { foo: 'bar', baz: 42 };
- Object.keys(obj)
- // ["foo", "baz"]
ES2017 引入了跟 Object.keys 配套的 Object.values 和 Object.entries, 作为遍历一个对象的补充手段, 供 for...of 循环使用.
- let {keys, values, entries} = Object;
- let obj = { a: 1, b: 2, c: 3 };
- for (let key of keys(obj)) {
- console.log(key); // 'a', 'b', 'c'
- }
- for (let value of values(obj)) {
- console.log(value); // 1, 2, 3
- }
- for (let [key, value] of entries(obj)) {
- console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
- }
- 2.Object.values()
Object.values 方法返回一个数组, 成员是参数对象自身的 (不含继承的) 所有可遍历 (enumerable) 属性的键值.
- const obj = { foo: 'bar', baz: 42 };
- Object.values(obj)
- // ["bar", 42]
返回数组的成员顺序,
- const obj = { 100: 'a', 2: 'b', 7: 'c' };
- Object.values(obj)
- // ["b", "c", "a"]
上面代码中, 属性名为数值的属性, 是按照数值大小, 从小到大遍历的, 因此返回的顺序是 b,c,a.
Object.values 只返回对象自身的可遍历属性.
- const obj = Object.create({}, {p: {value: 42}});
- Object.values(obj) // []
上面代码中, Object.create 方法的第二个参数添加的对象属性(属性 p), 如果不显式声明, 默认是不可遍历的, 因为 p 的属性描述对象的 enumerable 默认是 false,Object.values 不会返回这个属性. 只要把 enumerable 改成 true,Object.values 就会返回属性 p 的值.
- const obj = Object.create({}, {p:
- {
- value: 42,
- enumerable: true
- }
- });
- Object.values(obj) // [42]
如果 Object.values 方法的参数是一个字符串, 会返回各个字符组成的一个数组.
- Object.values('foo')
- // ['f', 'o', 'o']
上面代码中, 字符串会先转成一个类似数组的对象. 字符串的每个字符, 就是该对象的一个属性. 因此, Object.values 返回每个属性的键值, 就是各个字符组成的一个数组.
如果参数不是对象, Object.values 会先将其转为对象. 由于数值和布尔值的包装对象, 都不会为实例添加非继承的属性. 所以, Object.values 会返回空数组.
- Object.values(42) // []
- Object.values(true) // []
- 3.Object.entries()
Object.entries 方法返回一个数组, 成员是参数对象自身的 (不含继承的) 所有可遍历 (enumerable) 属性的键值对数组.
- const obj = { foo: 'bar', baz: 42 };
- Object.entries(obj)
- // [ ["foo", "bar"], ["baz", 42] ]
除了返回值不一样, 该方法的行为与 Object.values 基本一致.
Object.entries 的基本用途是遍历对象的属性.
- let obj = { one: 1, two: 2 };
- for (let [k, v] of Object.entries(obj)) {
- console.log(
- `${JSON.stringify(k)}: ${JSON.stringify(v)}`
- );
- }
- // "one": 1
- // "two": 2
Object.entries 方法的另一个用处是, 将对象转为真正的 Map 结构.
- const obj = { foo: 'bar', baz: 42 };
- const map = new Map(Object.entries(obj));
- map // Map { foo: "bar", baz: 42 }
自己实现 Object.entries 方法, 非常简单.
- // Generator 函数的版本
- function* entries(obj) {
- for (let key of Object.keys(obj)) {
- yield [key, obj[key]];
- }
- }
- // 非 Generator 函数的版本
- function entries(obj) {
- let arr = [];
- for (let key of Object.keys(obj)) {
- arr.push([key, obj[key]]);
- }
- return arr;
- }
扩展运算符
对象的扩展运算符 (...) 用于取出参数对象的所有可遍历属性, 拷贝到当前对象之中.
- let z = { a: 3, b: 4 };
- let n = { ...z };
- n // { a: 3, b: 4 }
这等同于使用 Object.assign 方法.
- let aClone = { ...a };
- // 等同于
- let aClone = Object.assign({}, a);
扩展运算符可以用于合并两个对象.
- let ab = { ...a, ...b };
- // 等同于
- let ab = Object.assign({}, a, b);
来源: http://www.qdfuns.com/article/18271/d78faeab54dedd9501e9fdac3597356b.html