原型继承:为什么false不等于false?

3
我快速起草了一个小助手方法,使未定义变量的检查更加容易。
Object.prototype.is = function() {
   for(var i in arguments) {
       if(this === arguments[i]) {
           return true;
       }
   }
   return false;
};

这段代码的设计初衷是这样使用的:foo.is(undefined, false),用来检查 foo 是否为 undefined 或 false。我使用的测试案例是:

var a = false;
a.is(false);
> false

有点困惑,我又稍微试了一下。一些 console.logging 显示,等式检查失败是因为被比较的两个对象不同。
Boolean {is: function} === false
> false

所以,a从它的曾祖父Object.prototype继承了is方法,但比较中的false没有继承。
我想我可以通过使用new Boolean(false)强制继承,这将确保创建一个对象的新实例(希望规避在扩展Object的原型之前创建的对象的引用所可能导致的任何风险)。结果如下:
Boolean {is: function} === Boolean {is: function}
> false

为什么相等性检查失败?

在解决这个问题的过程中,我检查了函数被调用时传入的单个布尔值参数后的参数数组,只发现其长度为两个,额外的参数是在 Object.prototype 中声明的 is 函数。

a.is(false);
arguments -> [false, is: function]

这是怎么出现在这里的?

顺便说一下,我知道这样像猴子补丁是个坏主意!这不是生产代码,我只是感兴趣。


1
你应该像这样迭代arguments: for (var i = 0; i < arguments.length; i++)而不是你现在的方式。此外,只有当它们是完全相同的对象(不仅内容相同,而且是物理上不同的对象)时,才传递===== - jfriend00
1个回答

5

问题是什么?

这是另一个关于this值被强制转换为对象的问题 - 解决方法是在函数开头添加'use strict',它可以防止发生这种强制转换。但是,旧版浏览器可能无法识别此指令。

如果函数不是严格模式,则会出现以下情况:

var a = false;
Object.prototype.is.call(Object(a), undefined, false);

当将 a 转换为对象(通过调用 Object(a))时,它会变成一个布尔对象,这与布尔值不同。例如,考虑以下代码:
false === new Boolean(false); // false

此外,只有引用同一对象时对象才相等:

new Boolean(false) === new Boolean(false); // false
var test = new Boolean(false);
test === test; // true, they are the same object

另一个可能出现的问题是,当你使用null调用is()函数时(且不是严格模式),它将失败:

Object.prototype.is.call(null, null); // becomes window === null (false)

我该怎么做?

除了将其作为严格函数之外,另一种选择是比较两者的对象值(尽管这会使类似于 new Boolean(false).is(false)new Boolean(false).is(new Boolean(false)) 的内容变为true,这可能是不打算的结果。

你可以采取的另一种选择是将其变成一个非原型函数,就像 Object.is 一样(但要注意ES6很可能定义一个本地的Object.is函数,它的行为与你的不同):

Object.oneIsEqual = function (arg, compare)
{   for(var i = 0; i < compare.length; ++i) if (arg === compare[i]) return true;
    return false;
};

你编辑的注意事项:

你在 for-in 循环中获取原始函数的原因是因为你在原型上定义了它,并且它是可枚举的(稍后会详细解释)。

arguments 对象有一个原型,即 Object.prototype,这使得 is 函数可以在 arguments 上访问。

Object.prototype.is = function() {
   for(var i in arguments) {
       if(this === arguments[i]) {
           return true;
       }
   }
   return false;
};
(function () { return arguments.is; })(); // is the same function as to Object.prototype.is

默认情况下(正常定义时),所有属性(无论是直接在对象上还是在原型链上)都是可枚举的,这意味着您可以在 for-in 循环和其他类似结构中看到它。但是,这(大多数情况下)不是期望的行为 - 大多数本机原型函数都不是可枚举的。为了解决这个问题,您应该遍历函数的索引,而不是属性:

for (var i = 0; i < arguments.length; ++i) // code here

非常有趣,感谢解释。我稍微更新了问题,并出现了另一个奇怪的问题。有什么想法吗? - Dan Prince
@DanPrince:我已经编辑了我的答案,涵盖了我认为你在谈论的内容。 - Qantas 94 Heavy
另一个选项是,在进行比较之前检查this instanceof Boolean/Number/String,如果是这种情况,则在比较中使用this.valueOf()。或者为了避免触发继承类(如果这是一个问题),可以检查this.constructor - Fabio Beltramini
@FabioBeltramini:这个问题和我在答案中讨论的一样 - new Boolean(false).valueOf() === false 是 true,但它们实际上并不相同。 - Qantas 94 Heavy
我理解可枚举对象,但这与for-in无关。如果我从函数体中删除循环,则参数数组的值不会改变。我知道参数数组应该有一个is方法,但它应该出现在“数组”的属性而不是元素中,对吗? - Dan Prince
@DanPrince:这是因为你的控制台很可能执行了类似于for-in循环的操作来查看其中的元素。 - Qantas 94 Heavy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接