Object


表达式还是语句

表达式和语句的区别:表达式必须有一个结果
JavaScript规定,如果行首是大括号,一律解释为语句(即代码块)。如果要解释为表达式(即对象),必须在大括号前加上圆括号。

1
({ foo: 123})

对象浅拷贝和深拷贝的区别

浅拷贝:一个对象的属性为非对象时,改变属性值不会发生联动变化,只会一个对象会变,假如是对象的话,就会变,因为对象存储的是地址
深拷贝:改变所有属性值都不会发生联动变化

删除属性

delete: 用于删除对象的属性,删除后不管属性存在与否都会返回true。

1
2
3
var o = {p: 1};
Object.keys(o) // ["p"]
delete o.p // true

Object.prototype.toString()
1
2
3
4
5
6
7
8
let obj = {}
obj.toString() // '[object Object]'

// 实用:区分数组、对象和函数
let arr = [1,2,3]
let fn = function(){console.log(111)}
Object.prototype.toString.call(arr) // [object Array]
Object.prototype.toString.call(fn) // [object Function]
ES5对象api

Object.create(proto, [propertiesObject])方法创建一个新对象,使用现有的对象来提供新创建的对象的proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function fn(){
this.a = 1
this.b = 2
}
fn.prototype.c = 1
let obj1 = new fn()
let obj2 = Object.create(obj1) // 等价于obj2.__proto__ === obj1
console.log(obj2) // {}因为只是把obj1绑定到obj2的原型对象上
obj2.a === 1
obj2.b === 1
obj2.c === 1

// 带第二参数时,是带属性描述对象
let obj3 = Object.create(obj1, {d: {}})
console.log(obj3) // {d: undefined}
Object.getOwnPropertyDescriptor(obj3, 'd')
// {value: undefined, enumerable: false, configurable: false, writable: false}

let obj4 = Object.create(obj1, {e: { value: 4, enumerable: true, writable: true, configurable: true }})
console.log(obj4) // {e: 4}

// 用Object.create()浅拷贝一个新对象,并继承对象的属性
let obj5 = Object.create(Object.getPrototypeOf(obj1), Object.getOwnPropertyDescriptors(obj1))
console.log(obj5)

对象属性、方法的简写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};

function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}

const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
属性名表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
// 方法三
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
// 表达式还可以用于定义方法名。
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
扩展运算符

解构赋值:浅拷贝,能赋值继承的属性
扩展运算符:浅拷贝,不能继承继承的属性。扩展运算符等同于Object.assign(),Object.assign()也不能继承继承的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let {x, y} = null // 报错
let { x, y } = undefined // 报错
let {x, ...y, z} = {1,2,3,4} // 报错
// 案例
let obj1 = {a: 1, b: {b: 1}}
let obj2 = {c: {c: 1}}
obj1.__proto__ = obj2
let {c, ...obj3} = obj1
// 结果
c // {c: 1}
obj3 // {a:1, b: {b: 1}}
obj3.c // undefined
// 修改
obj1.a = 2
obj1.b.b = 2
obj1.c.c = 2
// 结果(重点)
obj3 // {a:1, b: {b: 2}}

let aClone = {...a}
// 等同于
let aClone = Object.assign({}, a)

运用扩展运算符或Object.assign()拷贝对象和对象的继承属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let fn = function(){}
fn.prototype.b = 1
let obj = new fn()
obj.a = 1
// 写法一,非浏览器的环境不一定有效
const clone1 = {
__proto__: Object.getPrototypeOf(obj), // 等价于__proto__: fn.prototype
...obj
};

// 写法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)), // 因为Object.create()就是将对象绑定到__proto__上
obj
);

// 写法三
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
用api定义修改属性

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

1
2
3
4
5
6
7
8
9
10
11
12
Object.defineProperty(obj, prop, descriptor)
descriptor是对象的描述对象
let obj = {}
Object.defineProperty(obj, 'a', {
enumerable: true, // 属性是否可枚举
configurable: true, // 是否可重新定义属性描述,为false时不能再重新定义属性描述,也不能delete属性
value: '1', // 属性值,默认undefined
writable: true // value是否可编辑,为false时即使给对象属性赋值了,也不能修改value
})
// 对象中的属性描述符有2种:数据描述符和存取描述符。数据描述符和存取描述符不能共存,两者共有的属性为enumerable和configurable。
// 数据描述符:value和writable
// 存取描述符:get和set函数

默认情况下(为数据描述符)

1
2
3
let obj = {}
Object.defineProperty(obj, 'a', {}) // {a:undefined}
Object.getOwnPropertyDescriptor(obj, 'a') // {value: undefined, writable: false, enumerable: false, configurable: false}

存取描述符情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let obj = { firstName: 'zhuang', lastName: 'cheng' }
Object.defineProperty(obj, 'fullName', {
enumerable: true,
configurable: true,
get () {
return this.firstName + ' ' + this.lastName
},
set (val) {
this.firstName = val.split(' ')[0]
this.lastName = val.split(' ')[1]
}
})
console.log(obj.fullName) // 'zhuang cheng'
obj.firstName = 'jiang'
obj.lastName = 'jie'
console.log(obj.fullName) // 'jiang jie'
obj.fullName = 'chen xi'
console.log(obj.firstName) // 'chen'
console.log(obj.lastName) // 'xi'

