ECMAScript 6之对象的扩展

1. 属性的简洁表示法

ES6 允许在大括号({})里面,直接写入变量和函数,作为对象的属性和方法。

const name = "jidi";
const age = 22;

let person = {name, age};
//  等价于
let person = {name: name, age: age}

person; // {name: "jidi", age: 22}

方法也可以简写。

const cat = {
	name: "tudou",
	age: 1,
	eat() {
		console.info(this.name + "在吃饭!");
	}
	// 等价于
	eating: function() {
		console.info(this.name + "在吃饭!");
	}
}

cat.eat(); // tudou在吃饭!

但是构造函数中,不能使用简写的方法。

function Person(mame, age) {
    this.name = name;
    this.age = age;
    getAge () {
        return age;
    }
} // Uncaught SyntaxError: Unexpected token '{'

2. 属性名表达式

JavaScript中定义对象的属性,有以下方式。

var person = { name: "jidi" };

// 方式一,直接使用标识符作为属性名
person.age = 22;
person; //  {name: "jidi", age: 22}

// 方式二,使用表达式作为属性名
person["sex"] = "男";
person; // {name: "jidi", age: 22, sex: "男"}

上面代码中,方式一是直接使用标识符作为属性名,方式二是在方括号([])里面使用表达式作为属性名。

在ES5中使用字面量定义对象属性,只能使用方式一。

// ES5中,直接使用标识符作为变量名
const obj = {
	name: 'jidi',
	age: 22
}

obj; // {name: "jidi", age: 22}

在ES6中使用字面量定义对象属性,允许在方括号([])中使用表达式作为属性名。

// ES6写法
let m = "sex";
const obj = {
	["n" + "ame"]: "jidi",
	["age"]: 22,
	[m]: "男"
}

obj; // {name: "jidi", age: 22, sex: "男"}

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

let x = "printName";

const obj = {
	["name"]: "jidi",
	[x]() {
		return this.name;
	}
}

obj[x](); // "jidi"
obj.printName(); // "jidi"

属性名表达式与简洁表示法,不能同时使用。

let name = "jidi";

// 不能这样使用
let obj = {
	[name]
}

// 可以这样使用
let obj = {
	name: name
}

3. 方法的name属性

函数的name属性,会返回函数名。由于对象方法也是函数,因此也有name属性。

var person = {
	name: "jidi",
	age: 22,
	eat() {
		console.log(`${this.name}在吃饭!`)
	}
}

// 获取对象方法name
person.eat.name; // "eat"

如果对象的方法使用了存取器,则name属性在该方法的属性的描述对象的getset属性上面。

// 通过属性描述对象定义对象属性
var person = Object.defineProperty({}, "name", {
    get: function () {
        return "jidi-2020";
    },
    set: function (name) {
        console.info(name);
    } 
} )

// 通过对象本身调用,获取不到方法名
person.name.name; // undefined

// 通过属性描述对象的get和set属性获取方法名
Object.getOwnPropertyDescriptor(person,"name").get.name; // "get"
Object.getOwnPropertyDescriptor(person,"name").set.name; // "set"

上面的代码中,定义了对象person属性name的存取器,只能通过调用对象属性描述对象的getset才能获取到方法名,但是由于存取器使用的是匿名函数,所以返回的方法名为getset。在上面例子的基础上稍微进行一下修改。

var person = Object.defineProperty({}, "name", {
    get: function m() {
        return "jidi-2020";
    },
    set: function n(name) {
        console.info(name);
    } 
} )

Object.getOwnPropertyDescriptor(person,"name").get.name; // "m"
Object.getOwnPropertyDescriptor(person,"name").set.name; // "n"

上面例子,存取器使用的函数具有名字,就会返回对应的名字。
下面是另一个采用了另一种写法的存取器例子。

var person = {
  get name() {
    return "jidi"
  },
  set name(value) {
    console.info(value)    
  }
};

Object.getOwnPropertyDescriptor(person,"name").get.name; // "get name"
Object.getOwnPropertyDescriptor(person,"name").set.name; // "set name"

