Js でオブジェクトのディープ オブジェクトを安全に取得するメソッドの例

Js でオブジェクトのディープ オブジェクトを安全に取得するメソッドの例

序文

フロントエンドで作業している友人は、バックエンドから返されるデータが複数のレイヤーにネストされている状況に遭遇したことがあるはずです。深いオブジェクトの値を取得したいときは、次のようにレイヤーごとに空でないかどうかのチェックを行ってエラーを防止します。

定数オブジェクト = {
    品:
        名前: 'a',
     タグ: {
            名前: 'Fast'、
         id: 1,
         タグタイプ: {
                名前: 'ラベル'
           }
       }
   }
}

tagType.nameを取得する必要がある場合、判断は次のようになります

(obj.goods !== nullの場合
    && obj.goods.tags !== null
    && obj.goods.tags.tagType !== null) {
 
 }

属性名が長いと、コードが判読できなくなります。

もちろん、ECMAScript 2020 ではこの問題を解決するために ?. が導入されました。

name = obj?.goods?.tags?.tageType?.name; とします。

しかし、ES2020 と互換性のないブラウザにはどのように対処すればよいのでしょうか?

文章

lodash を使用したことがある学生は、lodash に get メソッドがあることを知っているかもしれません。公式 Web サイトには次のように書かれています。

_.get(オブジェクト, パス, [デフォルト値])

オブジェクトのパスに応じて値を取得します。 解決された値が未定義の場合は、defaultValue に置き換えられます。

パラメータ

  1. object (オブジェクト): 取得するオブジェクト。
  2. path (配列|文字列): プロパティを取得するパス。
  3. [defaultValue] ()* : 解決された値が未定義の場合、この値が返されます。


var オブジェクト = { 'a': [{ 'b': { 'c': 3 } }] };
​
_.get(オブジェクト、'a[0].b.c');
// => 3 
​
_.get(オブジェクト、['a', '0', 'b', 'c']);
// => 3
​
_.get(オブジェクト, 'abc', 'デフォルト');
// => 'デフォルト'

これで問題は解決しますが、(残念ながら「しかし」があります)

プロジェクトや会社の要件など、さまざまな理由により lodash ライブラリを使用できない場合はどうなりますか?

さて、lodash がどのように実装されているかを見てみましょう。コードを抽出するだけではだめでしょうか? そうすれば、再び楽しくレンガを動かすことができます~~

Lodash 実装:

関数 get(オブジェクト, パス, デフォルト値) {
  const 結果 = オブジェクト == null ? 未定義 : baseGet(オブジェクト、パス)
  結果を返す === undefined ? defaultValue : 結果
}

ここでやっていることはとても簡単です。まず戻り値を見てみましょう。オブジェクトがデフォルト値を返す場合、コアコードはbaseGetにあります。baseGetの実装を見てみましょう。

関数baseGet(オブジェクト、パス) {
  //入力文字列パスを配列に変換します。
  パス = castPath(パス、オブジェクト)
​
  インデックスを0にする
  定数長さ = パス.長さ
  // 配列を走査してオブジェクトの各レイヤーを取得します while (object != null && index < length) {
    object = object[toKey(path[index++])] // toKeyメソッド}
  戻り値 (インデックス && インデックス == 長さ) ? オブジェクト: 未定義
}
​

ここでは、castPath(入力パスを配列に変換する)とtoKey(実際のキーを変換する)という2つの関数を使用します。

トーキー機能:

/** さまざまな `Number` 定数の参照として使用されます。 */
定数無限大 = 1 / 0
​
/**
 * `value` が文字列またはシンボルでない場合は、文字列キーに変換します。
 *
 * @プライベート
 * @param {*} value 検査する値。
 * @returns {string|symbol} キーを返します。
 */
関数toKey(値) {
  if (typeof value === 'string' || isSymbol(value)) {
    戻り値
  }
  const 結果 = `${値}`
  戻り値 (結果 == '0' && (1 / 値) == -INFINITY) ? '-0' : 結果
}

ここで行われる主なことは 2 つあります。

  • キータイプが文字列またはシンボルの場合は、直接返されます。
  • キーが他の型の場合は、文字列に変換されて返されます。

isSymbol関数は、Symbol型かどうかを判断するためにもここで使用されています。コードはここには掲載されていません。興味のある学生はlodashソースコードを確認してください。

castPath関数:

'./isKey.js' から isKey をインポートします。
'./stringToPath.js' から stringToPath をインポートします。
​
/**
 * `value` がパス配列でない場合はパス配列にキャストします。
 *
 * @プライベート
 * @param {*} value 検査する値。
 * @param {Object} [object] キーを照会するオブジェクト。
 * @returns {Array} キャストプロパティパス配列を返します。
 */
関数castPath(値、オブジェクト) {
  Array.isArray(値)の場合{
    戻り値
  }
  isKey(値、オブジェクト)を返します? [値] : stringToPath(値)
}
​

castPath は主に、入力パスを配列に変換して、後続のトラバーサルで深いオブジェクトを取得できるように準備します。

