gpt4 book ai didi

javascript - 不同 Web 浏览器上 "use strict"的范围不一致(关于 argument.callee 和 caller)

转载 作者:行者123 更新时间:2023-12-03 00:00:50 29 4
gpt4 key购买 nike

情况:

我发现 Javascript 中的严格模式有些奇怪。

  • 我正在使用外部第三方 Javascript 库,该库
    • 被缩小,
    • 有超过 4000 行代码,
    • 根本使用use strict,并且
    • 正在使用arguments.callee
  • 我在自己的代码中使用 use strict,范围在函数内。

当我调用库提供的函数之一时,它会抛出错误。然而,

  • 仅当我使用 use strict 时才会引发错误
  • 除 Chrome 之外的所有浏览器都会引发该错误
<小时/>

代码:

我删除了所有不相关的内容,并将代码简化为以下内容 ( online demo on jsFiddle ):

// This comes from the minified external JS library.
// It creates a global object "foo".
(function () {
foo = {};
foo.bar = function (e) {
return function () {
var a5 = arguments.callee;
while (a5) {
a5 = a5.caller // Error on this line in all browsers except Chrome
}
}
}("any value here");
})();

// Here's my code.
(function() {
"use strict"; // I enable strict mode in my own function only.

foo.bar();
alert("done");
})();

<小时/>

测试结果:

+-----------------------+-----+--------------------------------------------------------------+
| Browser | OS | Error |
+-----------------------+-----+--------------------------------------------------------------+
| Chrome 27.0.1453.94 m | Win | <<NO ERROR!>> |
| Opera 12.15 | Win | Unhandled Error: Illegal property access |
| Firefox 21.0 | Win | TypeError: access to strict mode caller function is censored |
| Safari 5.1.7 | Win | TypeError: Type error |
| IE 10 | Win | SCRIPT5043: Accessing the 'caller' property of a function or |
| | | arguments object is not allowed in strict mode |
| Chrome 27.0.1543.93 | Mac | <<NO ERROR!>> |
| Opera 12.15 | Mac | Unhandled Error: Illegal property access |
| Firefox 21.0 | Mac | TypeError: access to strict mode caller function is censored |
| Safari 6.0.4 | Mac | TypeError: Function.caller used to retrieve strict caller |
+-----------------------+-----+--------------------------------------------------------------+

注意:对于操作系统Win = Windows 7,Mac = Mac OS 10.7.5

<小时/>

我的理解:

  • 所有现代桌面浏览器都支持使用严格(请参阅 Can I use)。
  • use strict 的作用域位于我的函数内,因此在其作用域之外定义的所有内容都不会受到影响(请参阅 this Stack Overflow question )。
<小时/>

问题:

那么,除了 Chrome 之外的所有浏览器都是错误的吗?或者反过来呢?或者这是未定义的行为,因此浏览器可以选择以任何一种方式实现它?

最佳答案

前言

在我们深入讨论之前,先简单介绍几点:

  • All modern desktop browsers support use strict...

不,一点也不。 IE8 是一个相当现代的浏览器 (2015 年不再是了),而 IE9 是一个相当相当现代的浏览器。它们都不支持严格模式(IE9 支持部分)。 IE8 将会陪伴我们很长一段时间,因为它是 Windows XP 上最高的版本。尽管 XP 现在已经完全终止生命(好吧,您可以从 MS 购买特殊的“自定义支持”计划),但人们仍会继续使用它一段时间。

  • The use strict is scoped within my function, so everything defined outside its scope is not affected

不完全是。该规范对非严格代码如何使用严格模式下创建的函数施加了限制。所以严格模式可以突破它的限制。事实上,这就是您正在使用的代码所发生的事情的一部分。

概述

So, are all browsers except Chrome wrong? Or is it the other way round? Or is this undefined behaviour so the browsers may choose to implement it in either way?

稍微研究一下,它看起来像:

  1. Chrome 正在以一种方式实现这一目标,

  2. Firefox 以不同的方式实现这一目标,

  3. ...而 IE10 的错误非常轻微。 :-) (IE9 肯定会出错,尽管不是特别有害。)

我没有看其他人,我想我们已经覆盖了地面。

从根本上造成问题的代码是这个循环

var a5 = arguments.callee;
while (a5) {
a5 = a5.caller // Error on this line in all browsers except Chrome
}

...它依赖于函数对象的 caller 属性。那么让我们从这里开始吧。

函数#caller

第三版规范中从未定义过Function#caller 属性。有些实现提供了它,有些则没有。这是一个一个令人震惊的坏主意(抱歉,这是主观的,不是吗?)一个实现问题(甚至比arguments.caller),特别是在多线程环境(并且存在多线程 JavaScript 引擎)以及递归代码中,正如 Bergi 在该问题的评论中指出的那样。

因此,在第五版中,他们通过指定在严格函数上引用 caller 属性会引发错误,明确地摆脱了它。 (这是在 §13.2, Creating Function Objects, Step 19 中。)

这是一个严格函数。然而,在非严格函数上,行为是未指定的并且依赖于实现。这就是为什么有这么多不同的方法可以做到这一点。

检测代码

返回检测代码比调试 session 更容易,所以让我们use this :