从上面的例子可以看出,对象属性采用了存取器,则name属性在该方法的属性的描述对象的getset属性上面,只是不同的写法,返回形式有所区别。

注意:对于bind方法创造的函数,name属性返回bound加上原函数的名字;Function构造函数创造的函数,name属性返回anonymous

(new Function()).name; // "anonymous"

var do = function() {
  // ...
};
do.bind().name // "bound do"

4. 属性的可遍历与枚举

4.1 可枚举性

对象的每个属性都有一个对应的属性描述对象,用来控制该属性的行为。

var person = {
	name: "jidi",
	age: 22
}

Object.getOwnPropertyDescriptor(person, "name" ); 
// {
//		value: "jidi",
//		writable: true, 
//		enumerable: true, 
//		configurable: true
// }

上面代码,通过Object.getOwnPropertyDescriptor()方法,获得person对象name属性的属性描述对象。

属性描述对象的enumerable属性,布尔值,表示该属性是否可枚举。目前有四个操作会忽略enumerable属性值为false的对象属性。

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

4.2 可遍历性

ES6中有以下方法可以遍历对象的属性。
1.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属性键,按照加入时间升序排序。

5. super关键字

ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。

let proto = {
    name: "jidi" 
}

let sun = {
	name: "xuxiake"
    get name(){
        return super.name;
    }
}

// 设置原型对象
Object.setPrototypeOf(sun, proto);

sun.name; // "jidi"

上面代码中,将对象proto设置为对象sun的原型对象,然后在存取器中使用super调用原型对象的属性name,结果返回的是原型对象的name

super关键字表示原型对象时,只能用在对象的方法之中(需要写成使用方法简写),用在其他地方都会报错。

6. 对象的扩展运算符

ES2018 将扩展运算符(...)引入了对象。

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

let person = {
	name: "jidi",
	age: 22
}

let p = {...person}; // {name: "jidi", age: 22}

如果扩展运算符后面是一个空对象,则没有任何效果。

let obj = {...{}, a: 1}; // {a: 1}

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

{...1}; // {}
{...true}; // {}

上面代码中,整数1和布尔值true会被自动转为各自的包装对象,由于包装对象本身没有自身属性,故会返回空对象{}

如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象。

{..."Hello"}; // {0: "H", 1: "e", 2: "l", 3: "l", 4: "o"}

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

var person = {
	name: "jidi",
	age: 22
}

{...person}; // {name: "jidi", age: 22}	
// 等价于
Object.assign({}, person); // {name: "jidi", age: 22}

6.1 对象扩展运算符与解构赋值结合使用

对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。

// 对象扩展运算符和解构赋值结合使用
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
z; // {a: 3, b: 4}

对象的解构赋值要求等号右边是一个对象,否则会报错。

// 解构赋值右边必须是对象
let { ...z } = null; // Uncaught TypeError: Cannot destructure 'null' as it is null.
let { ...z } = undefined; // Uncaught TypeError: Cannot destructure 'undefined' as it is undefined

解构赋值必须是最后一个参数,否则会报错。

let person = {
	name: "jidi",
	age: 22,
	sex, "男"
}

let {sex,...last} = person;
sex; // "男"
last; // {name: "jidi", age: 22}

// 如果解构赋值中,扩展运算符不是最后一个参数,会报错
var {...last, name} = person; // Uncaught SyntaxError: Rest element must be last element

注意:解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值,那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

7. 链判断运算符

要读取对象内部属性,一般会判断一下该属性是否存在,如果该对象是一个多层嵌套对象,往往判断起来特别麻烦。

var person = {
	name: {
		firstWord: {
			english: "ji",
			chinese: "基"
		}
	}
}

// 读取english属性
var english = (person && person.name && person.name.firstWord && person.name.firstWord.english ); // "ji"

上面代码中,一层一层的进行判断非常麻烦,因此ES2020引入了链判断运算符(?.),简化上面的写法。

var english = person ?. name ?. firstWord ?. english; // "ji"

