ES6扩展 对象扩展

学习归纳下ES6中和对象相关的新方法

简洁表示法(简写对象属性、方法)

  • 简写对象属性:对象的属性名与变量名同名后可从属性名:变量名简写为属性名
  • 【常用但易忘】简写对象方法:对象的方法可以直接省略冒号和function,从方法名:function(){函数体}简写为方法名(){函数体}
  • 例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const getUserInfo = (id = 1) => {
    //AJAX...

    const name = "xiaoming";
    const age = 10;

    return {
    name,//相当于`name:name`简写以后属性名name会自动去找到和它同名的变量,把它的值拿过来当值
    age,//相当于`age:age`
    say() {//相当于say:function(){},简写可以省略冒号与function
    console.log(this.name + this.age);
    }
    };
    };
    const xiaoming = getUserInfo();
    console.log(xiaoming);//{name: "xiaoming", age: 10, say: ƒ}
    console.log(xiaoming.say());//xiaoming10

属性名表达式

  • 定义属性时可以用一个简单的表达式。
    • 即:对象属性名可以包含 常量/变量
  • 语法:先定义一个常量/变量,然后用[包含常量/变量的简单表达式]: 属性值定义属性
    • 补充:ES6用const定义常量,let定义变量
      1
      2
      3
      4
      5
      6
      7
      const key="age"; // 先定义一个常量key
      const xiaoming ={
      name: "xiaoming",
      [`now${key}`]: 14, // 注意:模板字符串是反引号
      };
      // xiaoming[`now${key}`] = 14 // 或者这样定义
      console.log(xiaoming);//{name: "xiaoming", nowage: 14}
  • 补充:对象属性名带特殊字符(比如-):
    • 例子:定义let obj = { 'SVC-TYD': '1' },调用obj['SVC-TYD'] = '2'
  • 注意:对象属性名包含 常量/变量,使用 模板字符串 时,必须搭配中括号[``]对象属性名带特殊字符,使用引号时可以不用中括号

复习:扩展运算符复制对象

  • 使用扩展运算符复制对象浅拷贝的。
  • 也就是说当你复制的对象obj1中包含一个对象c时,你实际上复制的是c的引用,当你修改复制所得到的对象copyObj1中的对象c的aa属性时,被复制的obj1对象中的对象c的属性aa也会被改变。
  • 而修改对象copyObj1的第一层属性b时是不会改动到复制对象obj1的属性b的值的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const obj1={
a:1,
b:2,
c:{
aa:3,
bb:4
}
};
const copyObj1={...obj1};
console.log(copyObj1.c.aa);//3
copyObj1.c.aa=999;
console.log(copyObj1.c.aa);//999
console.log(obj1.c.aa);//999,可以发现obj1的c中的aa也被修改了

console.log(copyObj1.b);//2
copyObj1.b=222;
console.log(copyObj1.b);//222
console.log(obj1.b);//2,可以看到修改对象copyObj1的第一层属性b时是不会改动到复制对象obj1的属性b的值的

复习:扩展运算符合并对象

  • 如果合并的两个对象中都含有相同属性名的属性,则属性顺序还是按照前面对象的,但后面对象的属性值覆盖前面的
  • 不管是使用扩展运算符复制对象还是合并对象,他们都是浅拷贝的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const obj1 = {
a: 1,
b: 2,
c: {
aa: 3,
bb: 4
},
s:8
};
const obj2={
a:555,
d:33
};
const newObj={
...obj1,
...obj2
};
console.log(newObj);//{a: 555, b: 2, c: {…}, s: 8, d: 33}

newObj.c.aa=999;
console.log(obj1.c.aa);//999,证明合并对象也是浅拷贝的

部分新方法

