主题
数组新增特性与对象新增特性(Day2 4/4)
本页对应 Day02_ES6,共 5 段视频,约 1 小时 4 分。
学习目标
- 能使用对象直接量新语法编写代码,包括属性简写、方法简写和用表达式作为属性名,并正确解释其展开形式。
- 能说明
super的指向规则及使用限制,编写示例验证其只与定义方法时所在对象有关、且只能在简写方法中使用。 - 能使用对象扩展运算符完成对象拷贝与合并,并正确说明同名属性覆盖规则及返回值/目标对象变化。
- 能列举并演示
Object.is、Object.keys、Object.values、Object.entries、Object.fromEntries、Object.getPrototypeOf、Object.setPrototypeOf、Object.hasOwn等Object构造函数的属性方法。 - 能使用
Object.assign合并或克隆对象,并区分“修改第一个参数对象”与“合并到空对象以返回新对象”的写法及结果。
知识点
本节笔记标题写作“4 数组新增特性”,但转写内容实际围绕对象新增特性展开:对象字面量的简写、super、对象扩展运算符、Object 构造函数自身方法以及对象合并。下面按转写稿中的推导顺序整理。
对象属性简写:变量名与属性名同名
为什么需要:用对象字面量声明对象时,如果属性值来自外部变量,传统写法要重复写“属性名: 变量名”,代码冗余。
怎么想:先准备变量,再在对象中把变量放进去;如果变量名恰好和属性名同名,就可以只写变量名。
怎么做:
js
const username = '高小乐';
const age = 19;
const address = '上海';
function say() {
console.log('say');
}
const user = { username, age, address, say };
console.log(user);易错点:简写的前提是“属性名和变量名同名”,不同名不能简写;say 这种函数变量也能按属性简写放入对象。
对象方法简写:去掉 function 关键字
为什么需要:对象里写方法时,传统 say: function () {} 比较啰嗦。
怎么想:方法也是对象属性的一种,ES6 允许在对象字面量中直接写“方法名() {}”。
怎么做:
js
const user = {
name: '高小乐',
getInfo() {
console.log(this.name);
},
setAge(age) {
this.age = age;
}
};
user.getInfo();易错点:方法简写不是“属性简写”,它是新的语法结构;简写方法之间仍要用逗号分隔。传统写法、箭头函数写法也能混用,但 super 的使用会受限制。
表达式作为属性名:中括号不能省
为什么需要:有时属性名来自变量或计算结果,不能提前写死。
怎么想:对象字面量中给属性名套上中括号,中括号里写表达式,运行时取表达式的值作为属性名。
怎么做:
js
const prop = 'homeAddress';
const user = {
[10 + 10]: '二十',
[prop]: '上海'
};
console.log(user); // 有 20 和 homeAddress 两个属性易错点:直接写 10 + 10: '二十' 是语法错误;写 prop: '上海' 时属性名是字符串 prop,不是变量值,必须写 [prop]。
super 关键字:看方法定义在哪里
为什么需要:对象方法中有时要访问原型上的同名属性,this 指向调用者,不能直接代替。
怎么想:this 看“谁调用方法”,super 看“方法定义在哪个对象里”,并指向该对象的原型。
怎么做:
js
const proto = { name: '高小乐原型' };
const user01 = {
name: '高小乐',
say() {
console.log(this.name);
console.log(super.name);
}
};
Object.setPrototypeOf(user01, proto);
user01.say(); // 高小乐 高小乐原型
const user02 = { name: '高老乐', say: user01.say };
Object.setPrototypeOf(user02, { name: '高老乐原型' });
user02.say(); // 高老乐 高小乐原型易错点:super 只与定义方法时所在的对象有关,与谁调用无关;它只能在对象简写方法中使用,写在普通 function 或箭头函数中会报错。调试时若把 user02.say 写成错误属性会报错,改正后才能看到上述结果。
对象扩展运算符:拷贝、合并与解构剩余
为什么需要:对象拷贝和合并很常见,直接赋值只是引用同一对象。
怎么想:...对象 会把对象拆成键值对序列,放进新的大括号中就形成新对象;合并时后面的同名属性覆盖前面的。
怎么做:
js
const user = { name: '高小乐', age: 45, address: '上海' };
const user01 = { ...user };
user01.name = '高老乐';
console.log(user.name); // 高小乐
const user02 = {
...user,
...{ homeAddress: '北京', address: '广州' }
};
console.log(user02.address); // 广州
const { name, age, ...rest } = user;
console.log(rest); // { address: '上海' }易错点:不能写 console.log(...user),对象拆成键值对序列后不能直接作为函数参数;合并时同名属性以后者为准;解构剩余得到的是对象。
Object 构造函数自身方法:判等、取值与原型
为什么需要:有些操作需要更精确的判断或批量取对象信息。
怎么想:注意这些是 Object 构造函数自身的方法,不是实例方法;keys/values/entries 返回数组。
怎么做:
js
const user = { name: '高小乐', age: 45, address: '上海' };
Object.is(NaN, NaN); // true
Object.is(+0, -0); // false
Object.keys(user); // ['name', 'age', 'address']
Object.values(user); // ['高小乐', 45, '上海']
Object.entries(user); // [['name','高小乐'], ...]
Object.fromEntries([['name', '高小乐'], ['age', 45]]);
Object.getPrototypeOf(user);
Object.hasOwn(user, 'name'); // true
Object.hasOwn(user, 'constructor'); // false易错点:Object.is 与 === 不完全一样:它能判断 NaN 等于 NaN,但认为 +0 和 -0 不相等;setPrototypeOf 改原型会丢失原原型方法,不能随意改数组原型。
Object.assign:合并到第一个参数
为什么需要:除了扩展运算符,还需要一种合并对象的方式。
怎么想:Object.assign(目标对象, 源对象1, 源对象2...) 把后面对象的属性合并到第一个参数,并返回第一个参数。
怎么做:
js
const user = { name: '高小乐', age: 45 };
const data = { homeAddress: '北京', address: '广州' };
const merged = Object.assign({}, user, data);
console.log(merged);
console.log(user); // 未被修改易错点:若写 Object.assign(user, data),会直接修改 user;想避免修改原对象,第一个参数传空对象 {}。它和扩展运算符都能做浅拷贝与合并,但 assign 的“目标对象”语义更明显。
属性简写:变量名和属性名同名才可省
为什么需要?用对象字面量声明对象时,经常要把外部变量塞进对象属性里,如果每次都写 name: name,重复且啰嗦。怎么想?ES6 允许在“属性名和变量名同名”时只写变量名。怎么做:
js
const userName = '高小乐';
const age = 19;
const address = '上海';
function say() {
console.log('say');
}
const user = {
userName,
age,
address,
say
};
console.log(user);这等价于 userName: userName, age: age...。易错点:只有同名才能简写;如果属性叫 name,变量叫 userName,仍要写 name: userName。转写稿里“自变量/直接量”是口误,实际是对象字面量;简写不是新概念,只是语法糖。
方法简写与表达式属性名
为什么需要?对象里写方法时,传统写法要写 function,比较冗长;属性名有时还需要根据变量或计算结果动态生成。怎么想?方法可以省略 function,写成 方法名(){};属性名想用表达式,就给属性名套中括号。怎么做:
js
const prop = 'homeAddress';
const obj = {
[10 + 10]: '二十',
[prop]: '上海',
getName() {
return this.name;
},
setAge(value) {
this.age = value;
}
};
console.log(obj[20]); // 二十
console.log(obj.homeAddress); // 上海易错点:[prop] 取的是变量 prop 的值作为属性名;直接写 prop 时属性名就是字符串 "prop"。方法简写和“属性简写”不是一回事:属性简写是用同名变量表示属性值,方法简写更像一种新的方法写法。普通函数、箭头函数不能替代这里的简写方法,尤其涉及 super 时。
super:指向方法定义时所在对象的原型
为什么需要?对象方法里有时要访问“当前对象原型”上的同名属性,用 this 只能拿到调用者自身或原型链结果,不能稳定表达“我定义在哪个对象的原型上”。怎么想?this 看谁调用,super 只看方法定义时所在的对象。怎么做:
js
const user01 = {
name: '高小乐',
say() {
console.log(this.name);
console.log(super.name);
}
};
Object.setPrototypeOf(user01, { name: '高小乐原型' });
user01.say(); // 高小乐 / 高小乐原型
const user02 = { name: '高老乐' };
Object.setPrototypeOf(user02, { name: '高老乐原型' });
user02.say = user01.say;
user02.say(); // 高老乐 / 高小乐原型调试时容易以为复制方法后 super 会跟着 user02 变,实际不会:this.name 变成“高老乐”,super.name 仍是 user01 原型上的“高小乐原型”。易错点:super 只能在对象字面量的简写方法中使用;写成普通函数或箭头函数会报语法错误。它和 this 的规则要对比记:this 与调用者有关,super 与定义位置有关。
对象扩展运算符:拷贝、合并、解构剩余
为什么需要?对象拷贝和合并很常见,手写循环麻烦。怎么想?...obj 在对象字面量中会把对象“拆成键值对序列”再放入新对象。怎么做:
js
const user = { name: '高小乐', age: 45, address: '上海' };
const copy = { ...user };
const merged = {
...user,
...{ homeAddress: '北京', address: '广州' }
};
const { name, age, ...rest } = user;
console.log(copy);
console.log(merged.address); // 广州,后者覆盖前者
console.log(rest); // { address: '上海' }易错点:不能写 console.log(...user),对象不能像数组那样直接拆成函数参数;{...user} 必须放在对象字面量或解构场景里;无变量直接写 {...} 可能被解析成块作用域,通常要赋值给变量。它是浅拷贝,同名属性后面的覆盖前面的。
Object.is:判等,但和 === 有差异
为什么需要?=== 判断 NaN 和 NaN 为 false,判断 +0 和 -0 为 true,有些场景需要更细的判等。怎么想?Object.is(a,b) 返回布尔值。怎么做:
js
console.log(Object.is(100, '100')); // false
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(0, -0)); // false
console.log(0 === -0); // true易错点:它和 === 很像但不完全一样:NaN 判等为 true,+0/-0 判等为 false。不要把它当万能相等判断。
Object.keys/values/entries/fromEntries 与原型、自身属性
为什么需要?经常要遍历对象属性、把对象转数组、再由数组转回对象,或查看/修改原型、判断自身属性。怎么想?这些是 Object 构造函数自身的静态方法,不是实例方法。怎么做:
js
const user = { name: '高小乐', age: 45, address: '上海' };
console.log(Object.keys(user)); // ['name','age','address']
console.log(Object.values(user)); // ['高小乐',45,'上海']
console.log(Object.entries(user)); // [['name','高小乐'], ...]
const obj = Object.fromEntries([['name', '高小乐'], ['age', 45]]);
console.log(obj);
console.log(Object.getPrototypeOf(user));
Object.setPrototypeOf(user, { address: '北京' });
console.log(Object.getOwnPropertyDescriptors(user));
console.log(Object.hasOwn(user, 'name')); // true
console.log(Object.hasOwn(user, 'constructor')); // false易错点:数组也有 keys/values/entries,但数组实例方法返回迭代器;这里 Object.keys 等返回数组。Object.fromEntries 的参数必须是“键值对数组”形式。setPrototypeOf 改原型后,原原型上的方法会丢,例如把数组原型改成普通对象后 push 就没了。hasOwn 判断的是对象自身属性,原型上的不算。
Object.assign:合并到第一个参数并返回它
为什么需要?除了扩展运算符,还需要一种“把多个源对象合并到目标对象”的方法。怎么想?Object.assign(target, ...sources) 把后面源对象的可枚举属性合并到 target,并返回 target。怎么做:
js
const user = { name: '高小乐', age: 45 };
const data = { address: '上海', homeAddress: '北京' };
const result = Object.assign({}, user, data);
console.log(result);
console.log(result === user); // false易错点:如果第一个参数写 user,会直接修改 user;不想改原对象就用 {} 作为目标。同名属性后面的覆盖前面的;它是浅拷贝。转写稿里“口贝/口背”是“拷贝”,“建值对”是“键值对”,“圆形”是“原型”,“辨离器”是“迭代器”。这些语法点不复杂,关键是记住规则并在调试时区分 this/super、扩展运算符和 Object.assign 的差异。
课堂画面





