esnext:Function.prototype.toString 终于有规范了
从 ES1 到 ES5 的这 14 年时间里,Function.prototype.toString 的规范一字未变:
An implementation-dependent representation of the function is returned. This representation has the syntax of a FunctionDeclaration. Note in particular that the use and placement of white space, line terminators, and semicolons within the representation String is implementation-dependent.
这段话说了两点内容:
1. toString() 返回的字符串应该符合 FunctionDeclaration 的语法
2. 要不要保留原始的空白符和分号,规范不管
规范管的一点引擎们从来没遵守
先说第一点,规范管的。FunctionDeclaration 就是我们通常说的函数声明,语法是这样的:
function BindingIdentifier (FormalParameters) { FunctionBody }
规范要求所有函数 toString() 时返回的字符串都得符合函数声明的语法,但其实从 1995 年到今天没有一个 JS 引擎做到过,违背这个约束的主要有下面两种情况:
1. 匿名函数表达式 toString() 时返回的是 FunctionExpression 而不是 FunctionDeclaration
var f = function (){}
f.toString() // "function (){}"
"function (){}" 不符合函数声明的语法,因为缺少函数名,返回的实际上是个函数表达式,直到现在所有引擎也都这样。
额外小知识:V8 去年实现过将推断出的函数名放到 function 和 参数列表之间,后来又删了
2. 内置函数、宿主函数、绑定函数 toString() 时返回的 FunctionDeclaration 不合法
Object.toString() // "function Object() { [native code] }"
alert.toString() // "function alert() { [native code] }"
(function (){}).bind().toString() // "function () { [native code] }"
包含 [native code] 字样的函数体显然不是合法的 JS 语法,更不可能符合 FunctionDeclaration,实际上内置函数和宿主函数根本不是用 JS 写的,他们不可能有真正的函数体。
这两点都是需要规范来澄清的,esdiscuss 上也有过多次讨论,ES4 的规范草案曾经专门澄清过第一点:
Description
The intrinsic
toStringmethod converts the executable code of the function to a string representation. This representation has the syntax of a FunctionDeclaration or FunctionExpression. Note in particular that the use and placement of white space, line terminators, and semicolons within the representation string is implementation-dependent.COMPATIBILITY NOTE ES3 required the syntax to be that of a FunctionDeclaration only, but that made it impossible to produce a string representation for functions created from unnamed function expressions.
也就是说 ES4 想把曾经限制的 FunctionDeclaration 扩展成 “FunctionDeclaration 或 FunctionExpression”,但后来的事你就知道了,ES4 流产了,ES5 并没有改 ES3 里的这一段话。
规范不管的一点更是一团糟
关于空白符和分号的处理,引擎爱怎么实现就怎么实现,比如下面这个简单的函数:
function f (){return 1}
f.toString()
// Chrome 下是 "function f(){return 1}",函数名右边的空白符没了,左边也只剩下一个空格
// Firefox 17 前曾是 "function f() {\n return 1;\n}",除了同上面 Chrome 相同的一点外,函数体内多了一些空白符,还多了个分号
// Firefox 17 之后是 "function f(){return 1}",和 Chrome 一样了
// IE 所有版本都是 "function f (){return 1}",源代码原封不动返回
实际上各引擎实现有差异的不止空白符、分号这两个语法元素,还有注释,甚至还有常规的语句,比如:
function f() {
// foo
/* bar */
1+2
return 2 + 2
}
console.log(f.toString())
上面的代码在 Firefox 17 之前输出会是:
function f() {
return 4;
}
函数体内部只剩下了一行,注释都丢了,一些代码也被优化了。
还有下面的代码:
(function() {
"use strict"
function f() {1+1}
console.log(f.toString())
})()
在 Firefox 48 之前会输出:
function f() {
"use strict";
1+1}
就是说它会把继承自上层作用域的严格模式在自己的源码中体现出来。
还有各种曾经的浏览器有着各种各样的奇怪表现,kangax 在 09 年和 14 年分别写文章讲过 http://perfectionkills.com/those-tricky-functions/ http://perfectionkills.com/state-of-function-decompilation-in-javascript/ 时到如今,研究这些历史表现已经意义不大了,我们统统跳过。
ES6 的澄清
可以这么说,函数的 toString() 方法在 ES6 之前就没有规范。ES6 中引入了箭头函数、生成器函数、类等 7 种新的函数语法,同时对函数的 toString() 方法做了更详细的规定:
The string representation must have the syntax of a FunctionDeclaration, FunctionExpression,GeneratorDeclaration, GeneratorExpression, ClassDeclaration, ClassExpression, ArrowFunction,MethodDefinition, or GeneratorMethod depending upon the actual characteristics of the object.
The use and placement of white space, line terminators, and semicolons within the representation String is implementation-dependent.
If the object was defined using ECMAScript code and the returned string representation is not in the form of a MethodDefinition or GeneratorMethod then the representation must be such that if the string is evaluated, using
evalin a lexical context that is equivalent to the lexical context used to create the original object, it will result in a new functionally equivalent object. In that case the returned source code must not mention freely any variables that were not mentioned freely by the original function’s source code, even if these “extra” names were originally in scope.If the implementation cannot produce a source code string that meets these criteria then it must return a string for which
evalwill throw a SyntaxError exception.
第一点是对旧规范的澄清,说返回的字符串不必须是函数声明了;第二点没变化;第三四点是新加的,三是说一个函数 fn 和通过 eval(fn.toString()) 生成的新函数功能要等效;四是说假如引擎做不到前面规定的这些,那就必须让 toString() 返回一个包含非法语法的字符串,即向前不兼容。
真正的规范来了
但其实 ES6 里的新规定仍然很模糊,比如说两个函数功能等效,那究竟啥是功能等效,还有仍然不管空白符和分号,这些导致各浏览器中 toString() 的返回结果仍然可以是五花八门。
ES6 之后,一个新的提案尝试对 Function.prototype.toString 进行真正的规定,目前在 Stage 3 阶段,Chrome 和 Firefox 已经基本实现了这一提案,其实这个新的规范很好记忆:
1. 凡是有完整源码的,一字不落把源码返回,比如:
function f (){return 1}
"function f (){return 1}" === f.toString() // true
Chrome 和 Firefox 以前都是把从参数列表左侧的那个小括号开始到函数体右侧那个大括号结束的源码保存下来,用的时候前面补上了“function 函数名”,现在是从 “function” 关键字就开始保存源码,如果是 async function,会从 “async” 关键字开始保存。
如果是方法,会从方法名开始保存;如果是生成器方法,会从 * 号开始保存;如果是 getter/setter,会从 “get” 或 “set” 开始保存:
({m/*注释*/(){}}).m.toString() // "m/*注释*/(){}"
({* g/*注释*/(){}}).g.toString() // "* g/*注释*/(){}"
Object.getOwnPropertyDescriptor({get/*A*/f/*B*/(/*C*/ /*D*/)/*E*/{/*F*/}}, "f").get.toString()
// "get/*A*/f/*B*/(/*C*/ /*D*/)/*E*/{/*F*/}"
总之最核心的理念就是,源码是什么,toString() 就返回什么,ES6 里曾经要求的什么“功能等效”和“向前不兼容”,全部作废。
2. 通过 Function()/GeneratorFunction()/AsyncFunction() 这些“函数的构造函数”动态生成的函数(没有真实的源码)在 toString() 时返回什么,这个提案也做了详细的规定,没有模棱两可的地方。
Function("a","b","a+b").toString()
/*
function anonymous(a,b
) {
a+b
}
*/
基本上就是 "function anonymous(" + 参数名列表.join(",") + ""\n) {\n" + 函数体 + "\n}"
3. 内置函数、宿主函数、绑定函数返回的函数体得是 { [native code] },不过这其中的空白符可以任意放置,用代码来说话的话,这些函数 toString() 的返回结果要能匹配下面这个正则:
/\bfunction\b[\s\S]*\([\s\S]*\)[\s\S]*\{[\s\S]*\[[\s\S]*\bnative\b[\s\S]+\bcode\b[\s\S]*\][\s\S]*\}/
总结
本文故意省略很多细枝末节,读完之后你只要记的一句就够了:“Function.prototype.toString 已经有了严格的规范,规范的核心就是函数的源码是什么就返回什么”。
esnext:Function.prototype.toString 终于有规范了的更多相关文章
- Function.prototype.toString 的使用技巧
Function.prototype.toString这个原型方法可以帮助你获得函数的源代码, 比如: function hello ( msg ){ console.log("hello& ...
- Function.prototype.toString
语法:fn.toString(indentation) 改方法返回当前函数源代码的字符串,而且还可对此字符串进行操作,比如: function num(){ }; var str = num.toSt ...
- 判断js中各种数据的类型方法之typeof与0bject.prototype.toString讲解
提醒大家,Object.prototype.toString().call(param)返回的[object class]中class首字母是大写,像JSON这种甚至都是大写,所以,大家判断的时候可以 ...
- JavaScript:Object.prototype.toString方法的原理
在JavaScript中,想要判断某个对象值属于哪种内置类型,最靠谱的做法就是通过Object.prototype.toString方法. var arr = []; console.log(Obje ...
- JavaScript:Object.prototype.toString进行数据类型判定
在JavaScript中,想要判断某个对象值属于哪种内置类型,最靠谱的做法就是通过Object.prototype.toString方法. var arr = []; console.log(Obje ...
- JavaScript中Object.prototype.toString方法的原理
在JavaScript中,想要判断某个对象值属于哪种内置类型,最靠谱的做法就是通过Object.prototype.toString方法. ? 1 2 var arr = []; console.lo ...
- Object.prototype.toString.call(obj)使用方法以及原理
这几天看vue-router的源码 发现了Object.prototype.toString.call()这样的用法,当时以为这就是转成字符串的用的,但是越看越觉得不太对劲,赶紧查查资料,一查才知道没 ...
- js Object.prototype.toString.call()
Object.prototype.toString.call(obj)使用方法以及原理 这几天看vue-router的源码 发现了Object.prototype.toString.call()这 ...
- Object.prototype.toString.call(arg)详解
经常能碰到Object.prototype.toString.call对参数类型进行判断,一开始只知道怎么使用,却不了解具体实现的原理,最近恶补了一下相关知识,写个笔记加强理解,有什么不对的请指教. ...
随机推荐
- Eclipse中的快捷键
Ctrl+1:快捷修复(数字 1 不是字母 l) 将鼠标悬停到出错区域,按 Ctrl+1,出现快捷修复的菜单, 按上下方向键选择一种修复方式即可. 也可以将光标移动到出错区域,按 F2 + Enter ...
- JRE与JDK简介
如何进行 Java 开发: JRE: JDK:
- centos搭建ftp服务器
申请个京东云服务用着.上传文件想搭建个ftp服务.遇到个坑记录一下: 这里就简单的使用yum安装 ftp服务: vsftpd 全称 very secure ftp deamon (非常安全的ftp ...
- lombook安装以及在eclipse和idea上配置
一.安装 a.官网上的安装方法 1.点击Download! 2.点击确认下载 3.下载完成,双击打开,点击“open”即可 4.之后会自动找到你的IDE如Eclipse,点击“install/upda ...
- python使用http、https代理
在国内利用Python从Internet上爬取数据时,有些网站或API接口被限速或屏蔽,这时使用代理可以加速爬取过程,减少请求失败,Python程序使用代理的方法主要有以下几种: (1)如果是在代码中 ...
- ABP大型项目实战(2) - 调试与排错 - 日志 - 查看审计日志
这是<ABP大型项目实战>系列文章的一篇. 项目发布到生产环境后难免会有错误. 那么如何进行调试和排错呢? 我看到俱乐部里有人是直接登陆生产服务器把数据库下载到开发机器进行调试排错 ...
- Nginx 关于进程数 与CPU核心数相等时,进程间切换的代价是最小的-- 绑定CPU核心
在阅读Nginx模块开发与架构模式一书时: "Nginx 上的进程数 与CPU核心数相等时(最好每个worker进程都绑定特定的CPU核心),进程间切换的代价是最小的;" &am ...
- Python-序列号和模块复习-64
# 序列化模块 # 数据类型转化成字符串的过程就是序列化 # 为了方便存储和网络传输 # json # dumps # loads # dump 和文件有关 # load load不能load多次 i ...
- Java语法----Java中equals和==的区别
[正文] 平时在学Android和Java语言的时候,总是碰到“equals”和“==”这两个字符,老感觉差不多:其实还是有一些区别的,今天干脆把它们彻底弄清楚. 一.java当中的数据类型和“==” ...
- heapsort(Java)(最小堆)
public static void main(String[] args) { Scanner input = new Scanner(System.in); int n = input.nextI ...