ここではisKey()とstringToPath()が使用されます。

isKey は、現在の値がオブジェクトのキーであるかどうかを判断する比較的単純な関数です。

stringToPathは主に、入力パスが「abc[0].d」のような文字列である場合に処理します。

stringToPath 関数:

'./memoizeCapped.js' から memoizeCapped をインポートします。
​
const charCodeOfDot = '.'.charCodeAt(0)
定数 reEscapeChar = /\(\)?/g
定数rePropName = 正規表現(
  // ドットまたは括弧以外のものに一致します。
  '[^.[\]]+' + '|' +
  // または、括弧内のプロパティ名を一致させます。
  '\[(?:' +
    // 文字列以外の式に一致します。
    '([^"'][^[]*)' + '|' +
    // または文字列に一致します (エスケープ文字をサポートします)。
    '(["'])((?:(?!\2)[^\\]|\\.)*?)\2' +
  ')\]'+ '|' +
  // または、連続するドットまたは空の括弧の間のスペースとして "" と一致します。
  '(?=(?:\.|\[\])(?:\.|\[\]|$))'
  、 'g')
​
/**
 * `string` をプロパティ パス配列に変換します。
 *
 * @プライベート
 * @param {string} string 変換する文字列。
 * @returns {Array} プロパティ パス配列を返します。
 */
const stringToPath = memoizeCapped((文字列) => {
  定数結果 = []
  (文字列.charCodeAt(0)=== charCodeOfDot)の場合{
    結果.push('')
  }
  string.replace(rePropName, (一致, 式, 引用符, 部分文字列) => {
    キー = 一致させる
    (引用)の場合{
      キー = subString.replace(reEscapeChar, '$1')
   }
    そうでない場合 (式) {
      キー = 式.trim()
   }
    結果.push(キー)
  })
  結果を返す
})
​

ここでは主にパス内の.と[]を除外し、実際のキーを解析して配列に追加します。

memoizeCapped 関数:

'../memoize.js' から memoize をインポートします。
​
/** 最大メモ化キャッシュサイズとして使用されます。 */
定数 MAX_MEMOIZE_SIZE = 500
​
/**
 * メモ化された関数の内容をクリアする`memoize`の特殊バージョン
 * `MAX_MEMOIZE_SIZE` を超えるとキャッシュされます。
 *
 * @プライベート
 * @param {Function} func 出力をメモ化する関数。
 * @returns {Function} 新しくメモ化された関数を返します。
 */
関数 memoizeCapped(func) {
  const 結果 = memoize(func, (キー) => {
    const { キャッシュ } = 結果
    (キャッシュサイズ === MAX_MEMOIZE_SIZE)の場合{
      キャッシュをクリアする()
   }
    リターンキー
  })
​
  結果を返す
}
​
エクスポートデフォルトmemoizeCapped
​

キャッシュされたキーには制限があり、500に達するとキャッシュがクリアされます。

メモ化関数:

関数 memoize(func, リゾルバ) {
  if (typeof func !== 'function' || (resolver != null && typeofresolver !== 'function')) {
    throw new TypeError('関数が必要です')
  }
  const メモ化 = 関数(...引数) {
    const key = リゾルバ ? リゾルバ.apply(this, args) : args[0]
    const キャッシュ = メモ化されたキャッシュ
​
    キャッシュにキーがある場合
      cache.get(キー) を返す
   }
    const 結果 = func.apply(this, args)
    memoized.cache = cache.set(キー、結果) || キャッシュ
    結果を返す
  }
  memoized.cache = new (memoize.Cache || Map)
  メモ化されたものを返す
}
​
memoize.Cache = マップ

実は、最後の 2 つの関数がよくわかりません。入力パスが 'abc' の場合、それを配列に変換すればよいのではないでしょうか。キャッシュにクロージャを使用するのはなぜですか?

これを理解できる人が答えてくれることを願っています。

ソースコードは多くの関数を使用しており、異なるファイルに分かれているため、簡略化しました。

完全なコードは次のとおりです。