Object.is()判断两个值是否为同一个值

  • 可参考MDN
  • 语法:传入两个参数Object.is(value1, value2)
  • 返回值:全等则返回true,否则返回false。
  • 满足以下任意条件则两个值相等:
    • 都是 undefined
    • 都是 null
    • 都是 true 或都是 false
    • 都是相同长度、相同字符、按相同顺序排列的字符串
    • 都是相同对象(意味着都是同一个对象的值引用)
    • 都是数字且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 都是同一个值,非零且都不是 NaN
  • ==不同, == 运算符在判断相等前对两边的变量(如果它们不是同一类型)进行强制转换(这种行为将 "" == false 判断为 true),而 Object.is() 不会强制转换两边的值
  • ===类似但有区别,体现在+0与-0NaN与NaN的判断上
    • 它的判断规则与全等运算符(===)有一些不同。在比较引用类型时,Object.is() 只有在两个值的指向地址完全相同时才返回 true
      1
      2
      3
      4
      5
      6
      7
      8
      console.log(Object.is(+0, -0));//false
      console.log(+0 === -0);//true

      console.log(Object.is(NaN, NaN));//true
      console.log(NaN === NaN);//false

      console.log(Object.is(true, false));//false
      console.log(true === false);//false

Object.assign()合并对象(类似...扩展运算符)

  • 可参考MDN
  • 补充: assign: 分配/赋值
  • 语法Object.assign(target, ...sources);
  • 返回目标对象,此时的目标对象也会改变
  • Object.assign()类似...(扩展运算符),可用于合并对象
  • 拓展:在vue中使用 Object.assign()/...扩展运算符 可以使复制的对象保持双向绑定关系
  • Object.assign()与扩展运算符在合并对象时都是浅拷贝的。当合并的多个对象拥有相同属性时后面的属性值覆盖前面的
1
2
3
4
5
6
7
8
9
10
11
12
13
// 可以看到b的最终属性值是2,后面的覆盖前面的
const allObj = Object.assign({ a: 1 }, { b: 3 }, { b: 2 }, { c: 3, d: 4 });
console.log(allObj); // {a: 1, b: 2, c: 3, d: 4}
const obj = { h: 1, i: { j: 222, a: 66 } }
const newObj = Object.assign(allObj, obj);
console.log(newObj); // {a: 1, b: 2, c: 3, d: 4, h: 1, i: {j: 222, a: 66} }

// 返回目标对象,此时的目标对象也会改变:
// allObj === newObj // true

//浅拷贝
newObj.i.j=999;
console.log(obj.i.j);//999

Object.keys()Object.values()Object.entries()

  • 注意:这三个方法都是返回对象自身的对应元素的,当存在对象属性值为对象时,不包含属性值对象的元素
  • 参数:对象
  • 注意: “自身”也就是不包括继承自原型的属性
  • Object.keys()返回由对象自身可枚举属性名组成的数组 (不包括 Symbol 值作为名称的属性/原型链上的属性)
    • 注意:数组每一项都是放在引号内的【字符串形式
  • Object.values()返回由对象自身值(属性值)组成的数组
  • Object.entries()返回由对象自身键值(属性名和属性值)组成的数组
1
2
3
4
5
6
7
8
9
10
11
const obj = {
a: 1,
b: 2,
c: {
j: 222,
a: 66
}
}
console.log(Object.keys(obj));//["a", "b", "c"]
console.log(Object.values(obj)); //[1, 2, {j: 222, a: 66}]
console.log(Object.entries(obj));//[["a", 1], ["b", 2], ["c", {j: 222, a: 66}]]

区别for-in Object.keys() Object.getOwnPropertyNames() Reflect.ownKeys()

  • for-in遍历对象所有可枚举属性,包括原型链上的属性
  • Object.keys()遍历对象所有自身可枚举属性(不包括 Symbol 值作为名称的属性), 不包括原型链上的属性 (仅自身属性)
  • Object.getOwnPropertyNames()返回一个由对象的所有自身属性的属性名(包括 枚举/不可枚举属性 但不包括 Symbol 值作为名称的属性)组成的数组
  • Reflect.ownKeys() 返回对象自身全部属性键组成的数组,包括字符串属性、不可枚举的属性和 Symbol 属性
    • 它的返回值等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
  • 注意:操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for-in循环,而用for-ofObject.keys()结合来代替

