Skip to content

Latest commit

 

History

History
150 lines (118 loc) · 5.62 KB

16.精读react中shallowEqual方法源码.md

File metadata and controls

150 lines (118 loc) · 5.62 KB

前言

React的哲学里,不会为开发者提供层次过高的封装和魔法糖。shouldComponentUpdate也是基于此,设计暴露给开发者作性能优化的。不过有句题外话,现在的hooks把过多的优化责任留给了开发者,加重了我们的心智负担,期待社区给出优化过的hooks方案。

不记得React那个版本引入了React.PureComponent,它会在shouldComponentUpdate时对props做浅比较,也既shallowEqual。

源码

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import is from './objectIs';

const hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 */
function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) {
    return true;
  }

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    if (
      !hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true;
}

export default shallowEqual;

可以看到引入了objectIs.js,源码如下:

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

/**
 * inlined Object.is polyfill to avoid requiring consumers ship their own
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
 */
function is(x: any, y: any) {
  return (
    (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
  );
}

export default (typeof Object.is === 'function' ? Object.is : is);

其实是Object.is的polyfill,关于Object.is,MDN是这么解释的:

Object.is() 判断两个值是否相同。如果下列任何一项成立,则两个值相同:

  • 两个值都是 undefined
  • 两个值都是 null
  • 两个值都是 true 或者都是 false
  • 两个值是由相同个数的字符按照相同的顺序组成的字符串
  • 两个值指向同一个对象
  • 两个值都是数字并且
    • 都是正零 +0
    • 都是负零 -0
    • 都是 NaN
    • 都是除零和 NaN 外的其它同一个数字

MDN的文档里甚至还给出了polyfill,

if (!Object.is) {
  Object.is = function(x, y) {
    if (x === y) { 
      return x !== 0 || 1 / x === 1 / y;
    } else {
      return x !== x && y !== y;
    }
  };
}

相较于react里,更容易理解。

第一步:如果x不是0,而且x===y,就满足了1-5和6.d。

第二步:如果x===0,而且x===y,就需要排除+0与-0,由于1/-0===-Infinity,1/+0===Infinity,1 / x === 1 / y就将+0与-0排除了。就满足了6.a和6.b。

第三部:如果x!==y,而且x与y都不等于自身,就满足了6.c。

经过Object.is的比较之后,在排除掉不是object的类型:

 if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

这里要注意的是 typeof null==='object'。

接着拿到对象所有的key,先比较长度,再挨个比较值。但是如WeakMap,Map,Object.keys()方法不起作用的对象,进行浅比较是无效的。

总结

由于js设计之始的错误,长期以来js的值比较混乱而恼人,好在Object.is的出现,弥补了这方面的失误。

这种相等性判断逻辑和传统的 == 运算不同,== 运算符会对它两边的操作数做隐式类型转换(如果它们类型不同),然后才进行相等性比较,(所以才会有类似 "" == false 等于 true 的现象),但 Object.is 不会做这种类型转换。

这与 === 运算符的判定方式也不一样。=== 运算符(和== 运算符)将数字值 -0+0 视为相等,并认为 Number.NaN 不等于 NaN