refactor(core): remove `looseIdentical` in favor of built-in `Object.is` (#37191)

Remove `looseIdentical` implementation and instead use the ES2015 `Object.is` in its place.
They behave exactly the same way except for `+0`/`-0`.
`looseIdentical(+0, -0)` => `true`
`Object.is(+0, -0)` => `false`

Other than the difference noted above, this is not be a breaking change because:
1. `looseIdentical` is a private API
2. ES2015 is listed as a mandatory polyfill in the [browser support
guide](https://angular.io/guide/browser-support#mandatory-polyfills)
3. Also note that `Ivy` already uses `Object.is` in `bindingUpdated`.

PR Close #37191
This commit is contained in:
Andrew Scott 2020-05-18 11:08:23 -07:00 committed by Matias Niemelä
parent 92c436dd1a
commit 4456e7e4de
12 changed files with 20 additions and 35 deletions

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {looseIdentical} from '../util/comparison';
import {getSymbolIterator} from '../util/symbol'; import {getSymbolIterator} from '../util/symbol';
export function devModeEqual(a: any, b: any): boolean { export function devModeEqual(a: any, b: any): boolean {
@ -20,7 +19,7 @@ export function devModeEqual(a: any, b: any): boolean {
if (!isListLikeIterableA && isAObject && !isListLikeIterableB && isBObject) { if (!isListLikeIterableA && isAObject && !isListLikeIterableB && isBObject) {
return true; return true;
} else { } else {
return looseIdentical(a, b); return Object.is(a, b);
} }
} }
} }

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {looseIdentical} from '../../util/comparison';
import {stringify} from '../../util/stringify'; import {stringify} from '../../util/stringify';
import {isListLikeIterable, iterateListLike} from '../change_detection_util'; import {isListLikeIterable, iterateListLike} from '../change_detection_util';
@ -180,7 +179,7 @@ export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChan
for (let index = 0; index < this.length; index++) { for (let index = 0; index < this.length; index++) {
item = collection[index]; item = collection[index];
itemTrackBy = this._trackByFn(index, item); itemTrackBy = this._trackByFn(index, item);
if (record === null || !looseIdentical(record.trackById, itemTrackBy)) { if (record === null || !Object.is(record.trackById, itemTrackBy)) {
record = this._mismatch(record, item, itemTrackBy, index); record = this._mismatch(record, item, itemTrackBy, index);
mayBeDirty = true; mayBeDirty = true;
} else { } else {
@ -188,7 +187,7 @@ export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChan
// TODO(misko): can we limit this to duplicates only? // TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, itemTrackBy, index); record = this._verifyReinsertion(record, item, itemTrackBy, index);
} }
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item); if (!Object.is(record.item, item)) this._addIdentityChange(record, item);
} }
record = record._next; record = record._next;
@ -197,7 +196,7 @@ export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChan
index = 0; index = 0;
iterateListLike(collection, (item: V) => { iterateListLike(collection, (item: V) => {
itemTrackBy = this._trackByFn(index, item); itemTrackBy = this._trackByFn(index, item);
if (record === null || !looseIdentical(record.trackById, itemTrackBy)) { if (record === null || !Object.is(record.trackById, itemTrackBy)) {
record = this._mismatch(record, item, itemTrackBy, index); record = this._mismatch(record, item, itemTrackBy, index);
mayBeDirty = true; mayBeDirty = true;
} else { } else {
@ -205,7 +204,7 @@ export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChan
// TODO(misko): can we limit this to duplicates only? // TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, itemTrackBy, index); record = this._verifyReinsertion(record, item, itemTrackBy, index);
} }
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item); if (!Object.is(record.item, item)) this._addIdentityChange(record, item);
} }
record = record._next; record = record._next;
index++; index++;
@ -289,7 +288,7 @@ export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChan
if (record !== null) { if (record !== null) {
// We have seen this before, we need to move it forward in the collection. // We have seen this before, we need to move it forward in the collection.
// But first we need to check if identity changed, so we can update in view if necessary // But first we need to check if identity changed, so we can update in view if necessary
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item); if (!Object.is(record.item, item)) this._addIdentityChange(record, item);
this._moveAfter(record, previousRecord, index); this._moveAfter(record, previousRecord, index);
} else { } else {
@ -298,7 +297,7 @@ export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChan
if (record !== null) { if (record !== null) {
// It is an item which we have evicted earlier: reinsert it back into the list. // It is an item which we have evicted earlier: reinsert it back into the list.
// But first we need to check if identity changed, so we can update in view if necessary // But first we need to check if identity changed, so we can update in view if necessary
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item); if (!Object.is(record.item, item)) this._addIdentityChange(record, item);
this._reinsertAfter(record, previousRecord, index); this._reinsertAfter(record, previousRecord, index);
} else { } else {
@ -628,7 +627,7 @@ class _DuplicateItemRecordList<V> {
let record: IterableChangeRecord_<V>|null; let record: IterableChangeRecord_<V>|null;
for (record = this._head; record !== null; record = record._nextDup) { for (record = this._head; record !== null; record = record._nextDup) {
if ((atOrAfterIndex === null || atOrAfterIndex <= record.currentIndex!) && if ((atOrAfterIndex === null || atOrAfterIndex <= record.currentIndex!) &&
looseIdentical(record.trackById, trackById)) { Object.is(record.trackById, trackById)) {
return record; return record;
} }
} }

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {looseIdentical} from '../../util/comparison';
import {stringify} from '../../util/stringify'; import {stringify} from '../../util/stringify';
import {isJsObject} from '../change_detection_util'; import {isJsObject} from '../change_detection_util';
import {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory} from './keyvalue_differs'; import {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory} from './keyvalue_differs';
@ -229,7 +228,7 @@ export class DefaultKeyValueDiffer<K, V> implements KeyValueDiffer<K, V>, KeyVal
// Add the record or a given key to the list of changes only when the value has actually changed // Add the record or a given key to the list of changes only when the value has actually changed
private _maybeAddToChanges(record: KeyValueChangeRecord_<K, V>, newValue: any): void { private _maybeAddToChanges(record: KeyValueChangeRecord_<K, V>, newValue: any): void {
if (!looseIdentical(newValue, record.currentValue)) { if (!Object.is(newValue, record.currentValue)) {
record.previousValue = record.currentValue; record.previousValue = record.currentValue;
record.currentValue = newValue; record.currentValue = newValue;
this._addToChanges(record); this._addToChanges(record);

View File

@ -27,7 +27,6 @@ export {GetterFn as ɵGetterFn, MethodFn as ɵMethodFn, SetterFn as ɵSetterFn}
export {allowSanitizationBypassAndThrow as ɵallowSanitizationBypassAndThrow, BypassType as ɵBypassType, getSanitizationBypassType as ɵgetSanitizationBypassType, SafeHtml as ɵSafeHtml, SafeResourceUrl as ɵSafeResourceUrl, SafeScript as ɵSafeScript, SafeStyle as ɵSafeStyle, SafeUrl as ɵSafeUrl, SafeValue as ɵSafeValue, unwrapSafeValue as ɵunwrapSafeValue} from './sanitization/bypass'; export {allowSanitizationBypassAndThrow as ɵallowSanitizationBypassAndThrow, BypassType as ɵBypassType, getSanitizationBypassType as ɵgetSanitizationBypassType, SafeHtml as ɵSafeHtml, SafeResourceUrl as ɵSafeResourceUrl, SafeScript as ɵSafeScript, SafeStyle as ɵSafeStyle, SafeUrl as ɵSafeUrl, SafeValue as ɵSafeValue, unwrapSafeValue as ɵunwrapSafeValue} from './sanitization/bypass';
export {_sanitizeHtml as ɵ_sanitizeHtml} from './sanitization/html_sanitizer'; export {_sanitizeHtml as ɵ_sanitizeHtml} from './sanitization/html_sanitizer';
export {_sanitizeUrl as ɵ_sanitizeUrl} from './sanitization/url_sanitizer'; export {_sanitizeUrl as ɵ_sanitizeUrl} from './sanitization/url_sanitizer';
export {looseIdentical as ɵlooseIdentical,} from './util/comparison';
export {makeDecorator as ɵmakeDecorator} from './util/decorators'; export {makeDecorator as ɵmakeDecorator} from './util/decorators';
export {global as ɵglobal} from './util/global'; export {global as ɵglobal} from './util/global';
export {isObservable as ɵisObservable, isPromise as ɵisPromise} from './util/lang'; export {isObservable as ɵisObservable, isPromise as ɵisPromise} from './util/lang';

View File

@ -8,12 +8,6 @@
import {areIterablesEqual, isListLikeIterable} from './iterable'; import {areIterablesEqual, isListLikeIterable} from './iterable';
// JS has NaN !== NaN
export function looseIdentical(a: any, b: any): boolean {
return a === b || typeof a === 'number' && typeof b === 'number' && isNaN(a) && isNaN(b);
}
export function devModeEqual(a: any, b: any): boolean { export function devModeEqual(a: any, b: any): boolean {
const isListLikeIterableA = isListLikeIterable(a); const isListLikeIterableA = isListLikeIterable(a);
const isListLikeIterableB = isListLikeIterable(b); const isListLikeIterableB = isListLikeIterable(b);
@ -25,7 +19,7 @@ export function devModeEqual(a: any, b: any): boolean {
if (!isListLikeIterableA && isAObject && !isListLikeIterableB && isBObject) { if (!isListLikeIterableA && isAObject && !isListLikeIterableB && isBObject) {
return true; return true;
} else { } else {
return looseIdentical(a, b); return Object.is(a, b);
} }
} }
} }

View File

@ -10,7 +10,6 @@ import {devModeEqual, WrappedValue} from '../change_detection/change_detection';
import {SOURCE} from '../di/injector_compatibility'; import {SOURCE} from '../di/injector_compatibility';
import {ViewEncapsulation} from '../metadata/view'; import {ViewEncapsulation} from '../metadata/view';
import {RendererType2} from '../render/api'; import {RendererType2} from '../render/api';
import {looseIdentical} from '../util/comparison';
import {stringify} from '../util/stringify'; import {stringify} from '../util/stringify';
import {expressionChangedAfterItHasBeenCheckedError} from './errors'; import {expressionChangedAfterItHasBeenCheckedError} from './errors';
@ -81,7 +80,7 @@ export function checkBinding(
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean { view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
const oldValues = view.oldValues; const oldValues = view.oldValues;
if ((view.state & ViewState.FirstCheck) || if ((view.state & ViewState.FirstCheck) ||
!looseIdentical(oldValues[def.bindingIndex + bindingIdx], value)) { !Object.is(oldValues[def.bindingIndex + bindingIdx], value)) {
return true; return true;
} }
return false; return false;

View File

@ -941,9 +941,6 @@
{ {
"name": "locateHostElement" "name": "locateHostElement"
}, },
{
"name": "looseIdentical"
},
{ {
"name": "makeMetadataCtor" "name": "makeMetadataCtor"
}, },

View File

@ -9,7 +9,6 @@
import {IterableChangeRecord, IterableChanges} from '@angular/core/src/change_detection/differs/iterable_differs'; import {IterableChangeRecord, IterableChanges} from '@angular/core/src/change_detection/differs/iterable_differs';
import {KeyValueChangeRecord, KeyValueChanges} from '@angular/core/src/change_detection/differs/keyvalue_differs'; import {KeyValueChangeRecord, KeyValueChanges} from '@angular/core/src/change_detection/differs/keyvalue_differs';
import {looseIdentical} from '../../src/util/comparison';
import {stringify} from '../../src/util/stringify'; import {stringify} from '../../src/util/stringify';
export function iterableDifferToString<V>(iterableChanges: IterableChanges<V>) { export function iterableDifferToString<V>(iterableChanges: IterableChanges<V>) {
@ -64,7 +63,7 @@ export function iterableChangesAsString({
} }
function kvcrAsString(kvcr: KeyValueChangeRecord<string, any>) { function kvcrAsString(kvcr: KeyValueChangeRecord<string, any>) {
return looseIdentical(kvcr.previousValue, kvcr.currentValue) ? return Object.is(kvcr.previousValue, kvcr.currentValue) ?
stringify(kvcr.key) : stringify(kvcr.key) :
(stringify(kvcr.key) + '[' + stringify(kvcr.previousValue) + '->' + (stringify(kvcr.key) + '[' + stringify(kvcr.previousValue) + '->' +
stringify(kvcr.currentValue) + ']'); stringify(kvcr.currentValue) + ']');

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, forwardRef, Host, Input, OnDestroy, Optional, Renderer2, StaticProvider, ɵlooseIdentical as looseIdentical} from '@angular/core'; import {Directive, ElementRef, forwardRef, Host, Input, OnDestroy, Optional, Renderer2, StaticProvider} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
@ -121,7 +121,7 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
this._compareWith = fn; this._compareWith = fn;
} }
private _compareWith: (o1: any, o2: any) => boolean = looseIdentical; private _compareWith: (o1: any, o2: any) => boolean = Object.is;
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {} constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, forwardRef, Host, Input, OnDestroy, Optional, Renderer2, StaticProvider, ɵlooseIdentical as looseIdentical} from '@angular/core'; import {Directive, ElementRef, forwardRef, Host, Input, OnDestroy, Optional, Renderer2, StaticProvider} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
@ -118,7 +118,7 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
this._compareWith = fn; this._compareWith = fn;
} }
private _compareWith: (o1: any, o2: any) => boolean = looseIdentical; private _compareWith: (o1: any, o2: any) => boolean = Object.is;
constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {} constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {isDevMode, ɵlooseIdentical as looseIdentical} from '@angular/core'; import {isDevMode} from '@angular/core';
import {FormArray, FormControl, FormGroup} from '../model'; import {FormArray, FormControl, FormGroup} from '../model';
import {Validators} from '../validators'; import {Validators} from '../validators';
@ -156,7 +156,7 @@ export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any)
const change = changes['model']; const change = changes['model'];
if (change.isFirstChange()) return true; if (change.isFirstChange()) return true;
return !looseIdentical(viewModel, change.currentValue); return !Object.is(viewModel, change.currentValue);
} }
const BUILTIN_ACCESSORS = [ const BUILTIN_ACCESSORS = [

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, DoCheck, ElementRef, EventEmitter, Injector, OnChanges, OnDestroy, OnInit, SimpleChanges, ɵlooseIdentical as looseIdentical} from '@angular/core'; import {Directive, DoCheck, ElementRef, EventEmitter, Injector, OnChanges, OnDestroy, OnInit, SimpleChanges} from '@angular/core';
import {IAttributes, IAugmentedJQuery, IDirective, IInjectorService, ILinkFn, IScope, ITranscludeFunction} from '../../src/common/src/angular1'; import {IAttributes, IAugmentedJQuery, IDirective, IInjectorService, ILinkFn, IScope, ITranscludeFunction} from '../../src/common/src/angular1';
import {$SCOPE} from '../../src/common/src/constants'; import {$SCOPE} from '../../src/common/src/constants';
@ -206,7 +206,7 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
const newValue = this.bindingDestination[propName]; const newValue = this.bindingDestination[propName];
const oldValue = twoWayBoundLastValues[idx]; const oldValue = twoWayBoundLastValues[idx];
if (!looseIdentical(newValue, oldValue)) { if (!Object.is(newValue, oldValue)) {
const outputName = propertyToOutputMap[propName]; const outputName = propertyToOutputMap[propName];
const eventEmitter: EventEmitter<any> = (this as any)[outputName]; const eventEmitter: EventEmitter<any> = (this as any)[outputName];