為啥想要了解
前幾天看了下React.PureComponent中shouldComponentUpdate的默認(rèn)實(shí)現(xiàn),因?yàn)槲臋n中只說(shuō)是淺比較,就想知道有多淺,又提到不要用JSON.stringify()這種方法,耗費(fèi)性能,所以我好奇lodash的isEqual的實(shí)現(xiàn)
正文
版本4.17.15
第一步
function isEqual(value, other) {
return baseIsEqual(value, other);
}
第二步
function baseIsEqual(value, other, bitmask, customizer, stack) {
if (value === other) {
return true;
}
if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) {
return value !== value && other !== other;
}
return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);
}
baseIsEqual(value, other); 沒(méi)有傳bitmask, customizer, stack,暫時(shí)也不用理會(huì)!
isObjectLike 是這樣的
function isObjectLike(value) {
return value != null && typeof value == 'object';
}
if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) { ,因?yàn)榍懊嬗眠^(guò)全等比較了,所以在這里value 或 other 有任意一個(gè)是 null或undefined都不可能相等,當(dāng)value 和 other 都不是對(duì)象的時(shí)候也會(huì)進(jìn)入,此時(shí)我只能想到NaN 會(huì)使得value !== value && other !== other; 有意義。
我腳得這樣寫(xiě)更清晰些,有些囂張!
if (value == null || other == null){
return false;
}
if (!isObjectLike(value) && !isObjectLike(other)) {
let isNaN = value !== value && other !== other;
return isNaN;
}
_.isEqual(undefined, null)為false,盡管null == undefined為true
_.isEqual(Number.NaN, Number.NaN)為true,盡管NaN === NaN為false
那么問(wèn)題來(lái)了,如何實(shí)現(xiàn)一個(gè)isNaN函數(shù),記得之前用的是Number.NaN+""
第三步
注意 baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack); 參數(shù)中baseIsEqual 就是當(dāng)前函數(shù),所以下面equalFunc是有值得
baseIsEqualDeep 的實(shí)現(xiàn)
function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {
var objIsArr = isArray(object),
othIsArr = isArray(other),
objTag = objIsArr ? arrayTag : getTag(object),
othTag = othIsArr ? arrayTag : getTag(other);
objTag = objTag == argsTag ? objectTag : objTag;
othTag = othTag == argsTag ? objectTag : othTag;
var objIsObj = objTag == objectTag,
othIsObj = othTag == objectTag,
isSameTag = objTag == othTag;
if (isSameTag && isBuffer(object)) {
if (!isBuffer(other)) {
return false;
}
objIsArr = true;
objIsObj = false;
}
if (isSameTag && !objIsObj) {
stack || (stack = new Stack);
return (objIsArr || isTypedArray(object))
? equalArrays(object, other, bitmask, customizer, equalFunc, stack)
: equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
}
if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
if (objIsWrapped || othIsWrapped) {
var objUnwrapped = objIsWrapped ? object.value() : object,
othUnwrapped = othIsWrapped ? other.value() : other;
stack || (stack = new Stack);
return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);
}
}
if (!isSameTag) {
return false;
}
stack || (stack = new Stack);
return equalObjects(object, other, bitmask, customizer, equalFunc, stack);
}
蓬松的頭發(fā),淡黃的裙子,突然變態(tài)起來(lái)。
arrayTag = '[object Array]',
isArray 就是 Array.isArray

arrayTag = '[object Array]', 這個(gè)好找一些getTag就是baseGetTag
function baseGetTag(value) {
if (value == null) {
return value === undefined ? undefinedTag : nullTag;
}
return (symToStringTag && symToStringTag in Object(value))
? getRawTag(value)
: objectToString(value);
}
objectToString 本質(zhì)上就是Object.prototype.toString.call



undefinedTag = '[object Undefined]', 、nullTag = '[object Null]', ,調(diào)用Object.prototype.toString.call 得到的
symToStringTag 如果瀏覽器不支持Symbol類(lèi)型,為undefined,

