Array.prototype.slice.call () ทำงานอย่างไร


474

ฉันรู้ว่ามันใช้เพื่อทำให้อาร์กิวเมนต์เป็นอาร์เรย์จริง แต่ฉันไม่เข้าใจว่าเกิดอะไรขึ้นเมื่อใช้ Array.prototype.slice.call(arguments)


2
^ แตกต่างกันเล็กน้อยเนื่องจากลิงค์นั้นถามเกี่ยวกับโหนด DOM และไม่ใช่ข้อโต้แย้ง และฉันคิดว่าคำตอบที่นี่ดีกว่ามากโดยอธิบายว่า 'นี่' คืออะไรภายใน
sqram

คำตอบ:


870

สิ่งที่เกิดขึ้นภายใต้ประทุนคือเมื่อ.slice()ถูกเรียกตามปกติแล้วthisก็คืออาเรย์

เป็นวิธีการthisใน.slice()การทำงานของอาร์เรย์? เพราะเมื่อคุณ:

object.method();

ที่ ... objectจะกลายเป็นค่าของโดยอัตโนมัติในthis method()ดังนั้นด้วย:

[1,2,3].slice()

ที่ ... [1,2,3]อาร์เรย์ถูกตั้งค่าเป็นค่าของในthis.slice()


แต่ถ้าคุณสามารถทดแทนสิ่งอื่นเป็นthisค่าได้ ตราบใดที่สิ่งที่คุณแทนที่มี.lengthคุณสมบัติเป็นตัวเลขและมีคุณสมบัติมากมายที่เป็นดัชนีตัวเลขก็ควรใช้งานได้ ชนิดของวัตถุนี้มักจะถูกเรียกว่าวัตถุอาร์เรย์เหมือน

.call()และ.apply()วิธีการให้คุณด้วยตนเองกำหนดค่าของการthisในการทำงาน ดังนั้นถ้าเราตั้งค่าของthisใน.slice()สระอาร์เรย์เหมือนวัตถุ , .slice()ก็จะถือว่ามันทำงานกับอาร์เรย์และจะทำสิ่งที่ตน

นำวัตถุธรรมดานี้เป็นตัวอย่าง

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

เห็นได้ชัดว่าไม่ใช่ Array แต่ถ้าคุณสามารถตั้งค่าเป็นthisค่าของ.slice()มันก็จะทำงานได้เพราะมันดูเหมือน Array สำหรับ.slice()การทำงานอย่างถูกต้อง

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

ตัวอย่าง: http://jsfiddle.net/wSvkv/

ดังที่คุณเห็นในคอนโซลผลลัพธ์คือสิ่งที่เราคาดหวัง:

['three','four'];

ดังนั้นนี่คือสิ่งที่เกิดขึ้นเมื่อคุณกำหนดargumentsวัตถุที่เป็นค่าของthis .slice()เนื่องจากargumentsมี.lengthคุณสมบัติและดัชนีตัวเลขจำนวนหนึ่ง.slice()เพียงแค่ทำงานเกี่ยวกับมันราวกับว่ามันทำงานในอาร์เรย์ที่แท้จริง


7
คำตอบที่ดี! แต่น่าเศร้าที่คุณไม่สามารถแปลงเฉพาะวัตถุใด ๆ ด้วยวิธีนี้หากคีย์วัตถุของคุณเป็นค่าสตริงเช่นในคำที่เกิดขึ้นจริง .. จะล้มเหลวดังนั้นให้เนื้อหาวัตถุของคุณเป็น '0': 'ค่า' และไม่เหมือนกับ 'stringName' :'ราคา'.
joopmicroop

6
@Michael: การอ่านซอร์สโค้ดของการใช้งาน JS แบบโอเพ่นซอร์สนั้นเป็นไปได้ แต่การอ้างถึงข้อกำหนดภาษา "ECMAScript" ง่ายกว่า นี่คือลิงค์ไปยังArray.prototype.sliceคำอธิบายวิธีการ

1
เนื่องจากคีย์อ็อบเจ็กต์ไม่มีคำสั่งการสาธิตเฉพาะนี้อาจล้มเหลวในเบราว์เซอร์อื่น ผู้ขายบางรายไม่เรียงลำดับคีย์ของวัตถุตามลำดับการสร้าง
vsync

1
@vsync: เป็นfor-inคำสั่งที่ไม่รับประกันการสั่งซื้อ อัลกอริทึมที่ใช้โดย.slice()กำหนดลำดับตัวเลขเริ่มต้นด้วย0และสิ้นสุด (ไม่รวม) กับ.lengthวัตถุที่กำหนด (หรือ Array หรืออะไรก็ตาม) ดังนั้นคำสั่งนั้นรับประกันว่าจะสอดคล้องกันในทุกการใช้งาน
คุกกี้มอนสเตอร์

