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 を使用して静的リソース サーバーを構築する方法

推薦する

MySQLとNavicatプレミアムのインストールと設定の詳細な手順

前提条件: Mac、zsh がインストールされ、bash のときに mysql がダウンロードされ、...

VueのVuexの4つの補助機能について

目次1. 補助機能2. 例1. mapState と mapGetters 2. mapMutati...

Docker で MySQL を起動したときに SQL 文を自動的に実行する方法

Docker で MySQL コンテナを作成する場合、コンテナの起動後にデータベースとテーブルが自動...

CSSをiPhoneのフルスクリーンに適応させる方法

1. メディアクエリ方式 /*iPhone X への適応*/ @media 画面のみ、(デバイス幅:...

JavaScript におけるイベント バブリング メカニズムの詳細な分析

バブリングとは何ですか? DOM イベント フローには、イベント キャプチャ ステージ、ターゲット ...

WeChat アプレット wxs 日付と時刻処理の実装例

目次1. 日付までのタイムスタンプ2. UTCを北京時間に変換するWXS (WeiXin Scrip...

2時間のDocker入門チュートリアル

目次1.0 はじめに2.0 Dockerのインストール3.0基本的なDockerコマンド4.0 Do...

MySQL での Join の使用に関する詳細な説明

前の章では、1 つのテーブルからデータを読み取る方法を学習しました。これは比較的簡単ですが、実際のア...

要素のフォームコンポーネントに関する注意事項

要素フォームとコード表示詳細はエレメントフォーム公式サイトをご覧ください構造と機能の分析紹介とソース...

MySQL ユーザーのホスト属性を素早く変更する方法

MySQL にリモートでログインする場合、使用するアカウントには特別な要件があります。アカウントのデ...

XHTML コードで Marquee タグを使用する方法

フォーラムで、ネットユーザーの jeanjean20 が、Marquee を標準に適合させる方法につ...

CSS でフローティングにより親要素の高さが崩れる問題を解決するいくつかの方法

以前は、フロートはレイアウトによく使用されていましたが、フローティングレイアウトを使用すると親要素の...

仕事の効率を上げるJS略語スキル20選

目次複数の変数を同時に宣言する場合は、1 行に短縮できます。分割代入は複数の変数に同時に値を割り当て...

Vueカスケードドロップダウンボックスの設計と実装

目次1. データベース設計2. フロントエンドページ3. 完全なデモフロントエンド開発では、カスケー...

AngularJSにおける括弧の役割の詳細な説明

1. 括弧の役割1.1 角括弧 [ ]属性名が角括弧で囲まれている場合、右側には式の値が割り当てられ...