链判断运算符(?.)的规则是:如果运算符左侧的运算子为nullundefined,就不再往下运算,而是返回undefined。下面是链判断运算符常用形式。

a ?. b ;
// 等价于
a == null ? undefined : a.b	;

a ?. [x];
// 等价于
a == null ? undefined : a[x]

a ?. b();
// 等价于
a == null ? undefined : a.b()

a?.();
// 等价于
a == null ? undefined : a()

7.1 链判断运算符使用注意点

使用链判断运算符,有以下几个地方需要注意。
(1)短路机制,链判断运算符一旦为真,右侧的表达式不会进行计算。

(2)delete运算符

delete a?.b
// 等价于
a == null ? undefined : delete a.b

上面代码中,如果anullundefined,会直接返回undefined,而不会进行delete运算。

(3)括号
如果属性链有圆括号(()),链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。

(a?.b).c
// 等价于
(a == null ? undefined : a.b).c

上面代码,不管a是否存,.c都会执行。

(4)禁止使用场合
链判断运算符不能用在下列场合,否则会报错。

  • 构造函数
  • 链判断运算符右侧有模板字符串
  • 链判断运算符左侧是super
  • 链运算符用于赋值运算符左侧
// 构造函数
new a?.()
new a?.b()

// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`

// 链判断运算符的左侧是 super
super?.()
super?.foo

// 链运算符用于赋值运算符左侧
a?.b = c

(5)右侧不得为十进制的数

8. Null判断运算符

ES2020 引入了Null 判断运算符(??)。它的行为类似||,但是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

// 使用||
function add(x) {
	x = x || 5;
	return x*x;
}
add(); // 25
add(0); // 25
add(false); // 25
add(''); //25

// 使用??
function add (x) {
	x = x ?? 5;
	return x*x;
}
add(false); // 0
add(''); // 0
add(0); // 0

上面代码中,使用||,参数为0false'',都会使用默认值;而使用??只有当参数为nullundefined时,默认值才会生效。

9. Object.is()

ES5比较两个值是否相等,只能通过=====。它们都有一定的缺陷。前者会进行自动数据类型转换,后者+0等于-0,以及NaN不等于自身。
ES6引入了Object.is()方法,用来比较两个值是否严格相等,与===行为基本一致,只是有两处不同:NaN等于自身,+0不等于-0

+0 === -0; //true
NaN === NaN; // false

Object.is(+0, -0); // false
Object.is(NaN, NaN); // true

10. Object.assign()

Object.assign()方法用于对象的合并,将源对象的所有可枚举属性,复制到目标对象。Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象。

var person = {
	name: "jidi",
	age: 22
}
var man = {
	sex: "男"
}

let obj = {};
Object.assign(obj, person, man);
obj; // {name: "jidi", age: 22, sex: "男"}

如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

let x = { a: 1, b: 1 };

let y = { b: 2, c: 2 };
let z = { c: 3 };

Object.assign(x, y, z);
x; // {a:1, b:2, c:3}

如果只有一个参数,Object.assign会直接返回该参数。

let x = { a: 1};
Object.assign(x); // {a: 1}

如果该参数不是对象,则会先转成对象,然后返回。

Object.assign(22); // Number {22}

由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。

Object.assign(null); // Uncaught TypeError: Cannot convert undefined or null to object at Function.assign 
Object.assign(undefined);; // Uncaught TypeError: Cannot convert undefined or null to object at Function.assign 

如果非对象参数出现在源对象的位置。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefinednull不在首参数,就不会报错。

let x= {a: 1};
Object.assign(x, undefined); // {a: 1}
Object.assign(x, null); // {a: 1}

其他类型的值不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。

// 基本数据类型的值作为源对象,除了字符串外,都不会产生任何效果
Object.assign({},"jidi", 12, true, NaN); // {0: "j", 1: "i", 2: "d", 3: "i"}

Object.assign只拷贝源对象的自身可枚举的属性。

Object.assign({name: 'jidi'},
  Object.defineProperty({}, 'flag', {
    enumerable: false,
    value: true
  })
)
// {name: "jidi"}

