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
toString
method 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
eval
in 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
eval
will 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对参数类型进行判断,一开始只知道怎么使用,却不了解具体实现的原理,最近恶补了一下相关知识,写个笔记加强理解,有什么不对的请指教. ...
随机推荐
- Thirft简单使用
安装Thrift 到thrift官网下载thrift.exe http://thrift.apache.org/download 将thrift-0.10.0.exe复制到C:\Program Fil ...
- 【Linux基础】查看某一端口是否开放(1025为例)
1.使用lsof 命令来查看端口是否开放 lsof -i:1025 //如果有显示说明已经开放了,如果没有显示说明没有开放 lsof(list open files)是一个列出当前系统打开文件的工具. ...
- 日志学习系列(一)——Log4net的基础知识学习
今天把Log4net日志记录做了封装,作为一个公共的类库.记录一下应该注意的地方.先了解一下log4net的理论知识. 参考百度百科 一.log4net是什么? log4net库是Apache log ...
- Nginx 的 access log 如何以 json 形式记录?
Nginx 的 access log 默认是以空格分隔的字符串形式记录的,格式如下 log_format proxy '[$time_local] $remote_addr ' '$protocol ...
- 单元测试(qunit)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http ...
- android 实现点击edittext的“小眼睛”切换明密文
android 实现点击edittext的“小眼睛”切换明密文 版权声明:本文为博主原创文章,未经博主允许不得转载. 很多时候,我们为了用户的隐私安全,需要在密码输入的时候,显示密文.为了更 ...
- Java 通过地址获取经纬度 - 高德地图
一.添加依赖 <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-v ...
- P5239 回忆京都(洛谷3月月赛T2)
题目描述 射命丸文在取材中发现了一个好玩的东西,叫做组合数. 组合数的定义如下:从n个不同元素中,任取m(m≤n)个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合.所有组合的数量,就是组合数 ...
- python 判断网络通断同时检测网络的状态
思路:通过http判断网络通断,通过ping获取网络的状态 注意:不同平台下,调用的系统命令返回格式可能不同,跨平台使用的时候,注意调整字符串截取的值 主程序:network_testing_v0.3 ...
- 安装VM-tools
win10系统 VMware12 Ubuntu64位安装VM-tools时所遇到的提示信息: open-vm-tools are available from the OS vendor and VM ...