前端学习笔记(三十)-Object

174 阅读5分钟

js 中复杂的 Object 类型。

1. 对象

1.1 合并

Object.assign(dest, src1, src2, ...)
将 src 们的属性合并到 dest 上。其实是浅复制 src 的属性到 dest 上。
如果 src 们有同一属性名,后面会覆盖前面的。

1.2 相等判定

=== 有两种特殊情况:

  1. +0, 0, -0 被认为是完全相同的。
  2. NaN 不等于 NaN 但是对象提供了 Object.is(a, b) 方法,是这么判定的:
  3. +0 和 0 是相同的,但是 -0 和前面两个不同。
  4. NaN 和 NaN 相等

1.3 Es6 语法糖

1.3.1 属性值简写

类似于 name: name 这种写法可以简化为只写一个 name
Redux 写 action 时常用。

const name = "全人类";
const action = {
  name
}
// 相当于
const action = {
  name: name
}

1.3.2 可计算属性

如果属性是要通过某种计算的方式得到,可以用一下语法直接在字面量里定义:

const a = "alice";
const b = "blond";
const o = {
  [a]: "kiniro",
  [b]: "hair",
  [a+b]: "shinobu" // 当 js 解析到中括号的时候,会把中括号里的内容当做表达式来解析
}

// 在 ES6 前想要这么做只能下面这样
const o = {};
o[a] = "kiniro";
o[b] = "hair";
o[a+b] = "shinobu";

中括号里是表达式,因此完全可以很复杂。
注意:由于对象的创建是尽力而为,即使某一个属性表达式出错了,解析到这个表达式之前的属性不会被回滚,而是赋值成功。

1.3.3 简写方法名

方法的属性也可以被简写。
react-redux 的 mapDispatchToProps 常用。

// 以前的写法
const a = {
  getAlice: function() {
      // do something
    }
}
// ES6 的写法
const a = {
  getAlice() {
    // do something
  }
}

1.3.4 对象解构

不是 ... 那个语法,那个叫扩展运算符,也是 ES6 出的。
用法如下:

const o = {
  name: "The Cliff",
  tag: "stickman",
  desc: "no one survive"
}
// 使用 const 创建两个变量,videoName 和 videoTag,从对象 o 里获取值
const {name: videoName, tag: videoTag} = o;
console.log(videoName); // "The Cliff"
console.log(name); // 报错 reference error

// 如果想直接用对象中的属性
// 比如上面的对象,假设不想用 videoName 这种重新命名,就想用对象里本来的 name 这个名字,可以这样
const {name, desc} = o;
console.log(name); // "The Cliff"

// 还可以同时创建不存在对象中的属性并赋值
const {myName: "人类"} = o;
// myName 和 o 这个对象没有任何关系,单纯就是创建了个变量,值为 "人类"

// 如果要赋值给的变量是已经声明过的变量,则要在最外面加一个括号
let a, b; // 提前声明好变量
{name: a, tag: b} = o; // 错误
({name: a, tag: b} = o); // 正确
  • 和对象创建一样,赋值也是尽力而为,中间有一个赋值出错了,前面的都算完成,后面的都算失败。
  • 函数参数里也可以解构,不会影响 arguments(永远都是当时传入的参数)
function foo(a, {name: videoName}, b) {
  console.log(arguments);
  console.log(videoName);
}
foo(1, {name: "转动"}, 2); // [1, {name: "转动"}, 2]
                          // "转动"

1.4 原型

1.4.1 原型

每个函数都有原型,不只是构造函数。原型本身也是一个对象。(我为什么要说也,因为函数也是对象,只是 typeof 返回 function)。如果把该函数当做构造函数(或者就是构造函数)创建对象,那么所有通过该函数创建的对象都会共享原型上的属性和方法。

  • 所有函数都有 prototype 属性,也就是原型。同时所有原型都有 constructor 属性,指回这个函数。
  • 创建的对象也有 constructor 属性,但没有 prototype 属性。不过有无法直接访问的 [[prototype]] 属性,但是虽然说无法访问,在某些浏览器里还是提供了 __proto__ 属性以供通过对象来访问原型。
  • 可以说实例和构造函数没有关系。(虽然实例可以用 constructor 访问构造函数,但这个是原型上的方法,并不是实例上的方法)

1.4.2 对象迭代

ES2017 新增的两个方法 Object.values() 和 Object.entries() ,顾名思义分别返回值和键值对构成的的数组。

1.4.3 原型使用字面量的副作用

ler a = new Person();
// 本来要这样
Person.prototype.name = "魔理沙"// 可以通过字面量简写
Person.prototype = {
  name: "魔理沙"
}

但是有三个问题:

  1. constructor 指向问题:由于字面量是赋值语法, 重新指定原型。本来自动赋值给原型的 contructor 属性没有了,因此需要手动指定:
    Person.prototype = {
      constructor: Person,
      name: "魔理沙"
    }
    
  2. [[isEnumerable]] 问题:虽然手动指定了 constructor,但是这个 constructor 是一个可迭代对象的普通属性。会在 for-in 语法里被迭代出来。因此当需要的时候,需要将其改为不可迭代。
    Person.prototype = {
      constructor: Person,
      name: "魔理沙"
    }
    // Object 方法
    Object.defineProperty(Person.prototype, "constructor", {enumerable: false})
    
  3. 动态赋值问题:使用字面量赋值,只能在创建实例之前。不然在此之前创建的实例都没法使用该原型。但是如果不使用字面量赋值,则可以在任何时候给原型添加方法。并被所有实例使用。因为原型链是当使用的时候才会查找的。而字面量赋值会生成一个新的原型。在赋值之前的实例使用的是旧的原型,之后的实例使用的是新的原型。(如果没看懂,参考红宝书p236,第四版)

1.4.4 其他原型问题,以及原型继承

参考我自己的前端学习笔记(二)