补充:可枚举属性 与 不可枚举属性

  • 参考
  • 可枚举属性是指那些内部 “可枚举” 标志设置为 true 的属性。对于通过直接的赋值和属性初始化的属性,该标识值默认为即为 true。但是对于通过 Object.defineProperty 等定义的属性,该标识值默认为 false
  • 其中js中基本包装类型的 原型属性 是 不可枚举 的,如Object, Array, Number
  • 可枚举的属性可以通过for-in循环进行遍历(除非该属性名是一个Symbol),或者通过Object.keys()方法返回一个可枚举属性的数组。
  • 比如: length就是数组的不可枚举属性Object.keys()不可返回,但Object.getOwnPropertyNames()可返回.

补充:for-in与【ES6引入的】for-of

  • for-of是 ES6 引入的新特性之一,for-of常用于异步遍历(例子见《JS 异步进阶》),for-in以及forEach、for是常规的同步遍历
  • 无论是for-in还是for-of语句都是迭代一些东西,它们之间的主要区别在于它们的迭代方式
  • 区别例子可参考MDN区别例子可参考MDN
  • for-in 语句以任意顺序迭代对象的可枚举属性更适合用于遍历对象属性
    • 注意:会遍历到继承属性,需要**Object的hasOwnProperty()**判断对象/数组是否包含特定的自身(非继承)属性
    • 遍历的是数组的索引(即键名)/对象属性名
    • 会遍历数组所有的可枚举属性包括原型属性。index索引为字符串型数字,不能直接进行几何运算
    • 因为迭代的顺序是依赖于执行环境的,所以数组遍历不一定按次序访问元素。因此当迭代访问顺序很重要的数组时,最好用整数索引去进行for循环(或者使用 Array.prototype.forEach()for-of 循环)
  • for-of 语句按顺序遍历可迭代对象定义要迭代的数据,更适合用于遍历 数组元素/字符串 等拥有 迭代器 对象的集合
    • 不会遍历到继承属性
    • 但是for-of不能遍历普通对象,因为普通对象没有迭代器
      • 只能遍历提供了 ES6 引入的 Iterator 接口的数据类型,适用于可迭代对象(包括Array,Map,Set,String,TypedArray,arguments对象等等)
      • 但是对象的Object.keys()、Object.values()、Object.entries()这三个方法返回的是可迭代对象,使用起来就和Map差不多了
    • forEach()不同的是,for-of可以正确响应break、continue和return语句
    • 遍历的是数组元素值
    • 不会遍历原型属性(length之类的)
  • 不同于for(key of arr)中,key是数组元素for(key in arr)key是数组下标for(key in obj)中,key是对象属性名
    • 总结:for…in用于获取键(key),for…of用于获取值(value)
  • 以下示例显示了与Array一起使用时,for-of循环和for-in循环之间的区别
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Object.prototype.objCustom = function() {}; 
    Array.prototype.arrCustom = function() {};

    let iterable = [3, 5, 7];
    iterable.foo = 'hello';

    for (let i in iterable) {
    console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
    }

    for (let i in iterable) {
    if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs 0, 1, 2, "foo"
    }
    }

    for (let i of iterable) {
    console.log(i); // logs 3, 5, 7
    }

终止循环的语句

  • for、for-in、for-ofwhile都可以由 break, throw 或 return 终止,可以正确响应continue
    • 注意:forEach不行
  • break 和 continue
    • break 语句用于跳出整个循环
    • continue 跳过当前迭代的剩余代码,并直接进行下一次迭代
  • throw可创建自定义错误,搭配try-catch可用于定义catch捕捉到的error
    • 例子:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      function processItems(items) {
      for (const item of items) {
      if (item === 'stop') {
      throw new Error('Loop stopped.'); // 抛出异常中断循环
      }
      console.log(item);
      }
      }

      const items = ['apple', 'banana', 'stop', 'orange'];

      try {
      processItems(items); // apple banana
      } catch (error) {
      console.log(error.message); // 输出: Loop stopped.
      }
    • 注意:当你使用 throw 中断循环时,需要在适当的位置try-catch捕获异常。否则,异常会向上冒泡并可能导致程序的终止
  • return终止函数的执行并返回函数的值
    • 注意:return跳出的是函数,所以循环一定要写在函数内才能用return跳出循环,否则return会报错error: Illegal return statement
    • 错误范例:
      1
      2
      3
      4
      5
      6
      7
      const array1 = ['a', 'b', 'c'];

      for (const element of array1) {
      console.log(element);
      if(element == 'b') return
      }
      // 报错error: Illegal return statement
    • 正确范例:
      1
      2
      3
      4
      5
      6
      7
      8
      const array1 = ['a', 'b', 'c'];
      function a () {
      for (const element of array1) {
      console.log(element);
      if(element == 'b') return
      }
      }
      a() // a b

