ทำ <something> N ครั้ง (ไวยากรณ์ที่เปิดเผย)


97

มีวิธีใน Javascript ที่จะเขียนสิ่งนี้ได้อย่างง่ายดาย:

[1,2,3].times do {
  something();
}

ห้องสมุดใดที่อาจรองรับไวยากรณ์ที่คล้ายกันบ้าง

อัปเดต:เพื่อชี้แจง - ฉันต้องการsomething()ให้เรียกว่า 1,2 และ 3 ครั้งตามลำดับสำหรับการวนซ้ำแต่ละองค์ประกอบอาร์เรย์


2
ฉันจะบอกว่าไม่มีฟีเจอร์แบบนี้ใน JS และเป็นฟีเจอร์ที่ขาดหายไป 5 อันดับแรก มันมีประโยชน์มากสำหรับการทดสอบซอฟต์แวร์มากกว่าสิ่งอื่นใด
Alexander Mills

คำตอบ:


48

คำตอบนี้อยู่บนพื้นฐานArray.forEachโดยไม่ต้องห้องสมุดใด ๆ เพียงแค่พื้นเมืองของวานิลลา

ในการโทรโดยทั่วไปsomething()3 ครั้งให้ใช้:

[1,2,3].forEach(function(i) {
  something();
});

พิจารณาฟังก์ชั่นต่อไปนี้:

function something(){ console.log('something') }

เอาท์พุทจะ

something
something
something

หากต้องการตอบคำถามนี้ให้สมบูรณ์ต่อไปนี้เป็นวิธีโทรsomething()1, 2 และ 3 ครั้งตามลำดับ:

เป็นปี 2017 คุณสามารถใช้ ES6:

[1,2,3].forEach(i => Array(i).fill(i).forEach(_ => {
  something()
}))

หรือใน ES5 เก่าที่ดี:

[1,2,3].forEach(function(i) {
  Array(i).fill(i).forEach(function() {
    something()
  })
}))

ในทั้งสองกรณีการส่งออกจะเป็น

เอาท์พุทจะ

something

something
something

something
something
something

(หนึ่งครั้งจากนั้นสองครั้งจากนั้น 3 ครั้ง)


18
สิ่งนี้ไม่ถูกต้องเนื่องจากไม่ตรงตามส่วนนี้ของคำถาม: 'ฉันต้องการบางสิ่งบางอย่าง () ที่เรียกว่า 1,2 และ 3 ครั้ง' การใช้รหัสsomethingนี้เรียกเพียง 3 ครั้งควรเรียก 6 ครั้ง
Ian Newson

จากนั้นฉันเดาว่ามันถูกเลือกให้เป็นคำตอบที่ดีที่สุดเพราะมันอาจจะเป็นการเริ่มต้นที่ดีกว่า
vinyll

3
คุณยังสามารถใช้[...Array(i)]หรือArray(i).fill()ขึ้นอยู่กับความต้องการของคุณสำหรับดัชนีจริง
Guido Bouman

หากคุณไม่สนใจข้อโต้แย้งใด ๆ ที่ส่งผ่านให้ใช้.forEach(something)
kvsm

88

เพียงใช้ลูป:

var times = 10;
for(var i=0; i < times; i++){
    doSomething();
}

3
ขอบคุณ! ฉันต้องการได้รับประโยชน์จากไวยากรณ์ที่เปิดเผย (เช่นเดียวกับจัสมินเป็นต้น)
BreakPhreak

ถูกต้อง แต่ไวยากรณ์การประกาศสำหรับลูปที่ใช้งานได้ก็จะดีกว่ามากเช่นกัน
Alexander Mills

73

ทางเลือก ES6 ที่เป็นไปได้

Array.from(Array(3)).forEach((x, i) => {
  something();
});

และถ้าคุณต้องการให้ "เรียก 1,2 และ 3 ครั้งตามลำดับ"

Array.from(Array(3)).forEach((x, i) => {
  Array.from(Array(i+1)).forEach((x, i2) => {
    console.log(`Something ${ i } ${ i2 }`)
  });
});

อัปเดต:

นำมาจากการเติมอาร์เรย์ด้วยไม่ได้กำหนด

