[1].slice.call({ length: 1, 0: 3 }) 為什麼返回[3]?

[1].slice.call([2]) 返回[2] 這是合情合理的正常使用

這時候call的作用是把this由[1] 改成[2]

所以slice方法執行結果返回[2]

但[1].slice.call({ length: 1, 0: 3 }) 為什麼返回[3]

我認為這種現象已經不是call機制的原因了

是Array.prototype.slice內部原因

或者說slice既然是Array.prototype下的

它應該只作用於數組才對呀

那它為什麼能接收並成功作用於Array實例之外的對象

mdn上並沒有對這個函數的此種用法的原理做解釋

具體實現是怎樣的

如何編寫這樣的方法

V8這個方法的實現是JS版本還是c++

https://www.ecma-international.org/ecma-262/#sec-array.prototype.slice

這是slice章節

不是很能看懂

有人稍微幫忙解釋下嗎

謝謝

另外問個問題

ES規範5.1/6/7/8 各個版本slice步驟都有一些差異

怎麼看當前瀏覽器實現的是哪個版本


es 規範

如果你讀到了最後,就會看到:

NOTE 3

The slice function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.


V8

在 V8 中,有專門針對在非 Array 類型的對象上調用 Array 函數的測試用例:array-functions-non-arrays.js(看文件名就知道是什麼意思了)

shouldBe("Array.prototype.slice.call({}, 0, 1)", "[]");
shouldBe("Array.prototype.slice.call(["b", "a"], 0, 1)", "["b"]");
shouldBe("Array.prototype.slice.call({ length:2, 0:"b", 1:"a" }, 0, 1)", "["b"]");
shouldBe("Array.prototype.slice.call(new TwoItemConstructor, 0, 1)", "["b"]");

不僅如此。不論是你已經想到的,還是你沒有想到了,V8 的 test case 都想到了。

比如:

function f() { return arguments; }
var o = f();
o.length = -100;
Array.prototype.slice.call(o);

再比如:

(function () {
arguments.length = 7;
Array.prototype.slice.call(arguments);
})();

再比如:

shouldThrow("Array.prototype.slice.call(undefined, 0, 1)");

再比如:

var array = [];
var proxy = new Proxy(new Proxy(array, {}), {});
var Ctor = function() {};
var result;

array.constructor = function() {};
array.constructor[Symbol.species] = Ctor;

Array.prototype.slice.call(proxy);

還比如:

var o = { length: Number.MIN_VALUE };
var result = Array.prototype.slice.call(o);
assertEquals(0, result.length);

var o = { length: Number.MIN_VALUE };
var result = Array.prototype.slice.call(o, Number.MAX_VALUE);
assertEquals(0, result.length);

var o = { length: Number.MAX_VALUE };
var result = Array.prototype.slice.call(o, Number.MAX_VALUE - 1);
assertEquals(0, result.length);

由於我英語水平不是很好,上學時沒有好好學,都是工作之後又重新補的英語。很多時候讀那些規範也是很吃力的,所以我就喜歡看 V8 的 test case。


源碼

你也問道了源碼問題。其實一個好消息是,slice 的源碼是使用 js 寫的:v8/src/js/array.js slice。源碼中調用了一些以百分號(%)開頭的函數,在 js 中如果有百分號開頭的函數,說明這個函數不是用 js 寫的,而是直接使用 C++ 寫的。


最後是廣告時間,我的 V8 專欄: V8 源碼及周邊。


題主真的認真看MDN了嗎? Array.prototype.slice()這裡面寫的很清楚了啊,可以把類似數組的對象轉成數組,你這個對象{ length: 1, 0: 3 },有length,有0索引,正好類似數組

類似數組(Array-like)對象

slice 方法可以用來將一個類數組(Array-like)對象/集合轉換成一個數組。你只需將該方法綁定到這個對象上。下述代碼中 list 函數中的 arguments 就是一個類數組對象。

function list() {

return Array.prototype.slice.call(arguments);

}

var list1 = list(1, 2, 3); // [1, 2, 3]

補充一下,鑒於題主問的是「為什麼會這樣」,可以看看es規範,https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf,22.1.3.23節,最後的note3提到了

The slice function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.