判断对象自身属性

【不推荐】for-in+hasOwnProperty()

  • for-in 语句遍历对象属性时我们可以使用hasOwnProperty()检查对象是否包含属性名,但语句复杂且不是直接放入数组,故不推荐使用
    • obj.hasOwnProperty("属性名")
      • 参数:要检测的属性的 String 字符串形式表示的名称。
      • 返回:布尔值,表示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。是对象自身属性则true,不是则false。
  • 关于for-inhasOwnProperty()遍历对象自身属性的例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const obj = {
    a: 1,
    b: 2,
    c: {
    j: 222,
    a: 66
    }
    }
    for (let key in obj){
    if(obj.hasOwnProperty(key)){
    console.log(key);
    }
    }

    // 运行结果: a b c

【推荐】for-of+Object.keys()

  • 可使用Object.keys()代替上面例子中的hasOwnProperty()来判断是否为自身属性。
  • for-of语句遍历对象属性。
  • 使用for-of语句与Object.keys()打印自身属性的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {
a: 1,
b: 2,
c: {
j: 222,
a: 66
}
}
for (let key of Object.keys(obj)) {
console.log(key);
}

// 运行结果: a b c
  • 注意:for-of语句与Object.keys()搭配,for-inhasOwnProperty()搭配。

Reflect.ownKeys()所有属性组成的数组

  • 可参考MDN: 返回对象自身全部属性键组成的数组,包括不可枚举的属性和 Symbol 属性
  • 它的返回值等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
  • 区别Reflect.ownKeys()Object.keys():区别在于返回的属性键的类型和范围
    • Reflect.ownKeys(obj):返回一个由对象自身的所有属性键组成的数组,包括字符串属性、不可枚举的属性和 Symbol 属性
      1
      2
      3
      4
      5
      6
      7
      8
      9
      const obj = {
      key1: 'value1',
      [Symbol('key2')]: 'value2',
      };

      const keys = Reflect.ownKeys(obj);
      console.log(keys); // ['key1', Symbol(key2)]
      const keys2 = Object.keys(obj);
      console.log(keys2); // ['key1']
    • Object.keys(obj):返回一个由对象自身的所有可枚举属性键组成的数组,只包括字符串键,不包括不可枚举的属性和 Symbol 属性
      1
      2
      3
      4
      5
      6
      7
      const obj = {
      key1: 'value1',
      key2: 'value2',
      };

      const keys = Object.keys(obj);
      console.log(keys); // ['key1', 'key2']
    • 总结: Reflect.ownKeys() 返回对象自身的所有属性键,包括字符串属性、不可枚举的属性和 Symbol 属性,而 Object.keys() 只返回对象自身的可枚举的字符串属性

区分getOwnPropertyNames和getOwnPropertySymbols

  • Object.getOwnPropertyNames() : 返回一个包含了对象 obj 的所有自身可枚举属性的字符串键(不包括Symbol键)的数组。这些属性是直接在对象上定义的,而不是从原型链继承而来
  • Object.getOwnPropertySymbols(): 返回一个包含了对象 obj 的所有自身 Symbol 键(不包括字符串键)属性的数组,包括可枚举和不可枚举的属性。这些属性是直接使用 Symbol 定义在对象上的,而不是从原型链继承而来
  • 注意:这两个方法都只返回对象自身的属性键,而不会返回继承的属性键。如果你希望获取继承的属性键,可以使用 for...in 循环遍历对象或使用 Reflect.ownKeys() 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const symbolKey1 = Symbol('key1');
