表达式还是语句
表达式和语句的区别:表达式必须有一个结果
JavaScript规定,如果行首是大括号,一律解释为语句(即代码块)。如果要解释为表达式(即对象),必须在大括号前加上圆括号。1
({ foo: 123})
对象浅拷贝和深拷贝的区别
浅拷贝:一个对象的属性为非对象时,改变属性值不会发生联动变化,只会一个对象会变,假如是对象的话,就会变,因为对象存储的是地址
深拷贝:改变所有属性值都不会发生联动变化
删除属性
delete: 用于删除对象的属性,删除后不管属性存在与否都会返回true。1
2
3var o = {p: 1};
Object.keys(o) // ["p"]
delete o.p // true
Object.prototype.toString()
1 | let obj = {} |
ES5对象api
Object.create(proto, [propertiesObject])方法创建一个新对象,使用现有的对象来提供新创建的对象的proto1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function 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 | const foo = 'bar'; |
属性名表达式
1 | // 方法一 |
扩展运算符
解构赋值:浅拷贝,能赋值继承的属性
扩展运算符:浅拷贝,不能继承继承的属性。扩展运算符等同于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
22let {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 | let fn = function(){} |
用api定义修改属性
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。1
2
3
4
5
6
7
8
9
10
11
12Object.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
3let 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
19let 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
8let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true, // 可编辑
// enumerable: true, // 可枚举
// configurable: true // 对象的属性描述可改变或属性可删除
// }
描述对象的enumerable属性,称为”可枚举性“,如果该属性为false,就表示某些操作会忽略当前属性。有四个操作会忽略enumerable为false的属性。1
2
3
4for...in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign():只拷贝对象自身的可枚举的属性。
引入“可枚举”(enumerable)这个概念的最初目的,就是让某些属性可以规避掉for…in操作,不然所有内部属性和方法都会被遍历到。1
2
3
4Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false
Object.getOwnPropertyDescriptor([], 'length').enumerable
// false
ES6 规定,所有 Class 的原型的方法都是不可枚举的。
属性的遍历
1 | for...in |
super关键字
this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。1
2
3
4
5
6
7
8
9
10
11
12const 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
6var 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
10Object.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.getOwnPropertyDescriptors1
2
3
4
5
6
7
8
9
10
11
12const 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 | // 为对象添加属性 |
克隆对象1
2
3function 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
15const 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
13const 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
7const 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
16let 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 | let a = {a:1} |
Object.values(), Object.entries()
Object.values()、Object.entries()和Object.keys()一样,都只能遍历自身可枚举的属性1
2
3let 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 | Object.fromEntries([ |