7
@vsync: ไม่ใช่ข้อสันนิษฐาน คุณสามารถรับคำสั่งซื้อจากวัตถุใด ๆ หากคุณบังคับใช้ var obj = {2:"two", 0:"zero", 1: "one"}สมมติว่าผมมี ถ้าเราใช้for-inเพื่อระบุวัตถุไม่มีการรับประกันการสั่งซื้อ แต่ถ้าเราใช้เราด้วยตนเองสามารถบังคับใช้คำสั่ง:for for (var i = 0; i < 3; i++) { console.log(obj[i]); }ตอนนี้เรารู้แล้วว่าคุณสมบัติของวัตถุจะไปถึงลำดับตัวเลขที่เรากำหนดโดยforลูปของเรา นั่นคือสิ่งที่.slice()ทำ มันไม่สนใจว่ามันมีอาร์เรย์จริงหรือไม่ มันเพิ่งเริ่มต้น0และเข้าถึงคุณสมบัติในลูปจากน้อยไปมาก
มอนสเตอร์คุกกี้

88

argumentsวัตถุไม่จริงเป็นตัวอย่างของอาร์เรย์และไม่ได้มีวิธีการใด ๆ อาร์เรย์ ดังนั้น,arguments.slice(...)จะไม่ทำงานเพราะวัตถุข้อโต้แย้งไม่มีวิธีการแบ่งส่วน

อาร์เรย์มีวิธีการนี้และเนื่องจากargumentsวัตถุคล้ายกับอาร์เรย์ทั้งสองจึงเข้ากันได้ ซึ่งหมายความว่าเราสามารถใช้วิธีการอาร์เรย์กับวัตถุข้อโต้แย้ง และเนื่องจากวิธีการอาร์เรย์ถูกสร้างขึ้นโดยมีอาร์เรย์อยู่ในใจพวกเขาจะกลับอาร์เรย์มากกว่าวัตถุอาร์กิวเมนต์อื่น ๆ

เหตุใดจึงต้องใช้Array.prototype? Arrayเป็นวัตถุที่เราสร้างอาร์เรย์ใหม่จาก ( new Array()) และเหล่าอาร์เรย์ใหม่จะถูกส่งผ่านวิธีการและคุณสมบัติเช่นชิ้น วิธีการเหล่านี้ถูกเก็บไว้ใน[Class].prototypeวัตถุ ดังนั้นเพื่อประสิทธิภาพอย่างมีประสิทธิภาพแทนที่จะเข้าถึงวิธีการแบ่งส่วนด้วยวิธี(new Array()).slice.call()หรือ[].slice.call()เราแค่นำมันมาจากต้นแบบโดยตรง นี่คือเพื่อให้เราไม่ต้องเริ่มต้นอาร์เรย์ใหม่

แต่ทำไมเราต้องทำสิ่งนี้ตั้งแต่แรก? อย่างที่คุณบอกว่ามันจะแปลงอ็อบเจกต์อาร์กิวเมนต์เป็น Array instance อย่างไรก็ตามสาเหตุที่เราใช้ส่วนแบ่งเป็น "แฮ็ค" มากกว่าสิ่งใด วิธีการแบ่งจะใช้เวลาคุณเดามันฝานของอาร์เรย์และคืนชิ้นนั้นเป็นอาร์เรย์ใหม่ การส่งผ่านข้อโต้แย้งไม่มี (นอกเหนือจากวัตถุอาร์กิวเมนต์เป็นบริบท) ทำให้วิธีการแบ่งเต็มของอาร์เรย์ "ผ่าน" (ในกรณีนี้วัตถุอาร์กิวเมนต์) และส่งคืนเป็นอาร์เรย์ใหม่


คุณอาจต้องการใช้ชิ้นด้วยเหตุผลที่อธิบายไว้ที่นี่: jspatterns.com/arguments-considered-harmful
KooiInc

44

โดยปกติการโทร

var b = a.slice();

จะคัดลอกอาร์เรย์เข้าa bอย่างไรก็ตามเราทำไม่ได้

var a = arguments.slice();

เพราะargumentsไม่ใช่อาเรย์ที่แท้จริงและไม่มีsliceวิธีการ Array.prototype.sliceเป็นsliceฟังก์ชั่นสำหรับอาร์เรย์และcallทำงานฟังก์ชั่นที่มีการกำหนดให้thisarguments


2
ขอบคุณ แต่ทำไมใช้prototype? ไม่ได้เป็นsliceชาวArrayวิธี?
ilyo

2
ทราบว่าArrayเป็นฟังก์ชั่นคอนสตรัคและสอดคล้องกัน "ชั้น" Array.prototypeคือ คุณสามารถใช้[].slice
user123444555621

4
IlyaD sliceเป็นวิธีการของแต่ละArrayอินสแตนซ์ แต่ไม่ใช่Arrayฟังก์ชันตัวสร้าง คุณใช้prototypeเพื่อเข้าถึงวิธีการของอินสแตนซ์ตามทฤษฎีของตัวสร้าง
Delan Azabani

23

ครั้งแรกที่คุณควรอ่านวิธีการทำงานภาวนาทำงานใน JavaScript ฉันสงสัยว่าคนเดียวก็เพียงพอที่จะตอบคำถามของคุณ แต่นี่เป็นบทสรุปของสิ่งที่เกิดขึ้น:

Array.prototype.sliceสารสกัดจากวิธีจาก's ต้นแบบ แต่เรียกมันโดยตรงจะไม่ทำงานตามที่มันเป็นวิธีการ (ไม่ได้ฟังก์ชั่น)และดังนั้นจึงต้องมีบริบท (วัตถุโทร) มิฉะนั้นก็จะโยนslice ArraythisUncaught TypeError: Array.prototype.slice called on null or undefined

call()วิธีช่วยให้คุณระบุบริบทวิธีการของพื้นทำให้ทั้งสองสายเทียบเท่า:

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

ยกเว้นวิธีก่อนหน้านี้ต้องการsliceวิธีการที่มีอยู่ในsomeObjectห่วงโซ่ต้นแบบของ (ในขณะที่มันทำArray) ในขณะที่หลังช่วยให้บริบท ( someObject) จะถูกส่งผ่านไปยังวิธีการด้วยตนเอง

นอกจากนี้หลังสั้นสำหรับ:

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

ซึ่งเหมือนกับ:

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 (อาร์กิวเมนต์) เป็นวิธีที่ล้าสมัยในการแปลงอาร์กิวเมนต์เป็นอาร์เรย์

ใน ECMAScript 2015 คุณสามารถใช้ Array.from หรือตัวดำเนินการสเปรด:

let args = Array.from(arguments);

let args = [...arguments];

9

มันเป็นเพราะเป็นบันทึก MDN

วัตถุอาร์กิวเมนต์ไม่ใช่อาร์เรย์ มันคล้ายกับอาร์เรย์ แต่ไม่มีคุณสมบัติอาร์เรย์ใด ๆ ยกเว้นความยาว ตัวอย่างเช่นไม่มีวิธีการแบบป๊อป อย่างไรก็ตามมันสามารถแปลงเป็นอาร์เรย์จริงได้:

ที่นี่เรากำลังเรียกsliceใช้วัตถุดั้งเดิมArrayไม่ใช่การใช้งานและนั่นเป็นเหตุผลว่าทำไม.prototype

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

4

อย่าลืมว่าพื้นฐานระดับต่ำของพฤติกรรมนี้คือการหล่อแบบที่รวมอยู่ใน JS-engine ทั้งหมด

Slice ใช้เวลาวัตถุ (ขอบคุณคุณสมบัติ arguments.length ที่มีอยู่) และส่งคืนวัตถุแถวลำดับหลังจากทำการดำเนินการทั้งหมดในนั้น

Logics เดียวกันคุณสามารถทดสอบว่าคุณพยายามที่จะใช้ String-method ด้วยค่า INT หรือไม่:

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

และนั่นคือคำอธิบายข้างต้น


1

จะใช้sliceวิธีการที่มีอาร์เรย์และเรียกมันว่ามันthisเป็นargumentsวัตถุ ซึ่งหมายความว่ามันเรียกว่าราวกับว่าคุณได้arguments.slice()สมมติargumentsมีวิธีการดังกล่าว

การสร้างชิ้นโดยไม่มีข้อโต้แย้งใด ๆ ก็จะนำองค์ประกอบทั้งหมด - ดังนั้นมันเพียงแค่คัดลอกองค์ประกอบจากargumentsไปยังอาร์เรย์


1

สมมติว่าคุณมี: function.apply(thisArg, argArray )

วิธีการใช้จะเรียกใช้ฟังก์ชั่นผ่านในวัตถุที่จะถูกผูกไว้กับสิ่งนี้และอาร์เรย์ของตัวเลือกการขัดแย้ง

วิธีการแบ่ง () เลือกส่วนหนึ่งของอาร์เรย์และส่งกลับอาร์เรย์ใหม่

ดังนั้นเมื่อคุณเรียกArray.prototype.slice.apply(arguments, [0])ใช้วิธีการแบ่งอาร์เรย์จะถูกเรียกใช้ (ผูก) กับอาร์กิวเมนต์


1

อาจจะช้าไปหน่อย แต่คำตอบของระเบียบนี้คือการโทร () ถูกใช้ใน JS เพื่อรับมรดก หากเราเปรียบเทียบสิ่งนี้กับ Python หรือ PHP ตัวอย่างเช่นการโทรจะถูกใช้ตามลำดับเป็น super () ในนั้น () หรือ parent :: _ construct ()

นี่คือตัวอย่างของการใช้งานที่ชี้แจงทั้งหมด:

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

  this.subject = subject;
}

การอ้างอิง: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance


0

เมื่อ. slice () ถูกเรียกตามปกตินี่คืออาร์เรย์และจากนั้นมันจะวนซ้ำกว่าอาร์เรย์นั้นและทำงานของมัน

 //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

ฉันแค่เขียนสิ่งนี้เพื่อเตือนตัวเอง ...

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

หรือเพียงแค่ใช้ฟังก์ชั่นที่มีประโยชน์$ Aเพื่อเปลี่ยนสิ่งต่าง ๆ ให้เป็นอาเรย์

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);
}

ตัวอย่างการใช้ ...

function test() {
    $A( arguments ).forEach( function(arg) {
        console.log("Argument: " + arg);
    });
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.