6)プロトタイプの継承を深く理解する
従来のJavaScriptでは、カモフラージュ的継承という概念があります。これはプロトタイプの手法を使用しています。ES5、ES6で見られる新しいクラス構文はすべて、基本的なプロトタイプOOPの構文の見た目を良くしただけのものです。クラスの作成は、JavaScriptの関数を使用して行います。
var animalGroups = {
MAMMAL: 1,
REPTILE: 2,
AMPHIBIAN: 3,
INVERTEBRATE: 4
};
function Animal(name, type) {
this.name = name;
this.type = type;
}
var dog = new Animal("dog", animalGroups.MAMMAL);
var crocodile = new Animal("crocodile", animalGroups.REPTILE);
この例では、(新しいキーワードを使用して)クラスのオブジェクトを作成しています。このように、特定のクラス(関数)のメソッドを追加することができます。以下のように、クラスメソッドを追加してください。
Animal.prototype.shout = function() {
console.log(this.name + 'is ' + this.sound + 'ing……');
}
もしかすると、あなたは疑問に思うかもしれません。クラス内にまともなプロパティがありません。その通り!まともなプロパティがほとんど定義されていません。これは、クラスを継承している子クラスによって継承されることを意図しているからです。
JavaScriptでは、継承は以下のように行われます。
function Dog(name, type) {
Animal.call(this, name, type);
this.sound = "bow";
}
Dogという名前の具体的な関数を定義しました。ここで、Animalクラスを継承するためには、thisと他の引数を渡して(先ほど説明した)call関数を実行する必要があります。以下のように、ジャーマンシェパードをインスタンス化することができます。
var pet = Dog("germanShepard", animalGroups.MAMMAL);
console.log(pet); // returns Dog {name: "germanShepard", type: 1, sound: "bow"}
子関数に名前と型を代入するのではなく、超関数Animalを呼び出してそれぞれのプロパティを設定しています。petは親のプロパティ(名前、型)を持っています。しかし、メソッドはどうでしょう?メソッドも継承されているのでしょうか?見てみましょう!
pet.shout(); // Throws error
なぜ?どうしてこのようなことが起こったのでしょう?これは、親クラスのメソッドを継承するためにJavaScriptと宣言していないためです。修正するにはどうすればいいでしょう?
// Link prototype chains
Dog.prototype = Object.create(Animal.prototype);
var pet = new Dog("germanShepard", animalGroups.MAMMAL);
// Now shout method is available
pet.shout(); // germanShepard is bowing……
これで鳴き声メソッドが利用可能です。object.constructor関数を使用して、JavaScriptにおける特定のオブジェクトのクラスが何であるかを調べることができます。petのクラスが何になっているか確認してみましょう。
pet.constructor; // returns Animal
これは曖昧です。Animalは親クラスです。しかし、petの具体的な型は何でしょう?それは、Dog型です。これは、Dogクラスのコンストラクタによるものです。
Dog.prototype.constructor; // returns Animal
Animalになりました。クラスのすべてのインスタンス(オブジェクト)が、それが属するクラス名を正しく表示できるように、それをDogクラス自体に設定する必要があります。
Dog.prototype.constructor = Dog;
プロトタイプの継承について覚えておくべき4つのことは、以下のとおりです。
・クラスプロパティはthisを使用して拘束されます。
・クラスメソッドは、prototypeオブジェクトを使用して拘束されます。
・プロパティを継承するには、thisオブジェクトを渡すcall関数を使用してください。
・メソッドを継承するには、親と子のプロトタイプをリンクさせるためにObject.createを使用してください。
・オブジェクトの正しいアイデンティティを得るために、常に子クラスのコンストラクタを設定してください。
注:これらは目に見えないですが、新しいクラス構文でも起こることです。これらを理解することは、JSの知識を高める上でも重要です。
JSでは、call関数とprototypeオブジェクトは継承を提供します。
7)コールバックとPromiseを深く理解する
コールバックは、I/O操作(入出力操作)が完了した後に実行される関数です。I/O操作の時間がかかると、Python/Rubyでのさらなる実行を許可しないコードがブロックされる可能性があります。しかし、JavaScriptでは、非同期の実行が許可されているため、async関数にコールバックを提供できます。以下の例は、マウスやキーボードなどによって生成されたイベントである、ブラウザからサーバーへのAJAX(XMLHttpRequest)呼び出しです。見てみましょう。
function reqListener () {
console.log(this.responseText);
}
var req = new XMLHttpRequest();
req.addEventListener("load", reqListener);
req.open("GET", "http://www.example.org/example.txt");
req.send();
reqListenerは、GETリクエストが正常に返されたときに実行されるコールバックです。
Promiseは、コールバック用の美しいラッパーであり、非同期コードをエレガントにすることができます。Promiseについては、この記事で詳しく書いています。これもJSで知っておくべき重要なことです。
8)正規表現を深く理解する
正規表現には多くのアプリケーションがあります。テキストの処理、ユーザー入力に関するルールの強制などです。JavaScript開発者は、基本的な正規表現を実行して問題を解決する方法を知っておく必要があります。正規表現は普遍的な概念です。以下の例で、JSからそれを行う方法を見ていきます。
以下のように、新しい正規表現を作成することができます。
var re = /ar/;
var re = new RegExp('ar'); // This too works
上記の正規表現は、与えられた文字列のセットとマッチする式です。正規表現が定義されると、一致する文字列を照合して確認することができます。また、exec関数を使用して文字列をマッチさせることができます。
re.exec("car"); // returns ["ar", index: 1, input: "car"]
re.exec("cab"); // returns null
複雑な正規表現を書くための特殊文字クラスはあまり多くありません。
正規表現には多くの種類の要素があります。その一部を紹介します。
・文字例の例:\w(英数字)、\d(小数)、\D(非小数)
・文字クラスの例:[x-y](x~yの範囲)、[^ x](x以外)
・数量詞の例:+、?、*(貪欲で怠惰なマッチング)
・境界の例:^(入力の始め)、$(入力の終わり)
上記を使用して、いくつかの例を示しましょう。
/* Character class */
var re1 = /[AEIOU]/;
re1.exec("Oval"); // returns ["O", index: 0, input: "Oval"]
re1.exec("2456"); // null
var re2 = /[1-9]/;
re2.exec('mp4'); // returns ["4", index: 2, input: "mp4"]
/* Characters */
var re4 = /\d\D\w/;
re4.exec('1232W2sdf'); // returns ["2W2", index: 3, input: "1232W2sdf"]
re4.exec('W3q'); // returns null
/* Boundaries */
var re5 = /^\d\D\w/;
re5.exec('2W34'); // returns ["2W3", index: 0, input: "2W34"]
re5.exec('W34567'); // returns null
var re6 = /^[0-9]{5}-[0-9]{5}-[0-9]{5}$/;
re6.exec('23451-45242-99078'); // returns ["23451-45242-99078", index: 0, input: "23451-45242-99078"]
re6.exec('23451-abcd-efgh-ijkl'); // returns null
/* Quantifiers */
var re7 = /\d+\D+$/;
re7.exec('2abcd'); // returns ["2abcd", index: 0, input: "2abcd"]
re7.exec('23'); // returns null
re7.exec('2abcd3'); // returns null
var re8 = /<([\w]+).*>(.*?)<\/\1>/;
re8.exec('<p>Hello JS developer</p>'); //returns ["<p>Hello JS developer</p>", "p", "Hello JS developer", index: 0, input: "<p>Hello JS developer</p>"]
正規表現の詳細については、このチートシートのページを参照してください。
execに加えて、正規表現を使用して別の文字列内の文字列を検索するために見つけるために、match、search、replaceという他の関数を使用することもできます。しかし、これらの関数は文字列自体に使用する必要があります。
"2345-678r9".match(/[a-z A-Z]/); // returns ["r", index: 8, input: "2345-678r9"]
"2345-678r9".replace(/[a-z A-Z]/, ""); // returns 2345-6789
正規表現は、複雑な問題を簡単に解決するために開発者が理解しておくべき重要なトピックです。
9)Map、Reduce、Filterを深く理解する
関数型プログラミングは、近年のホットなトピックになっています。多くのプログラミング言語には、lambdaのような関数的な概念が含まれています(例:バージョン7以降のJava)。JavaScriptでは、関数型プログラミング構造のためのサポートが長い間存在しています。深く学ぶべき主な関数が3つあります。数学関数はいくつかの入力と戻り値(出力)を取ります。純粋関数は常に、特定の入力に対して同じ出力を返します。以下で説明する関数も純粋関数です。
map
map関数は、JavaScript配列で使用できます。この関数を使用すると、配列内のすべての要素に変換関数を適用して、新しい配列を取得することができます。JS配列のmap処理の一般的な構文は以下のとおりです。
arr.map((elem){
process(elem)
return processedValue
}) // returns new array with each element processed
最近作業しているシリアルキーに、いくつかの不要な文字が入力されていると想定してみましょう。それらの文字を取り除く必要があります。反復および検索を使って文字を削除する代わりに、mapを使用して同じ操作を実行し、結果の配列を取得することができます。
var data = ["2345-34r", "2e345-211", "543-67i4", "346-598"];
var re = /[a-z A-Z]/;
var cleanedData = data.map((elem) => {return elem.replace(re, "")});
console.log(cleanedData); // ["2345-34", "2345-211", "543-674", "346-598"]
注:JavaScript ES6における関数定義としてアロー構文を使用しました。
mapは引数として関数を取ります。その関数には引数があります。その引数は配列から取り出されます。私たちは処理された要素を返す必要があり、それは配列のすべての要素に当てはまります。
reduce
reduce関数は、特定のリストを1つの最終結果に減らします。また、配列を反復し、中間結果を変数で保存することで、同じことを行うこともできます。しかしここでは、配列を値へと減らすための明確な方法を示します。JSのreduce処理の一般的な構文は以下のとおりです。
arr.reduce((accumulator,
currentValue,
currentIndex) => {
process(accumulator, currentValue)
return intermediateValue/finalValue
}, initialAccumulatorValue) // returns reduced value
accumulatorは中間値と最終値を格納します。currentIndexとcurrentValueは、それぞれ、配列からの要素のインデックスおよび値です。initialAccumulatorValueは、その値をaccumulator引数に渡します。
reduceの実用的な応用例は、無数の配列をflattening(平坦化)することです。flattening(平坦化)とは、内部配列を単一の配列に変換することです。例えば、以下のような形です。
var arr = [[1, 2], [3, 4], [5, 6]];
var flattenedArray = [1, 2, 3, 4, 5, 6];
通常の反復でこれを達成することができます。しかし、reduceを使うと、シンプルなコードになるのです。マジック!
var flattenedArray = arr.reduce((accumulator, currentValue) => {
return accumulator.concat(currentValue);
}, []); // returns [1, 2, 3, 4, 5, 6]
filter
これは関数型プログラミングの第三の概念です。配列内の各要素を処理し、最後に別の配列を返す(reduceのように値を返さない)ので、mapに近いです。filterを通した(フィルタリングした)配列の長さを、元の配列の長さ以下にすることができます。なぜなら、私たちが渡すフィルタリング条件は、出力配列内のいくつかの入力/ゼロ入力を除外する場合があるからです。JSのfilter処理の一般的な構文は以下のとおりです。
arr.filter((elem) => {
return true/false
})
elemは配列のデータ要素であり、フィルタリングされた要素の包含/除外を示すために関数からtrue/falseが返されます。一般的な例は、特定の条件で開始および終了する単語の配列をフィルタリングすることです。例えば、tで始まりrで終わる単語の配列をフィルタリングする必要があると想定してみましょう。
var words = ["tiger", "toast", "boat", "tumor", "track", "bridge"]
var newData = words.filter((elem) => {
return elem.startsWith('t') && elem.endsWith('r') ? true:false;
}); // returns ["tiger", "tumor"]
これらの3つの関数は、誰かにJavaScriptの関数型プログラミングについて聞かれても、すぐに答えられる(使える)状態にしておく必要があります。見てのとおり、3つの事例すべてで元の配列は変更されておらず、これらの関数の純粋性を証明しています。
10)エラー処理パターンを理解する
これは、多くの開発者がJavaScriptであまり気を付けていない部分です。エラー処理について話している開発者は、ごく一握りです。優れた開発アプローチは、try/catchブロックの周りにJSコードを常に注意深くラップします。
YahooのUIエンジニアであるニコラス・C. ザカス(Nicholas C. Zakas)は、2008年にこう述べています。「自分のコードに不具合があると常に想定してください。イベントが正しく処理されない可能性があります!それらをサーバーに記録してください。自分自身のエラーをスローしてください。」
JavaScriptでは、気軽にコードを書くたびに、何かが失敗する可能性があります。例えば、以下のような形です。
$("button").click(function(){
$.ajax({url: "user.json", success: function(result){
updateUI(result["posts"]);
}});
});
この例では、結果が常にJSONオブジェクトになるという罠に陥っています。時には、サーバーがクラッシュすることがあり、結果の代わりにnullが返されることもあります。その場合、null ["posts"]はエラーをスローします。適切な処理はこちらです!
$("button").click(function(){
$.ajax({url: "user.json", success: function(result){
try {
updateUI(result["posts"]);
}
catch(e) {
// Custom functions
logError();
flashInfoMessage();
}
}});
});
logError関数は、エラーをサーバーに報告するためのものです。2番目の関数flashInfoMessageは、「サービスは現在利用できません」といったユーザーフレンドリーなメッセージを表示する関数です。
ニコラス・C. ザカス(Nicholas C. Zakas)は、予期せぬことが起こると感じるたびに手動でエラーをスローしなさいと言っています。致命的エラーと致命的でないエラーを区別してください。上記のエラーは、バックエンドサーバーのダウンに関連しているため、致命的です。この場合、何らかの理由でサービスが停止していることを顧客に知らせるべきです。場合によっては、エラーは致命的ではないかもしれませんが、サーバーに通知しておいた方がいいでしょう。そのようなコードを作成するには、まずエラーをスローし、windowオブジェクトレベルでエラーイベントをキャッチし、そのメッセージをサーバーに記録するためのAPI呼び出しを行います。
reportErrorToServer = function (error) {
$.ajax({type: "POST",
url: "http://api.xyz.com/report",
data: error,
success: function (result) {}
});
}
// Window error event
window.addEventListener('error', function (e) {
reportErrorToServer({message: e.message})
})}
function mainLogic() {
// Somewhere you feel like fishy
throw new Error("user feeds are having fewer fields than expected……");
}
このコードは基本的に3つのことを行います:
1. ウィンドウレベルでエラーについて聞く
2. エラーが発生するたびにAPI呼び出しを行う
3. サーバーのログに記録する
続行する前に、変数が有効であり、null(または)undefinedでないかどうかを確認するために、新しいBoolean関数(ES5、ES6)を使用することもできます。
if (Boolean(someVariable)) {
// use variable now
} else {
throw new Error("Custom message")
}
ブラウザに頼るのではなく、自分の力でエラーを処理する方法を常に考えてください。物事に失敗はつきものです!
他に知っておくべきこと(ホイスティング(巻き上げ)、イベントバブリング)
上記の概念はすべて、JavaScript開発者にとって主要なものです。それらが本当に役立つことを理解できる内部の詳細がいくつかあります。それらはJavaScriptエンジンがブラウザでどのように機能するかということです。ホイスティング(巻き上げ)とイベントバブリングとは何でしょう?
ホイスティング(巻き上げ)
ホイスティング(巻き上げ)は、宣言された変数を実行中にプログラムの先頭にプッシュするプロセスです。例えば、以下のような形です。
doSomething(foo); // used before
var foo; // declared later
上記のことをPythonのようなスクリプト言語で行うと、エラーがスローされます。最初に定義して使用する必要があります。JSはスクリプト言語ですが、ホイスティング(巻き上げ)のメカニズムがあります。このメカニズムにおいて、JavaScript VMは、プログラムの実行中に2つのことを行います。
1. 最初にプログラムをスキャンし、すべての変数宣言と関数宣言を収集し、そのためのメモリ空間を割り当てます。
2. 割り当てられた変数値を入力してプログラムを実行します。割り当てられていない場合はundefined値を入力します。
上記のコードスニペットでは、console.logは「undefined」を出力します。これは、最初のpass変数であるfooが収集されたためです。VMは、変数fooに定義された値を探します。このホイスティング(巻き上げ)は、コードが一部の場所でエラーをスローし、別の場所ではundefinedを何の表示もなく使用するというJavaScriptコードの状況につながります。曖昧さを解消するためにホイスティング(巻き上げ)について知っておきましょう!いくつかの例を参照してください!
イベントバブリング
次はイベントバブリングです!シニアソフトウェアエンジニアであるArun P氏によると、
イベントバブリングとキャプチャは、イベントが別の要素内の要素で発生し、両方の要素がそのイベントのハンドルを登録している場合、HTML DOM APIでイベントが伝播する2つの方法です。イベント伝搬モードは、要素がどのような順序でイベントを受け取るかを決定します。
バブリングを使うと、最初にイベントがキャプチャされ、最も内側の要素によって処理され、外側の要素に伝播します。キャプチャを使うと、プロセスは逆になります。通常は、addEventListener関数を使用してhandlerにイベントを追加します。
addEventListener("click", handler, useCapture=false)
3つ目の引数であるuseCaptureがキーです。デフォルト値はfalseです。そのため、イベントが最も内側の要素によって最初に処理され、親要素に到達するまで外側に伝播するバブリングモデルになります。その引数がtrueであれば、モデルをキャプチャします。
以下は、イベントバブリングの例です。
<div onClick="divHandler()">
<ul onClick="ulHandler">
<li id="foo"></li>
</ul>
</div>
<script>
function handler() {
// do something here
}
function divHandler(){}
function ulHandler(){}
document.getElementById("foo").addEventListener("click", handler)
</script>
リスト要素をクリックすると、バブリング(デフォルト)モデルにおけるhandlerの実行順序は以下のようになります。
handler() ⇒ ulHandler() ⇒ divHandler()
この図では、handlerが外側に向かって順番に発射されています。同様に、キャプチャモデルは、親からクリックされた要素まで、イベントを内側に発射しようとします。上記のコードにおけるこの1行を変更してみます。
document.getElementById("foo").addEventListener("click", handler, true)
handlerの実行順序は以下のようになります。
divHandler ⇒ ulHandler() ⇒ handler()
不要な振る舞いを避けるために、ユーザーインターフェース(UI)を実装するためには、イベントバブリングが適切かどうか(方向が親に向かっているのか、子供に向かっているのか)を理解する必要があります。
これらがJavaScriptの基本概念です。最初に述べたように、JavaScriptの面接をクラッキング(突破)するには、これらだけでなく、あなたの業務経験、知識、準備が必要です。常に勉強を続けてください。最新の開発状況(ES6)を把握してください。V6エンジン、テストといったJavaScriptの様々な側面を深く掘り下げてください。多くのことを教えてくれるビデオリソースを紹介しますね。
この記事を気に入ってくださった方、どうもありがとう!私のTwitterアカウントは@Narenarya3なので、フォローしてください。
参考文献
イベントバブリングとキャプチャとは何か?
イベントのバブリングとキャプチャの違いは何でしょう?2つのうち、より速くて優れた、使用した方がいいモデルはどちらでしょう?
stackoverflow.com
Naren Arya
Citrix社のソフトウェアエンジニア |(Python | Go | ReactJS)、「Goを使ったRESTFulウェブサービスの構築」の著者 | テクニカルスピーカー
CREDIT:原著者の許諾のもと翻訳・掲載しています。