1、对象的扩展

1、 属性的简洁表示法

const foo = 'bar';
const baz = {foo};
console.log(baz)   // {foo: "bar"}
// 等同于
const baz = {foo: foo};
1
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}
1
2
3
4
5
6
7
8

2、 属性名表达式

  JavaScript 定义对象的属性,有两种方法。

// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
1
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"
1
2
3
4
5
6
7
8

  表达式还可以用于定义方法名。

let obj = {
	['h' + 'ello']() {
	return 'hi';
	}
};
obj.hello() // hi
1
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"}
1
2
3
4
5
6
7

3、 方法的name属性

const person = {
	sayName() {
		console.log('hello!');
	},
};
person.sayName.name   // "sayName"
1
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"
1
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 // ""
1
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"
1
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
//  }
1
2
3
4
5
6
7
8

  目前,有四个操作会忽略enumerablefalse的属性。

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()]
1

5、 对象的解构赋值

  对象的解构赋值 (在=赋值左边) 用于从一个对象取值,相当于将目标对象所有可遍历的(包括原型上的)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。属于浅拷贝

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1    y // 2      z // { a: 3, b: 4 }
1
2

对象的解构赋值的注意事项

1、由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefinednull,就会报错,因为它们无法转为对象。

let { x, y, ...z } = null; // 运行时错误
let { x, y, ...z } = undefined; // 运行时错误
1
2

2、扩展运算符必须是最后一个参数,否则会报错。

let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误
1
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
1
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
1
2

  扩展运算符: (在等号右边) 对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

对象的扩展运算符的注意事项

  1、由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。

let foo = { ...['a', 'b', 'c'] };
console.log(foo);// {0: "a", 1: "b", 2: "c"}
1
2

  2、 如果扩展运算符后面不是对象,则会自动将其转为对象。

// 等同于 {...Object(1)}
{...1} // {}    由于该对象没有自身属性,所以返回一个空对象。
// 等同于 {...Object(true)}
{...true} // {}
// 等同于 {...Object(undefined)}
{...undefined} // {}
// 等同于 {...Object(null)}
{...null} // {}
1
2
3
4
5
6
7
8

  3、 如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。

{...'hello'}    // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
1

  4、 对象的扩展运算符等同于使用Object.assign()方法。

let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
1
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)
)
1
2
3
4
5
6
7
8
9
10

  6、 扩展运算符可以用于合并两个对象。

let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
1
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}       
1
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
1
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
});
1
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}
1
2
3
4
5
6

注意1:由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。如果undefinednull不在首参数,就不会报错。

Object.assign(undefined) // 报错
Object.assign(null) // 报错
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
1
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" }
1
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]
1
2

4、 取值函数的处理(求值后再复制)

const source = {
	get foo() { return 1 }
};
const target = {};
Object.assign(target, source)
// { foo: 1 }
1
2
3
4
5
6

Object.assign的常见用途:

1、 为对象添加属性

class Point {
	constructor(x, y) {
	    Object.assign(this, {x, y});
	}
}
1
2
3
4
5

2、 为对象添加方法

Object.assign(SomeClass.prototype, {
	someMethod(arg1, arg2) {},
	anotherMethod() {}
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {};
SomeClass.prototype.anotherMethod = function () {}
1
2
3
4
5
6
7

3、 克隆对象(克隆自身与其继承的值)

function clone(origin) {
	let originProto = Object.getPrototypeOf(origin);
	return Object.assign(Object.create(originProto), origin);
}
1
2
3
4

4、 合并多个对象

const merge = (target, ...sources) => Object.assign(target, ...sources);
1

5、 为属性指定默认值

const DEFAULTS = {
	logLevel: 0,
	outputFormat: 'html'
};
function processContent(options) {
	options = Object.assign({}, DEFAULTS, options);
}
1
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对象。如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。 由于 undefinednull无法转为对象,所以如果第一个参数是undefinednull,就会报错。

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
1
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
1
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;
1
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) // []
1
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] ]
1
2
3

  主要用途: 1、 遍历对象的属性。 2、 将对象转为真正的Map结构。

const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
1
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
1
2
3
4
5
Last Updated: 3/16/2020, 6:57:04 PM