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()
判断两个值是否相同。如果下列任何一项成立,则两个值相同:
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
。