目录
- 1. 属性的简洁表示法
- 2. 属性名表达式
- 3. 方法的name属性
- 4. 属性的可遍历与枚举
- 5. super关键字
- 6. 对象的扩展运算符
- 6.1 对象扩展运算符与解构赋值结合使用
- 7. 链判断运算符
- 8. Null判断运算符
- 9. Object.is()
- 10. Object.assign()
- 11. Object.getOwnPropertyDescriptors()
- 12. __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
- 13. Object.keys(),Object.values(),Object.entries()
- 14. Object.fromEntries()
- 15. 参考链接
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
属性在该方法的属性的描述对象的get
和set
属性上面。
// 通过属性描述对象定义对象属性
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
的存取器,只能通过调用对象属性描述对象的get
和set
才能获取到方法名,但是由于存取器使用的是匿名函数,所以返回的方法名为get
和set
。在上面例子的基础上稍微进行一下修改。
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
属性在该方法的属性的描述对象的get
和set
属性上面,只是不同的写法,返回形式有所区别。
注意:
对于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 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
- 遍历所有数值键,按照数值升序排列。
- 遍历所有字符串键,按照加入时间升序排序.。
- 遍历所有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"
链判断运算符(?.
)的规则是:如果运算符左侧的运算子为null
或undefined
,就不再往下运算,而是返回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
上面代码中,如果a
是null
或undefined
,会直接返回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
判断运算符(??
)。它的行为类似||
,但是只有运算符左侧的值为null
或undefined
时,才会返回右侧的值。
// 使用||
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
上面代码中,使用||
,参数为0
,false
,''
,都会使用默认值;而使用??
只有当参数为null
和undefined
时,默认值才会生效。
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}
由于undefined
和null
无法转成对象,所以如果它们作为参数,就会报错。
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
如果非对象参数出现在源对象的位置。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined
和null
不在首参数,就不会报错。
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
如果第一个参数是undefined
或null
,会报错。
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));
如果参数是undefined
或null
,它们无法转为对象,所以会报错。
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.values
和Object.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); // []
如果参数是undefined
或null
,它们无法转为对象,所以会报错。
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); // []
如果参数是undefined
或null
,它们无法转为对象,所以会报错。
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。