Symbol.toStringTag 是一個(gè)內(nèi)置的Symbol, Map、和Promise能夠被Object.prototype.toString() 識(shí)別是因?yàn)橐鏋樗麄冊(cè)O(shè)置好了Symbol.toStringTag這個(gè)值,同樣,你可以為自己得對(duì)象添加,詳情可點(diǎn)擊此處Object(value) 可以把基本類(lèi)型轉(zhuǎn)換為對(duì)象
可知 _.isEqual(new Map([[1, 2]]), { name: 2 }) 能夠進(jìn)入 getRawTag
function getRawTag(value) {
var isOwn = hasOwnProperty.call(value, symToStringTag),
tag = value[symToStringTag];
try {
value[symToStringTag] = undefined;
var unmasked = true;
} catch (e) {}
var result = nativeObjectToString.call(value);
if (unmasked) {
if (isOwn) {
value[symToStringTag] = tag;
} else {
delete value[symToStringTag];
}
}
return result;
}
result = nativeObjectToString.call(value);小朋友,你會(huì)不會(huì)有很多得問(wèn)號(hào)?最后返回得也是result,針對(duì)value[symToStringTag]得一系列操作暫時(shí)看不出端倪
value 類(lèi)型為Map時(shí),
tag = value[symToStringTag]; 為 Map
unmasked = true; 執(zhí)行了,value[symToStringTag] = undefined;和delete value[symToStringTag];都執(zhí)行,但是沒(méi)有效果
回到baseIsEqualDeep中
argsTag = '[object Arguments]', 前面的邏輯主要是為了區(qū)分?jǐn)?shù)組還是對(duì)象,以及他們是否為相同的標(biāo)簽,注意不數(shù)據(jù)類(lèi)型,如果不是就退出比較。

需要注意的是,如果類(lèi)型為argsTag,則會(huì)判定為objectTag
這個(gè)isBuffer 是用在nodejs上的
此時(shí)我們來(lái)到了
if (isSameTag && !objIsObj) {
stack || (stack = new Stack);
return (objIsArr || isTypedArray(object))
? equalArrays(object, other, bitmask, customizer, equalFunc, stack)
: equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
}
注意stack有值了,
isTypedArray 在瀏覽器下的實(shí)現(xiàn)為
function baseIsTypedArray(value) {
return isObjectLike(value) &&
isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
}
isLength 的實(shí)現(xiàn)
function isLength(value) {
return typeof value == 'number' &&
value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}
typedArrayTags 長(zhǎng)這樣