const symbolKey2 = Symbol('key2');

const obj = {
stringKey1: 'value1',
[symbolKey1]: 'value2',
stringKey2: 'value3',
[symbolKey2]: 'value4',
};

// 使用 Object.getOwnPropertyNames() 获取对象的自身属性键(字符串键)
const stringKeys = Object.getOwnPropertyNames(obj);
console.log(stringKeys); // ['stringKey1', 'stringKey2']

// 使用 Object.getOwnPropertySymbols() 获取对象的自身 Symbol 属性键
const symbolKeys = Object.getOwnPropertySymbols(obj);
console.log(symbolKeys); // [Symbol(key1), Symbol(key2)]
  • 在上面的例子中,我们创建了一个包含字符串键和 Symbol 键的对象 obj
  • 使用 Object.getOwnPropertyNames(),我们只能获取到对象的字符串键 stringKey1stringKey2
  • 而使用 Object.getOwnPropertySymbols(),我们只能获取到对象的 Symbol 键 Symbol(key1)Symbol(key2)

__proto__属性读取对象原型

  • __proto__属性代表当前对象的原型
  • 所有的引用类型(数组、对象、函数)的__proto__属性值 都指向它的 构造函数的prototype属性值。
    • “指向”即“全等”__proto__属性值===它的构造函数的prototype属性值
  • 很多浏览器不支持,只出现在ES6附录中没出现在正文。
  • 调试的时候使用就好,不要使用在正式代码中
  • 例子:(下方“Object.setPrototypeOf()修改对象原型”的例子中)
  • 比起__proto__属性更加推荐使用Object.getPrototypeOf()读取对象原型(下方详述)。

补充:Object.create()创建对象并指定原型

  • Object.create()用于创建一个对象时指定原型
  • 语法const objName = Object.create(原型对象名);
  • 例子:(下方“Object.setPrototypeOf()修改对象原型”的例子中)

Object.setPrototypeOf()修改对象原型

  • 语法:Object.setPrototypeOf(需要修改的对象名,新的原型名)
  • Object.setPrototypeOf()性能低下,建议不要使用。 可以用Object.create()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const obj1={
    a:1
    };

    const obj2={
    b:2
    }

    const obj=Object.create(obj1);//以obj1为原型创建对象obj
    console.log(obj.__proto__);//{a: 1}

    Object.setPrototypeOf(obj,obj2);//使用setPrototypeOf()将obj的原型改为obj2
    console.log(obj.__proto__);//{b: 2}
  • 注意:console.log(obj);打印出的是{}

Object.getPrototypeOf()读取对象原型

  • 语法Object.getPrototypeOf(对象名)
  • 比起__proto__属性更加推荐使用Object.getPrototypeOf()读取对象原型
    • 补充:所有的引用类型(数组、对象、函数)的__proto__属性值 都指向它的 构造函数的prototype属性值。
      • “指向”即“全等”__proto__属性值===它的构造函数的prototype属性值
      • 所以可以使用Object.getPrototypeOf()读取对象原型
1
2
3
4
5
6
7
8
9
const obj1 = {
a: 1
};

const obj = Object.create(obj1);//以obj1为原型创建对象obj
console.log(obj.__proto__);//{a: 1}

console.log(Object.getPrototypeOf(obj));//{a: 1}
console.log(obj.__proto__ === Object.getPrototypeOf(obj));//true

super关键字(访问原型对象)

  • 可以通过super关键字访问原型对象上的属性和方法
  • 注意只有使用简洁表示法写函数才可以调用super关键字,换成箭头函数或者普通函数写法去调用super关键字会报错。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const obj={name:"小明"};
    const copyObj={
    say(){
    console.log(`我的名字是${super.name}`);
    }
    }
    //将对象copyObj的原型改为对象obj才可以在copyObj中通过supeobj的属性name
    Object.setPrototypeOf(copyObj,obj);
    copyObj.say();//我的名字是小明