代码示例
代码一:对象直接量新语法(属性简写 / 方法简写 / 表达式属性名 / super)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>对象新增特性:直接量新语法 + super</title>
</head>
<body>
<script>
// 用 let 声明的全局变量不会挂到 window 上(只有 var 才会)
let name = '高小乐';
const userName = '高小乐';
const age = 19;
const address = '上海';
function say() {
console.log('say 被调用了');
}
// 1) 属性简写:变量名和属性名同名时,只写一个
const user = {
userName, // 等价于 userName: userName
age,
address,
say,
nickname: name // 不同名就不能简写,必须写全
};
console.log(user);
// 2) 方法简写:去掉 function 关键字
const obj = {
getInfo() { return 'getInfo'; },
setAge(v) { this.age = v; },
drink: () => 'drink' // 箭头函数也能定义方法,但里面不能用 super
};
obj.setAge(20);
console.log(obj.getInfo(), obj.age);
// 3) 表达式作为属性名:套上中括号,取表达式的值当属性名
const prop = 'homeAddress';
const dyn = {
[10 + 10]: '二十', // 属性名是 20
[prop]: '北京' // 属性名是 homeAddress
};
console.log(dyn['20'], dyn.homeAddress);
// 4) super:指向"方法所属对象"的原型,与谁调用该方法无关
const user01 = {
name: '高小乐',
say() {
console.log('this.name =', this.name); // 谁调用就指向谁
console.log('super.name =', super.name); // 方法所属对象的原型
}
};
// 相当于 user01.__proto__.name = '高小乐原型',
// 这里单独给一个原型对象,避免污染 Object.prototype
Object.setPrototypeOf(user01, { name: '高小乐原型' });
const user02 = { name: '高老乐' };
Object.setPrototypeOf(user02, { name: '高老乐原型' });
user02.say = user01.say; // 把方法复制给另一个对象
user02.say(); // this.name=高老乐,super.name=高小乐原型
const fn = user01.say;
fn(); // this 变成 window,super.name 仍是高小乐原型
// 易错点:super 只能写在"方法简写"里,下面两种都会语法报错
// const bad1 = { say: function () { console.log(super.name); } };
// const bad2 = { say: () => { console.log(super.name); } };
</script>
</body>
</html>要点:
- 属性简写的前提是变量名和属性名同名,不同名(如
nickname: name)必须写全,简写只是省字符,不是新语义。 - 方法简写
getInfo() {}是独立的新语法,和getInfo: function () {}等价;简写与不简写可以混用。 [表达式]里取的是表达式的值当属性名:[10 + 10]得到属性名20,[prop]取的是变量prop的值'homeAddress',不套中括号则属性名就是字符串"prop"。super指向方法定义时所在对象的原型,与调用者无关,是静态的;this才看谁调用。所以方法复制到user02上后,this变成高老乐,super仍指user01的原型。super只能在对象直接量的简写方法中使用,普通function方法和箭头函数里写super都会直接报语法错误。
代码二:对象的扩展运算符与 Object.assign
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>对象扩展运算符 / 合并对象</title>
</head>
<body>
<script>
const numbers = [100, 200, 300, 400, 500, 600];
const user = {
name: '高小乐',
age: 45,
address: '上海',
say() { console.log('say'); }
};
// 数组可以展开成函数参数
console.log(...numbers); // 100 200 300 400 500 600
// console.log(...user); // 报错:对象拆成键值对后不能当函数参数
// 1) 对象拷贝:{...user} 先创建了一个新对象,再把 user 拆进去
const user01 = { ...user };
user01.name = '高老乐';
console.log(user.name); // 高小乐(原对象不受影响)
console.log(user01.name); // 高老乐
// 2) 对象合并:同名属性,后边的覆盖前边的
const user02 = {
homeAddress: '北京',
address: '广州'
};
const user03 = { ...user02, ...user };
console.log(user03.address); // 上海(user 在后,覆盖了广州)
console.log(user03.homeAddress); // 北京
// 3) 解构赋值 + 剩余运算符,把剩下的属性收进一个新对象
const { name, age, ...rest } = user03;
console.log(name, age, rest); // 高小乐 45 { address, say, homeAddress }
// 数组也能被解构进对象,属性名是下标
const arr = [1, 2, 3];
const { ...arrObj } = arr;
console.log(arrObj); // { 0: 1, 1: 2, 2: 3 }
// 4) Object.assign:把后面对象合并进第一个参数,并返回第一个参数
const merged = Object.assign({}, user02, user);
console.log(merged.address); // 上海
console.log(user02.address); // 广州,原对象没有被改
</script>
</body>
</html>要点:
...对象是把对象拆成逗号分隔的键值对序列,只能放在{}里;console.log(...user)这种写法会报错,对象扩展运算符不能直接用于函数传参(数组可以)。- 写
{ ...user }时一定要赋值给变量或作为表达式使用;单独一个{...user}语句浏览器会当成块级作用域解析而报错。 - 合并时同名属性后者覆盖前者,且是浅拷贝,嵌套对象仍共享引用。
Object.assign(target, ...sources)会修改第一个参数并返回它;不想改动原对象时第一个参数传{},这与{...a, ...b}效果类似。- 解构中的
...rest是"剩余属性收集成对象",与扩展运算符方向相反;右边也可以直接是数组,得到的仍是普通对象(属性名为'0' '1' '2')。
代码三:Object 构造函数自身的方法
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Object 构造函数自身的方法</title>
</head>
<body>
<script>
// Object.is:判断两个值是否相等,返回布尔值
console.log(Object.is(100, 100)); // true
console.log(Object.is(100, '100')); // false(类型不同,像全等 ===)
console.log(Object.is(NaN, NaN)); // true (=== 判 NaN 是 false)
console.log(Object.is(+0, -0)); // false(=== 判正负 0 是 true)
console.log(NaN === NaN, +0 === -0); // false true
const user = {
name: '高小乐',
age: 45,
address: '上海',
say() {}
};
// keys / values / entries:注意返回的是数组,不是迭代器
console.log(Object.keys(user)); // ['name','age','address','say']
console.log(Object.values(user)); // ['高小乐',45,'上海',fn]
console.log(Object.entries(user)); // [['name','高小乐'],['age',45],...]
// fromEntries:entries 的逆运算,二维数组 -> 对象
const user02 = Object.fromEntries(Object.entries(user));
console.log(user02);
// getPrototypeOf / setPrototypeOf
console.log(Object.getPrototypeOf(user) === Object.prototype); // true
const arr = [];
Object.setPrototypeOf(arr, user); // 把 arr 的原型改成 user
console.log(arr.address); // 上海(沿原型链找到)
console.log(arr.push); // undefined —— 数组方法全没了!
// getOwnPropertyDescriptors:拿到每个属性的描述信息
console.log(Object.getOwnPropertyDescriptors(user));
// hasOwn:属性是否属于对象自身(原型上的、不存在的都是 false)
console.log(Object.hasOwn(user, 'name')); // true
console.log(Object.hasOwn(user, 'constructor')); // false(来自原型)
console.log(user.hasOwnProperty('name')); // true,老写法
</script>
</body>
</html>要点:
Object.is与===的差别只有两处:NaN与NaN判为true,+0与-0判为false,其余和全等一致。Object.keys/values/entries是构造函数自身的方法,返回数组;数组实例上的同名方法返回的是迭代器,两者不要混淆。Object.entries得到二维数组,Object.fromEntries是它的逆运算,但要求参数必须是"每个元素都是两个成员的数组",格式不对会报错。Object.setPrototypeOf会改掉原型链,arr改成以user为原型后push等数组方法全部丢失;一般用__proto__赋值或Object.create也能达到同样效果。Object.hasOwn(obj, key)只判断自身属性,原型上的constructor返回false;它与obj.hasOwnProperty(key)作用相同,但不受对象自身覆盖hasOwnProperty的影响。
常见错误
- 属性简写写成
{ username }但变量实际叫userName→ 变量名与属性名不同名,简写不成立,运行时取不到预期变量 → 改为{ userName: username }或统一变量名。 - 方法简写写成
say: () => { super.name }→ 箭头函数不能使用super,且普通函数形式也不允许 → 使用对象方法简写say() { ... }。 - 计算属性名写成
{ prop: 'homeAddress' }却期望属性名取变量值 → 未加中括号,prop被当成普通属性名 → 改为{ [prop]: 'homeAddress' }。 - 直接
console.log(...user)展开对象 → 对象扩展会拆成键值对序列,不能作为普通函数参数列表使用 → 用console.log({ ...user })或先拷贝到新对象。 - 用
Object.assign(user, data)后原user被改 →assign默认修改并返回第一个参数 → 不想改原对象时写Object.assign({}, user, data)。 - 用
Object.is(NaN, NaN)与Object.is(0, -0)时按===经验判断 → 两者规则不同:前者为true,后者为false→ 明确判等需求,必要时组合===与Object.is。
练习
- 给定
const name = '高小乐'; const age = 19;,用对象字面量简写创建user,并添加简写方法say()输出this.name。 - 给定
const prop = 'homeAddress';,创建对象使其拥有动态属性名homeAddress,并说明不加中括号时属性名是什么。 - 用两种方式实现对象浅拷贝:对象扩展运算符、
Object.assign,并写出不修改原对象的合并写法。 - 给定对象
user03,用解构赋值取出age、address,并用剩余属性得到rest;再说明rest的类型。 - 判断并写出结果:
Object.is(NaN, NaN)、Object.is(0, -0)、Object.hasOwn(user, 'name')与Object.hasOwn(user, 'constructor')的差异。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 直接量方式声明对象的新语法 | 11:09 |
| 2 | super关键字 | 14:35 |
| 3 | 对象的扩展运算符 | 11:48 |
| 4 | Object构造函数自身的属性方法 | 17:54 |
| 5 | 合并对象 | 09:06 |
小结
- 对象字面量简写只适用于“变量名与属性名同名”的属性;不同名必须显式写键值对。
- 方法简写不仅是语法糖,也是使用
super的前提;普通函数、箭头函数中写super会报错。 - 计算属性名必须使用
[表达式],否则表达式文本会被当作固定属性名。 super指向方法定义时所在对象的原型,与调用者无关;对象方法被复制到其他对象后该规则仍不变。- 对象扩展运算符和
Object.assign都常用于浅拷贝与合并;后者会修改第一个参数,前者不会修改被展开对象。 Object.keys/values/entries返回数组,fromEntries是entries的逆操作;Object.is与===在NaN、+0/-0上结果不同。- 对象解构中的剩余属性
...rest得到的是对象;数组也可被解构到对象剩余属性中,但结果形态是对象而非数组。