Skip to content

数组新增特性与对象新增特性(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 的差异。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

代码一:对象直接量新语法(属性简写 / 方法简写 / 表达式属性名 / 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 的影响。

常见错误 ​

  1. 属性简写写成 { username } 但变量实际叫 userName → 变量名与属性名不同名,简写不成立,运行时取不到预期变量 → 改为 { userName: username } 或统一变量名。
  2. 方法简写写成 say: () => { super.name } → 箭头函数不能使用 super,且普通函数形式也不允许 → 使用对象方法简写 say() { ... }。
  3. 计算属性名写成 { prop: 'homeAddress' } 却期望属性名取变量值 → 未加中括号,prop 被当成普通属性名 → 改为 { [prop]: 'homeAddress' }。
  4. 直接 console.log(...user) 展开对象 → 对象扩展会拆成键值对序列,不能作为普通函数参数列表使用 → 用 console.log({ ...user }) 或先拷贝到新对象。
  5. 用 Object.assign(user, data) 后原 user 被改 → assign 默认修改并返回第一个参数 → 不想改原对象时写 Object.assign({}, user, data)。
  6. 用 Object.is(NaN, NaN) 与 Object.is(0, -0) 时按 === 经验判断 → 两者规则不同:前者为 true,后者为 false → 明确判等需求,必要时组合 === 与 Object.is。

练习 ​

  1. 给定 const name = '高小乐'; const age = 19;,用对象字面量简写创建 user,并添加简写方法 say() 输出 this.name。
  2. 给定 const prop = 'homeAddress';,创建对象使其拥有动态属性名 homeAddress,并说明不加中括号时属性名是什么。
  3. 用两种方式实现对象浅拷贝:对象扩展运算符、Object.assign,并写出不修改原对象的合并写法。
  4. 给定对象 user03,用解构赋值取出 age、address,并用剩余属性得到 rest;再说明 rest 的类型。
  5. 判断并写出结果:Object.is(NaN, NaN)、Object.is(0, -0)、Object.hasOwn(user, 'name') 与 Object.hasOwn(user, 'constructor') 的差异。

原视频对照 ​

#视频时长
1直接量方式声明对象的新语法11:09
2super关键字14:35
3对象的扩展运算符11:48
4Object构造函数自身的属性方法17:54
5合并对象09:06

小结 ​

  1. 对象字面量简写只适用于“变量名与属性名同名”的属性;不同名必须显式写键值对。
  2. 方法简写不仅是语法糖,也是使用 super 的前提;普通函数、箭头函数中写 super 会报错。
  3. 计算属性名必须使用 [表达式],否则表达式文本会被当作固定属性名。
  4. super 指向方法定义时所在对象的原型,与调用者无关;对象方法被复制到其他对象后该规则仍不变。
  5. 对象扩展运算符和 Object.assign 都常用于浅拷贝与合并;后者会修改第一个参数,前者不会修改被展开对象。
  6. Object.keys/values/entries 返回数组,fromEntries 是 entries 的逆操作;Object.is 与 === 在 NaN、+0/-0 上结果不同。
  7. 对象解构中的剩余属性 ...rest 得到的是对象;数组也可被解构到对象剩余属性中,但结果形态是对象而非数组。