3

在这个 Jasmine 测试中,我比较了两个几乎相同的对象,唯一的区别是第二个对象有一个额外的未定义成员。

describe('Testing', function () {

  it('should compare two objects', function () {


    var obj1 = {a: 1, b: 2 };

    var obj2 = {a: 1, b: 2, c: undefined };

    console.log(JSON.stringify(obj1));
    console.log(JSON.stringify(obj2));

    expect(obj1).toEqual(obj2);


  });

测试失败,但是打印两个对象JSON.stringify会产生两个相同的输出。

{"a":1,"b":2}
{"a":1,"b":2}

浏览对象可以发现差异,但是在复杂对象中,这并不容易。关于如何解决这个问题的任何建议?

4

2 回答 2

1

您的问题基于两个误解:

  1. Inobj2 c是一个已定义的属性,其值为undefined
  2. stringify()undefined根据规范进行序列化 - 您的测试不安全

两个对象不相等

茉莉花toEqual()

toEqual使用内部的util.equals(),它将逐个键比较对象中定义的所有可枚举ab

经过某种类型检查后,它开始比较对象的键

关于定义属性

看看 ECMAscript 规范。创建对象字面量时会调用此内部Put方法:

11.1.5 对象初始化器

[...]

产生式 PropertyNameAndValueList : PropertyAssignment 的评估如下:

  1. 让 obj 成为创建新对象的结果,就像通过表达式 new Object() 一样,其中 Object 是具有该名称的标准内置构造函数。
  2. 让 propId 是评估 PropertyAssignment 的结果。
  3. 使用参数 propId.name、propId.descriptor 和false调用 obj 的 [[DefineOwnProperty]] 内部方法。
  4. 返回对象。

[...]

产生式 PropertyAssignment : PropertyName : AssignmentExpression 的评估如下:

  1. 让 propName 是评估 PropertyName 的结果。
  2. 令 exprValue 为评估 AssignmentExpression 的结果。
  3. 让 propValue 为 GetValue(exprValue)。
  4. 设 desc 为属性描述符{[[Value]]: propValue, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}

类似地通过成员表达式定义属性:

8.12.5 [[Put]] ( P, V, Throw ) 当调用 O 的 [[Put]] 内部方法时,使用属性 P、值 V 和布尔标志 Throw,执行以下步骤:

[...]

  1. 否则,在对象 O 上创建一个名为 P 的命名数据属性,如下所示

    一个。令 newDesc 为属性描述符 {[[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}。

    湾。调用 O 的 [[DefineOwnProperty]] 内部方法,将 P、newDesc 和 Throw 作为参数传递。

的实现DefineOwnProperty,在8.12.9 [[DefineOwnProperty]] (P, Desc, Throw)中描述,不再赘述。MDN 还说,即使是默认值也是undefined.

查看:

> var obj2 = {a: 1, b: 2, c: undefined };
> obj2.hasOwnProperty("c");
< true

stringify()

查看JSON 规范上的ECMAscriptstringify()规范:

可能的 JSON 值

(来源:json.org)

这是 ECMAscript 规范的一部分:

  1. 否则一个。令 K 是一个内部字符串列表,由 [[Enumerable]] 属性为 true 的 value 的所有自身属性的名称组成。字符串的顺序应与 Object.keys 标准内置函数使用的顺序相同。

他们说,该对象的枚举属性应该符合Object.keys(). 让我们测试一下...

> var obj2 = {a: 1, b: 2, c: undefined };
> Object.keys(obj2);
< ["a", "b", "c"]

嗯,他们是对的!

然后有一个Str()函数定义了处理undefined值的行为。有几个If Type() ...步骤不适用于未定义的值,以

  1. 返回未定义

当被对象序列化程序调用时:

  1. 对于 K 的每个元素 P。

    一个。令 strP 为使用参数 P 和值调用抽象操作 Str 的结果。

    湾。如果 strP 不是未定义的

    [...]

于 2017-02-20T20:03:51.883 回答
0

正如评论和其他答案之一中所解释的,这两个对象都是不平等的。

幸运的是,在 Jasmine 2.5 中,您可以使用自定义相等测试器(定义您自己的相等)来解决这个问题:

function customEquality(a, b) {
    let keys,
        key,
        equal = true;
    // Store unique list of keys over both objects
    keys = Object.keys(a).concat(Object.keys(b)).reduce(function(result, name) {
        if (!result.includes(name)) {
            result.push(name);
        }
        return result;
    }, []);
    for (key of keys) {
        // ignore when keys are defined in both objects, 
        // having the value undefined
        if (typeof a[key] === "undefined" && a.hasOwnProperty(key) &&
            typeof b[key] === "undefined" && b.hasOwnProperty(key)) {
                continue;
        }
        equal = equal && b[key] === a[key];
    }
    return equal;
}

jasmine.addCustomEqualityTester(customEquality);

将其插入到beforeEach()与您的测试相同的块中或实际的it().

基本测试器将忽略两个对象中存在的未定义值。如果一个对象中存在未定义的值,则它不会被视为相等。

请注意,此测试器的行为与 pure 有很大不同toEqual(),因为它不比较数组或嵌套对象,也不比较 DOM 节点等其他对象类型。这只是一个指导你的例子。

于 2017-02-20T20:08:05.783 回答