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 ...
随机推荐
- Xamarin Android 所见即所得问题
运行Xamarin 时出现以下问题. The layout could not be loaded : The operation failed due to an internal error : ...
- Eclipse导入MyEclipse创建的web项目报错的解决方法
将myeclipse中开发的动态web项目直接引入到eclipse中继续开发,Eclipse中会报项目有错,如下图
- Ubuntu1604中mysql的登录问题
自从Ubuntu 1604出来后,新安装了尝试了下,在安装到mysql的时候用root登录不了,在网上查了很多资料都没找到解决办法.后来su到系统的root后直接就可以登录mysql了,看到Ubunt ...
- 用MonoDevelop开发Linux游戏
鉴于微软将放弃XNA,MonoGame将是喜欢XNA的朋友们的新选择,他是XNA游戏引擎的开源实现,而且是跨平台的,这意味着你可以用他开发OS.android.windows以及linux应用程序,多 ...
- 洛谷P1262 间谍网络[强连通分量 BFS]
题目描述 由于外国间谍的大量渗入,国家安全正处于高度的危机之中.如果A间谍手中掌握着关于B间谍的犯罪证据,则称A可以揭发B.有些间谍收受贿赂,只要给他们一定数量的美元,他们就愿意交出手中掌握的全部情报 ...
- jdbc java数据库连接 3)Statement接口之执行DDL和DML语句的简化
上一章的代码中,可以发现,jdbc执行DDL和DML有几个步骤都是一样的: 1)执行语句开始时,创建驱动注册对象.获取连接的数据库对象.创建Statement对象 // 创建驱动注册对象 Class. ...
- Linux常用命令(二)
2.2.其他权限管理命令 命令名称:chown 功能:更改文件目录或文件的所有者语法:chown[用户][文件或目录]命令名称:chgrp 功能:改变文件或目录的所属组语法:chgrp[用户][文件或 ...
- jquery替换所有逗号
代码: var aa= $("input[name=aa]").val().replace(/\,/g,""); 原文:http://blog.csdn.net ...
- 仿原生app,native特效
为解决手机端wap项目中页面跳转响应慢的问题,基于jquery开发了fr.routeApp.js,使得wap项目拥有原生app的流畅,跳转时亦有native的特效 html部分 <!--定义容器 ...
- Java 数组基础
数组 数组(Array):相同类型数据的集合. 定义数组 方式1(推荐,更能表明数组类型) type[] 变量名 = new type[数组中元素的个数]; 比如: int[] a = new int ...