引題:爲何 JavaScript 中的 arguments 對象不是數組 http://www.zhihu.com/question/50803453git
1995 年, Brendan Eich 在 Netscape Navigator 2.0 中實現了 JavaScript 1.0,arguments 對象在那時候就已經有了。當時的 arguments 對象很像咱們如今的數組(如今也像),它有一些索引屬性,對應每一個實參,還有一個 length 屬性,表明實參的數量,還有一個如今瀏覽器已經都沒有了的 caller 屬性(等價於當前函數的 caller 屬性)。可是,和如今的 arguments 對象不一樣的是,當時的 arguments 不是一個特殊的本地變量,而是做爲函數的屬性(如今仍保留着)存在的:github
function foo(a, b, c) { alert(foo.arguments[0]) // 1 alert(foo.arguments[1]) // 2 alert(foo.arguments[2]) // 3 alert(foo.arguments.length) // 3 alert(foo.arguments.caller == foo.caller) // true,都等於 bar,現代瀏覽器裏會是 false } function bar() { foo(1, 2, 3) } bar()
雖然做爲函數的屬性,但和如今的 arguments 同樣,它只能寫在本身的函數體內,不然值就是 null:web
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) // 10 a = 100 alert(arguments[0]) // 100 } 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 有意加的額外特性,而是他的第一反應就是應該這麼去實現?ecmascript
讀到這裏,不少同窗覺的答案已經有了,arguments 對象不能是數組的緣由就是:「數組沒有 caller 屬性」 和 「數組實現不了這種雙向綁定」。前者並非緣由,由於給數組添加一個額外的非索引屬性是很容易的事情,在 JS 層面也是一個簡單的賦值語句便可實現(雖然通常不這麼作),甚至如今引擎內部也會產出這樣的數組 - 從 ES6 開始,引擎在調用模板字符串的標籤函數時傳入的第一個參數就是個擁有額外的 raw 屬性的數組:函數
(function(arr) { console.log(arr, arr.raw) // arr 和 arr.raw 都是數組 }) `\0`
我在兩年前看到引擎會產生這樣的數組也覺的很奇怪,還專門問了 ES 規範當時的編輯。oop
2016.10.5 追加,今天才想到,不只 ES6 裏有這樣的數組,早在 ES3 裏就已經有了:性能
arr = /./g.exec("123") // [ '1', index: 0, input: '123' ] alert(Array.isArray(arr)) // true正則的 exec 方法和字符串的 match 方法返回的就是個擁有額外的 index 及 input 屬性的數組。this
那後者算是個緣由嗎?一點點又或者徹底不是,說一點點是由於在當時尚未 __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]) // 1 arguments[0] = 10 alert(a) // 10
說徹底不是呢,是由於我知道另一個更明顯的,在當時,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 等。
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) // 1 alert(arguments.b) // 2 alert(arguments.c) // 3 alert(arguments.arguments == arguments) // true } foo(1, 2)
也就是說,全部的形參變量和本地變量都成了 arguments 對象的屬性,有沒有想起來點什麼?這不就是 ES1-3 裏的活動對象嘛。
雖然有數組了,但這個時候的 arguments 對象更不像數組了。
ES1 在這時候發佈了,裏面雖然提到了函數的 arguments 屬性,但已經不推薦使用了。
實現於 1997 年發佈的 Netscape Navigator 4.0 中,新增了 arguments.callee 屬性,用來獲取當前執行的函數。
函數的 arguments 屬性相關的文字已經徹底刪除了。
實現於 1998 年發佈的 Netscape Navigator 4.5 中。廢棄了 arguments.caller 屬性(用 arguments.callee.caller 代替),廢棄並刪除了上一版里加的形參變量和本地變量做爲 arguments 屬性的功能。
沒有 arguments 相關的修改
有兩個相關的提議(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的嚴格模式禁用了:函數的 arguments 屬性、argument.callee/argument.caller 以及 arguments 和形參的綁定,也就是隻能用最簡單的索引和 length 屬性了。
箭頭函數沒有 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