นี่ดูเหมือนจะเป็นวิธีที่ดีที่สุดในการสร้างอาร์เรย์เริ่มต้นฉันได้อัปเดตสิ่งนี้เพื่อใช้ฟังก์ชันแผนที่พารามิเตอร์ที่สองที่แนะนำโดย @ felix-eve

Array.from({ length: 3 }, (x, i) => {
  something();
});

3
ฉันควรตั้งข้อแม้โดยบอกว่านี่ใช้ได้ถ้าคุณเพิ่งเขียนสคริปต์บางอย่างอย่างรวดเร็ว แต่ประสิทธิภาพนั้นแย่มากดังนั้นอย่าใช้มันเพื่อการเรียกซ้ำแบบเข้มข้นหรือในการผลิตเลย
nverba

หากคุณกำลังจะไป ES6 คุณสามารถใช้ map () แทน forEach ()
Andy Ford

3
หากความสั้นเป็นเป้าหมาย (และในความเป็นจริงแม้ว่าจะไม่ใช่) ให้ส่งผ่านฟังก์ชันแทนการเรียกมัน:Array.from(Array(3)).forEach(something)
kvsm

1
ใช้งานได้กับการแสดงผลนิพจน์ปฏิกิริยาเช่นกัน
Josh Sharkey

4
Array.from()มีพารามิเตอร์ที่สองmapFnซึ่งเป็นทางเลือกซึ่งช่วยให้คุณสามารถเรียกใช้ฟังก์ชันแผนที่ในแต่ละองค์ประกอบของอาร์เรย์ได้ดังนั้นจึงไม่จำเป็นต้องใช้ forEach คุณสามารถทำได้:Array.from({length: 3}, () => somthing() )
เฟลิกซ์อีฟ

18

เนื่องจากคุณพูดถึงขีดล่าง:

สมมติว่าfเป็นฟังก์ชันที่คุณต้องการเรียกใช้:

_.each([1,2,3], function (n) { _.times(n, f) });

จะทำเคล็ดลับ ตัวอย่างเช่นf = function (x) { console.log(x); }คุณจะเข้าสู่คอนโซลของคุณ: 0 0 1 0 1 2


ฉันคิดว่าคุณต้องการแยกทาง
ggozad

2
_(3).times(function(n){return n;});ควรทำเคล็ดลับ ดูเอกสารที่นี่
ชิป

18

ด้วยlodash :

_.each([1, 2, 3], (item) => {
   doSomeThing(item);
});

//Or:
_.each([1, 2, 3], doSomeThing);

หรือถ้าคุณต้องการทำอะไรสัก N ครั้ง :

const N = 10;
_.times(N, () => {
   doSomeThing();
});

//Or shorter:
_.times(N, doSomeThing);

อ้างอิงลิงค์นี้สำหรับlodashการติดตั้ง


15

สร้างอาร์เรย์และfillรายการทั้งหมดที่มีundefinedเพื่อให้mapวิธีการสามารถทำงาน:

Array.fill ไม่มีการสนับสนุน IE

// run 5 times:
Array(5).fill().map((item, i)=>{ 
   console.log(i) // print index
})

หากคุณต้องการทำให้ "เปิดเผย" ข้างต้นมากขึ้นวิธีแก้ปัญหาตามความคิดเห็นของฉันในปัจจุบันคือ:


ใช้ลูปโรงเรียนเก่า (ย้อนกลับ):

// run 5 times:
for( let i=5; i--; )
   console.log(i) 

หรือเป็นการประกาศ"while" :


1
ในส่วนของจิตใจฉันใช้ฟังก์ชัน uuid 50k ครั้งเพื่อให้แน่ใจว่าจะไม่ซ้ำซ้อนกับ uuid ดังนั้นฉันจึงทำโปรไฟล์ลูปด้านบนเทียบกับด้านล่างสำหรับการเตะเพียงแค่ทำงานตรงกลางของการโหลดหน้าเว็บปกติโดยใช้เครื่องมือ Chrome dev ถ้าฉันไม่โง่ฉันคิดว่ามัน ~ 1.2 พันล้านเปรียบเทียบโดยใช้ Array.indexOf ()บวกกับการสร้าง 50k uuids newschool = 1st-5561.2ms 2nd-5426.8ms | oldschool = 1-4966.3ms / 2nd-4929.0ms คติธรรมของเรื่องราวถ้าคุณไม่ได้อยู่ในช่วงพันล้าน + คุณจะไม่สังเกตเห็นความแตกต่างที่วิ่ง 200, 1k, ถึง 10k ครั้งเพื่อทำบางสิ่ง คิดว่าอาจมีคนอยากรู้อยากเห็นเหมือนฉัน
rifi2k

