Array.prototype.slice.call () hoạt động như thế nào?


474

Tôi biết nó được sử dụng để biến các đối số thành một mảng thực sự, nhưng tôi không hiểu điều gì xảy ra khi sử dụng Array.prototype.slice.call(arguments)


2
^ hơi khác vì liên kết đó hỏi về các nút DOM và không phải là đối số. Và tôi nghĩ rằng câu trả lời ở đây tốt hơn nhiều bằng cách mô tả 'cái này' là gì trong nội bộ.
sqram

Câu trả lời:


870

Điều xảy ra dưới mui xe là khi .slice()được gọi bình thường, thislà một Mảng, và sau đó nó chỉ lặp lại trên Mảng đó và thực hiện công việc của nó.

Làm thế nào là thistrong .slice()hàm một mảng? Bởi vì khi bạn làm:

object.method();

... objecttự động trở thành giá trị thistrong method(). Vì vậy:

[1,2,3].slice()

... các [1,2,3]mảng được thiết lập như giá trị của thistrong .slice().


Nhưng nếu bạn có thể thay thế một cái gì đó khác như thisgiá trị thì sao? Miễn là bất cứ thứ gì bạn thay thế đều có .lengththuộc tính số và một loạt các thuộc tính là chỉ số số, nó sẽ hoạt động. Loại đối tượng này thường được gọi là một đối tượng giống như mảng .

Các phương thức .call().apply()cho phép bạn đặt thủ công giá trị của thismột hàm. Vì vậy, nếu chúng ta thiết lập giá trị của thistrong .slice()một mảng giống như đối tượng , .slice()chỉ sẽ giả định nó làm việc với một mảng, và sẽ làm điều này.

Lấy đối tượng đơn giản này làm ví dụ.

var my_object = {
    '0': 'zero',
    '1': 'one',
    '2': 'two',
    '3': 'three',
    '4': 'four',
    length: 5
};

Đây rõ ràng không phải là một Array, nhưng nếu bạn có thể đặt nó làm thisgiá trị của .slice()nó, thì nó sẽ chỉ hoạt động, bởi vì nó trông giống như một Array .slice()để hoạt động chính xác.

var sliced = Array.prototype.slice.call( my_object, 3 );

Ví dụ: http://jsfiddle.net/wSvkv/

Như bạn có thể thấy trong bảng điều khiển, kết quả là những gì chúng ta mong đợi:

['three','four'];

Vì vậy, đây là những gì xảy ra khi bạn đặt một argumentsđối tượng là thisgiá trị của .slice(). Bởi vì argumentscó một thuộc .lengthtính và một loạt các chỉ số số, .slice()chỉ cần thực hiện công việc của nó như thể nó đang làm việc trên một mảng thực.


7
Câu trả lời chính xác! Nhưng thật đáng buồn, bạn không thể chuyển đổi bất kỳ đối tượng nào theo cách này, nếu các khóa đối tượng của bạn là giá trị chuỗi, như trong các từ thực tế .. Điều này sẽ thất bại, vì vậy hãy giữ nội dung đối tượng của bạn là '0': 'value' và không giống như 'stringName' :'giá trị'.
joopmicroop

6
@Michael: Có thể đọc mã nguồn của các triển khai JS nguồn mở, nhưng đơn giản hơn là chỉ cần tham khảo đặc tả ngôn ngữ "ECMAScript". Đây là một liên kết đến Array.prototype.slicemô tả phương pháp.

1
do các khóa đối tượng không có thứ tự, trình diễn cụ thể này có thể thất bại trong các trình duyệt khác. không phải tất cả các nhà cung cấp sắp xếp các khóa đối tượng của họ theo thứ tự tạo.
vsync

1
@vsync: Đó là for-intuyên bố không đảm bảo trật tự. Thuật toán được sử dụng bằng cách .slice()định nghĩa một thứ tự số bắt đầu bằng 0và kết thúc (không bao gồm) với .lengthđối tượng đã cho (hoặc Mảng hoặc bất cứ thứ gì). Vì vậy, thứ tự được đảm bảo là nhất quán trong tất cả các triển khai.
quái vật cookie

7
@vsync: Đây không phải là giả định. Bạn có thể nhận được lệnh từ bất kỳ đối tượng nào nếu bạn thi hành nó. Hãy nói tôi có var obj = {2:"two", 0:"zero", 1: "one"}. Nếu chúng ta sử dụng for-inđể liệt kê đối tượng, không có gì đảm bảo trật tự. Nhưng nếu chúng ta sử dụng for, chúng ta có thể tự thực thi lệnh : for (var i = 0; i < 3; i++) { console.log(obj[i]); }. Bây giờ chúng ta biết rằng các thuộc tính của đối tượng sẽ đạt được theo thứ tự số tăng dần mà chúng ta đã xác định bởi forvòng lặp của chúng tôi . Đó là những gì .slice(). Nó không quan tâm nếu nó có một mảng thực tế. Nó chỉ bắt đầu tại 0và truy cập các thuộc tính trong một vòng lặp tăng dần.
quái vật cookie

