arguments 对象的老历史
引题:为什么 JavaScript 中的 arguments 对象不是数组 http://www.zhihu.com/question/50803453
JavaScript 1.0
1995 年, Brendan Eich 在 Netscape Navigator 2.0 中实现了 JavaScript 1.0,arguments 对象在那时候就已经有了。当时的 arguments 对象很像我们现在的数组(现在也像),它有一些索引属性,对应每个实参,还有一个 length 属性,代表实参的数量,还有一个现在浏览器已经都没有了的 caller 属性(等价于当前函数的 caller 属性)。但是,和现在的 arguments 对象不同的是,当时的 arguments 不是一个特殊的本地变量,而是作为函数的属性(现在仍保留着)存在的:
function foo(a, b, c) {
alert(foo.arguments[0]) //
alert(foo.arguments[1]) //
alert(foo.arguments[2]) //
alert(foo.arguments.length) //
alert(foo.arguments.caller == foo.caller) // true,都等于 bar,现代浏览器里会是 false
} function bar() {
foo(1, 2, 3)
} bar()
虽然作为函数的属性,但和现在的 arguments 一样,它只能写在自己的函数体内,否则值就是 null:
function foo() {
alert(bar.arguments) // 当时是 null,现在不是了
} function bar() {
alert(bar.arguments) // {0: 1}
foo()
} bar(1) alert(bar.arguments) // null
同时,arguments 对象和各形参变量的“双向绑定”特性在那时候就已经存在了:
function foo(a) {
arguments[0] = 10
alert(a) //
a = 100
alert(arguments[0]) //
} foo(1)
然而,这个特性在当时算是个隐藏特性,Netscape 在自己的文档上从来没有提到过,而且即便到现在,还是有相当多的人不知道。为什么 20 年了还会有人不知道呢,我觉的是因为它基本没用,虽然很 cool。
大家都知道发明 JavaScript 1.0 只用了 10 天时间,当时时间非常有限,Brendan 为什么要额外实现这么一个特性?我看了下目前能找到的最旧的 js 源码,js1.4.1,发现无论是 arguments 对象的索引属性,还是形参变量,它们底层都是通过相同的 getter(js_GetArgument)和 setter(js_SetArgument)读取和设置着 fp->argv 这个 c 数组,所以它们才会有相互映射的能力。所以有没有可能并不是 Brendan 有意加的额外特性,而是他的第一反应就是应该这么去实现?
读到这里,很多同学觉的答案已经有了,arguments 对象不能是数组的原因就是:“数组没有 caller 属性” 和 “数组实现不了这种双向绑定”。前者并不是原因,因为给数组添加一个额外的非索引属性是很容易的事情,在 JS 层面也是一个简单的赋值语句即可实现(虽然一般不这么做),甚至现在引擎内部也会产出这样的数组 - 从 ES6 开始,引擎在调用模板字符串的标签函数时传入的第一个参数就是个拥有额外的 raw 属性的数组:
(function(arr) {
console.log(arr, arr.raw) // arr 和 arr.raw 都是数组
})
`\0`
我在两年前看到引擎会产生这样的数组也觉的很奇怪,还专门问了 ES 规范当时的编辑。
2016.10.5 追加,今天才想到,不仅 ES6 里有这样的数组,早在 ES3 里就已经有了:
arr = /./g.exec("123") // [ '1', index: 0, input: '123' ]
alert(Array.isArray(arr)) // true正则的 exec 方法和字符串的 match 方法返回的就是个拥有额外的 index 及 input 属性的数组。
那后者算是个原因吗?一点点又或者完全不是,说一点点是因为在当时还没有 __defineSetter__/__defineGetter__/Object.defineProperty,如果把 arguments 设计成数组,同时引擎层面把它实现成和形参相互映射的,会让人觉的太 magic 了,因为当时还写不出下面这样代码:
let a = 1
let arguments = []
Object.defineProperty(arguments, 0, {
get() {
return a
},
set(v) {
a = v
}
})
alert(arguments[0]) //
arguments[0] = 10
alert(a) //
说完全不是呢,是因为我知道另外一个更明显的,在当时,arguments 不能是数组的原因,那就是,“当时还没有数组呢”。是的,不要一脸懵逼,你现在知道的 JavaScript 的特性,有很多在 JavaScript 1.0 里是不存在的,包括 typeof 运算符,undefined 属性,Object 字面量语法等等。这个消息是我在两年前查阅历史文档发现并经 Brendan 在 Twitter 上亲自确认过的。但,还是得眼见为实,我们得在 Netscape 2.0 里确认一下:
“What?说好的没有数组呢?”,不要着急,让我们再多试几次:
多试几次就会发现,原来虽然 Array 构造函数已经存在,但它构造出来的数组还没有实际的功能,length 是 undefined,元素都是 null,这。。。我猜,是还没写完就发布了吧。
除了 arguments,在当时的 Netscape 2.0 里,还有另外一些 DOM 0(当时还没这个叫法)对象也是我们现在说的类数组形式,比如 document.forms/anchors/links 等。
JavaScript 1.1
1996 年, Netscape Navigator 3.0 发布,JavaScript 也升级到了 1.1 版本,这时才有了我们的数组:
同时 arguments 不再仅仅是函数的属性,还像 this 一样成了函数内部的一个特殊变量(说是为了性能考虑):
function foo() {
alert(arguments == foo.arguments) // true,现代浏览器是 false
} foo()
此外还新增了个神奇的特性:
function foo(a, b) {
var c = 3
alert(arguments.a) //
alert(arguments.b) //
alert(arguments.c) //
alert(arguments.arguments == arguments) // true
} foo(1, 2)
也就是说,所有的形参变量和本地变量都成了 arguments 对象的属性,有没有想起来点什么?这不就是 ES1-3 里的活动对象嘛。
虽然有数组了,但这个时候的 arguments 对象更不像数组了。
ES1
ES1 在这时候发布了,里面虽然提到了函数的 arguments 属性,但已经不推荐使用了。
JavaScript 1.2
实现于 1997 年发布的 Netscape Navigator 4.0 中,新增了 arguments.callee 属性,用来获取当前执行的函数。
ES2
函数的 arguments 属性相关的文字已经完全删除了。
JavaScript 1.3
实现于 1998 年发布的 Netscape Navigator 4.5 中。废弃了 arguments.caller 属性(用 arguments.callee.caller 代替),废弃并删除了上一版里加的形参变量和本地变量作为 arguments 属性的功能。
ES3
没有 arguments 相关的修改
ES4
有两个相关的提议(2007 年 3 月份):
2.4 Richer reflection capability on function and closures
It is not possible to determine the name of a function or the names of its arguments without parsing the function's source – this is a hole in the reflection functionality available through ES3. Functions should have a “name” property that returns the name of the function as a string. The “name” of anonymous functions can be the empty string.
Functions should also have an “arguments” array, containing the names of the arguments. So, for the example function foo(bar, baz) {…}, foo.name is "foo" and foo.arguments is ["bar", "baz"].
Similar reflection capability must be made available on closures.2.5 arguments array as “Array”
Make the arguments array an “Array”. That will enable consumers to iterate over its properties using the for .. in loop.
2.4 是说想把函数的 arguments 属性重新规范化一下,让它从包含实参的值改成包含形参的名字,挺有用,对吧,不用再从函数 toString() 后的字符串中正则提取了;2.5 是说想把 arguments 对象变成真实的数组。
还有一个更新一点(2007 年 10 月份)的 ES4 文档,讲到 ES4 里会有剩余参数代替 arguments,还会有 this function 代替 arguments.callee,前者 ES6 里有了,后者 ES6 里还没有,还说了句有意思的话,把 arguments 变成数组是个 bug fix?
还有一个文档(2007 年 11 月)提到了,Opera 居然实现过带有数组方法的 arguments:
ES5
ES5的严格模式禁用了:函数的 arguments 属性、argument.callee/argument.caller 以及 arguments 和形参的绑定,也就是只能用最简单的索引和 length 属性了。
ES6
箭头函数没有 arguments 对象
拥有默认参数、剩余参数、解构参数的函数中的 arguments 对象不和形参绑定
arguments 对象是 iterable 的(拥有 Symbol.iterator 属性),所以可以用 for-of 来遍历了。
总结
这么多年来,arguments 对象给规范的设计和引擎的实现都带来相当大的复杂度,如果能在早期把它修正成一个最朴素的数组,就没这么多事了。本文仅仅是做了一些 arguments 对象的考古工作,至于 arguments 对象为什么这么多年来都没变成数组,笼统点说应该是缺少合适的契机,导致越拖越难改,结果就是再也无法修改了,明确点说就是我也不知道答案啊。如果看了这样的考古你还不过瘾,可以在 Twitter 上问问 Brendan 本人。
2016.10.6 追加,arguments.caller 只在 Netscape 和 IE 里真实存在过,从 MDN 的兼容性表格 看到:
除了 IE,没有一个 21 世纪的浏览器支持过它,ES1-3 规范里也从来没提到过 arguments.caller 这个东西。但也许就是因为 IE,在 ES5 引入严格模式的时候,规范中提到了在严格模式中访问 arguments.caller 要报错,即便在非严格模式中 arguments.caller 是 undefined 的浏览器,严格模式中也要报错:
onerror = alert;
(function(){"use strict";arguments.caller})()如今,IE 已经停止开发,为了兼容老浏览器而在规范中记录 caller 已经没什么必要了,是时候给庞大的规范减减负了。上周,经过 TC39 开会讨论,ES 2017 删除了规范中所有提到 arguments.caller 的文字。这也就意味着,严格模式中访问 arguments.caller 也可以不用报错了,但我估计引擎们短时间内是不会改的。
2016.10.20 追加,半个月前我猜引擎们短时间内不会去掉这个报错,但马上就打脸了,V8 已经准备删掉这个报错,让 caller 变成一个普通的不存在的属性了:https://bugs.chromium.org/p/v8/issues/detail?id=5535
arguments 对象的老历史的更多相关文章
- arguments 对象
在函数体内,标识符arguments是指向实参对象的引用,实参对象是一个类数组对象 arguments[0],arguments.length arguments是什么? 答:1:arguments是 ...
- javascript中的arguments对象
在js中一切都是对象,连函数也是对象,函数名其实是引用函数定义对象的变量. 1.什么是arguments? 这个函数体内的arguments非常特殊,实际上是所在函数的一个内置类数组对象,可以用数组的 ...
- 永远不要修改arguments对象
案例复现 var obj = { plus: function(arg0, arg1) { return arg0 + arg1; } }; function callMethod(context, ...
- [Effective JavaScript 笔记]第23条:永远不要修改arguments对象
arguments对象并不是标准的Array类型的实例.arguments对象不能直接调用Array方法. arguments对象的救星call方法 使得arguments可以品尝到数组方法的美味,知 ...
- [Effective JavaScript 笔记]第24条:使用变量保存arguments对象
迭代器(iterator)是一个可以顺序存取数据集合的对象.其一个典型的API是next方法.该方法获得序列中的下一个值. 迭代器示例 题目:希望编写一个便利的函数,它可以接收任意数量的参数,并为这些 ...
- 理解Javascript参数中的arguments对象
ECMAScript中函数没有标签名的特性,所以ECMAScript函数中没有重载. Javascript中arguments的存在可以弥补javascript中函数没有重载的不足. Javascri ...
- 关于arguments对象以及函数的柯里化;
1.arguments对象 Arguments是个类似数组但不是数组的对象,说他类似数组是因为其具备数组相同的访问性质及方式,能够由arguments[n]来访问对应的单个参数的值,并拥有数组长度属性 ...
- JavaScript之arguments对象讲解
javascript的arguments对象类似于PHP的extract()函数实现. 在不确定函数参数个数的情况下,可以通过arguments访问参数,并以索引0为起始. function sayH ...
- JavaScript对象(正则表达式,Date对象,function对象 arguments对象)
好用的技术教程:http://www.w3school.com.cn/index.html 1:正则表达式 正则表达式通常用于验证表单 定义语法为 / / 2:Date对象 var now = new ...
随机推荐
- 一个"如何使用示波器安全测试接市电电路板"的问题
最近犯了一个错误测试操作: 测试场景:直接从市电插座取电接入3W非隔离开关电源电路板,使用示波器测试输出电压,此时示波器通过另外一个插座直接从市电取电 测试后果:在将示波器接到输出负极的一瞬间,漏电保 ...
- CNI插件源码示例,对于github.com/rajatchopra/ocicni库的分析
CNI插件初始化 // ocicni.go 1.func InitCNI(pluginDir string) (CNIPlugin, error) (1).先调用plugin := probeNetw ...
- log4j.properties配置
一.日志:除了能记录异常信息,还可以记录程序正常运行时的关键信息. 使用log4j来进行日志文件记录经典步骤: 01.在项目中创建一个lib文件夹,然后将下载好的jar包copy到该文件夹下 02.对 ...
- jenkins,jmeter,ant持续集成
1.安装 jenkins, jmeter, ant 2.将 jmeter下extras中的 ant-jmeter-1.1.1.jar拷贝到ant的lib下面 3.将 jmeter下collapse ...
- [No000077]打造自己的Eclipse
下载官网的Eclipse IDE for Java EE Developers 在根目录下的eclipse.ini文件中添加"-Dfile.encoding=UTF-8", 作用: ...
- [bzoj1911][Apio2010特别行动队] (动态规划+斜率优化)
Description Input Output Sample Input - - Sample Output HINT Solution 斜率优化动态规划 首先易得出这样的一个朴素状态转移方程 f[ ...
- c语言中的scanf在java中应该怎么表达,Scanner类。
1 java是面向对象的语言 它没有像C语言中的scanf()函数,但是它的类库中有含有scanf功能的函数 2 java.util包下有Scanner类 Scanner类的功能与scanf类似 3 ...
- BZOJ1081[SCOI2005]超级格雷码
Description 著名的格雷码是指2n个不同n位二进制数(即0~2n-1,不足n位在前补零)的一个排列,这个排列满足相邻的两个二进制数的n位数字中最多只有一个数字不同(例如003和001就有一个 ...
- JS中判断鼠标按键的问题
JS中判断鼠标按键的问题.IE左键是 window.event.button = 1右键是 window.event.button = 2中键是 window.event.button = 4没有按键 ...
- Editplus常用快捷键
EditPlus 快捷键的使用 如果一个来你们公司面试程序员,连Ctrl + C 和Ctrl + V 都不用,而是使用“选中文本”→ 鼠标右键 → [复制],然后再鼠标右键→ [粘贴].你会不会录用他 ...