ที่ถูกต้องและเป็นที่รู้จักกันมาหลายปี ไม่ได้นำเสนอแนวทางต่างๆเพื่อประโยชน์ด้านความเร็ว แต่สำหรับการรองรับเบราว์เซอร์รุ่นเก่า
vsync

3
เห็นได้ชัดว่าทุกคนที่อ่านหัวข้อนี้รู้ว่าคุณไม่ได้นำเสนอตัวอย่างเพื่อเปรียบเทียบความเร็วของพวกเขา ฉันเพิ่งเคยใช้มันเพื่อทำการทดสอบเล็กน้อยและคิดว่าฉันจะแบ่งปันข้อมูลบางอย่างที่ใครบางคนบนท้องถนนอาจคิดว่าน่าสนใจ ฉันไม่ถูกต้องจริงๆเพราะฉันไม่ได้ตอบคำถามเพียงแค่แสดงข้อมูลและเตือนความจำว่าอย่าใช้ความเร็วของการวนซ้ำเมื่อคุณทำเพียงไม่กี่อย่างซึ่งจะจบลงในสองสามมิลลิวินาที ยังไม่เป็นที่ทราบแน่ชัดเนื่องจากการทดสอบเดียวกันเมื่อปีที่แล้วในกล่าวคืออาจพบว่าช้าลง 50% เนื่องจากเบราว์เซอร์เปลี่ยนตลอดเวลา
rifi2k

10

คุณสามารถทำสิ่งเดียวกันกับการทำลายโครงสร้างได้ดังนี้

[...Array(3)].forEach( _ => console.log('do something'));

หรือหากคุณต้องการดัชนี

[...Array(3)].forEach(( _, index) => console.log('do something'));

8

หากคุณไม่สามารถใช้ Underscorejs ได้คุณสามารถใช้งานได้ด้วยตนเอง ด้วยการแนบวิธีการใหม่เข้ากับต้นแบบ Number และ String คุณสามารถทำได้เช่นนี้ (โดยใช้ฟังก์ชันลูกศร ES6):

// With String
"5".times( (i) => console.log("number "+i) );

// With number variable
var five = 5;
five.times( (i) => console.log("number "+i) );

// With number literal (parentheses required)
(5).times( (i) => console.log("number "+i) );

คุณต้องสร้างนิพจน์ฟังก์ชัน (ชื่ออะไรก็ได้) และกำหนดให้กับชื่อคุณสมบัติ (บนต้นแบบ) ที่คุณต้องการเข้าถึงเป็น:

var timesFunction = function(callback) {
  if (typeof callback !== "function" ) {
    throw new TypeError("Callback is not a function");
  } else if( isNaN(parseInt(Number(this.valueOf()))) ) {
    throw new TypeError("Object is not a valid number");
  }
  for (var i = 0; i < Number(this.valueOf()); i++) {
    callback(i);
  }
};

String.prototype.times = timesFunction;
Number.prototype.times = timesFunction;

1
ฉันจะต้องตรวจสอบอีกครั้งว่าการแก้ไขต้นแบบนั้นแย่แค่ไหน แต่โดยปกติแล้วมันก็โอเค
Alexander Mills

2

มีห้องสมุดที่ยอดเยี่ยมชื่อว่า Ramda ซึ่งคล้ายกับ Underscore และ Lodash แต่มีประสิทธิภาพมากกว่า

const R = require('ramda');

R.call(R.times(() => {
    console.log('do something')
}), 5);

Ramda มีฟังก์ชั่นที่มีประโยชน์มากมาย ดูเอกสาร Ramda


ฉันชอบห้องสมุดนี้ในฐานะโซลูชัน FP ที่ทันสมัยและสง่างาม
momocow