console.log("1. Getting a5 from arguments.callee");
var a5 = arguments.callee;
console.log("2. What did we get? " +
Object.prototype.toString.call(a5));
while (a5) {
console.log("3. Getting a5.caller");
a5 = a5.caller; // Error on this line in all browsers except Chrome
console.log("4. What is a5 now? " +
Object.prototype.toString.call(a5));
}

Chrome 如何做到这一点

在 V8(Chrome 的 JavaScript 引擎)上,上面的代码为我们提供了以下内容:

1. Getting a5 from arguments.callee2. What did we get? [object Function]3. Getting a5.caller4. What is a5 now? [object Null]

So we got a reference to the foo.bar function from arguments.callee, but then accessing caller on that non-strict function gave us null. The loop terminates and we don't get any error.

Since Function#caller is unspecified for non-strict functions, V8 is allowed to do anything it wants for that access to caller on foo.bar. Returning null is perfectly reasonable (although I was surprised to see null rather than undefined). (We'll come back to that null in the conclusions below...)

How Firefox Gets It Right

SpiderMonkey (Firefox's JavaScript engine) does this:

1. Getting a5 from arguments.callee2. What did we get? [object Function]3. Getting a5.callerTypeError: access to strict mode caller function is censored

We start out getting foo.bar from arguments.callee, but then accessing caller on that non-strict function fails with an error.

Since, again, the access to caller on a non-strict function is unspecified behavior, the SpiderMonkey folks can do what they want. They decided to throw an error if the function that would be returned is a strict function. A fine line, but as this is unspecified, they're allowed to walk it.

How IE10 Gets It Very Slightly Wrong

JScript (IE10's JavaScript engine) does this:

 1. Getting a5 from arguments.callee  2. What did we get? [object Function]  3. Getting a5.caller SCRIPT5043: Accessing the 'caller' property of a function or arguments object is not allowed in strict mode

As with the others, we get the foo.bar function from arguments.callee. Then trying to access that non-strict function's caller gives us an error saying we can't do that in strict mode.

I call this "wrong" (but with a very lower-case "w") because it says that we can't do what we're doing in strict mode, but we're not in strict mode.

But you could argue this is no more wrong that what Chrome and Firefox do, because (again) the caller access is unspecified behavior. So the IE10 folks decided that their implementation of this unspecified behavior would throw a strict-mode error. I think it's misleading, but again, if it's "wrong," it certainly isn't very wrong.

BTW, IE9 definitely gets this wrong:

1. Getting a5 from arguments.callee 2. What did we get? [object Function] 3. Getting a5.caller 4. What is a5 now? [object Function] 3. Getting a5.caller 4. What is a5 now? [object Null]

It allows Function#caller on the non-strict function, and then allows it on a strict function, returning null. The spec is clear that that second access should have thrown an error, as it was accessing caller on a strict function.

Conclusions and Observations

What's interesting about all of the above is that in addition to the clearly-specified behavior of throwing an error if you try to access caller on strict functions, Chrome, Firefox, and IE10 all (in various ways) prevent your using caller to get a reference to a strict function, even when accessing caller on a non-strict function. Firefox does this by throwing an error. Chrome and IE10 do it by returning null. They all support getting a reference to a non-strict function via caller (on a non-strict function), just not a strict function.

I can't find that behavior specified anywhere (but then, caller on non-strict functions is entirely unspecified...). It's probably the Right Thing(tm), I just don't see it specified.

This code is also fun to play with: Live Copy | Live Source

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Strict and Loose Function#caller</title>
<style>
p {
font-family: sans-serif;
margin: 0.1em;
}
.err {
color: #d00;
}
</style>
</head>
<body>
<script>
function display(msg, cls) {
var p = document.createElement('p');
if (cls) {
p.className = cls;
}
p.innerHTML = String(msg);
document.body.appendChild(p);
}

// The loose functions
(function () {
function loose1() {
display("loose1 calling loose2");
loose2();
}
loose1.id = "loose1"; // Since name isn't standard yet

function loose2() {
var c;

try {
display("loose2: looping through callers:");
c = loose2;
while (c) {
display("loose2: getting " + c.id + ".caller");
c = c.caller;
display("loose2: got " +
((c && c.id) || Object.prototype.toString.call(c)));
}
display("loose2: done");
}
catch (e) {
display("loose2: exception: " +
(e.message || String(e)),
"err");
}
}
loose2.id = "loose2";

window.loose1 = loose1;

window.loose2 = loose2;
})();

// The strict ones
(function() {
"use strict";

function strict1() {
display("strict1: calling strict2");
strict2();
}
strict1.id = "strict1";

function strict2() {
display("strict2: calling loose1");
loose1();
}
strict2.id = "strict2";

function strict3() {
display("strict3: calling strict4");
strict4();
}
strict3.id = "strict3";

function strict4() {
var c;

try {
display("strict4: getting strict4.caller");
c = strict4.caller;
}
catch (e) {
display("strict4: exception: " +
(e.message || String(e)),
"err");
}
}
strict4.id = "strict4";

strict1();
strict3();
})();
</script>
</body>
</html>

关于javascript - 不同 Web 浏览器上 "use strict"的范围不一致(关于 argument.callee 和 caller),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16871050/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com