前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >解决'Function.caller used to retrieve strict caller'报错

解决'Function.caller used to retrieve strict caller'报错

原创
作者头像
windseeker
发布2021-12-02 09:58:28
8490
发布2021-12-02 09:58:28
举报

最近一个活动项目中,在IOS的浏览器中会必现一个bug, 这个bug的起因是,我们在一个vue开发的项目中,通过script方式引入了一个历史有点久的动画库,通过eruda定位到问题,调用栈指向的就是这个动画库,具体报错信息即Function.caller used to retrieve strict caller。但是,为什么在PC上的chrome模拟器没有这个bug,为什么不同浏览器的对于Function.caller这个API实现的差异这么大呢?基于此,我总结了一些经验,如果你不幸也遇到这个问题,希望可以参考这篇文章并获得一些帮助。

Function.caller的表现跟严格模式和非严格模式是有区别的,在MDN可以看到定义:它会返回调用指定函数的函数,在严格模式中禁止使用主要是因为尾调用优化。并且有一段警告:

Non-standard This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.

你可以在不同浏览器执行这段代码:

代码语言:txt
复制
  function fun1() {
    console.log(arguments.callee.caller);
  }

  function fun2() {
    fun1();
  }

  function fun3() {
    'use strict';
    fun1();
  }

  fun2();
  fun3();

然后你会发现,正如 MDN 的警告所言,不同的引擎实现的差异非常大,fun2都能正常执行,但是执行fun3函数的时候,这是我的测试结果:

代码语言:txt
复制
Safari12/JavaScriptCore:

    TypeError: Function.caller used to retrieve strict caller

Firefox63/SpiderMonkey:

    TypeError: access to strict mode caller function is censored
    
IE10/Trident:

	Accessing the 'caller' property of a function or arguments object is not allowed in strict mode

Chrome70/v8:
    不会报错,返回null

对于非严格模式的函数,EcmaScript对于caller的实现的定义非常模糊,Forbidden Extensions中,有一段是这样说的:

If an implementation extends non-strict or built-in function objects with an own property named “caller” the value of that property, as observed using Get or GetOwnProperty, must not be a strict function object. If it is an accessor property, the function that is the value of the property’s Get attribute must never return a strict function when called.

总结来说,非严格模式函数的“caller”属性唯一的限制是,如果它要产生一个值,那么该值不能是严格模式函数。这一点大部分js引擎实现的都还不错。

V8引擎严格模式为什么不报错,而是返回NULL?

比较有趣的是,2017年还有人在V8项目下提过一个issue,function.caller differs in behavior from other browsers (但这个其实不算issue)。

V8引擎开发者之一 Benedikt Meurer 写过一篇文章

caller-considered-harmful,他有解释当你调用 foo.caller时, 在Chrome和Node.js中的工作原理:

(在理解下面这段话之前,最好先了解下什么是活动对象,可以参考这篇文章)

  1. 我们试图找到函数foo的最新活动对象,即最后一次调用foo的且未return的调用者。
  2. 如果foo没有当前活动对象,我们立即返回null。
  3. 如果有活动,我们会用一些奇技淫巧来查找到父活动对象,一直会查询到最顶级的非用户JavaScript活动对象的代码。
  4. 如果根据这些规则没有父活动,我们返回null。
  5. 此外,如果有父活动对象,但它是严格模式函数或我们无法访问它,那么我们也返回null。
  6. 其他情况,我们从父活动对象中返回闭包。

V8的项目可以在github上找到,FindCaller(https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/src/accessors.cc#L670

)这个函数就是其caller的核心代码。根据这几条规则我们已经可以知道,在最开始的例子中,我们命中的是第5条规则,父活动对象是严格模式函数,所以得到的结果是null。

代码语言:txt
复制
MaybeHandle<JSFunction> FindCaller(Isolate* isolate,
                                   Handle<JSFunction> function) {
  FrameFunctionIterator it(isolate);
  if (function->shared()->native()) {
    return MaybeHandle<JSFunction>();
  }
  // Find the function from the frames. Return null in case no frame
  // corresponding to the given function was found.
  if (!it.Find(function)) {
    return MaybeHandle<JSFunction>();
  }
  // Find previously called non-toplevel function.
  if (!it.FindNextNonTopLevel()) {
    return MaybeHandle<JSFunction>();
  }
  // Find the first user-land JavaScript function (or the entry point into
  // native JavaScript builtins in case such a builtin was the caller).
  if (!it.FindFirstNativeOrUserJavaScript()) {
    return MaybeHandle<JSFunction>();
  }

  // Materialize the function that the iterator is currently sitting on. Note
  // that this might trigger deoptimization in case the function was actually
  // materialized. Identity of the function must be preserved because we are
  // going to return it to JavaScript after this point.
  Handle<JSFunction> caller = it.MaterializeFunction();

  // Censor if the caller is not a sloppy mode function.
  // Change from ES5, which used to throw, see:
  // https://bugs.ecmascript.org/show_bug.cgi?id=310
  if (is_strict(caller->shared()->language_mode())) {
    return MaybeHandle<JSFunction>();
  }
  // Don't return caller from another security context.
  if (!AllowAccessToFunction(isolate->context(), *caller)) {
    return MaybeHandle<JSFunction>();
  }
  return caller;
}

其他引擎抛出异常的解决方案

1、移除严格模式(不推荐)

用一些插件移除编译之后的"use strict",比如这个remove-strict-webpack-plugin,原理非常简单,就是替换掉"use strict",但这种方式无疑是舍本逐末的方式。除非是历史包袱太严重的项目, 否则最好不要这么做。

因为严格模式有助于防止一些bug,并且它也有助于安全的使用 JS 。在 ES5 中, 严格模式是可选项,但是在 ES6 中,许多特性要求必须使用严格模式。 因此大多数开发者和 babel 之类的工具默认添加 use strict 到 JS 文件的头部,以确保整个 JS 文件的代码都采用严格模式,这个习惯有助于我们写更好的 JS 。

2、模拟caller方法(不推荐)
代码语言:txt
复制
function getCallerName (){
    var callerName;
    try { throw new Error(); }
    catch (e) { 
        var re = /(\w+)@|at (\w+) \(/g, st = e.stack, m;
        re.exec(st), m = re.exec(st);
        callerName = m[1] || m[2];
    }
    return callerName;
}

用这种 hack 方式可以获取到 caller的函数名,但是跟原来的 caller 的语义是不同的,原来的 caller返回的是函数的引用。而且这个方法毕竟只是一个 hack,可能会随着引擎的升级而失效。

3、禁用 caller

本来该属性就不是ECMA-262第3版标准的一部分,只是大部分浏览器实现了它,但是大部分的实现又有各自的问题,比如IE10中的in strict mode报错信息是错误的。

所以,最好的解决方式就是不要去使用它,如果之前的项目有用到,那就去改造它,总会有不使用Function.caller也可以实现的方式。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • V8引擎严格模式为什么不报错,而是返回NULL?
  • 其他引擎抛出异常的解决方案
    • 1、移除严格模式(不推荐)
      • 2、模拟caller方法(不推荐)
        • 3、禁用 caller
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档