可枚举性

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。只能获取自身的属性,不能获取继承的属性

1
2
3
4
5
6
7
8
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true, // 可编辑
// enumerable: true, // 可枚举
// configurable: true // 对象的属性描述可改变或属性可删除
// }

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

1
2
3
4
for...in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign():只拷贝对象自身的可枚举的属性。

引入“可枚举”(enumerable)这个概念的最初目的,就是让某些属性可以规避掉for…in操作,不然所有内部属性和方法都会被遍历到。

1
2
3
4
Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false
Object.getOwnPropertyDescriptor([], 'length').enumerable
// false

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

属性的遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
for...in
// for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

Object.keys(obj)
// Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

Object.getOwnPropertyNames(obj)
// Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

Object.getOwnPropertySymbols(obj)
// Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

Reflect.ownKeys(obj)
// Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
super关键字

this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。

1
2
3
4
5
6
7
8
9
10
11
12
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
// super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。

es6对象的api

Object.keys查看对象自身的所有属性(不包含继承属性)

1
2
3
4
5
6
var o = {
key1: 1, key2: 2
};

Object.keys(o);
// ['key1', 'key2']

ES5 比较两个值是否相等,只有两个运算符:相等运算符和严格相等运算符。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。
Object.is()提出“Same-value equality”(同值相等)算法,用来解决这个问题。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

1
2
3
4
5
6
7
8
9
10
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

// 不同之处
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

Object.assign()

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。特殊情况:1、只有一个参数,直接返回该参数;2、若不是对象,则转成对象再返回;undefined和null无法转成对象,所以报错;3、如果非对象参数出现在源对象的位置(即非首参数),不会报错
注意:Object.assign()无法正确拷贝get属性和set属性的问题,可以用Object.getOwnPropertyDescriptors

1
2
3
4
5
6
7
8
9
10
11
12
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
source1.a = 2;
Object.assign(target, source1, source2);
target // {a:2,b:2,c:3}

// 属性名为 Symbol 值的属性,也会被Object.assign拷贝。
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }

Object.assign()实用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 为对象添加属性
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
// 将x属性和y属性添加到Point类的对象实例。

// 为对象添加原型方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};

克隆对象

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

Object.getOwnPropertyDescriptors()

ES5 的Object.getOwnPropertyDescriptor()方法会返回某个对象属性的描述对象(descriptor)。ES2017 引入了Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性(非继承属性)的描述对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const obj = {
foo: 123,
get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true } }

问题:Object.assign()无法正确拷贝get属性和set属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 案例
const source = {
set foo(value) {
console.log(value);
}
};
const target1 = {};
Object.assign(target1, source);
Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined,
// writable: true,
// enumerable: true,
// configurable: true }
// source对象的foo属性的值是一个赋值函数,Object.assign方法将这个属性拷贝给target1对象,结果该属性的值变成了undefined。
// 这是因为Object.assign方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。

Object.getOwnPropertyDescriptors()方法配合Object.defineProperties()方法,就可以实现正确拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
const source = {
set foo(value) {
console.log(value);
}
};

const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
// set: [Function: set foo],
// enumerable: true,
// configurable: true }

Object.getOwnPropertyDescriptors()方法的另一个用处,是配合Object.create()方法,将对象属性克隆到一个新对象。这属于浅拷贝。

1
2
3
4
5
6
7
const clone = Object.create(Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));
// 或者
const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);

Object.getOwnPropertyDescriptors()也可以用来实现 Mixin(混入)模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let mix = (object) => ({
with: (...mixins) => mixins.reduce(
(c, mixin) => Object.create(
c, Object.getOwnPropertyDescriptors(mixin)
), object)
});

// multiple mixins example
let a = {a: 'a'};
let b = {b: 'b'};
let c = {c: 'c'};
let d = mix(c).with(a, b);

d.c // "c"
d.b // "b"
d.a // "a"

proto属性用Object.create(),Object.setPrototypeOf()和Object.getPrototypeOf()代替
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = {a:1}
let b = {b:2}
b.__proto__ = a
b.a === 1

// 用Object.create()替换
let c = Object.create(a) // 相当于c.__proto__指向了a
c.a === 1

// 用Object.setPrototypeOf()替换__proto__
let d = {}
Object.setPrototypeOf(d, a) // 相当于d.__proto__ = a
d.a === 1
Object.getPrototypeOf(d) // a
Object.values(), Object.entries()

Object.values()、Object.entries()和Object.keys()一样,都只能遍历自身可枚举的属性

1
2
3
let a = { a: 1, b: 2 }
let b = Object.values(a) // [1,2]
let c = Object.entries(a) // [['a', 1], ['b', 2]]

Object.fromEntries()是Object.entries()的逆操作
1
2
3
4
5
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// { foo: "bar", baz: 42 }