88

Đối argumentstượng không thực sự là một thể hiện của Mảng và không có bất kỳ phương thức Mảng nào. Vì vậy, arguments.slice(...)sẽ không hoạt động vì đối tượng đối số không có phương thức lát.

Mảng có phương thức này và vì argumentsđối tượng rất giống với một mảng, hai mảng tương thích. Điều này có nghĩa là chúng ta có thể sử dụng các phương thức mảng với đối tượng đối số. Và vì các phương thức mảng được xây dựng với các mảng trong tâm trí, chúng sẽ trả về các mảng thay vì các đối tượng đối số khác.

Vậy tại sao nên sử dụng Array.prototype? Các Arraylà đối tượng mà chúng tôi tạo ra các mảng mới từ ( new Array()), và những mảng mới được thông qua phương pháp và tài sản, như lát. Các phương thức này được lưu trữ trong [Class].prototypeđối tượng. Vì vậy, vì lợi ích hiệu quả, thay vì truy cập phương thức lát bằng (new Array()).slice.call()hoặc [].slice.call(), chúng ta chỉ cần lấy nó trực tiếp từ nguyên mẫu. Điều này là do đó chúng ta không phải khởi tạo một mảng mới.

Nhưng tại sao chúng ta phải làm điều này ngay từ đầu? Như bạn đã nói, nó chuyển đổi một đối tượng đối số thành một đối tượng Array. Lý do tại sao chúng tôi sử dụng lát, tuy nhiên, là một "hack" hơn bất cứ điều gì. Phương thức lát sẽ lấy một, bạn đoán nó, lát một mảng và trả lại lát đó dưới dạng một mảng mới. Việc không truyền đối số cho nó (ngoài đối tượng đối số làm bối cảnh của nó) làm cho phương thức lát cắt lấy một đoạn hoàn chỉnh của "mảng" đã truyền (trong trường hợp này là đối tượng đối số) và trả về nó như một mảng mới.


Bạn có thể muốn sử dụng một lát vì những lý do được mô tả ở đây: jspatterns.com/arguments-considered-harmful
KooiInc

44

Thông thường, gọi

var b = a.slice();

sẽ sao chép mảng avào b. Tuy nhiên, chúng tôi không thể làm

var a = arguments.slice();

bởi vì argumentskhông phải là một mảng thực sự và không có slicephương thức. Array.prototype.sliceslicehàm cho mảng và callchạy hàm với thisset arguments.


2
thanx nhưng tại sao lại sử dụng prototype? không phải slicelà một Arrayphương pháp bản địa ?
ilyo

2
Lưu ý rằng đó Arraylà một hàm tạo, và "lớp" tương ứng là Array.prototype. Bạn cũng có thể sử dụng[].slice
user123444555621

4
IlyaD, slicelà một phương thức của mỗi Arraythể hiện, nhưng không phải là Arrayhàm tạo. Bạn sử dụng prototypeđể truy cập các phương thức của các trường hợp lý thuyết của nhà xây dựng.
Delan Azabani

23

Trước tiên, bạn nên đọc cách gọi chức năng hoạt động trong JavaScript . Tôi nghi ngờ rằng một mình là đủ để trả lời câu hỏi của bạn. Nhưng đây là một bản tóm tắt về những gì đang xảy ra:

Array.prototype.slicechiết xuất các phương pháp từ của nguyên mẫu . Nhưng gọi nó trực tiếp sẽ không hoạt động, vì nó là một phương thức (không phải là một hàm) và do đó đòi hỏi một bối cảnh (một đối tượng gọi ), nếu không nó sẽ ném .slice ArraythisUncaught TypeError: Array.prototype.slice called on null or undefined

Các call()phương pháp cho phép bạn xác định bối cảnh của một phương pháp, về cơ bản làm cho hai cuộc gọi này tương đương với:

someObject.slice(1, 2);
slice.call(someObject, 1, 2);

Ngoại trừ cái trước đòi hỏi slicephương thức tồn tại trong someObjectchuỗi nguyên mẫu (như nó làm Array), trong khi cái sau cho phép bối cảnh ( someObject) được truyền thủ công cho phương thức.

Ngoài ra, sau này là viết tắt của:

var slice = Array.prototype.slice;
slice.call(someObject, 1, 2);

Điều này giống như:

Array.prototype.slice.call(someObject, 1, 2);

22
// We can apply `slice` from  `Array.prototype`:
Array.prototype.slice.call([]); //-> []

// Since `slice` is available on an array's prototype chain,
'slice' in []; //-> true
[].slice === Array.prototype.slice; //-> true