_.isEqual(new Int8Array(32), new Int8Array(32))、_.isEqual([1, 2], [1, 2]) 能進(jìn)入equalArrays
function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
arrLength = array.length,
othLength = other.length;
if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
return false;
}
// Assume cyclic values are equal.
var stacked = stack.get(array);
if (stacked && stack.get(other)) {
return stacked == other;
}
var index = -1,
result = true,
seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined;
stack.set(array, other);
stack.set(other, array);
// Ignore non-index properties.
while (++index < arrLength) {
var arrValue = array[index],
othValue = other[index];
if (customizer) {
var compared = isPartial
? customizer(othValue, arrValue, index, other, array, stack)
: customizer(arrValue, othValue, index, array, other, stack);
}
if (compared !== undefined) {
if (compared) {
continue;
}
result = false;
break;
}
// Recursively compare arrays (susceptible to call stack limits).
if (seen) {
if (!arraySome(other, function(othValue, othIndex) {
if (!cacheHas(seen, othIndex) &&
(arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
return seen.push(othIndex);
}
})) {
result = false;
break;
}
} else if (!(
arrValue === othValue ||
equalFunc(arrValue, othValue, bitmask, customizer, stack)
)) {
result = false;
break;
}
}
stack['delete'](array);
stack['delete'](other);
return result;
}
到這里我已經(jīng)快吐了!
這次是equalFunc和stack有值,如果長(zhǎng)度不相等就會(huì)退出,先排除stack的一些邏輯,后續(xù)就是按照下標(biāo)去比較。
arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack),如果值不全等,會(huì)調(diào)用equalFunc,就是baseIsEqual做深度比較
回到baseIsEqualDeep
_.isEqual(new Map([[1, 2]]), new Map([[1, 2]])) 會(huì)觸發(fā)equalByTag
function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
switch (tag) {
case dataViewTag:
if ((object.byteLength != other.byteLength) ||
(object.byteOffset != other.byteOffset)) {
return false;
}
object = object.buffer;
other = other.buffer;
case arrayBufferTag:
if ((object.byteLength != other.byteLength) ||
!equalFunc(new Uint8Array(object), new Uint8Array(other))) {
return false;
}
return true;
case boolTag:
case dateTag:
case numberTag:
// Coerce booleans to `1` or `0` and dates to milliseconds.
// Invalid dates are coerced to `NaN`.
return eq(+object, +other);
case errorTag:
return object.name == other.name && object.message == other.message;
case regexpTag:
case stringTag:
// Coerce regexes to strings and treat strings, primitives and objects,
// as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
// for more details.
return object == (other + '');
case mapTag:
var convert = mapToArray;
case setTag:
var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
convert || (convert = setToArray);
if (object.size != other.size && !isPartial) {
return false;
}
// Assume cyclic values are equal.
var stacked = stack.get(object);
if (stacked) {
return stacked == other;
}
bitmask |= COMPARE_UNORDERED_FLAG;
// Recursively compare objects (susceptible to call stack limits).
stack.set(object, other);
var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
stack['delete'](object);
return result;
case symbolTag:
if (symbolValueOf) {
return symbolValueOf.call(object) == symbolValueOf.call(other);
}
}
return false;
}
似乎到了熟悉的地方,注意:
dataViewTag、arrayBufferTag是一對(duì)。
boolTag、dateTag、numberTag 是一起的
用eq(+object, +other);做的驗(yàn)證
_.isEqual(new Number(1), 1) 他們會(huì)獲得同樣的標(biāo)簽,也會(huì)走到這里,因?yàn)?code>Object.prototype.toString.call兩者返回的標(biāo)簽一樣
function eq(value, other) {
return value === other || (value !== value && other !== other);
}
errorTag 比較了name和message兩個(gè)屬性
mapTag 和 setTag,讀到這里我裂開(kāi)了呀,如果匹配到mapTag也會(huì)執(zhí)行 setTag下面的代碼,
我只記得
switch ("0") {
case "0":
case "1":
case "2":
console.log("120");
}
最后轉(zhuǎn)換為數(shù)組再調(diào)用equalArrays
function mapToArray(map) {
var index = -1,
result = Array(map.size);
map.forEach(function(value, key) {
result[++index] = [key, value];
});
return result;
}
function setToArray(set) {
var index = -1,
result = Array(set.size);
set.forEach(function(value) {
result[++index] = value;
});
return result;
}
感嘆擴(kuò)展運(yùn)算符的強(qiáng)大!
再次回到baseIsEqualDeep,看一下equalObjects
_.isEqual({ name: 1 }, { name: 2 })會(huì)進(jìn)入
function equalObjects(object, other, bitmask, customizer, equalFunc, stack) {
var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
objProps = getAllKeys(object),
objLength = objProps.length,
othProps = getAllKeys(other),
othLength = othProps.length;
if (objLength != othLength && !isPartial) {
return false;
}
var index = objLength;
while (index--) {
var key = objProps[index];
if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
return false;
}
}
// Assume cyclic values are equal.
var stacked = stack.get(object);
if (stacked && stack.get(other)) {
return stacked == other;
}
var result = true;
stack.set(object, other);
stack.set(other, object);
var skipCtor = isPartial;
while (++index < objLength) {
key = objProps[index];
var objValue = object[key],
othValue = other[key];
if (customizer) {
var compared = isPartial
? customizer(othValue, objValue, key, other, object, stack)
: customizer(objValue, othValue, key, object, other, stack);
}
// Recursively compare objects (susceptible to call stack limits).
if (!(compared === undefined
? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack))
: compared
)) {
result = false;
break;
}
skipCtor || (skipCtor = key == 'constructor');
}
if (result && !skipCtor) {
var objCtor = object.constructor,
othCtor = other.constructor;
// Non `Object` object instances with different constructors are not equal.
if (objCtor != othCtor &&
('constructor' in object && 'constructor' in other) &&
!(typeof objCtor == 'function' && objCtor instanceof objCtor &&
typeof othCtor == 'function' && othCtor instanceof othCtor)) {
result = false;
}
}
stack['delete'](object);
stack['delete'](other);
return result;
}
如果是個(gè)普通對(duì)象,則調(diào)用Object.keys,本身不會(huì)返回原型鏈上的屬性
先判斷了長(zhǎng)度
然后循環(huán)遍歷通過(guò)hasOwnProperty 判斷在前者有的后者自身屬性上也有,而非繼承而來(lái)
然后循環(huán)比較值,通過(guò)(objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack)) 比較深層次的值
總結(jié)
最大的收獲是Symbol.toStringTag,由于技術(shù)迭代的原因,寫(xiě)法很陳舊,有一些本就是病垢,不值得借鑒,對(duì)于業(yè)務(wù)開(kāi)發(fā)來(lái)說(shuō),實(shí)現(xiàn)顯得笨重了些。
很多寫(xiě)法減少了if、else,一定程度上復(fù)用了代碼,使得邏輯緊湊,卻也使得清晰性和可讀性下降了??赡苓@就是一個(gè)庫(kù)的研發(fā)人員,和一個(gè)庫(kù)的使用人員視角的不同。
作為一個(gè)使用者,lodash是十分好用的。