js プログラミングが初めての方は、イライラするかもしれません。すべての言語にはそれぞれ特有の特徴がありますが、強力な型付けに基づくサーバーサイド言語から移行する開発者は混乱する可能性があります。数年前、フルタイムの JavaScript 開発者になることを強いられたとき、私もそうでした。最初に知っていればよかったと思うことがたくさんありました。この記事では、私の癖のいくつかを紹介し、私に多くの頭痛の種を与えた経験のいくつかを皆さんと共有できればと思っています。これは完全なリストではなく、単なる部分的なリストですが、この言語の力と、これまで障害だと思っていたものに目を開かせていただければ幸いです。
次のテクニックを見ていきます:
if(x == "1"){
console.log( "yay!等しい!");この闇の魔法?整数 1 はどのようにして文字列「1」と等しいのでしょうか?
- JavaScriptには等価(==)と厳密等価(===)があります。等価演算子は、両方のオペランドを同じ型に強制した後、厳密な等価比較を実行します。したがって、上記の例では、文字列「1」は整数 1 に変換され、このプロセスはバックグラウンドで行われ、変数 x と比較されます。
厳密等価では型変換は実行されません。オペランドの型が異なる場合 (整数と文字列など)、それらは一致しません (厳密に等しい)。
- var x = 1;
-
// 厳密に等価、型は同じである必要があります
-
if(x === " 1") {
console.log("残念ながら、これをコンソールには書きません");
}
if(x === 1) {
console.log("YES! 厳密な平等FTW。")
}
あなたキャストによって発生する可能性のあるあらゆる恐ろしいことについて考えているかもしれません。そのような変換が参照内で発生すると仮定すると、問題がどこにあるのかを見つけるのが非常に困難になる可能性があります。これは驚くべきことではなく、経験豊富な JavaScript 開発者が常に厳密な等価性を使用することを推奨する理由です。
2.) ピリオドと括弧 あなたが他の言語から来たかによって、これが行われるのを見たことがあるかもしれませんし、見たことがないかもしれません(それはでたらめです)。
// personオブジェクトのfirstName値を取得します
var name = person.firstName;
-
// 3 番目の要素を取得します配列の Element
var theOneWeWant = myArray[2]; // 0 から始まるインデックスを覚えておいてください。最初の要素のインデックスは 0 であることを忘れないでください
括弧も使用できます。 オブジェクトのメンバーへの参照?例:
var name = person["firstName"];
なぜこれが役立つのでしょうか?ほとんどの場合はドット表記を使用しますが、括弧が使用されている場合には、特定のメソッドがドット表記に失敗する可能性があります。たとえば、私は大きな switch ステートメントをスケジュールにリファクタリングすることが多いので、次のようなものになります:
なぜこれが機能するのですか?以前はドットの使用に慣れていたかもしれませんが、括弧表記のみが使用できる特殊なケースがいくつかあります。たとえば、私は switch ステートメントを (高速に) ルックアップ テーブルにリファクタリングすることがよくあります。実際には次のようになります。 {
case "doThisThing":
// さらにコード...
Break;
ケース「doThatThing」: ing":
Break;
// 追加のケースなど
- デフォルト:
- // デフォルトの動作
Break;
}
}
は以下のように変換できます:
-
var thingsWeCanDo = {
doThisThing : function() { /* 動作 */ },
doThatThing : function() { /* 動作 */ },
-
doThisOtherThing : 関数() { /* 動作 */ },
デフォルト : function() { /* 動作 */ }
};
var do何か= function(doWhat) {
var thingToDo = thingsWeCanDo.hasOwnProperty(doWhat) ? doWhat : "デフォルト"
thingsWeCanDo[thingToDo]();
}
switch を使用することに何も問題はありません (そして、多くの場合、スイッチが何度も反復され、パフォーマンスが大きな懸念事項である場合、switch の方がルックアップ テーブルよりも優れたパフォーマンスを発揮する可能性があります)。ただし、ルックアップ テーブルはコードを整理して拡張するための優れた方法を提供し、括弧を使用するとプロパティを遅延評価できます。
3.) 関数コンテキスト JavaScript でこのコンテキストを適切に理解している優れたブログがすでにいくつかあります (記事の最後に素敵なリンクをいくつか示します) が、実際にはこれを追加する必要があります。 「知っていればよかった」リスト。コードを理解し、どの位置でもその値を自信を持って知ることは非常に困難です。必要なのは一連のルールを学ぶことだけです。残念ながら、私が早い段階で読んだ説明の多くは、私の混乱をさらに悪化させるだけでした。そこで、簡潔に説明してみました。
まず - まずグローバル状況 (Global) を考慮してください デフォルトでは、何らかの理由で実行コンテキストが変更されるまで、this の値はグローバル オブジェクトを指します。ブラウザでは、これはウィンドウ オブジェクト (または、node.js のグローバル) になります。
2 番目 - メソッド内の this 値 関数メンバーを持つオブジェクトがあり、親オブジェクトでこのメソッドを呼び出すと、this の値は親オブジェクトを指します。例:
var marty = {
firstName: "Marty",
lastName: "McFly",
タイムトラベル: function (年) {
console.log(this.firstName + " " + this.lastName + " は " + year にタイムトラベルします);
}
}
-
marty.timeTravel(1955);
-
// マーティ・マクフライが 1955 年にタイムトラベルします
おそらく、Marty オブジェクトの timeTravel メソッドを参照して、別のオブジェクトへの新しい参照を作成できることはすでにご存じでしょう。これは実際には JavaScript の非常に強力な機能で、さまざまなインスタンスでの動作 (関数の呼び出し) を参照できるようになります。
var doc = {
名: "エメット"、
-
姓: "ブラウン"、
}
-
doc.timeTravel = marty.timeTravel;
では、doc.timeTravel(1885) を呼び出すとどうなるでしょうか?
doc.timeTravel(1885);
// エメット・ブラウンは再び 1885 年にタイムトラベルします
- ダークマジック。いや、そうではありません。メソッドを呼び出すとき、このコンテキストは呼び出された関数の親の親オブジェクトであることに注意してください。
marty.TimeTravel メソッドへの参照を保存し、保存した参照を呼び出すとどうなりますか?見てみましょう:
var getBackInTime = marty.timeTravel;
getBackInTime(2014);
// 未定義は時間です2014年への旅 なぜ「未定義未定義」なのでしょうか? ! 「マトリー・マクフライ」の代わりに?
重要な質問をしてみましょう: getBackInTime 関数を呼び出すときの親/コンテナ オブジェクトは何ですか? getBackIntTime 関数がウィンドウ内に存在する場合、オブジェクト メソッドではなく関数として呼び出します。コンテナ オブジェクトを使用せずにこのような関数を呼び出すと、このコンテキストはグローバル オブジェクトになります。 David Shariff がこれについて素晴らしい説明をしています:
関数を呼び出すときはいつでも、すぐに括弧の左側に注目する必要があります。括弧の左側に参照がある場合、呼び出し元の関数に渡される this 値は、その参照が属するオブジェクトであると判断されます。そうでない場合は、完全なオブジェクトになります。
getBackInTime のこのコンテキストはウィンドウであるため、firstName プロパティと lastName プロパティはありません。これが、「未定義 未定義」が表示される理由を説明しています。
つまり、コンテナ オブジェクトを使用せずに関数を直接呼び出すと、このコンテキストの結果がグローバル オブジェクトになることがわかります。ただし、getBackInTime 関数がウィンドウ上に存在することはすでに知っていたとも言いました。どうすればわかりますか?さて、別のコンテキストで getBackInTime をラップした上記とは異なり (関数式をすぐに実行することについて話していました)、宣言した変数はすべてウィンドウに追加されました。 Chrome コンソールからの検証:
ここで、これが関係する主な領域の 1 つであるサブスクリプション イベント処理について説明します。
3 番目 (#2 の単なる拡張) - メソッド内でこの値を非同期的に呼び出す それでは、誰かがボタンをクリックしたときに marty.timeTravel メソッドを呼び出すとしましょう:
var flux = document.getElementById ("flux-capacitor"); コードでは、ユーザーがボタンをクリックすると、「未定義 未定義は [オブジェクト MouseEvent] にタイムトラベルします」と表示されます。何? OK - まず第一に、非常に明白な問題は、timeTravel メソッドに year パラメーターを提供しなかったことです。代わりに、このメソッドをイベント ハンドラーとして直接サブスクライブし、MouseEvent パラメーターが最初の引数としてイベント ハンドラーに渡されます。これを修正するのは簡単ですが、本当の問題は、「未定義、未定義」が再び表示されることです。絶望的にならないでください。なぜこれが起こるのか、あなたはすでに知っています(たとえまだ気づいていなくても)。事実を理解するために、timeTravel 関数を変更してこれを出力してみましょう:
marty.timeTravel = function(year) {
console.log(this.firstName + " " + this.lastName + " は " + year) へのタイムトラベルです);
console.log(this);
};
さて、このボタンをクリックすると、ブラウザのコンソールに次のような出力が表示されます:
メソッドが呼び出されたときの 2 番目のコンソール ログ出力は次のとおりです。このコンテキスト - 実際には、イベントをサブスクライブしたボタン要素です。びっくりした?以前と同様に、marty.timeTravel を getBackInTime 変数に割り当てたとき、marty.timeTravel への参照はイベント ハンドラーに保存されて呼び出されますが、コンテナ オブジェクトは marty オブジェクトではなくなります。この場合、ボタン インスタンスのクリック イベントで非同期的に呼び出されます。
それで、これを希望の結果に設定することは可能でしょうか?絶対に!この場合、解決策は非常に簡単です。イベント ハンドラーで marty.timeTravel を直接サブスクライブする代わりに、匿名関数をイベント ハンドラーとして使用し、その匿名関数で marty.timeTravel を呼び出します。これにより、年パラメータが欠落している問題も解決できます。
flux.addEventListener("click", function(e) {
marty.timeTravel(some YearValue);
});
ボタンをクリックすると、コンソールに次のような情報が出力されます:
成功!しかし、なぜこれで大丈夫なのでしょうか? timeTravel メソッドをどのように呼び出すかを考えてみましょう。ボタン クリックの最初の例では、イベント ハンドラーでメソッド自体への参照をサブスクライブしたため、親 marty から呼び出されませんでした。 2 番目の例では、これは button 要素の匿名関数であり、marty.timeTravel を呼び出すときに、その親オブジェクト marty から呼び出すため、これは marty です。
4 番目 - コンストラクター内の this 値 コンストラクターを使用してオブジェクト インスタンスを作成する場合、関数内の this 値は新しく作成されたオブジェクトになります。例:
var TimeTraveler = function(fName, lName) {
this.firstName = fName;
-
this.lastName = lName;
// コンストラクター関数は
を返します //
でない限り、新しく作成されたオブジェクト // 特に他のものを返します
-
};
var marty = new TimeTraveler("Marty", "McFly");
-
console.log(marty.firstName + " " + marty.lastName);
// マーティ・マクフライ
Call、Apply、BindCall 上記の例では、実行時に呼び出し関数の this 値を指定できる言語レベルの機能はないのではないかと疑問に思うかもしれません。あなたが正しいです。関数プロトタイプに存在する call メソッドと apply メソッドを使用すると、関数を呼び出して this 値を渡すことができます。
callメソッドの最初のパラメータはこれで、その後に呼び出される関数のパラメータシーケンスが続きます:
someFn.call(this, arg1, arg2, arg3);
apply の最初のパラメータもこれで、その後に残りのパラメータの配列が続きます:
someFn.apply(this, [arg1, arg2, arg3]);
Oursドクとマーティは自分でタイムトラベルできますが、アインシュタイン (アインシュタイン) がタイムトラベルを完了するには彼らの助けが必要です。それでは、ドキュメントインスタンスにメソッドを追加して、ドキュメントが Einstein のタイムトラベルを支援できるようにしましょう。
doc.timeTravelFor = function(instance, year) {
this.timeTravel.call(instance, year);
//適用する次の構文を使用します
// this.timeTravel.apply(instance, [year]);
};
これで、Einstein を転送できるようになります。
var einstein = {
名: "アインシュタイン"、
-
姓: "(犬)" };
-
doc.timeTravelFor(einstein, 1985);
-
// アインシュタイン (犬) は 1985 年へタイムトラベルします
この例は少し突飛であることはわかっていますが、しかし、他のオブジェクトに関数を適用する能力を理解するには十分です。 この方法には、私たちがまだ発見していない別の用途があります。 goHome メソッドを this.timeTravel(1985) へのショートカットとして marty インスタンスに追加しましょう。 Omarty.gohome = Function () {
This.timetravel (1985)
}
ただし、ボタンのクリック イベント ハンドラーとして marty.goHome をサブスクライブすると、この値がボタンになることがわかっています。そして、残念ながらボタンには timeTravel メソッドがありません。上記の方法で解決できます - イベント ハンドラーとして匿名関数を使用し、その中で上記のメソッドを呼び出します - しかし、別のオプション - バインド関数があります:
flux.addEventListener( "click", marty.goHome.bind(marty));
bind 関数は実際には新しい関数を返し、新しい関数の this 値は指定したパラメーターに従って設定されます。以前のバージョンのブラウザ (例: ie9 より前のバージョン) をサポートする必要がある場合は、bind 関数の shim が必要になる場合があります (または、jQuery を使用している場合は、代わりに $.proxy を使用できます。アンダースコアと lodash は両方とも_.bind メソッド) 。
プロトタイプで直接バインド メソッドを使用すると、インスタンス メソッドが作成され、プロトタイプ メソッドの利点がバイパスされることを覚えておくことが重要です。これは間違いではありません。頭の中ではっきりと理解してください。この問題についてはこちらに詳しく書きました。
4.) 関数式と関数宣言 関数宣言には var キーワードは必要ありません。実際、アンガス・クロール氏は次のように述べています。「これらを変数宣言の兄弟と考えると役立ちます」。例:
function timeTravel(year) {
console.log(this.firstName + " " + this.lastName + " is time travel to " + year);
} 上記の例の関数名 timeTravel は、それが宣言されているスコープ内で可視であるだけでなく、関数自体内でも可視です (これは再帰的な関数呼び出しに非常に便利です)。関数宣言は基本的に名前付き関数です。つまり、上記の関数の name 属性は timeTravel です。
関数式は関数を定義し、それを変数に代入します。典型的なアプリケーションは次のとおりです。
var someFn = function() {
console.log("I like to express myself...");
}; 也可以对函数表达式命名——然而,不像函数声明,命名函数表达式的名字仅在它自身函数体内可访问:
var someFn = function iHazName() {
console.log("I like to express myself...");
if(needsMoreExpressing) {
iHazName(); // 函数的名字在这里可以访问
}
};
// 你可以在这里调用someFn(),但不能调用iHazName()
someFn();
「ホイスティング」について触れずに関数式と関数宣言を議論することはできません。関数と変数の宣言はコンパイラによってスコープの先頭に移動されます。ここではホイスティングについて詳しく説明できませんが、Ben Cherry と Angus Croll による 2 つの素晴らしい説明を読むことができます。
5.) 名前付き関数と匿名関数 今までの議論に基づいて、「匿名」関数は実際には名前のない関数であると推測したかもしれません。ほとんどの JavaScript 開発者は、最初のパラメーターが匿名関数であることをすぐに識別します:
someElement.addEventListener("click", function(e) {
// 私は匿名です!
});
しかし、同様に、marty.timeTravvel メソッドも匿名関数です:
var marty = {
firstName: "Marty",
lastName: " "、
timeTravel: function(year) {
Console.log(this.firstName + " " + this.lastName + " は " + year) へのタイムトラベルです);
}
}
関数宣言には一意の名前が必要であるため、関数式のみが名前を持たないことができます。
6.) 即時実行される関数式 関数式について話しているので、知っておきたかったことが 1 つあります。それは、即時実行される関数式 (IIFE) です。 IIFE については良い記事がたくさんあります (記事の最後にまとめます) が、一言で説明すると、関数式をスカラーに代入して後で実行するのではなく、関数式を理解することで実行されます。実行。このプロセスはブラウザのコンソールで見ることができます。
まず - 関数式を入力しましょう - ただし変数を代入しないでください - そして何が表示されるか見てみましょう:
構文エラー - これは関数宣言とみなされ、関数名が欠落しています。ただし、式にするには、単に括弧で囲むだけです:
これを式にすると、コンソールは匿名関数を返します (値は割り当てられていませんが、式は値を返します)。したがって、「関数式」は「すぐに呼び出す関数式」の一部であることがわかります。実行待機機能を取得するには、式の後にかっこを追加して (他の関数を呼び出すのと同じように) 返された式を呼び出します:
" でも待ってください、ジム!筆者)この通話は前にも見たことがある気がします。 実際、見たことがあるかもしれません - これは正当な構文です (ダグラス・クロックフォードの推奨構文として知られています)
どちらの方法でも機能しますが、こちらを読むことを強くお勧めします。
OK、わかりました – IIFE が何であるかが分かりました – そしてなぜそれを使用する必要があるのでしょうか?
これは、JavaScript チュートリアルの非常に重要な部分であるスコープの制御に役立ちます。前に見た例の多くはグローバル スコープで作成されました。これは、ウィンドウ (環境がブラウザであると仮定した場合) オブジェクトが多くのプロパティを持つことを意味します。もし私たちがこの方法で JavaScript コードを書いたとしたら、すぐに大量の (大げさな) 変数宣言がグローバル スコープに蓄積され、ウィンドウ コードが汚染されてしまうでしょう。たとえ最良の状況であっても、グローバル変数で多くの詳細を公開するのは悪いアドバイスですが、変数の名前が既存のウィンドウ プロパティの名前と同じ場合はどうなるのでしょうか? window プロパティはオーバーライドされます。
たとえば、お気に入りの「Amelia Earhart」Web サイトがグローバル スコープでナビゲーター変数を宣言している場合、設定前後の結果は次のとおりです。
おっと!
明らかに - 汚染されたグローバル変数は悪いものです。 JavaScript は関数スコープを使用します (ブロック スコープではありません。これは、C# または Java から来た場合には非常に重要です!)。そのため、コードをグローバル スコープから分離しておく方法は、IIFE を使用して新しいスコープを作成することです。その内容は独自の関数スコープ内にあるため、実装されます。以下の例では、コンソールに window.navigator の値を表示してから、Amelia Earhart の動作とデータをラップする IIFE (即時実行関数式) を作成します。 IIFE が終了すると、オブジェクトが「プログラム名前空間」として返されます。 IIFE 内で宣言したナビゲーター変数は、window.navigator の値をオーバーライドしません。
追加のボーナスとして、上で作成した IIFE は JavaScript のモジュール パターンを啓発したものです。最後に、私が調査したいくつかのモジュール パターンへのリンクを記載します。
7.) 「typeof」演算子と「Object.prototype.toString」 最終的には、場合によっては、関数に渡されるパラメーターの型などを確認する必要があることがわかるかもしれません。 typeof 演算子は当然の選択ですが、万能薬ではありません。たとえば、オブジェクト、配列、文字列、または正規表現に対して typeof 演算子を呼び出すとどうなりますか?
悪くない - 少なくとも文字列をオブジェクト、配列、正規表現から区別できるでしょう?幸いなことに、さまざまな方法でより正確な型情報を取得できます。 Object.prototype.toString メソッドを使用し、前に説明した call メソッドを適用します。
Object.prototype で toString メソッドを使用するのはなぜですか?サードパーティのライブラリまたは独自のコードがインスタンスの toString メソッドをオーバーライドする可能性があるためです。 Object.prototype を通じて、インスタンスの元の toString 動作を強制できます。
どの typeof が返されるかがわかっている場合は、追加のチェックを行う必要はありません (たとえば、文字列かどうかを知る必要があるだけです)。この場合、typeof を使用するのは非常に良いです。ただし、配列とオブジェクト、正規表現とオブジェクトなどを区別する必要がある場合は、Object.prototype.toString を使用します。
|