3

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

启动 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)

?

4

4 回答 4

3

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

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

obj.method()

这将导致 JavaScript在调用时设置this为.objmethod()

如果你做这样的事情:

var myFunction = obj.method;

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

var myFunction = obj.method;
myFunction();

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

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

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

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

那么为什么会发生这种情况:

hasOwnProperty("x")

TypeError:无法将 undefined 或 null 转换为对象

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

global.hasOwnProperty("a")

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


仅供参考,有更多方法可以控制this传递给函数的内容,而不仅仅是obj.method(). 您可以在此其他答案中了解其他方式。它们包括诸如.call(), .apply(), 箭头函数(在ES6中)等...

于 2017-01-05T22:55:29.783 回答
1

使用时hasOwnProperty("x")

  1. hasOwnProperty是一个标识符。查看运行时语义

  2. 标识符是使用ResolveBinding解析的,它调用GetIdentifierReference

  3. 可能在一些递归之后,这会产生参考

    { base: globalEnvRec, referencedName: "hasOwnProperty", strict: strictFlag }
    
  4. 然后你调用它。查看运行时语义

  5. 它使用GetValue获取函数,如下所示:

    1. 为全局环境记录调用GetBindingValue
    2. 假设没有声明绑定,它将为对象记录调用GetBindingValue
    3. 这将使用绑定对象调用Get
  6. 由于引用的基础是环境记录,因此thisValueWithBaseObject获取全局环境记录,它总是返回undefined

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

使用时globalObj.hasOwnProperty("x")

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

  2. 评估属性访问器。查看运行时语义

  3. 它返回引用

    { base: globalObj, referencedName: "hasOwnProperty", strict: strictFlag }
    
  4. 然后你调用它。查看运行时语义

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

  6. 由于引用是属性引用,因此thisValueGetThisValue获得,它返回基(全局对象)。

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

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

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

最后,在值上Object.prototype.hasOwnProperty使用ToObjectthis的定义。null如果它是or ,那将抛出undefined

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

那么,是hasOwnProperty在严格模式下定义的吗?那么,对于内置函数对象

对于每个内置函数,当使用 [[Call]] 调用时,[[Call]] thisArgument提供this值,[[Call]] argumentsList提供命名参数,而 NewTarget 值 未定义

因此,该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

或吸气剂方法:

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开始,它完全依赖于实现。

如果主机需要使用外来对象作为领域全局对象,则让global成为以实现定义的方式创建的这样一个对象。

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

即使你的实现没问题,它仍然很糟糕hasOwnProperty可以被遮蔽。比如我刚才在网上说window继承自Object.prototype,但是全局污染者在原型链中更接近,可能有一个元素带有id="hasOwnProperty"!!

相反,我推荐其中之一:

Object.prototype.hasOwnProperty.call(globalObj, prop)
Object.getOwnPropertyDescriptor(globalObj, prop) !== undefined
于 2017-01-06T01:26:50.843 回答
1

2017-01-16 更新strict mode:Node.js 在明确设置之前无法使用。与 Firefox 中的 Node.js 之间仍然存在差异non strict mode(在 Firefox 中,简单的调用hasOwnProperty无异常)。当/如果我找到结果时,我将进一步搜索并更新此答案。


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

在 中strict mode,如果this没有由执行上下文定义,它仍然是undefined

(这是我直接调用 hasOwnProperty 时的情况。)

更多信息在这里(在简单调用部分)

于 2017-01-05T22:24:48.727 回答
1

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

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

  • 当函数作为“裸”函数(例如bar())调用时,该this值是

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

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

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

hasOwnPropertyNode.js 的方法似乎处于严格模式,因此作为其值hasOwnProperty("baz")提供。询问它是否具有任何属性是不明智的,因此此调用会产生错误。undefinedthisundefined

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

于 2017-01-05T22:32:59.753 回答