JavaScriptで未定義を処理するための7つのヒント
Ruby、Python、Javaなどの最新言語のほとんどには、単一のnull値(nil
またはnull
)、これは合理的なアプローチのようです。
しかし、JavaScriptは異なります。
null
だけでなく、undefined
も、JavaScriptの空の値を表します。では、それらの正確な違いは何ですか?
簡単に言うと、JavaScriptインタープリターは、まだ初期化されていない変数またはオブジェクトプロパティにアクセスすると、undefined
を返します。例:
反対側では、null
は欠落しているオブジェクトを表します参照。 JavaScriptは、null
を使用して変数またはオブジェクトプロパティを初期化しません。
String.prototype.match()
などの一部のネイティブメソッドは、null
を返して不足しているオブジェクトを示すことができます。サンプルを見てください:
JavaScriptは寛容であるため、開発者は初期化されていない値にアクセスしたいという誘惑に駆られます。私もそのような悪い習慣に罪を犯しています。
このような危険なアクションは、多くの場合、undefined
関連のエラーを生成します:
-
TypeError: "undefined" is not a function
-
TypeError: Cannot read property "<prop-name>" of undefined
- 同様のタイプエラー。
JavaScript開発者はこのジョークの皮肉を理解できます。 :
このようなエラーを減らすには、undefined
が生成されます。 undefined
とそのコードの安全性への影響を調べてみましょう。
1。未定義のもの
JavaScriptには6つのプリミティブ型があります:
そして分離されたオブジェクト型:{name: "Dmitri"}
、。
6つのプリミティブ型からundefined
は、独自の型Undefinedを持つ特別な値です。 ECMAScript仕様によると:
変数に値が割り当てられていない場合、未定義の値プリミティブ値が使用されます。
標準では、初期化されていない変数、存在しないオブジェクトプロパティにアクセスすると、undefined
を受け取ることが明確に定義されています。存在しない配列要素など。
いくつかの例:
上記の例は、以下にアクセスすることを示しています。
- 初期化されていない変数
number
- 存在しないオブジェクトプロパティ
movie.year
- または存在しない配列要素
movies
はundefined
に評価されます。
ECMAScript仕様は、undefined
値のタイプを定義します。
未定義のタイプは唯一の値が
undefined
値であるタイプ。
この意味で、typeof
演算子はundefined
値の"undefined"
文字列を返します:
もちろんtypeof
は、変数にundefined
値が含まれているかどうかを確認するのに適しています。
2。未定義を作成するシナリオ
2.1初期化されていない変数
宣言された変数であるが、まだ値が割り当てられていない(初期化されていない)のは、デフォルトで
undefined
です。
わかりやすくシンプル:
myVariable
が宣言されており、まだ値が割り当てられていません。変数にアクセスすると、undefined
に評価されます。
初期化されていない変数の問題を解決するための効率的なアプローチは、可能な限り初期値を割り当てることです。初期化されていない状態で存在する変数が少ないほど、優れています。
理想的には、宣言const myVariable = "Initial value"
の直後に値を割り当てます。しかし、それが常に可能であるとは限りません。
ヒント1:const
を優先するか、let
を使用しますが、
私の意見では、ECMAScript 2015の最も優れた機能の1つは、const
と。それは大きな前進です。
const
とlet
はブロックスコープです(以前の関数スコープvar
)であり、宣言行まで一時的なデッドゾーンに存在します。
値が変更されない場合は、const
変数をお勧めします。不変のバインディングを作成します。
const
の優れた機能の1つは、変数const myVariable = "initial"
に初期値を割り当てる必要があることです。変数は初期化されていない状態にさらされておらず、undefined
にアクセスすることはできません。
単語が回文であるかどうかを確認する関数を確認しましょう:
length
およびhalf
変数には値が1回割り当てられます。これらの変数は変更されないため、const
として宣言するのが妥当と思われます。
値が変更される可能性のある変数には、let
宣言を使用します。可能な限り、すぐに初期値を割り当てます。 let index = 0
。
古い学校のvar
はどうですか?私の提案はそれを使うのをやめることです。
var
宣言の問題は、関数スコープ内での変数の巻き上げです。関数スコープの最後のどこかでvar
変数を宣言できますが、それでも宣言前にアクセスできます。undefined
。
myVariable
はアクセス可能であり、宣言行の前でもundefined
が含まれています:var myVariable = "Initial value"
。
逆に、const
またはlet
変数は、宣言行の前にアクセスできません—変数宣言前の一時的なデッドゾーンにあります。 undefined
にアクセスする機会が少ないので便利です。
上記の例はlet
で更新されています(代わりにof var
)は、一時的な不感帯の変数にアクセスできないため、ReferenceError
をスローします。
不変のバインディングにconst
を使用することを推奨するか、let
を使用すると、初期化されていないものの外観を減らすことができます。変数。
ヒント2:凝集度を高める
凝集度は、モジュールの要素(名前空間、クラス、メソッド、コードのブロック)がどの程度一緒に属するかを示します。凝集力は高くても低くてもかまいません。
このようなモジュールの要素は単一のタスクのみに焦点を当てているため、高凝集性モジュールが望ましいです。モジュールを次のようにします。
- 焦点を絞って理解できる:モジュールの機能を理解しやすくする
- 保守可能でリファクタリングしやすい:モジュールの変更が影響するモジュールの数が少なくなります
- 再利用可能:単一のタスクに焦点を合わせているため、モジュールの再利用が容易になります
- テスト可能:単一のタスクに焦点を合わせたモジュールを簡単にテストできます
疎結合を伴う高い凝集度は、適切に設計されたシステムの特徴です。
コードブロックは小さなモジュールと見なすことができます。高い凝集度の利点を活用するには、変数を使用するコードブロックにできるだけ近づけてください。
たとえば、ブロックスコープのロジックを形成するために変数が単独で存在する場合は、そのブロック内でのみ変数を宣言して有効にします(const
またはlet
宣言)。外側のブロックはこの変数を気にする必要がないため、この変数を外側のブロックスコープに公開しないでください。
変数の寿命が不必要に長くなる典型的な例の1つは、関数内のサイクル:
index
、item
、length
変数は関数本体の先頭で宣言されます。ただし、それらは終わり近くでのみ使用されます。このアプローチの問題は何ですか?
上部の宣言とfor
ステートメントでの使用の間で変数index
、item
は初期化されておらず、undefined
に公開されています。それらは、機能範囲全体で不当に長いライフサイクルを持っています。
より良いアプローチは、これらの変数を使用場所にできるだけ近づけることです:
index
およびitem
変数は、for
ステートメントのブロックスコープにのみ存在します。 for
以外では意味がありません。
length
変数もその使用元の近くで宣言されています。
変更されたバージョンが最初のバージョンよりも優れているのはなぜですか?見てみましょう:
- 変数は初期化されていない状態にさらされていないため、
undefined
- にアクセスするリスクはありません。変数を使用場所にできるだけ近づけると、コードの可読性が向上します
- 必要に応じて、コードのまとまりのあるチャンクをリファクタリングして個別の関数に抽出する方が簡単です
2.2アクセス存在しないプロパティ
存在しないオブジェクトプロパティにアクセスすると、JavaScriptは
undefined
を返します。
例でそれを示しましょう:
favoriteMovie
は、単一のプロパティtitle
を持つオブジェクトです。プロパティアクセサーfavoriteMovie.actors
を使用して存在しないプロパティactors
にアクセスすると、undefined
と評価されます。
存在しないプロパティにアクセスしても、エラーはスローされません。この問題は、最も一般的なundefined
トラップである存在しないプロパティからデータを取得しようとすると発生し、よく知られているエラーメッセージTypeError: Cannot read property <prop> of undefined
。
前のコードスニペットを少し変更して、TypeError
のスローを説明しましょう:
favoriteMovie
にはプロパティactors
がないため、favoriteMovie.actors
undefined
に評価されます。
その結果、式favoriteMovie.actors
を使用してundefined
値の最初の項目にアクセスすると、
存在しないプロパティへのアクセスを許可するJavaScriptの寛容な性質は、非決定性の原因です。プロパティが設定されているかどうかは不明です。この問題を回避する良い方法は、オブジェクトが保持するプロパティを常に定義するようにオブジェクトを制限することです。
残念ながら、多くの場合、オブジェクトを制御できません。このようなオブジェクトは、さまざまなシナリオで異なるプロパティのセットを持つ場合があります。したがって、これらすべてのシナリオを手動で処理する必要があります。
新しい要素の配列の最初または最後に追加する関数append(array, toAppend)
を実装しましょう。 toAppend
パラメーターは、プロパティを持つオブジェクトを受け入れます:
-
first
: -
last
:array
の最後に挿入された要素。
この関数は、元の配列を変更せずに、新しい配列インスタンスを返します。
append()
の最初のバージョンは、少しナイーブですが、次のようになります。
toAppend
オブジェクトはfirst
またはlast
プロパティを省略できます。これらのプロパティが。
プロパティが存在しない場合、プロパティアクセサーはundefined
と評価します。 first
またはlast
プロパティが存在するかどうかを確認する最初の誘惑は、undefined
。これは、条件付きif(toAppend.first){}
およびif(toAppend.last){}
…
では実行されません。このアプローチには欠点があります。 undefined
、およびfalse
、null
、0
、NaN
、""
は偽の値です。
append()
の現在の実装では、関数は偽の要素を挿入できません:
以下のヒントでは、プロパティの存在を正しく確認する方法について説明しています。
ヒント3:プロパティの存在を確認する
幸い、JavaScriptはオブジェクトに特定のプロパティがあるかどうかを判断するさまざまな方法:
-
obj.prop !== undefined
:undefined
直接 -
typeof obj.prop !== "undefined"
:プロパティ値のタイプを確認します -
obj.hasOwnProperty("prop")
:オブジェクトに独自のプロパティがある -
"prop" in obj
:オブジェクトに独自のプロパティがあるか継承されているプロパティがあるかを確認する
推奨事項はin
演算子を使用します。短くて甘い構文です。 in
演算子の存在は、実際のプロパティ値にアクセスせずに、オブジェクトに特定のプロパティがあるかどうかを確認するという明確な意図を示唆しています。
obj.hasOwnProperty("prop")
も優れたソリューションです。 in
演算子よりもわずかに長く、オブジェクト自体のプロパティでのみ検証されます。
in
演算子を使用してappend(array, toAppend)
関数を改善しましょう:
"first" in toAppend
(および"last" in toAppend
)は、対応するプロパティが存在するかどうかにかかわらず、true
、false
それ以外の場合。
in
演算子は、偽の要素0
およびfalse
。ここで、これらの要素をの最初と最後に追加すると、期待される結果
が生成されます。
ヒント4:オブジェクトプロパティにアクセスするための破棄
オブジェクトプロパティにアクセスするときに、プロパティが存在しない場合はデフォルト値を設定する必要がある場合があります。
これを実現するには、in
を三項演算子と一緒に使用できます。
チェックするプロパティの数が増えると、三項演算子の構文が難しくなります。プロパティごとに、デフォルトを処理するための新しいコード行を作成する必要があり、似たような三項演算子の醜い壁が増えます。
より洗練されたアプローチを使用するために、オブジェクトの破棄と呼ばれるES2015の優れた機能について理解しましょう。
オブジェクトの非構造化により、オブジェクトのプロパティ値を変数に直接インライン抽出し、プロパティが存在しない場合はデフォルト値を設定できます。 undefined
を直接処理しないようにするための便利な構文。
実際、プロパティの抽出は正確になりました。
動作を確認するために、文字列を引用符で囲む便利な関数を定義しましょう。
quote(subject, config)
は、最初の引数をラップする文字列として受け入れます。 2番目の引数config
は、プロパティを持つオブジェクトです。
オブジェクトの破壊の利点を適用して、quote()
:
const { char = """, skipIfQuoted = true } = config
割り当てを1行で破棄すると、プロパティchar
とskipIfQuoted
from config
オブジェクト。
config
オブジェクトに一部のプロパティがない場合、破棄割り当てによってデフォルト値が設定されます。 :char
の場合は"""
、skipIfQuoted
の場合はfalse
。
幸いなことに、この機能にはまだ改善の余地があります。
破壊する割り当てをパラメータセクションに移動しましょう。また、config
パラメータのデフォルト値(空のオブジェクト{ }
)を設定して、デフォルト設定で十分な場合に2番目の引数をスキップします。
非構造化割り当ては、関数のシグネチャのconfig
パラメータを置き換えます。私はそれが好きです:quote()
は1行短くなります。
= {}
は、非構造化割り当ての右側にあり、2番目の引数がまったく指定されていない場合、空のオブジェクトが使用されるようにしますquote("Sunny day")
。
オブジェクトの破棄は、オブジェクトからのプロパティの抽出を効率的に処理する強力な機能です。アクセスしたプロパティが存在しない場合に返されるデフォルト値を指定できるのが好きです。その結果、undefined
とその周りの煩わしさを回避できます。
ヒント5:オブジェクトにデフォルトのプロパティを入力する
破壊の割り当てのように、すべてのプロパティに変数を作成する必要がない場合は、一部のプロパティが欠落しているオブジェクトを入力できます。デフォルト値で。
ES2015 Object.assign(target, source1, source2, ...)
は、列挙可能なすべての独自プロパティの値を1つ以上のソースオブジェクトからターゲットオブジェクトにコピーします。この関数はターゲットオブジェクトを返します。
たとえば、unsafeOptions
オブジェクトのプロパティにアクセスする必要がありますが、プロパティの完全なセットが常に含まれているとは限りません。
unsafeOptions
から存在しないプロパティにアクセスするときにundefined
を回避するには、いくつかの調整を行います。
- デフォルトのプロパティ値を保持するオブジェクト
defaults
を定義します -
Object.assign({ }, defaults, unsafeOptions)
を呼び出してビルドします新しいオブジェクトoptions
。新しいオブジェクトはunsafeOptions
からすべてのプロパティを受け取りますが、欠落しているプロパティはdefaults
から取得されます。
unsafeOptions
にはfontSize
プロパティのみが含まれます。 defaults
オブジェクトは、プロパティfontSize
およびcolor
のデフォルト値を定義します。
Object.assign()
は、最初の引数をターゲットオブジェクト{}
として受け取ります。ターゲットオブジェクトは、unsafeOptions
ソースオブジェクトからfontSize
プロパティの値を受け取ります。また、unsafeOptions
には含まれていないため、defaults
ソースオブジェクトのcolor
プロパティの値color
。
ソースオブジェクトが列挙される順序は重要です。後のソースオブジェクトのプロパティは、前のプロパティを上書きします。
options.color
を含め、options
オブジェクトのプロパティに安全にアクセスできるようになりました。 div id = “5cc93a0102”> 最初は。
幸い、オブジェクトをデフォルトのプロパティで埋める簡単な方法があります。オブジェクト初期化子でspreadプロパティを使用することをお勧めします。
Object.assign()
の呼び出しの代わりに、オブジェクト拡散構文を使用して、ソースオブジェクトからすべての独自の列挙可能なプロパティをターゲットオブジェクトにコピーします。
オブジェクト初期化子は、defaults
およびunsafeOptions
ソースオブジェクトからプロパティを拡散します。ソースオブジェクトを指定する順序は重要です。後のソースオブジェクトのプロパティが前のプロパティを上書きします。
不完全なオブジェクトをデフォルトのプロパティ値で埋めることは、コードを安全で耐久性のあるものにするための効率的な戦略です。状況に関係なく、オブジェクトには常にプロパティの完全なセットが含まれています。undefined
は生成できません。
ボーナスのヒント:nullish合体
演算子nullish合体は、オペランドがundefined
または:
null合体演算子は、デフォルト値を使用しながらオブジェクトプロパティにアクセスするのに便利です。このプロパティはundefined
またはnull
です:
styles
オブジェクトにはプロパティcolor
がないため、styles.color
プロパティアクセサはundefined
です。styles.color ?? "black"
はデフォルト値"black"
に評価されます。
styles.fontSize
は18
であるため、ヌル合体演算子はプロパティ値。
2.3関数パラメーター
関数パラメーターのデフォルトは暗黙的に
undefined
です。
通常、特定の数のパラメーターで定義された関数は、同じ数の引数で呼び出す必要があります。そのとき、パラメーターは期待する値を取得します。
multiply(5, 3)
の場合、パラメータa
とb
はそれぞれ5
と3
値。乗算は期待どおりに計算されます:5 * 3 = 15
。
呼び出しで引数を省略するとどうなりますか?関数内の対応するパラメーターはundefined
になります。
引数を1つだけ指定して関数を呼び出すことにより、前の例を少し変更してみましょう。
呼び出しmultiply(5)
は単一の引数で実行されます。結果としてa
パラメーターは5
ですが、 b
パラメーターはundefined
です。
ヒント6:デフォルトのパラメーター値を使用する
関数は、呼び出し時に引数の完全なセットを必要としない場合があります。値を持たないパラメータのデフォルトを設定できます。
前の例を思い出して、改善してみましょう。 b
パラメータがundefined
の場合、デフォルトで2
:
この関数は、単一の引数multiply(5)
で呼び出されます。最初は、a
パラメーターは2
であり、b
はundefined
。
条件ステートメントは、b
がundefined
であるかどうかを確認します。発生した場合、b = 2
割り当てによってデフォルト値が設定されます。
デフォルト値を割り当てるために提供されている方法は機能しますが、undefined
と直接比較することはお勧めしません。それは冗長で、ハックのように見えます。
より良いアプローチは、ES2015のデフォルトパラメータ機能を使用することです。短く、表現力があり、undefined
と直接比較することはありません。
パラメータb = 2
にデフォルト値を追加すると見栄えが良くなります:
関数シグネチャの
b = 2
は、b
がundefined
の場合、パラメータのデフォルトは2
です。
ES2015のデフォルトパラメータ機能は直感的で表現力豊かです。オプションのパラメータのデフォルト値を設定するには、常にこれを使用してください。
2.4関数の戻り値
暗黙的に、
return
ステートメントなしでJavaScript関数undefined
を返します。
return
ステートメントは暗黙的にundefined
を返します:
square()
関数は計算結果を返しません。関数の呼び出し結果はundefined
です。
return
ステートメントが存在するが、近くに式がない場合にも同じ状況が発生します:
return;
ステートメントが実行されますが、式は返されません。呼び出し結果もundefined
です。
もちろん、return
の近くを示すと、返される式は期待どおりに機能します:
これで、関数の呼び出しは4
に評価されます。これは、2
の2乗です。
ヒント7:セミコロンの自動挿入を信頼しないでください
JavaScriptの次のステートメントのリストは、セミコロンで終了する必要があります(;
) :
- 空のステートメント
-
let
、const
、var
、import
、export
宣言 - 式ステートメント
-
debugger
ステートメント -
continue
ステートメント、break
ステートメント -
throw
ステートメント -
return
ステートメント
If上記のステートメントのいずれかを使用する場合は、必ず最後にセミコロンを指定してください。
両方の最後にlet
宣言とreturn
ステートメントには必須のセミコロンが記述されています。
これらを示したくない場合はどうなりますかセミコロン?このような状況では、ECMAScriptは自動セミコロン挿入(ASI)メカニズムを提供し、欠落しているセミコロンを挿入します。
ASIの助けを借りて、前の例からセミコロンを削除できます。
上記のテキストは有効なJavaScriptコードです。不足しているセミコロンは自動的に挿入されます。
一見すると、かなり有望に見えます。 ASIメカニズムを使用すると、不要なセミコロンをスキップできます。 JavaScriptコードを小さくして読みやすくすることができます。
ASIによって作成された、小さいながらも厄介なトラップが1つあります。改行がreturn
と返される式return \n expression
の間にある場合、ASIは改行の前にセミコロンを自動的に挿入しますreturn; \n expression
。
return;
ステートメントを持つ関数内とはどういう意味ですか?この関数はundefined
を返します。 ASIのメカニズムの詳細がわからない場合、予期せず返されるundefined
は誤解を招く恐れがあります。
たとえば、getPrimeNumbers()
呼び出しの戻り値を調べてみましょう。
return
ステートメントと配列リテラル式の間に改行があります。 JavaScriptは、return
の後にセミコロンを自動的に挿入し、コードを次のように解釈します。
ステートメントreturn;
は、関数getPrimeNumbers()
が期待される配列の代わりにundefined
を返すようにします。
この問題は、return
と配列リテラルの間の改行を削除することで解決されます:
このような状況を回避するために、自動セミコロン挿入がどのように機能するかを正確に調査することをお勧めします。
もちろん、return
と返された式の間に改行を入れないでください。
2.5void演算子
void <expression>
は式を評価し、結果に関係なくundefined
を返します評価の。
void
演算子の1つの使用例は、式の評価をundefined
、評価の副作用に依存します。
3。配列で未定義
範囲外のインデックスを持つ配列要素にアクセスすると、undefined
が表示されます。
colors
配列には3つの要素があるため、有効なインデックスは0
、1
、および2
。
インデックス5
と-1
には配列要素がないため、アクセサーcolors
とcolors
はundefined
です。
JavaScriptでは、いわゆるスパース配列に遭遇する可能性があります。これらはギャップのある配列です。つまり、一部のインデックスでは、要素が定義されていません。
スパース配列内でギャップ(別名空のスロット)にアクセスすると、undefined
も取得されます。
次の例では、スパース配列を生成し、それらの空のスロットにアクセスしようとします。
sparse1
は、最初の引数が数値のコンストラクター。3つの空のスロットがあります。
sparse2
は、2番目の要素が欠落している配列リテラルを使用して作成されます。
これらのスパース配列のいずれかで、空のスロットにアクセスすると、undefined
と評価されます。
配列を操作するときは、undefined
を回避するために、必ず有効な配列インデックスを使用し、スパース配列が作成されないようにしてください。
4。未定義とnullの違い
undefined
とnull
?両方の特別な値は、空の状態を意味します。
undefined
はまだ初期化されていない変数の値を表し、null
は、オブジェクトが意図的に存在しないことを表します。
いくつかの例で違いを調べてみましょう。
変数number
が定義されていますただし、初期値は割り当てられていません:
number
変数はundefined
で、初期化されていない変数を示します。
存在しないオブジェクトプロパティにアクセスすると、同じ初期化されていない概念が発生します。
div id = “0a5d62ecec”> プロパティがobj
に存在しない場合、JavaScriptはobj.lastName
をundefined
。
反対側では、変数がオブジェクトを期待していることがわかります。ただし、何らかの理由で、オブジェクトをインスタンス化できません。このような場合、null
はオブジェクトが見つからないことを示す意味のある指標です。
たとえば、clone()
は次のような関数です。プレーンなJavaScriptオブジェクトのクローンを作成します。この関数はオブジェクトを返すことが期待されています:
ただし、clone()
は、オブジェクト以外の引数で呼び出される場合があります:15
またはnull
。このような場合、関数はクローンを作成できないため、null
(欠落しているオブジェクトのインジケーター)を返します。
typeof
演算子は、undefined
とnull
を区別します。
また、厳密な品質演算子===
は from null
:
5。結論
undefined
の存在は、次の使用を許可するJavaScriptの寛容な性質の結果です。
- 初期化されていない変数
- 存在しないオブジェクトのプロパティまたはメソッド
- 配列要素にアクセスするための範囲外のインデックス
- 何も返さない関数の呼び出し結果
undefined
と直接比較することは、上記の許可されているが推奨されていない方法に依存しているため、安全ではありません。
効率的な戦略は、次のような適切な習慣を適用することにより、コード内のundefined
キーワードの出現を少なくとも減らすことです。
- 初期化されていない変数の使用を減らす
- 変数のライフサイクルを短くし、使用元に近づけます
- 可能な限り変数に初期値を割り当てます
- favor
const
、それ以外の場合はlet
- 重要でない関数パラメーターにデフォルト値を使用
- プロパティを確認します安全でないオブジェクトが存在するか、デフォルトのプロパティで埋める
- スパース配列の使用を避ける
JavaScriptに両方のundefined
およびnull
?