1

คุณสามารถใช้ความยาวของอาร์เรย์เพื่อดำเนินการตามจำนวนครั้งที่งานของคุณ

var arr = [1,2,3];

for(var i=0; i < arr.length; i++){
    doSomething();
}

หรือ

 var arr = [1,2,3];

 do
 {


 }
 while (i++ < arr.length);

1

คุณสามารถใช้ได้

Array.forEach

ตัวอย่าง:

function logArrayElements(element, index, array) {  
    console.log("a[" + index + "] = " + element);  
}  
[2, 5, 9].forEach(logArrayElements)

หรือด้วย jQuery

$.each([52, 97], function(index, value) { 
  alert(index + ': ' + value); 
});

http://api.jquery.com/jQuery.each/


ดูเหมือนว่าforEachจะรองรับเฉพาะใน IE จากเวอร์ชัน 9: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Bruno

1
times = function () {
    var length = arguments.length;
    for (var i = 0; i < length ; i++) {
        for (var j = 0; j < arguments[i]; j++) {
            dosomthing();
        }
    }
}

เรียกแบบนี้ก็ได้ว่า

times(3,4);
times(1,2,3,4);
times(1,3,5,7,9);

+1 - สิ่งนี้ใช้ความสามารถของ JavaScript ดั้งเดิมในการเรียกใช้ฟังก์ชันที่มีพารามิเตอร์จำนวนตัวแปร ไม่จำเป็นต้องมีห้องสมุดเพิ่มเติม ทางออกที่ดี
RustyTheBoyRobot


1
var times = [1,2,3];

for(var i = 0; i < times.length;  i++) {
  for(var j = 0; j < times[i];j++) {
     // do something
  }
}

ใช้ jQuery .each()

$([1,2,3]).each(function(i, val) {
  for(var j = 0; j < val;j++) {
     // do something
  }
});

หรือ

var x = [1,2,3];

$(x).each(function(i, val) {
  for(var j = 0; j < val;j++) {
     // do something
  }
});

แก้ไข

คุณสามารถทำเช่นด้านล่างด้วย JS บริสุทธิ์:

var times = [1,2,3];
times.forEach(function(i) {
   // do something
});

0

เพียงใช้ลูปที่ซ้อนกัน (อาจอยู่ในฟังก์ชัน)

function times( fct, times ) {
  for( var i=0; i<times.length; ++i ) {
    for( var j=0; j<times[i]; ++j ) {
      fct();
    }
  }
}

จากนั้นเรียกสิ่งนี้ว่า:

times( doSomething, [1,2,3] );

0

คำตอบเหล่านี้ดีและดีและ IMO @Andreas ดีที่สุด แต่หลายครั้งใน JS เราต้องทำสิ่งต่าง ๆ แบบอะซิงโครนัสในกรณีนี้คุณได้กล่าวถึง async:

http://caolan.github.io/async/docs.html#times

const async = require('async');

async.times(5, function(n, next) {
    createUser(n, function(err, user) {
        next(err, user);
    });
}, function(err, users) {
    // we should now have 5 users
});

คุณสมบัติ 'เวลา' เหล่านี้ไม่มีประโยชน์มากสำหรับโค้ดแอปพลิเคชันส่วนใหญ่ แต่ควรเป็นประโยชน์สำหรับการทดสอบ



0

ให้ฟังก์ชั่นsomething:

function something() { console.log("did something") }

และวิธีใหม่ที่timesเพิ่มเข้ามาในArrayต้นแบบ:

Array.prototype.times = function(f){
  for(v of this) 
    for(var _ of Array(v))
      f();
}

รหัสนี้:

[1,2,3].times(something)

ผลลัพธ์นี้:

did something
did something
did something
did something
did something
did something

ซึ่งฉันคิดว่าตอบคำถามที่อัปเดตของคุณ (5 ปีต่อมา) แต่ฉันสงสัยว่าการทำงานนี้กับอาร์เรย์มีประโยชน์อย่างไร? ผลจะไม่เหมือนกับการโทร[6].times(something)ซึ่งสามารถเขียนได้ว่า:

for(_ of Array(6)) something();