上面代码中,属性flag不可枚举,不会被Object.assign()拷贝。

10.1 Object.assign()使用注意点

(1)浅拷贝

Object.assign方法实行的是浅拷贝,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。

let x = {a: {b: 1}};
let y = Object.assign({}, x);

// 浅拷贝,对象的变化会反应到目标对象上面
x.a.b = 2;
y.a.b; // 2

(2)同名属性的替换

遇到同名属性,Object.assign()会进行替换。

let x = { a: { b: 'jidi', d: 'e' } };
let y = { a: { b: '基地' } };

Object.assign(x, y); 
x; // { a: { b: '基地' } }

(3)数组的处理

数组也是对象,Object.assign()可以用来处理数组。

Object.assign([1, 2, 3], [4, 5]); //  [4, 5, 3]

上面代码中,数组[1, 2, 3][4, 5]会被当成对象处理,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1

(4)取值函数的处理
Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。

ar person = {
  get name() { return "jidi" }
};
let p = {};

// 取值函数先取值再赋值
Object.assign(p, person); // {name: "jidi"}

10.2 Object.assign()用途

Object.assign()方法有很多用处。

(1)为对象添加属性

var person = {
	name: "jidi"
}

// 将属性age添加给person对象
Object.assign( person, {age: 22});

person; // {name: "jidi", age: 22}

(2)为对象添加方法

// 将方法print添加给对象person
Object.assign(person, {
    print () {
        console.info("哈哈");
    }
})

person.print(); // 哈哈

(3)克隆对象

// 定义函数,用来克隆对象
function cloneObj(obj) {
	return Object.assign({}, obj);
}

(4)合并多个对象

let obj = (...objs) => Object.assign({}, ...objs);

11. Object.getOwnPropertyDescriptors()

ES2017 引入了Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性的描述对象。

var person = {
	name: "jidi",
	age: 22
}

Object.getOwnPropertyDescriptors(person);

//{	name: {
//	value: "jidi", 
//	writable: true, 
//	enumerable: true, 
//	configurable: true }
//	age: {
//	value: 22, 
//	writable: true, 
//	enumerable: true, 
//	configurable: true	}
//}

Object.getOwnPropertyDescriptors()的实现比较容易。

fuction getOwnPropertyDescriptors(obj) {
	const res = [};
	 for (let key of Reflect.ownKeys(obj)) {
		 res[key] = Object.getOwnPropertyDescriptor(obj, key);
	}
	return res;
}

12. __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()

12.1 __proto__属性

__proto__属性,用来读取或设置当前对象的prototype对象。目前,所有浏览器都部署了这个属性。该属性没有写入 ES6 的正文,而是写入了附录。只有浏览器必须部署这个属性,其他运行环境不一定需要部署。

12.2 Object.setPrototypeOf()

Object.setPrototypeOf()方法用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

let proto = {
	eat () {
		console.info("吃饭.....")
	},
	sleep () {
		console.info("睡觉.....")
	}
}

let person = {
	name: "jidi",
	age: 22
}

// 将proto对象设置为person对象的原型对象
Object.setPrototypeOf(person, proto);

person.eat(); // "吃饭....."

如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。

Object.setPrototypeOf(1, {}) === 1 // true
Object.setPrototypeOf('jidi', {}) === 'jidi' // true
Object.setPrototypeOf(true, {}) === true // true

如果第一个参数是undefinednull,会报错。

Object.setPrototypeOf(null, {}); // TypeError: Object.setPrototypeOf called on null or undefined
Object.setPrototypeOf(undefined, {}); // TypeError: Object.setPrototypeOf called on null or undefined

12.3 Object.getPrototypeOf()

该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。

let s = "jidi";

