apeescape2.com
  • メイン
  • アジャイルタレント
  • その他
  • アジャイル
  • ライフスタイル
Webフロントエンド

バギーJavaScriptコード:JavaScript開発者が犯す最も一般的な10の間違い

今日、 JavaScript 事実上すべての最新のWebアプリケーションの中核です。特に過去数年間は、強力なJavaScriptベースのライブラリとフレームワークが幅広く普及しているのを目の当たりにしてきました。 シングルページアプリケーション(SPA) 開発、グラフィックス、アニメーション、さらにはサーバーサイドのJavaScriptプラットフォーム。 JavaScriptは、Webアプリ開発の世界で本当にユビキタスになっているため、習得することがますます重要なスキルになっています。

一見すると、JavaScriptは非常に単純に見えるかもしれません。実際、基本的なJavaScript機能をWebページに組み込むことは、JavaScriptを初めて使用する場合でも、経験豊富なソフトウェア開発者にとってはかなり簡単な作業です。それでも、この言語は、最初に信じられていたよりもはるかに微妙で、強力で、複雑です。 実際、JavaScriptの微妙な点の多くは、JavaScriptが機能しないようにする多くの一般的な問題につながります。そのうちの10個については、ここで説明します。これらは、JavaScriptになるための探求において認識し、回避することが重要です。 マスターJavaScript開発者 。

よくある間違い#1:thisへの誤った参照

私はかつてコメディアンが言うのを聞いた:



私は実際にはここにいません。なぜなら、「t」なしで、そこ以外に何があるのでしょうか。

そのジョークは多くの点で、開発者が以下に関してしばしば存在する混乱のタイプを特徴づけます。 JavaScriptのthisキーワード 。つまり、thisです本当にこれですか、それともまったく別のものですか?それとも未定義ですか?

JavaScriptのコーディング手法とデザインパターンが年々高度化するにつれて、「これ/その混乱」のかなり一般的な原因であるコールバックとクロージャ内の自己参照スコープの急増に対応して増加しています。

このサンプルコードスニペットについて考えてみます。