(แม้ว่าการใช้_เป็นตัวแปรขยะอาจทำให้เกิดการอุดตันหรือขีดล่างหากคุณใช้งาน)


1
การเพิ่มเมธอดแบบกำหนดเองให้กับออบเจ็กต์ JS ดั้งเดิมถือเป็นแนวทางปฏิบัติที่ไม่ดี
Lior Elrom

คุณสามารถใช้letเป็นfor (let _ of Array(6)) something()เพื่อป้องกันการอุดตันของ lodash ภายนอกเป็นเวลาอย่างน้อย
Ciro Santilli 郝海东冠状病六四事件法轮功

0

Array.from (ES6)

function doSomthing() {
    ...
}

ใช้มันดังนี้:

Array.from(Array(length).keys()).forEach(doSomthing);

หรือ

Array.from({ length }, (v, i) => i).forEach(doSomthing);

หรือ

// array start counting from 1
Array.from({ length }, (v, i) => ++i).forEach(doSomthing);


0

สมมติว่าเราสามารถใช้ไวยากรณ์ ES6 บางอย่างเช่นตัวดำเนินการสเปรดเราจะต้องการทำบางสิ่งหลาย ๆ ครั้งเป็นผลรวมของตัวเลขทั้งหมดในคอลเลกชัน

ในกรณีนี้ถ้าคูณเท่ากับ[1,2,3]จำนวนครั้งทั้งหมดจะเป็น 6 เช่น 1 + 2 + 3

/**
 * @param {number[]} times
 * @param {cb} function
 */
function doTimes(times, cb) {
  // Get the sum of all the times
  const totalTimes = times.reduce((acc, time) => acc + time);
  // Call the callback as many times as the sum
  [...Array(totalTimes)].map(cb);
}

doTimes([1,2,3], () => console.log('something'));
// => Prints 'something' 6 times

โพสต์นี้น่าจะเป็นประโยชน์หากตรรกะเบื้องหลังการสร้างและการแพร่กระจายอาร์เรย์ไม่ชัดเจน


0

การใช้งาน TypeScript:

สำหรับผู้ที่สนใจวิธีการใช้งานString.timesและNumber.timesวิธีการพิมพ์ที่ปลอดภัยและใช้งานได้กับสิ่งเหล่าthisArgนี้ไปเลย:

declare global {
    interface Number {
        times: (callbackFn: (iteration: number) => void, thisArg?: any) => void;
    }
    interface String {
        times: (callbackFn: (iteration: number) => void, thisArg?: any) => void;
    }
}

Number.prototype.times = function (callbackFn, thisArg) {
    const num = this.valueOf()
    if (typeof callbackFn !== "function" ) {
        throw new TypeError("callbackFn is not a function")
    }
    if (num < 0) {
        throw new RangeError('Must not be negative')
    }
    if (!isFinite(num)) {
        throw new RangeError('Must be Finite')
    }
    if (isNaN(num)) {
        throw new RangeError('Must not be NaN')
    }

    [...Array(num)].forEach((_, i) => callbackFn.bind(thisArg, i + 1)())
    // Other elegant solutions
    // new Array<null>(num).fill(null).forEach(() => {})
    // Array.from({length: num}).forEach(() => {})
}

String.prototype.times = function (callbackFn, thisArg) {
    let num = parseInt(this.valueOf())
    if (typeof callbackFn !== "function" ) {
        throw new TypeError("callbackFn is not a function")
    }
    if (num < 0) {
        throw new RangeError('Must not be negative')
    }
    if (!isFinite(num)) {
        throw new RangeError('Must be Finite')
    }
    // num is NaN if `this` is an empty string 
    if (isNaN(num)) {
        num = 0
    }

    [...Array(num)].forEach((_, i) => callbackFn.bind(thisArg, i + 1)())
    // Other elegant solutions
    // new Array<null>(num).fill(null).forEach(() => {})
    // Array.from({length: num}).forEach(() => {})
}

ลิงก์ไปยังTypeScript Playgroundพร้อมตัวอย่างบางส่วนสามารถพบได้ที่นี่

โพสต์นี้ใช้วิธีแก้ปัญหาที่โพสต์โดยAndreas Bergström , vinyll , Ozay DumanและSeregPie

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.