配列または文字列内の要素を見つける必要がある場合、indexOf()はあなたの親友の1人です。

配列内のindexOf

最初にコードを記述し、後で説明します。

モジュール:findSpencer.js
const zoo = ['🐒', '🦄', '🐊', '🐸', '🐙'];
const spencersIndex = zoo.indexOf('🐊');
// spencersIndex === 2
const spencer = zoo[spencersIndex];
// spencer === '🐊'

最も単純なバージョンでは、indexOfメソッドは、検索しようとしている要素である1つの引数を取ります。 次に、el === targetを満たす配列で最初に見つかった要素のインデックスを返します。 これは、配列に2つの一致がある場合でも、indexOfは1つの結果のみを返すことを意味します。 結果は、配列内で最初に出現します(配列を左から右に読み取ります)。

配列内のどの項目もel === targetチェックを満たさない場合、-1の値が返されます。

しかし、Skyler(Spencerの妹)も探しているとしましょう。 次に、オプションの引数を追加して、別のインデックスから検索を開始できます。

モジュール:findSkyler.js
const zoo = ['🐒', '🦄', '🐊', '🐸', '🐙',  '🐊']; // Skyler just arrived!
const spencersIndex = zoo.indexOf('🐊'); // === 2 same as before

// We start searching after 
const skylersIndex = zoo.indexOf('🐊', spencersIndex + 1);
// skylersIndex === 5

indexOfに基づいてすべてのインデックスを返すArrayプロトタイプにメソッドを作成することもできます。

モジュール:indexesOf.js
// Try to think of ways to make indicesOf more performant
Array.prototype.indicesOf = function (target) {
  const result = [];
  let currentIndex = 0;
  while(true) {
    // here this ===  the array on which we call `indicesOf`
    const targetIndex = this.indexOf(target, currentIndex);
    if (targetIndex == -1)
      break;

    result.push(targetIndex);

    currentIndex = targetIndex +1;
  }

  return result;
}
const zoo = ['🐒', '🦄', '🐊', '🐸', '🐙',  '🐊']; // Skyler just arrived!
const alligatorsIndices = zoo.indicesOf('🐊');
// alligatorsIndices returns [2, 5]

indexOfで何が見つかりませんか?

厳密な等式比較であるトリプル等式比較el === targetを使用して、検索を中断していることに気付いたかもしれません。 これは、たとえば、参照以外の方法で配列、オブジェクト、または関数をテストできないことを意味します。

const simpleArray = [1, 2, 3];
const simpleFunction = () => {console.log('hey')};
const simpleObject = {alligator: 'cage'};

const compositeArray = [simpleArray, simpleFunction, simpleObject];

// These all work as expected because we compare by reference
compositeArray.indexOf(simpleArray); // returns 0
compositeArray.indexOf(simpleFunction); // returns 1
compositeArray.indexOf(simpleObject); // returns 2

// These won't work 
compositeArray.indexOf([1, 2, 3]); // returns -1
compositeArray.indexOf(() => {console.log('hey')}); // returns -1
compositeArray.indexOf({alligator: 'cage'}) // returns -1

深いindexOf

オブジェクト、配列、関数も再帰的にチェックするユーティリティを作成するとします。

モジュール:inDepthIndexOf.js
Array.prototype.deepIndexOf = function (target) {
  // If the target is an object, array or a function, we give it a special treatment
  if (typeof target === 'object' || typeof target === 'function') {
    // We stringify the target 
    const searchTarget = target.toString()
    // We loop through all of the elements of the array
    for (let index = 0; index < this.length; index++){
      const element = this[index]
      // We check if the element in the array is an object or a function AND if so we check if its' stringified value is equal to our target
      if ((typeof element === 'object' || typeof target === 'function') && element.toString() === searchTarget) {
        // if we have a match we interrupt the loop and return the index
        return index
      }
    }
    // if nothing matched we return -1
    return -1
  }
  return this.indexOf(target)
}

const simpleArray = [1, 2, 3];
const simpleFunction = () => {console.log('hey')};
const simpleObject = {alligator: 'cage'};

const compositeArray = [simpleArray, simpleFunction, simpleObject];

// These all work as expected because we compare by reference
compositeArray.deepIndexOf(simpleArray); // returns 0
// ... You know the rest
// These will work!
compositeArray.deepIndexOf([1, 2, 3]); // returns 0
compositeArray.deepIndexOf(() => {console.log('hey')}); // returns 1
compositeArray.deepIndexOf({alligator: 'cage'}) // returns 2

このコードを改善する方法はたくさんあります。 手元に時間があれば、より正確でパフォーマンスの高いものにする方法を考えてみてください。 Twitterであなたのアイデアを読んでみたいです。

パフォーマンス

indexOfの使用は、単にfor loopを実行するよりもはるかに低速です。 それはindexOfが遅いという意味ではありません。 配列が小さい場合、indexOf()とforループの違いはわかりません。 配列が非常に大きいために2つの方法の違いに気付く場合は、配列が非常に大きい理由と、検索を最適化する方法について疑問に思うはずです。 パフォーマンスベンチマークはJSPerfにあります。

var spencersIndex
// This is faster than indexOf('🐊') but it is much uglier
for(var index = 0; index < zoo.length; index ++) {
  if (zoo[index] === '🐊')
    spencersIndex = index
}
// spencers Index === 2

文字列のindexOf

Array.indexOfからこのセクションにすべてのロジックを移植できます。 コーディングしましょう!

モジュール:whereIsSpencer.js
const whereIsSpencer = "We are all looking for Spencer the alligator. Spencer is a dear friend. Lookout here comes 🐊!"

const spencersIndex = whereIsSpencer.indexOf('Spencer');
// spencersIndex ===  23

// to find find the second occurence of 'Spencer',
// we need to add one to the position of spencer #1
const secondSpencerIndex = whereIsSpencer.indexOf('Spencer', spencersIndex + 1);
// secondSpencerIndex ===  46

const alligatorIndex = whereIsSpencer.indexOf('🐊');
// alligatorIndex ===  91

// Be careful the search is case sensitive!
const lowerCaseSpencer = whereIsSpencer.indexOf('spencer');
// lowerCaseSpencer === -1

これで、Array.prototype.stringに対して同じindicesOf関数を作成できます。

モジュール:indexesOfStrings.js
// This is a bit more concise than the previous indicesOf function
// But it is exactly the same logic
String.prototype.indicesOf = function (target) {
  let currentIndex = this.indexOf(target);
  const allIndices = []
  while(currentIndex != -1) {
    allIndices.push(currentIndex);
    currentIndex =  this.indexOf(target, currentIndex +1 );
  }

  return allIndices;
}

const whereIsSpencer = "We are all looking for Spencer the alligator. Spencer is a dear friend. Lookout here comes 🐊!";

const spencerIndices = whereIsSpencer.indicesOf('Spencer');
// spencerIndices equals [23, 46]

この投稿を楽しんでいただけたでしょうか。 ご提案、ご質問、ご意見がございましたら、Twitterまでお気軽にお問い合わせください。