Game.prototype.restart = function () { this.clearLocalStorage(); this.timer = setTimeout(function() { this.clearBoard(); // what is 'this'? }, 0); };

上記のコードを実行すると、次のエラーが発生します。

Uncaught TypeError: undefined is not a function

どうして?

コンテキストがすべてです。上記のエラーが発生する理由は、setTimeout()を呼び出すと、実際にはwindow.setTimeout()を呼び出すためです。その結果、匿名関数がsetTimeout()に渡されますwindowのコンテキストで定義されていますclearBoard()を持たないオブジェクト方法。

従来の古いブラウザ準拠のソリューションは、thisへの参照を保存するだけです。クロージャーによって継承できる変数内。例えば。:

Game.prototype.restart = function () { this.clearLocalStorage(); var self = this; // save reference to 'this', while it's still this! this.timer = setTimeout(function(){ self.clearBoard(); // oh OK, I do know who 'self' is! }, 0); };

または、新しいブラウザでは、bind()を使用できます。適切な参照を渡すメソッド:

Game.prototype.restart = function () { this.clearLocalStorage(); this.timer = setTimeout(this.reset.bind(this), 0); // bind to 'this' }; Game.prototype.reset = function(){ this.clearBoard(); // ahhh, back in the context of the right 'this'! };

よくある間違い#2:ブロックレベルのスコープがあると考える

私たちで議論されているように JavaScript採用ガイド 、JavaScript開発者の間でよくある混乱の原因(したがって、よくあるバグの原因)は、JavaScriptが各コードブロックに新しいスコープを作成すると想定しています。これは他の多くの言語にも当てはまりますが、 ない JavaScriptではtrue。たとえば、次のコードについて考えてみます。

for (var i = 0; i <10; i++) { /* ... */ } console.log(i); // what will this output?

console.log()だと思うなら呼び出しはundefinedを出力しますまたはエラーをスローします、あなたは間違って推測しました。信じられないかもしれませんが、10を出力します。どうして?

他のほとんどの言語では、変数iの「寿命」(つまりスコープ)が原因で、上記のコードはエラーにつながります。 forに制限されますブロック。ただし、JavaScriptでは、これは当てはまらず、変数i forの後でもスコープ内に留まりますループが完了し、ループを終了した後も最後の値を保持します。 (ちなみに、この動作は次のように知られています。 可変巻き上げ )。

ただし、ブロックレベルのスコープのサポートは注目に値します。 です を介してJavaScriptに移行します 新規letキーワード 。 letキーワードはJavaScript1.7ですでに利用可能であり、正式にサポートされるJavaScriptキーワードになる予定です。 ECMAScript 6 。

JavaScriptは初めてですか?よく読んで スコープ、プロトタイプなど。

よくある間違い#3:メモリリークの作成

メモリリークを回避するために意識的にコーディングしていない場合、メモリリークはほぼ避けられないJavaScriptの問題です。それらが発生する方法は多数あるため、より一般的な発生のいくつかを強調します。

メモリリークの例1:無効なオブジェクトへのダングリング参照

次のコードについて考えてみます。

var theThing = null; var replaceThing = function () { var priorThing = theThing; // hold on to the prior thing var unused = function () { // 'unused' is the only place where 'priorThing' is referenced, // but 'unused' never gets invoked if (priorThing) { console.log('hi'); } }; theThing = { longStr: new Array(1000000).join('*'), // create a 1MB object someMethod: function () { console.log(someMessage); } }; }; setInterval(replaceThing, 1000); // invoke `replaceThing' once every second

上記のコードを実行してメモリ使用量を監視すると、1秒あたり1メガバイトの大規模なメモリリークが発生していることがわかります。 また、手動のGCでも役に立ちません。リークしているようですlongStr毎回replaceThingと呼ばれます。しかし、なぜ?

もっと詳しく調べてみましょう。

各theThingオブジェクトには独自の1MBが含まれていますlongStrオブジェクト。毎秒、replaceThingを呼び出すと、前のtheThingへの参照が保持されます。 priorThing内のオブジェクト。しかし、それでもこれが問題になるとは思わないでしょう。なぜなら、毎回、以前に参照されたpriorThing逆参照されます(priorThingがpriorThing = theThing;を介してリセットされた場合)。さらに、replaceThingの本体でのみ参照されます。および関数内unused実際、これは使用されません。

ですから、なぜここにメモリリークがあるのか​​疑問に思っています!?

何が起こっているのかを理解するには、JavaScriptの内部で物事がどのように機能しているかをよりよく理解する必要があります。クロージャを実装する一般的な方法は、すべての関数オブジェクトに、その字句スコープを表す辞書スタイルのオブジェクトへのリンクがあることです。両方の関数がreplaceThing内で定義されている場合実際に使用されるpriorThing、たとえpriorThingであっても、両方が同じオブジェクトを取得することが重要です。は何度も割り当てられるため、両方の関数が同じ字句環境を共有します。ただし、変数がクロージャによって使用されるとすぐに、そのスコープ内のすべてのクロージャによって共有される字句環境になります。そして、その小さなニュアンスが、この厄介なメモリリークにつながるのです。 (これに関する詳細は入手可能です ここに 。)

メモリリークの例2:循環参照

このコードフラグメントを検討してください。

function addClickHandler(element) { element.click = function onClick(e) { alert('Clicked the ' + element.nodeName) } }

ここで、onClick elementへの参照を保持するクロージャがあります(element.nodeName経由)。 onClickも割り当てるelement.clickに、循環参照が作成されます。つまり:element -> onClick -> element -> onClick -> element…

興味深いことに、たとえelementがDOMから削除されると、上記の循環自己参照によりelementが防止されます。およびonClick収集されないため、メモリリークが発生します。

メモリリークの回避:知っておくべきこと

JavaScriptのメモリ管理(特に、 ガベージコレクション )は、主にオブジェクトの到達可能性の概念に基づいています。

以下のオブジェクトは、 到達可能 そして「ルーツ」として知られています:

  • 現在のどこからでも参照されているオブジェクト コールスタック (つまり、現在呼び出されている関数内のすべてのローカル変数とパラメーター、およびクロージャースコープ内のすべての変数)
  • すべて グローバル 変数

オブジェクトは、少なくとも参照または参照のチェーンを介してルートからアクセスできる限り、メモリに保持されます。

ブラウザには、到達不能なオブジェクトが占有しているメモリをクリーンアップするガベージコレクタ(GC)があります。つまり、オブジェクトはメモリから削除されます 場合に限り GCは、それらが到達不能であると考えています。残念ながら、実際には使用されなくなったが、GCはまだ「到達可能」であると考えている、機能しなくなった「ゾンビ」オブジェクトになってしまうのはかなり簡単です。

関連: ApeeScape開発者によるJavaScriptのベストプラクティスとヒント

よくある間違い#4:平等についての混乱

JavaScriptの便利な点の1つは、ブールコンテキストで参照されている値をブール値に自動的に強制変換することです。しかし、これは便利であると同時に混乱を招く場合があります。たとえば、次のいくつかは、多くのJavaScript開発者を噛むことが知られています。

// All of these evaluate to 'true'! console.log(false == '0'); console.log(null == undefined); console.log(' ' == 0); console.log('' == 0); // And these do too! if ({}) // ... if ([]) // ...

最後の2つに関しては、空であるにもかかわらず(falseと評価されると思われる可能性があります)、両方とも{}および[]実際にはオブジェクトであり、 どれか オブジェクトはブール値trueに強制変換されますJavaScriptで、 ECMA-262仕様 。

これらの例が示すように、型強制のルールは泥のように明確な場合があります。したがって、型強制が明示的に望まれない限り、通常は===を使用するのが最善です。および!== (==および!=ではなく)型強制の意図しない副作用を回避するため。 (==と!=は、2つのものを比較するときに自動的に型変換を実行しますが、===と!==は、型変換なしで同じ比較を実行します。)

そして完全に副次的なものとして-しかし、型強制と比較について話しているので-比較することは言及する価値がありますNaNと 何でも (NaN!でも) 常に falseを返します。 したがって、等価演算子(==、===、!=、!==)を使用して、値がNaNであるかどうかを判別することはできません。か否か。 代わりに、組み込みのグローバルisNaN()を使用してください関数:

console.log(NaN == NaN); // false console.log(NaN === NaN); // false console.log(isNaN(NaN)); // true

よくある間違い#5:非効率的なDOM操作

JavaScriptを使用すると、DOMの操作(要素の追加、変更、削除など)が比較的簡単になりますが、効率的な操作を促進することはできません。

一般的な例は、一連のDOM要素を一度に1つずつ追加するコードです。 DOM要素の追加はコストのかかる操作です。複数のDOM要素を連続して追加するコードは非効率的であり、うまく機能しない可能性があります。

複数のDOM要素を追加する必要がある場合の1つの効果的な代替手段は、 ドキュメントの断片 代わりに、それによって効率とパフォーマンスの両方が向上します。

例えば:

var div = document.getElementsByTagName('my_div'); var fragment = document.createDocumentFragment(); for (var e = 0; e

このアプローチの本質的に改善された効率に加えて、アタッチされたDOM要素の作成にはコストがかかりますが、デタッチ中にそれらを作成および変更してからアタッチすると、パフォーマンスが大幅に向上します。

よくある間違い#6:for内の関数定義の誤った使用ループ

このコードを検討してください:

var elements = document.getElementsByTagName('input'); var n = elements.length; // assume we have 10 elements for this example for (var i = 0; i

上記のコードに基づいて、入力要素が10個ある場合は、 どれか そのうちの「これは要素#10です」と表示されます。これは、onclickまでにのために呼び出されます どれか 要素のうち、上記のforループが完了し、iの値がすでに10になります( すべて そのうちの)。

ただし、上記のコードの問題を修正して、目的の動作を実現する方法は次のとおりです。

var elements = document.getElementsByTagName('input'); var n = elements.length; // assume we have 10 elements for this example var makeHandler = function(num) { // outer function return function() { // inner function console.log('This is element #' + num); }; }; for (var i = 0; i

この改訂版のコードでは、makeHandlerループを通過するたびに、その時点での値i+1を受け取るたびに、すぐに実行されます。スコープ付きnumにバインドします変数。外側の関数は、内側の関数(このスコープのnum変数も使用します)と要素のonclickを返します。その内部関数に設定されます。これにより、各onclickが確実になります適切なiを受信して​​使用します値(スコープ付きnum変数を介して)。

よくある間違い#7:プロトタイプの継承を適切に活用できない

驚くほど高い割合のJavaScript開発者は、プロトタイプの継承の機能を完全に理解しておらず、したがって完全に活用できていません。

これが簡単な例です。このコードを検討してください:

BaseObject = function(name) { if(typeof name !== 'undefined') { this.name = name; } else { this.name = 'default' } };

かなり簡単なようです。名前を指定する場合はそれを使用し、それ以外の場合は名前を「デフォルト」に設定します。例えば。:

var firstObj = new BaseObject(); var secondObj = new BaseObject('unique'); console.log(firstObj.name); // -> Results in 'default' console.log(secondObj.name); // -> Results in 'unique'

しかし、これを行うとしたらどうなるでしょうか。

delete secondObj.name;

次に、次のようになります。

console.log(secondObj.name); // -> Results in 'undefined'

しかし、これを「デフォルト」に戻す方が良いのではないでしょうか。次のように、プロトタイプの継承を活用するように元のコードを変更すると、これを簡単に行うことができます。

BaseObject = function (name) { if(typeof name !== 'undefined') { this.name = name; } }; BaseObject.prototype.name = 'default';

このバージョンでは、BaseObject nameを継承しますそのprototypeからのプロパティオブジェクト。(デフォルトで)'default'に設定されています。したがって、コンストラクターが名前なしで呼び出された場合、名前はデフォルトでdefaultになります。同様に、nameの場合プロパティがBaseObjectのインスタンスから削除され、プロトタイプチェーンが検索され、nameが検索されます。プロパティはprototypeから取得されますその値がまだ'default'であるオブジェクト。だから今私たちは得る:

var thirdObj = new BaseObject('unique'); console.log(thirdObj.name); // -> Results in 'unique' delete thirdObj.name; console.log(thirdObj.name); // -> Results in 'default'

よくある間違い#8:インスタンスメソッドへの誤った参照の作成

次のように、単純なオブジェクトを定義し、そのオブジェクトを作成してインスタンス化します。

Uber vs lyft2017の運転
var MyObject = function() {} MyObject.prototype.whoAmI = function() { console.log(this === window ? 'window' : 'MyObj'); }; var obj = new MyObject();

ここで、便宜上、whoAmIへの参照を作成しましょう。メソッド、おそらくwhoAmI()だけでアクセスできるようにするため長いobj.whoAmI()ではなく:

var whoAmI = obj.whoAmI;

そして、すべてが共食いに見えることを確認するために、新しいwhoAmIの値を印刷してみましょう。変数:

console.log(whoAmI);

出力:

function () { console.log(this === window ? 'window' : 'MyObj'); }

うんいいね。うまく見えます。

しかし、ここで、obj.whoAmI()を呼び出すときの違いを見てください。対私たちの便利なリファレンスwhoAmI():

obj.whoAmI(); // outputs 'MyObj' (as expected) whoAmI(); // outputs 'window' (uh-oh!)

何が悪かったのか?

ここでの偽物は、割り当てを行ったときにvar whoAmI = obj.whoAmI;、新しい変数whoAmIであるということです。で定義されていた グローバル 名前空間。その結果、thisの値はwindow、 ない obj MyObjectのインスタンス!

したがって、オブジェクトの既存のメソッドへの参照を本当に作成する必要がある場合は、thisの値を保持するために、そのオブジェクトの名前空間内で必ず作成する必要があります。これを行う1つの方法は、たとえば、次のようになります。

var MyObject = function() {} MyObject.prototype.whoAmI = function() { console.log(this === window ? 'window' : 'MyObj'); }; var obj = new MyObject(); obj.w = obj.whoAmI; // still in the obj namespace obj.whoAmI(); // outputs 'MyObj' (as expected) obj.w(); // outputs 'MyObj' (as expected)

よくある間違い#9:setTimeoutの最初の引数として文字列を指定するまたはsetInterval

手始めに、ここで何かを明確にしましょう:setTimeoutへの最初の引数として文字列を提供するまたはsetIntervalです ない それ自体が間違いです。これは完全に正当なJavaScriptコードです。ここでの問題は、パフォーマンスと効率の問題です。めったに説明されないのは、内部では、setTimeoutへの最初の引数として文字列を渡した場合です。またはsetInterval、それはに渡されます 関数コンストラクター 新しい関数に変換されます。このプロセスは遅くて非効率的である可能性があり、必要になることはめったにありません。

これらのメソッドの最初の引数として文字列を渡す代わりに、代わりに 関数 。例を見てみましょう。

ここでは、setIntervalのかなり典型的な使用法になりますおよびsetTimeout、 ストリング 最初のパラメータとして:

setInterval('logTime()', 1000); setTimeout('logMessage('' + msgValue + '')', 1000);

より良い選択は、渡すことです 関数 最初の引数として;例えば。:

setInterval(logTime, 1000); // passing the logTime function to setInterval setTimeout(function() { // passing an anonymous function to setTimeout logMessage(msgValue); // (msgValue is still accessible in this scope) }, 1000);

よくある間違い#10:「厳密モード」の使用の失敗

私たちので説明されているように JavaScript採用ガイド 、「厳密モード」(つまり、JavaScriptソースファイルの先頭に'use strict';を含める)は、実行時にJavaScriptコードに対してより厳密な解析とエラー処理を自発的に適用し、より安全にする方法です。

確かに、厳密モードを使用しないこと自体は「間違い」ではありませんが、その使用はますます奨励されており、その省略はますます悪い形と見なされるようになっています。

厳密モードの主な利点は次のとおりです。

  • デバッグが容易になります。 無視されたり、サイレントに失敗したりするコードエラーは、エラーを生成したり、例外をスローしたりして、コードの問題をより早く警告し、より迅速にソースに誘導します。
  • 偶発的なグローバルを防ぎます。 strictモードがない場合、宣言されていない変数に値を割り当てると、その名前のグローバル変数が自動的に作成されます。これは、JavaScriptで最も一般的なエラーの1つです。 strictモードでは、そうしようとするとエラーがスローされます。
  • thisを排除します強制 。厳密モードがない場合、thisへの参照nullまたはundefinedの値は、自動的にグローバルに強制変換されます。これは、多くのヘッドフェイクや髪の毛を抜くようなバグを引き起こす可能性があります。厳密モードでは、thisを参照しますnullまたはundefinedの値は、エラーをスローします。
  • プロパティ名またはパラメータ値の重複を禁止します。 厳密モードでは、オブジェクト内の重複した名前付きプロパティ(var object = {foo: 'bar', foo: 'baz'};など)または関数の重複した名前付き引数(function foo(val1, val2, val1){}など)を検出するとエラーがスローされ、ほぼ確実にバグが検出されます。コード内では、追跡するのに多くの時間を浪費していた可能性があります。
  • eval()をより安全にします。 方法にはいくつかの違いがありますeval()厳密モードと非厳密モードで動作します。最も重要なのは、厳密モードでは、eval()内で宣言された変数と関数です。ステートメントは ない 包含スコープで作成されます( です 非厳密モードの包含スコープで作成されます。これも問題の一般的な原因となる可能性があります)。
  • deleteの無効な使用でエラーをスローします。 delete演算子(オブジェクトからプロパティを削除するために使用)は、オブジェクトの構成不可能なプロパティには使用できません。非strictコードは、構成不可能なプロパティを削除しようとするとサイレントに失敗しますが、strictモードはそのような場合にエラーをスローします。

要約

他のテクノロジーにも当てはまりますが、JavaScriptが機能する理由と方法をよく理解すればするほど、コードはより堅固になり、言語の真の力を効果的に活用できるようになります。逆に、JavaScriptのパラダイムと概念を正しく理解していないことは、実際に多くのJavaScriptの問題が存在する場所です。

言語のニュアンスと微妙さを完全に理解することは、あなたの習熟度を向上させ、あなたの能力を高めるための最も効果的な戦略です。 生産性 。 JavaScriptが機能していない場合は、JavaScriptでよくある多くの間違いを回避することが役立ちます。

関連: JavaScriptの約束:例を含むチュートリアル

ApeeScapeグローバルメンター:どこでも教育

ライフスタイル

ApeeScapeグローバルメンター:どこでも教育
STEM教育による技術供給のサポート

STEM教育による技術供給のサポート

仕事の未来

人気の投稿
ポストフラッシュ時代のWebアニメーション
ポストフラッシュ時代のWebアニメーション
Swiftプロパティのラッパーにアプローチする方法
Swiftプロパティのラッパーにアプローチする方法
42億ドルは合理的ですか?インスタカートの評価を評価する方法
42億ドルは合理的ですか?インスタカートの評価を評価する方法
スマートソフトウェアの価格戦略の究極のガイド
スマートソフトウェアの価格戦略の究極のガイド
ビジネスインテリジェンスの歴史を探る
ビジネスインテリジェンスの歴史を探る
 
ApeeScapeがフリーランサー向けの無料タイムトラッキングアプリTopTrackerをリリース
ApeeScapeがフリーランサー向けの無料タイムトラッキングアプリTopTrackerをリリース
シニアフルスタックエンジニア、タレントポストハイヤーチーム
シニアフルスタックエンジニア、タレントポストハイヤーチーム
いいねの予測:単純なレコメンデーションエンジンのアルゴリズムの内部
いいねの予測:単純なレコメンデーションエンジンのアルゴリズムの内部
ツリーカーネル:ツリー構造化データ間の類似性の定量化
ツリーカーネル:ツリー構造化データ間の類似性の定量化
Angular 4フォームの操作:ネストと入力の検証
Angular 4フォームの操作:ネストと入力の検証
人気の投稿
  • システムをカスタマイズする際に考慮すべき重要な原則ではないものは次のうちどれですか
  • 新しい出会い系サイト2015無料
  • ゲームのUIとは
  • c ++学習ガイド
  • 子供向けのアプリを作成する方法
カテゴリー
製品の担当者とチーム ライフスタイル 製品ライフサイクル プロセスとツール アジャイルタレント ヒントとツール リモートの台頭 プロジェクト管理 技術 アジャイル

© 2021 | 全著作権所有

apeescape2.com