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 5.7.21 解凍版インストール Navicat データベース操作ツールインストール

MySQL解凍版とNavicatデータベース操作ツールのインストールは、以下のとおりです。 1. M...

オンライン MYSQL 同期エラーのトラブルシューティング方法の概要 (必読)

序文フェイルオーバーが発生した後、よくある問題は同期エラーです。データベースが小さい場合は、ダンプし...

JavaScript コンソールのその他の機能

目次概要コンソールログコンソール.infoコンソール.警告コンソールエラーコンソールテーブルコンソー...

HTML埋め込みタグの使用方法と属性の詳細な説明

1. 基本的な文法コードをコピーコードは次のとおりです。埋め込み src=url注: 埋め込みはさま...

iptables および firewalld ツールを使用して Linux ファイアウォール接続ルールを管理する

ファイアウォールファイアウォールは一連のルールです。パケットが保護されたネットワーク空間に出入りする...

Python 仮想環境のインストールとアンインストールの方法と発生する問題

Ubuntu16.04 のインストールとアンインストール pip実験環境Ubuntu 16.04; ...

Linux の一般的なコマンドとショートカット キーの紹介

目次1 システムの紹介2 システムショートカット3 一般的なシステムコマンド1 システムの紹介 1....

単一/複数行テキストを含む div を垂直方向に中央揃えする N 通りの方法 (高さ不明/高さ固定)

この問題について話すとき、垂直方向の中央揃えを設定するための vertical-align 属性が ...

httpsウェブサイトにリファラーhttpsとhttpジャンプリファラーを送信させる方法

この記事では、HTTP プロトコルのリファラーのメタデータ パラメータの提案について説明します。この...

Linuxでファイルの作成時間を表示する方法

1. はじめにLinux でファイルの作成時刻が見つかるかどうかは、ファイル システムの種類によって...

MySQL 5.7.20 圧縮版のダウンロードとインストールの簡単なチュートリアル

1. ダウンロードアドレス:参考: http://dev.mysql.com/downloads/m...

CentOS7 は rpm を使用して MySQL 5.7 をインストールするチュートリアル図

1. 4つのrpmパッケージをダウンロードする mysql-コミュニティクライアント-5.7.26-...

Linux centos7 に phpMyAdmin をインストールするチュートリアル

yum install httpd php mariadb-server –yランプの動作環境を設定...

Linux プロセス管理ツール スーパーバイザーのインストールと設定のチュートリアル

環境: CentOS 7公式ドキュメント: http://supervisord.org/インストー...

CSS でインラインブロック要素間のギャップを削除するいくつかの方法の詳細な説明

最近、モバイルページを制作する際には、レイアウトにインラインブロック要素がよく使われますが、インライ...