## 1 引言

本周精读的文章是：[surprising-polymorphism-in-react-applications](https://medium.com/@bmeurer/surprising-polymorphism-in-react-applications-63015b50abc)，看看作者是如何解释这个多态性含义的。

读完文章才发现，文章标题改为 Redux 的多态性更妥当，因为整篇文章都在说 Redux，而 Redux 使用场景不局限于 React。

## 2 概述

Redux immutable 特性可能产生浏览器无法优化的性能问题，也就是浏览器无法做 [shapes 优化](https://github.com/dt-fe/weekly/blob/master/62.%E7%B2%BE%E8%AF%BB%E3%80%8AJS%20%E5%BC%95%E6%93%8E%E5%9F%BA%E7%A1%80%E4%B9%8B%20Shapes%20and%20Inline%20Caches%E3%80%8B.md#shapes)，也就是上一篇精读《JS 引擎基础之 Shapes and Inline Caches》 里提到的。

先看看普通的 redux 的 reducer：

```typescript
const todo = (state = {}, action) => {
  switch (action.type) {
    case "ADD_TODO":
      return {
        id: action.id,
        text: action.text,
        completed: false
      };
    case "TOGGLE_TODO":
      if (state.id !== action.id) {
        return state;
      }

      return Object.assign({}, state, {
        completed: !state.completed
      });

    default:
      return state;
  }
};
```

我们简化一下使用场景，假设基于这个 reducer `todo`，生成了两个新 store `s1` `s2`：

```typescript
const s1 = todo(
  {},
  {
    type: "ADD_TODO",
    id: 1,
    text: "Finish blog post"
  }
);

const s2 = todo(s1, {
  type: "TOGGLE_TODO",
  id: 1
});
```

看上去很常见，也的确如此，我们每次 dispatch 都会根据 reducer 生成新的 store 树，而且是一个新的对象。然而对 js 引擎而言，这样的代码可能做不了 Shapes 优化（关于 Shapes 优化建议阅读上一期精读 [Shapes 优化](https://github.com/dt-fe/weekly/blob/master/62.%E7%B2%BE%E8%AF%BB%E3%80%8AJS%20%E5%BC%95%E6%93%8E%E5%9F%BA%E7%A1%80%E4%B9%8B%20Shapes%20and%20Inline%20Caches%E3%80%8B.md#shapes)），也就是最需要做优化的全局 store，在生成新 store 时无法被浏览器优化，这个问题很容易被忽视，但的确影响不小。

至于为什么会阻止 js 引擎的 shapes 优化，看下面的代码：

```javascript
// transition-trees.js
let a = {x:1, y:2, z:3};

let b = {};
b.x = 1;
b.y = 2;
b.z = 3;

console.log("a is", a);
console.log("b is", b);
console.log("a and b have same map:", %HaveSameMap(a, b));
```

通过 `node --allow-natives-syntax test.js` 执行，通过调用 node 原生函数 `%HaveSameMap` 判断这种情况下 `a` 与 `b` 是否共享一个 shape（v8 引擎的 Shape 实现称为 Map）。

![image](https://user-images.githubusercontent.com/7970947/42121947-089a1796-7c6c-11e8-89de-3eaaf81eb02e.png)

结果是 `false`，也就是 js 引擎无法对 `a` `b` 做 Shapes 优化，这是因为 `a` 与 `b` 对象初始化的方式不同。

同样，在 Redux 代码中常用的 `Object.assign` 也有这个问题：

![image](https://user-images.githubusercontent.com/7970947/42121964-55d0f3ae-7c6c-11e8-9d6e-995cac2f83d3.png)

因为新的对象以 `{}` 空对象作为最初状态，js 引擎会为新对象创建 Empty Shape，这与原对象的 Shape 一定不同。

顺带一提 es6 的解构语法也存在同样的问题，因为 `babel` 将解构最终解析为 `Object.assign`：

![image](https://user-images.githubusercontent.com/7970947/42121971-9c0bb8d6-7c6c-11e8-85ed-e15f27654d5b.png)

对这种尴尬的情况，作者的建议是对所有对象赋值时都是用 `Object.assign` 以保证 js 引擎可以做 Shapes 优化：

```javascript
let a = Object.assign({}, {x:1, y:2, z:3});

let b = Object.assign({}, a);

console.log("a is", a);
console.log("b is", b);
console.log("a and b have same map:", %HaveSameMap(a, b)); // true
```

## 3 精读

这篇文章需要与上一篇 [精读《JS 引擎基础之 Shapes and Inline Caches》](https://github.com/dt-fe/weekly/blob/master/62.%E7%B2%BE%E8%AF%BB%E3%80%8AJS%20%E5%BC%95%E6%93%8E%E5%9F%BA%E7%A1%80%E4%B9%8B%20Shapes%20and%20Inline%20Caches%E3%80%8B.md) 连起来看容易理解。

作者描述的性能问题是引擎级别的 Shapes 优化问题，读过上篇精读就很容易知道，只有相同初始化方式的对象才被 js 引擎做优化，而 Redux 频繁生成的 immutable 全局 store 是否能被优化呢？答案是“往往不能”，因为 immutable 赋值问题，我们往往采用 `Object.assign` 或者解构方式赋值，这种方式产生的新对象与原对象的 Shape 不同，导致 Shape 无法复用。

这里解释一下疑惑，为什么说 immutable 对象之间也要优化呢？这不是两个不同的引用吗？这是因为 js 引擎级别的 Shapes 优化就是针对不同引用的对象，将对象的结构：Shape 与数据分离开，这样可以大幅优化存储效率，对数组也一样，上一篇精读有详细介绍。

所以笔者更推荐使用比如 [immutable-js](https://github.com/facebook/immutable-js) 这种库操作 immutable 对象，而不是 Object.assign，因为封装库内部是可能通过统一对象初始化方式利用 js 引擎进行优化的。

## 4 总结

原文提到的多态是指多个相同结构对象，被拆分成了多个 Shape；而单态是指这些对象可以被一个 Shape 复用。

笔者以前也经历过从 `Object.assign` 到 Immutablejs 库，最后又回到解构新语法的经历，觉得在层级不深情况下解构语法可以代替 Immutablejs 库。

通过最近两篇精读的分析，我们需要重新思考这样做带来的优缺点，因为在 js 环境中，`Object.assign` 的优化效率比 Immutablejs 库更低。

最后，也完全没必要现在就开始重构，因为这只是 js 运行环境中很小一部分影响因素，比如为了引入 Immutablejs 让你的网络延时增加了 100%？所以仅在有必要的时候优化它。

## 5 更多讨论

> 讨论地址是：[精读《React 的多态性》 · Issue #92 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/92)

**如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。**