/**
   * `object`の`path`にある値を取得します。解決された値が
   * `undefined` の場合、代わりに `defaultValue` が返されます。
   * @例
   * 定数オブジェクト = { 'a': [{ 'b': { 'c': 3 } }] }
   *
   * get(オブジェクト、'a[0].b.c')
   * // => 3
   *
   * get(オブジェクト、['a', '0', 'b', 'c'])
   * // => 3
   *
   * get(オブジェクト、'abc'、'default')
   * // => 'デフォルト'
   */
  safeGet (オブジェクト、パス、デフォルト値) {
    結果を出す
    オブジェクトが null ではない場合
      if (!Array.isArray(path)) {
        const type = typeof パス
        if (type === 'number' || type === 'boolean' || path == null ||
        /^\w*$/.test(パス) || !(/.|[(?:[^[]]*|(["'])(?:(?!\1)[^\]|\.)*?\1)]/.test(パス)) ||
       (オブジェクト != null && Object(オブジェクト) 内のパス)) {
          パス = [パス]
       } それ以外 {
          定数結果 = []
          (path.charCodeAt(0) === '.'.charCodeAt(0)の場合){
            結果.push('')
         }
          定数rePropName = 正規表現(
            // ドットまたは括弧以外の任意の文字に一致します。
            '[^.[\]]+|\[(?:([^"'][^[]*)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))'
           、 'g')
          path.replace(rePropName, (一致, 式, 引用符, 部分文字列) => {
            キー = 一致させる
            (引用)の場合{
              キー = subString.replace(/\(\)?/g, '$1')
           } else if (式) {
              キー = 式.trim()
           }
            結果.push(キー)
         })
          パス = 結果
       }
     }
      インデックスを0にする
      定数長さ = パス.長さ
      const toKey = (値) => {
        if (typeof value === 'string') {
          戻り値
       }
        const 結果 = `${値}`
        戻り値 (結果 === '0' && (1 / 値) === -(1 / 0)) ? '-0' : 結果
     }
      while (オブジェクト != null && インデックス < 長さ) {
        オブジェクト = オブジェクト[toKey(パス[インデックス++])]
     }
      結果 = (インデックス && インデックス === 長さ) ? オブジェクト: 未定義
   }
    結果を返す === undefined ? defaultValue : 結果
  }

コードはlodashから借用しました

参考文献:

  • lodash 公式ドキュメント
  • ギットハブ

要約する

これで、Js で Object のディープ レベル オブジェクトを安全に取得する方法に関するこの記事は終了です。Js で Object のディープ レベル オブジェクトを取得することに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後も 123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • JSオブジェクト(Object)と文字列(String)の変換方法
  • jsでオブジェクトを印刷する方法
  • Javascript のオブジェクトの詳細な説明
  • JavaScript におけるオブジェクトクローンの詳細な理解
  • js における配列とオブジェクトの違いについての詳細な説明
  • Javascript オブジェクト指向オブジェクト
  • JavaScript で Object.create() を使用してオブジェクトを作成する方法の紹介
  • Javascript オブジェクトマージ操作例の分析

<<:  MySQLはmysqldump+binlogを使用して、削除されたデータベースの原理分析を完全に復元します。

>>:  nginx を使用して静的リソース サーバーを構築する方法

推薦する

クラウド CentOS で Docker リモート サービス リンクを有効にするための実装手順

ここでは、dockerがインストールされたcentosサーバーを紹介し、リモートリンクサービスを開始...

Vue の詳細な入門ノート

目次1. はじめに2. 初期ビュー(I) Vueの概念を理解する(II) MVVMアーキテクチャ(I...

JSはBaidu Newsナビゲーションバーの効果を実現

この記事では、Baidu News Navigation Barの効果を実現するための具体的なJSコ...

MySQLデーモンの起動に失敗したエラーの解決方法

MySQLデーモンの起動に失敗したエラーの解決方法数日前、公開されたウェブサイトはこれらのアクティビ...

JavaScript のコールバック関数の理解と使用

目次概要コールバックまたは高階関数とは何ですか?コールバック関数はどのように機能しますか?コールバッ...

テーブル内の要素のドラッグと並べ替えの問題について簡単に説明します

最近、要素テーブルを使用すると、並べ替えの問題によく遭遇します。単純な並べ替えであれば、要素の公式が...

MySQL パフォーマンス チューニングについて知っておくべき 15 個の重要な変数 (要約)

序文: MYSQL は最も人気のある WEB バックエンド データベースです。最近、NOSQL がま...

Vue ルーターにパラメータを渡すときにページを更新するとパラメータが失われる問題に対処する方法

目次概要方法1: params経由でパラメータを渡す方法2: クエリを通じてパラメータを渡す方法3:...

CentOS 8 に Postfix メール サーバーをインストールして設定する方法

Postfix は、Linux システム上で電子メールをルーティングまたは配信するために使用される無...

CentOS 7 で Docker のポート転送をファイアウォールと互換性のあるように設定する方法

CentOS 7 では、次のようなコマンドを使用してホスト ポートをコンテナー ポートにマッピングす...

VMware 仮想マシンの 3 つのネットワーク方式と原則 (概要)

1. ブリッジ: デフォルトでは VMnet0 が使用されます1. 原則:ブリッジは、それぞれ 2...

mysql8.0.11 winx64 手動インストールと設定チュートリアル

まず、私の日常生活についてお話しします。MySQLの急速なアップデートにより、MySQLはバージョン...

Tomcat10 Catalinaのログの文字化けの問題を解決する

実行環境、Idea2020バージョン、Tomcat10、実行時にTomcat CatalinaLog...

JSは5つ星の賞賛効果を達成

JS を使用してオブジェクト指向メソッドを実装し、JD.com の 5 つ星レビュー効果を実現します...

CSS3 で翻訳効果 (transfrom: translate) を実装する例

移動を実現するためにtranslateパラメータを使用しますtranslateX: X 軸に沿って移...