1、对象的扩展
1、 属性的简洁表示法
const foo = 'bar';
const baz = {foo};
console.log(baz) // {foo: "bar"}
// 等同于
const baz = {foo: foo};
2
3
4
5
ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // {x: 1, y: 2}
2
3
4
5
6
7
8
2、 属性名表达式
JavaScript 定义对象的属性,有两种方法。
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
2
3
4
ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
2
3
4
5
6
7
8
表达式还可以用于定义方法名。
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
2
3
4
5
6
注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}
2
3
4
5
6
7
3、 方法的name属性
const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // "sayName"
2
3
4
5
6
如果对象的方法使用了取值函数(getter
)和存值函数(setter
),则name
属性不是在该方法上面,而是在 该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。
const obj = {
get foo() {},
set foo(x) {}
};
obj.foo.name
// TypeError: Cannot read property 'name' of undefined
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
2
3
4
5
6
7
8
9
如果对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述。
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""
2
3
4
5
6
7
8
有两种特殊情况: bind
方法创造的函数,name
属性返回bound
加上原函数的名字;Function
构造函数创造的函数,name属性返回anonymous(匿名)。
(new Function()).name // "anonymous"
var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"
2
3
4
5
4、 属性的可枚举性和遍历
1、可枚举性 对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,//可枚举性
// configurable: true
// }
2
3
4
5
6
7
8
目前,有四个操作会忽略enumerable
为false
的属性。
1、 for...in循环:只遍历对象自身的和继承的可枚举的属性。
2、 Object.keys():返回对象自身的可枚举的属性的属性。
3、 JSON.stringify():只串行化对象自身的可枚举的属性。
4、 Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
总结:尽量不要用for...in循环,而用Object.keys()代替。
2、属性的遍历
1、 for…in:for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
2、 Object.keys(obj)
返回一个数组,包括对象自身的所有可枚举属性(不含 Symbol 属性)的键名。
3、 Object.getOwnPropertyNames(obj)
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
4、 Object.getOwnPropertySymbols(obj)
返回一个数组,包含对象自身的所有 Symbol 属性的键名。
5、 Reflect.ownKeys(obj)
返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
1、首先遍历所有数值键,按照数值升序排列。
2、其次遍历所有字符串键,按照加入时间升序排列。
3、最后遍历所有 Symbol 键,按照加入时间升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 }) // ['2', '10', 'b', 'a', Symbol()]
5、 对象的解构赋值
对象的解构赋值 (在=赋值左边) 用于从一个对象取值,相当于将目标对象所有可遍历的(包括原型上的
)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。属于浅拷贝。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1 y // 2 z // { a: 3, b: 4 }
2
对象的解构赋值的注意事项
1、由于解构赋值要求等号右边是一个对象,所以如果等号右边是
undefined
或null
,就会报错,因为它们无法转为对象。
let { x, y, ...z } = null; // 运行时错误
let { x, y, ...z } = undefined; // 运行时错误
2
2、扩展运算符必须是最后一个参数,否则会报错。
let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误
2
3、扩展运算符的解构赋值,不能复制继承自原型对象的属性。
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined
Object.create({ x: 1, y: 2 });//创建的是原型对象
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o;//newObj只能获取z的值
let { y, z } = newObj;
x // 1
y // undefined
z // 3
2
3
4
5
6
7
8
9
10
11
12
13
14
4、 变量声明语句之中,如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式。
let { x, ...{ y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contexts
2
扩展运算符: (在等号右边) 对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
对象的扩展运算符的注意事项
1、由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
let foo = { ...['a', 'b', 'c'] };
console.log(foo);// {0: "a", 1: "b", 2: "c"}
2
2、 如果扩展运算符后面不是对象,则会自动将其转为对象。
// 等同于 {...Object(1)}
{...1} // {} 由于该对象没有自身属性,所以返回一个空对象。
// 等同于 {...Object(true)}
{...true} // {}
// 等同于 {...Object(undefined)}
{...undefined} // {}
// 等同于 {...Object(null)}
{...null} // {}
2
3
4
5
6
7
8
3、 如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
{...'hello'} // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
4、 对象的扩展运算符等同于使用Object.assign()方法。
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
2
3
5、 如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法。
// 写法1
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
// 写法2
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
2
3
4
5
6
7
8
9
10
6、 扩展运算符可以用于合并两个对象。
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
2
3
7、 如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
let a={x:2,y:3;z:4}
let aWithOverrides = { ...a, x: 1, y: 2 };
console.log(aWithOverrides)// {x: 1, y: 2, z: 4}
let arr={a:1,b:2}
let arr1={b:3,c:4}
let arr2={...arr,...arr1}
console.log(arr2) // {a: 1, b: 3, c: 4}
2
3
4
5
6
7
8
2、对象的新增方法
2、1 Object.is()
用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。与ES5的不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
2
3
4
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
});
2
3
4
5
6
7
8
9
10
11
12
13
2、 Object.assign()
用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
const target={};
Object.assign(target, source1, source2);
Console.log(target); // {a:1, b:2, c:3}
2
3
4
5
6
注意1:由于undefined
和null
无法转成对象,所以如果它们作为参数,就会报错。如果undefined
和null
不在首参数,就不会报错。
Object.assign(undefined) // 报错
Object.assign(null) // 报错
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
2
3
4
5
注意2:其他类型的值(即数值、字符串和布尔值)不在首参数,不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
2
3
4
5
注意3:Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),不拷贝不可枚举的属性(enumerable: false)。
Object.assign的特点
1、 Object.assign()是浅拷贝。
2、 同名属性的替换;(后者替换前者)
3、 数组的处理。Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
2
4、 取值函数的处理(求值后再复制)
const source = {
get foo() { return 1 }
};
const target = {};
Object.assign(target, source)
// { foo: 1 }
2
3
4
5
6
Object.assign的常见用途:
1、 为对象添加属性
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
2
3
4
5
2、 为对象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {},
anotherMethod() {}
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {};
SomeClass.prototype.anotherMethod = function () {};
2
3
4
5
6
7
3、 克隆对象(克隆自身与其继承的值)
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
2
3
4
4、 合并多个对象
const merge = (target, ...sources) => Object.assign(target, ...sources);
5、 为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
}
2
3
4
5
6
7
3、 Object.getOwnPropertyDescriptors()
返回指定对象所有自身属性(非继承属性)的描述对象。主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。
4、 __proto__属性
__proto__属性:
用来读取或设置当前对象的prototype
对象。目前,所有浏览器(包括 IE11)都部署了这个属性。建议不要使用此属性。使用下面的Object.setPrototypeOf()
(写操作)、Object.getPrototypeOf()
(读操作)、Object.create()
(生成操作)代替。
Object.setPrototypeOf():
用来设置一个对象的prototype对象。如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。 由于 undefined
和null
无法转为对象,所以如果第一个参数是undefined
或null
,就会报错。
Object.setPrototypeOf(1, {}) === 1 // true
Object.setPrototypeOf('foo', {}) === 'foo' // true
Object.setPrototypeOf(true, {}) === true // true
Object.setPrototypeOf(undefined, {})
// TypeError: Object.setPrototypeOf called on null or undefined
Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefined
2
3
4
5
6
7
Object.getPrototypeOf() 用于读取一个对象的原型对象。如果参数不是对象,会被自动转为对象。如果参数是undefined或null,它们无法转为对象,所以会报错。
Object.getPrototypeOf(1) === Number.prototype // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true
Object.getPrototypeOf(null)
// TypeError: Cannot convert undefined or null to object
Object.getPrototypeOf(undefined)
// TypeError: Cannot convert undefined or null to object
2
3
4
5
6
7
Object.create() 从指定原型对象创建一个新的对象.
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.sayName=function(){
console.log(this.name)
}
function Teacher(subject,name,age){
this.subject=subject;
return Person.call(this,name,age);//继承Person实例属性
}
//继承原型属性,
Teacher.prototype=Object.create(Person.prototype);
var person1=new Person();
var person2=Object.create(person1);
person2.__proto__===person1;//true;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
5、 Object.keys(),Object.values(),Object.entries()
Object.keys(): 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
Object.values(): 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是b、c、a。Object.values会过滤属性名为 Symbol 值的属性。
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"]
Object.values({ [Symbol()]: 123, foo: 'abc' });
// ['abc'] 会过滤属性名为 Symbol 值的属性。
如果参数不是对象,Object.values会先将其转为对象。如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。
Object.values('foo') // ['f', 'o', 'o']
Object.values(42) // []
Object.values(true) // []
2
3
4
5
6
7
8
9
Object.entries(): 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。除了返回值不一样,该方法的行为与Object.values基本一致。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
2
3
主要用途: 1、 遍历对象的属性。 2、 将对象转为真正的Map结构。
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
2
3
Object.fromEntries(): 是Object.entries()的逆操作,用于将一个键值对数组转为对象。
6、 Obj.hasOwnProperty(obj.prop)
返回一个布尔值,指示对象自身属性中是否具有指定的属性。和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
o = new Object();
o.prop = 'exists';
o.hasOwnProperty('prop'); // 返回 true
o.hasOwnProperty('toString'); // 返回 false
o.hasOwnProperty('hasOwnProperty'); // 返回 false
2
3
4
5