Object.getPrototypeOf(s); // String {"", constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}length: 0constructor: ƒ String()anchor: ƒ anchor()big: ƒ big()blink: ƒ blink()bold: ƒ bold()charAt: ƒ charAt()charCodeAt: ƒ charCodeAt()codePointAt: ƒ codePointAt()concat: ƒ concat()endsWith: ƒ endsWith()fontcolor: ƒ fontcolor()fontsize: ƒ fontsize()fixed: ƒ fixed()includes: ƒ includes()indexOf: ƒ indexOf()italics: ƒ italics()lastIndexOf: ƒ lastIndexOf()link: ƒ link()localeCompare: ƒ localeCompare()match: ƒ match()matchAll: ƒ matchAll()normalize: ƒ normalize()padEnd: ƒ padEnd()padStart: ƒ padStart()repeat: ƒ repeat()replace: ƒ replace()search: ƒ search()slice: ƒ slice()small: ƒ small()split: ƒ split()strike: ƒ strike()sub: ƒ sub()substr: ƒ substr()substring: ƒ substring()sup: ƒ sup()startsWith: ƒ startsWith()toString: ƒ toString()trim: ƒ trim()trimStart: ƒ trimStart()trimLeft: ƒ trimStart()trimEnd: ƒ trimEnd()trimRight: ƒ trimEnd()toLocaleLowerCase: ƒ toLocaleLowerCase()toLocaleUpperCase: ƒ toLocaleUpperCase()toLowerCase: ƒ toLowerCase()toUpperCase: ƒ toUpperCase()valueOf: ƒ valueOf()Symbol(Symbol.iterator): ƒ [Symbol.iterator]()__proto__: Object[[PrimitiveValue]]: ""

如果参数不是对象,会被自动转为对象。

Object.getPrototypeOf(1);
// 等价于
Object.getPrototypeOf(Number(1))


Object.getPrototypeOf(true);
// 等价于 
Object.getPrototypeOf(Boolean(true));

如果参数是undefinednull,它们无法转为对象,所以会报错。

Object.getPrototypeOf(null); //Uncaught TypeError: Cannot convert undefined or null to object at Function.getPrototypeOf 

13. Object.keys(),Object.values(),Object.entries()

ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的所有可遍历属性的键名。
ES2017 引入了跟Object.keys配套的Object.valuesObject.entries,作为遍历一个对象的补充手段,供for...of循环使用。

13.1 Object.values()

Object.values方法返回一个数组,成员是参数对象自身的所有可遍历属性的键值。

let person = {
	name: "jidi",
	age: 22
}

 Object.values(person); // ["jidi", 22] 

Object.values只返回对象自身的可遍历属性。

let person = Object.create({}, { name:
  {
    value: "jidi",
    enumerable: false
  }
});

// 只返回对象自身可遍历的属性
Object.values(person ) // []

如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。

Object.values("jidi"); // ["j", "i", "d", "i"]

如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。

Object.values(42); // []
Object.values(true); // []

如果参数是undefinednull,它们无法转为对象,所以会报错。

Object.values(null); // Uncaught TypeError: Cannot convert undefined or null to object at Function.values 

13.2 Object.entries()

Object.entries()方法返回一个数组,成员是参数对象自身的所有可遍历属性的键值对数组。

let person = {
	name: "jidi",
	age: 22
}

Object.entries(person); // [["name", "jidi"], ["age", 22]]

如果Object.entries方法的参数是一个字符串,会返回各个字符组成的键值对。

Object.entries("jidi");  // [ ["0", "j"],  ["1", "i"], ["2", "d"], ["3", "i"]]

如果参数不是对象,Object.entries会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.entries会返回空数组。

Object.entries(true); // []
Object.entries(22); // []

如果参数是undefinednull,它们无法转为对象,所以会报错。

Object.entries(null); // Uncaught TypeError: Cannot convert undefined or null to object at Function.entries

14. Object.fromEntries()

Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。

let person = {
	name: "jidi",
	age: 22
}

let x = Object.entries(person); // [["name", "jidi"], ["age", 22]]

Object.fromEntries(x); // {name: "jidi", age: 22}

15. 参考链接

本篇博文是我自己学习笔记,原文请参考:ECMAScript 6 入门
如有问题,请及时指出!
欢迎沟通交流,邮箱:jidi_jidi@163.com

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值