Node.js:为什么调用 hasOwnProperty 与 global.hasOwnProperty 不同?

Node.js: why is calling hasOwnProperty different from global.hasOwnProperty?

也许这是一个新手问题,但我找不到或想不出解释。

启动 Node.js 控制台,然后:

> global.hasOwnProperty === hasOwnProperty
true

那为什么

> global.hasOwnProperty("x")
false

但是

> hasOwnProperty("x")
TypeError: Cannot convert undefined or null to object
at hasOwnProperty (<anonymous>)
at repl:1:1
at sigintHandlersWrap (vm.js:22:35)
at sigintHandlersWrap (vm.js:96:12)
at ContextifyScript.Script.runInThisContext (vm.js:21:12)
at REPLServer.defaultEval (repl.js:313:29)
at bound (domain.js:280:14)
at REPLServer.runBound [as eval] (domain.js:293:12)
at REPLServer.<anonymous> (repl.js:513:10)
at emitOne (events.js:101:20)

?

2017-01-16 更新:Node.js 在 strict mode 中不起作用,除非明确设置它。 non strict mode 和 Firefox 中的 Node.js 仍然存在差异(在 Firefox 中,对 hasOwnProperty 的简单调用无一例外地起作用)。我会进一步搜索并更新此答案 when/if 我找到了结果。


解决方案:Node.js 方法适用于 strict mode

in strict mode, if this was not defined by the execution context, it remains undefined.

(我直接调用hasOwnProperty时就是这样的)

这里有更多信息 (in the section Simple Call)

这里您需要了解的要点是函数执行中 this 的值会根据调用函数的方式而变化。这里的两个相关方式是:

  • 当函数作为对象的 属性 调用时(例如,foo.bar()),则 this 设置为拥有对象

  • 当一个函数被调用为“裸”函数时(例如,bar()),那么 this 值是

    • 全局对象,如果函数有非严格模式代码,或者
    • undefined,如果函数有严格模式代码

hasOwnProperty 函数的目的是测试某个对象(作为 this 值提供)是否具有具有特定名称的 属性(作为函数参数提供) . foo.hasOwnProperty("baz") 使用 foothis 值并测试 this 值是否具有名为 baz.

的 属性

当您调用 hasOwnProperty("baz") 时,独立标识符 hasOwnProperty 引用来自 window.hasOwnProperty 的全局可访问值,但调用的形式为 bar(...),而不是形式 foo.bar(...)。因此,适用上面提供 this 值的第二条规则。

看来 Node.js' hasOwnProperty 方法处于严格模式,因此 hasOwnProperty("baz") 被提供 undefined 作为其 this 值。询问 undefined 是否有任何 属性 是不明智的,因此此调用会产生错误。

相比之下,Firefox 的hasOwnProperty 方法似乎是非严格的,因此它获取全局对象为this。这使得调用 window.hasOwnproperty(...)hasOwnproperty(...) 的结果相同,因为它们都得到 this 等于 window.

这里的问题是hasOwnProperty()是一个对象的方法,它的全部功能就是对该对象的属性进行操作。因此,它只有在被调用时被赋予适当的对象上下文时才有效。通常它们是编写的方法,期望对象上下文在调用方法时到达 this 的值。

在JavaScript中的大多数情况下(使用箭头语法定义的函数除外),this的值由方法的调用方式决定。在适当的对象上调用方法的通常和最常见的方法是:

obj.method()

这将导致 JavaScript 在调用 method() 时将 this 设置为 obj

如果你这样做:

var myFunction = obj.method;

并且,然后您在没有对象引用的情况下调用该方法,如:

var myFunction = obj.method;
myFunction();

然后,obj 中的对象引用将丢失,并且不会以任何方式提供给方法。 JavaScript 解释器将为 this.

选择默认值

在严格模式下,this 将被设置为 undefined 并且任何尝试使用该值(期望它是对象引用)的方法都将失败。

在非严格模式下,浏览器会将 this 设置为指向“某个默认值”。在浏览器中,即 window 对象。因此,如果您尝试在 window 对象上使用该方法,您瞧,它恰好会起作用。我认为这有点意外,不是好的代码。

IMO,这个故事的寓意是任何希望与对象关联的方法都应该使用显式对象引用来调用。然后,这消除了所有混淆,消除了严格模式和非严格模式之间的所有差异,并消除了浏览器和 Node.js.

之间的所有差异

So why does this happen:

hasOwnProperty("x")

TypeError: Cannot convert undefined or null to object

如果您尝试调用 hasOwnProperty() 来测试 node.js 中全局对象的 属性,则使用 global 的上下文调用该方法对象如:

global.hasOwnProperty("a")

这将适用于任何地方,被认为是好的和适当的 Javascript 代码。在没有正确对象上下文的情况下调用它会导致 this 值被设置为默认值。在 node.js 中,默认值不一定是全局对象。但是,在所有情况下,您都不应依赖默认值。通过始终指定所需的对象引用来正确编程,您的代码将在任何地方正常工作。


仅供参考,有更多方法可以控制 this 传递给函数的内容,而不仅仅是 obj.method()。您可以阅读其他方式 here in this other answer. They include things such as .call(), .apply(), arrow functions (in ES6),等等...

当你使用hasOwnProperty("x"),

  1. hasOwnProperty 是一个 identifier. See runtime semantics

  2. 标识符使用ResolveBinding, which calls GetIdentifierReference

    解析
  3. 可能在一些递归之后,这会产生引用

    { base: globalEnvRec, referencedName: "hasOwnProperty", strict: strictFlag }
    
  4. 然后你调用它。参见 runtime semantics

  5. 使用GetValue获取函数,像这样:

    1. 为全局环境记录调用GetBindingValue
    2. 假设没有声明绑定,它会调用GetBindingValue对象记录
    3. 这将使用绑定对象调用 Get
  6. 由于引用的基础是环境记录,所以thisValue是从WithBaseObject获取全局环境记录,总是returnsundefined.

  7. 最后,调用由 EvaluateDirectCall 评估,它使用 thisValue 设置为 undefined

当你使用globalObj.hasOwnProperty("x"),

  1. 标识符解析发生在 globalObj。假设它获取了全局对象。

  2. 属性 访问器被求值。参见 runtime semantics

  3. 它returns参考

    { base: globalObj, referencedName: "hasOwnProperty", strict: strictFlag }
    
  4. 然后你调用它。参见 runtime semantics

  5. 使用GetValue获取函数。由于它是一个 属性 引用并且基数是一个对象,因此使用 [[Get]] 内部方法

  6. 由于引用是属性引用,thisValue是从GetThisValue获得的,returns是基础(全局对象) .

  7. 最后,调用由 EvaluateDirectCall 评估,它使用 thisValue 设置为全局对象。

现在重要的是函数将如何处理 this 值。

默认情况下,[[Call]] uses OrdinaryCallBindThis 在草率模式下将 nullundefined thisArgument 转换为全局对象。如果函数是在严格模式下定义的,则不会发生这种情况。

最后,在this值上定义Object.prototype.hasOwnProperty uses ToObject。如果它是 nullundefined.

就会抛出

console.log(function(){ return this }() === window);
console.log(function(){ "use strict"; return this }() === undefined);

那么,hasOwnProperty是在严格模式下定义的吗?那么,对于 built-in function objects

For each built-in function, when invoked with [[Call]], the [[Call]] thisArgument provides the this value, the [[Call]] argumentsList provides the named parameters, and the NewTarget value is undefined

所以this值原样传递,没有将nullundefined转换为全局对象。就像在严格的函数中一样。

作为一个推论,如果你想直接使用 hasOwnProperty 而不指定全局对象作为基础,你可以用一个 sloppy-mode 函数重新定义它。我不推荐这个;纯属娱乐

(function() {
  var has = Object.prototype.hasOwnProperty;
  Object.prototype.hasOwnProperty = function() {
    return has.apply(this, arguments);
  };
})();
console.log(hasOwnProperty('hello')); // false
console.log(hasOwnProperty('window')); // true

或getter方法:

Object.defineProperty(window, 'hasOwnProperty', {get: function() {
  return Object.prototype.hasOwnProperty.bind(this);
}});
console.log(hasOwnProperty('hello')); // false
console.log(hasOwnProperty('window')); // true

但是,ECMASCript 标准不保证全局对象会继承自Object.prototype

InitializeHostDefinedRealm 开始,它完全依赖于实现。

If the host requires use of an exotic object to serve as realm's global object, let global be such an object created in an implementation defined manner.

所以一般来说你不应该使用 globalObj.hasOwnProperty 或者 hasOwnProperty。对于某些实现,它可能没问题(例如,对于网络浏览器,这是由 W3C and WHATWG 标准强制执行的),但对于其他实现,它可能完全失败。

即使你的实现没问题,它仍然很糟糕hasOwnProperty 可以隐藏。比如我刚才在网上说window继承自Object.prototype,但是global polluter离原型链更近,可能会有一个元素是id="hasOwnProperty"!!

相反,我推荐其中之一:

Object.prototype.hasOwnProperty.call(globalObj, prop)
Object.getOwnPropertyDescriptor(globalObj, prop) !== undefined