// … we can just invoke it directly:
[].slice(); //-> []

// `arguments` has no `slice` method
'slice' in arguments; //-> false

// … but we can apply it the same way:
Array.prototype.slice.call(arguments); //-> […]

// In fact, though `slice` belongs to `Array.prototype`,
// it can operate on any array-like object:
Array.prototype.slice.call({0: 1, length: 1}); //-> [1]

16

Array.prototype.slice.call (đối số) là cách thức cũ để chuyển đổi một đối số thành một mảng.

Trong ECMAScript 2015, bạn có thể sử dụng Array.from hoặc toán tử trải rộng:

let args = Array.from(arguments);

let args = [...arguments];

9

Bởi vì, như MDN lưu ý

Đối tượng đối số không phải là một mảng. Nó tương tự như một mảng, nhưng không có bất kỳ thuộc tính mảng nào ngoại trừ chiều dài. Ví dụ, nó không có phương thức pop. Tuy nhiên, nó có thể được chuyển đổi thành một mảng thực sự:

Ở đây chúng ta đang kêu gọi sliceđối tượng bản địa Arraychứ không phải thực hiện nó và đó là lý do tại sao thêm.prototype

var args = Array.prototype.slice.call(arguments);

4

Đừng quên rằng, một điều cơ bản ở mức độ thấp của hành vi này là kiểu truyền được tích hợp hoàn toàn trong công cụ JS.

Slice chỉ lấy đối tượng (nhờ thuộc tính argument.length hiện có) và trả về đối tượng mảng được truyền sau khi thực hiện tất cả các hoạt động trên đó.

Các logic tương tự bạn có thể kiểm tra nếu bạn cố xử lý phương thức String bằng giá trị INT:

String.prototype.bold.call(11);  // returns "<b>11</b>"

Và điều đó giải thích tuyên bố trên.


1

Nó sử dụng các slicephương pháp mảng có và gọi nó với nó thisargumentsđối tượng. Điều này có nghĩa là nó gọi nó như thể bạn đã arguments.slice()giả sử argumentscó một phương pháp như vậy.

Tạo một lát mà không có bất kỳ đối số nào sẽ chỉ đơn giản là lấy tất cả các phần tử - vì vậy nó chỉ cần sao chép các phần tử từ argumentsmột mảng.


1

Giả sử bạn có: function.apply(thisArg, argArray )

Phương thức áp dụng gọi một hàm, truyền vào đối tượng sẽ bị ràng buộc với điều này và một mảng các đối số tùy chọn.

Phương thức lát () chọn một phần của mảng và trả về mảng mới.

Vì vậy, khi bạn gọi Array.prototype.slice.apply(arguments, [0])phương thức mảng được gọi (liên kết) trên các đối số.


1

Có thể hơi muộn, nhưng câu trả lời cho tất cả mớ hỗn độn này là lệnh gọi () được sử dụng trong JS để kế thừa. Ví dụ, nếu chúng ta so sánh nó với Python hoặc PHP, cuộc gọi được sử dụng tương ứng là super (). init () hoặc cha mẹ :: _ xây dựng ().

Đây là một ví dụ về việc sử dụng nó làm rõ tất cả:

function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}

Tham khảo: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inherribution


0

khi .slice () được gọi bình thường, đây là một mảng và sau đó nó chỉ lặp lại trên mảng đó và thực hiện công việc của nó.

 //ARGUMENTS
function func(){
  console.log(arguments);//[1, 2, 3, 4]

  //var arrArguments = arguments.slice();//Uncaught TypeError: undefined is not a function
  var arrArguments = [].slice.call(arguments);//cp array with explicity THIS  
  arrArguments.push('new');
  console.log(arrArguments)
}
func(1,2,3,4)//[1, 2, 3, 4, "new"]

-1

Tôi chỉ viết điều này để nhắc nhở bản thân ...

    Array.prototype.slice.call(arguments);
==  Array.prototype.slice(arguments[1], arguments[2], arguments[3], ...)
==  [ arguments[1], arguments[2], arguments[3], ... ]

Hoặc chỉ sử dụng hàm tiện dụng $ A này để biến hầu hết mọi thứ thành một mảng.

function hasArrayNature(a) {
    return !!a && (typeof a == "object" || typeof a == "function") && "length" in a && !("setInterval" in a) && (Object.prototype.toString.call(a) === "[object Array]" || "callee" in a || "item" in a);
}

function $A(b) {
    if (!hasArrayNature(b)) return [ b ];
    if (b.item) {
        var a = b.length, c = new Array(a);
        while (a--) c[a] = b[a];
        return c;
    }
    return Array.prototype.slice.call(b);
}

sử dụng ví dụ ...

function test() {
    $A( arguments ).forEach( function(arg) {
        console.log("Argument: " + arg);
    });
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.