這應該可以解決題主的疑惑了,規範就是這麼規定的,各種實現當然要按照規範去實現了。


這事情跟 duck type 沒什麼關係。標準庫有些方法是會強制檢驗類型,不符合就扔 TypeError,有些則不會,視情(xin)況(qing)而定。

去讀下標準就知道,Array 上的大量方法早在 ES3 時代就是故意設計為 generic 的,也就是並不一定要求 this 為 Array。通常我們稱這種可以被這些 Array 方法操作的類型為 ArrayLike。

BTW,輪子又來提供打臉素材了。

你改用TypeScript來寫,編譯就會失敗。

說明輪子根本沒有用 TS 試過。

直接 Array.prototype.slice.call({ length: 1, 0: 3 }) 調用返回的是 any 類型(因為 call 的類型標註比較『隨便』),所以根本不會爆類型錯。

就算將來 TS 改進之後,也不應爆類型錯,因為 TS 早已經有 ArrayLike& 類型 https://github.com/Microsoft/TypeScript/blob/release-2.6/lib/lib.es5.d.ts#L1309-L1312 了。

輪子身為軟狗,真是給巨硬丟人啊!


一種遠古的用法

Array.prototype.slice.call(arguments);

用來把類數組對象arguments轉換為一個真正的數組。

沒有規定Array.prototype下的方法只能作用於數組。

最後是你給的ECMA規範

NOTE 3

The slice function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.


這就是duck type啊

另外 js的數組就是key是整數並且有length屬性的object


帶有length類數組,沒毛病。當成數組就行。


醒醒,JavaScript的類型都是假的,只要對象長得像就可以了。你改用TypeScript來寫,編譯就會失敗。


類數組就長這樣:{0: "a", 1: "b", length: 2}

[1].slice.call({length: 1, 0: 3}) 其實就是借用Array.prototype.slice.call({length: 1, 0: 3})

然後返回一個純數組 [3] 這裡[1] 就是誤導你的,其實就是借用數組原型, slice省略了傳入的參數


樓主你還沒有真正理解js,js裡面數據類型的邊界是比較模糊的,除了幾個基礎類型,其他都是對象,數組也只是一堆key為數字(實際上是數字字元串)的對象,外加一個length屬性。只不過數組的原型上有一堆「專門」作用於數組的方法而已,說是專門,但是實際上這些方法是很沒有節操的,通過call, apply, bind可以作用於任意擁有length屬性的對象。為什麼一定要有length才能用呢?因為數組的方法基本上都要去讀寫length,沒有length沒辦法幹活。


因為內部的實現是用for循環的,你傳進去的對象是一個類數組。什麼是類數組?就是一個有length屬性並且每一個key都是數字的對象,比如你getElementByClassName獲取到的DOM列表,比如你題目中給call穿進去的參數。for循環並不是數組的專利,只要你有length屬性,只要你的下標(或者key)是一個數字,就可以用for循環。

es6之前通常用這個方式把類數組轉成真正的數組從而使用push這種真正數組才能用的方法,用了es6的Array.from就沒必要這樣了。

手機碼字沒排版賊不爽,回去排下版


觀V8源碼中的array.js,解析 Array.prototype.slice為什麼能將類數組對象轉為真正的數組?

"call這個神奇的方法、slice的處理缺一不可"....


不知道怎麼解釋更多了 不返回這個還能返回什麼呢


{ length: 1, 0: 3 }是一個類數組,長度為1,第一項為3,可以理解為[3]


看到了length了嗎?每個數組裡都有一個length,你把它改改啊,改了之後就知道,為什麼這裡得到的是【3】了。


slice 可以把類數組轉換成數組


js高設,請看


同為動態語言,在這個問題上可以做一個類比。


推薦閱讀:

本人前端,剛入手了mac本,以前沒用過,請各位大大推薦一下mac本上做前端的編碼開發或者調試輔助工具?
哪裡有比較成熟的 React.js 項目案例?
960px 寬度的網格布局過時了嗎?
想掌握前端的構建工具,有沒有學習路線推薦?
如何看待只用CSS畫圖?

TAG:前端開發 | JavaScript | Nodejs | 前端工程師 | V8 |