面接の候補者のためにJavaScriptを少々
JavaScript(JS)の面接は容易ではありません。私も、あなたも、全員その経験があるはずです。JavaScriptの面接で聞かれる可能性のある質問の数は多いです。どのようにすればJSの面接をクラッキング(突破)することができるのでしょう?どこから始めればいいのでしょうか?この記事は、すべてのJavaScript開発志望者が基本的な概念を理解してJSの知識を深められるように指導する内容です。これらは、少なくともJSの面接に臨む際に行うべき小さな一歩です。もし私が候補者ならば、これらの概念について準備しておくと思います。もし私が面接者ならば、その知識を十分に持っているものとして面接を進めると思います。
注:私のことを知らない方向けに説明すると、私はPython、Go、JavaScriptの専門知識を持つフルスタック開発者です。最近、本を執筆しました!
このガイドは出発点ですが、JS開発者にとっての特効薬ではありません。より厳しい面接のために準備を行うことは、あなた自身の責任です。また、面接の質問は、取り組んだことのあるドメインや技術(例:React JS、WebPack、Node JSなど)に関連する可能性もあることを覚えておく必要があります。この記事では、優れたJavaScript開発者を名乗るために熟知しておくべき基本的なJSの要素を紹介します。素晴らしいJS開発者は素晴らしいReact開発者になることができますが、その逆は保証されていません。悲しいことに、JSは、規律の欠如した無数のスクリプトキディを作ったことで評判が悪いです(そしてそれは部分的に正しいです)。JavaScriptを使用すると、開発者はあまり不満を言うことなく何かを行うことができます。コーディングも楽しいです。ジョン・レシグ(John Resig、jQueryのクリエイター)、ブレンダン・アイク(Brendan Eich、JSのクリエイター)、ラース・バク(Lars Bak、Google Chromeチーム)といった何人かの偉大なJavaScriptプログラマーは、言語の流行り廃りを理解していました。成功を収めるJSプログラマーは、常にライブラリからプレーンなJSコードを読み込んでいます。多くの人が、素晴らしいJavaScript開発者を見つけることは本当に難しいと言っています!
「仮想マシンは見知らぬ獣です。完璧な解決策などありません。その代わりに、「スイートスポット」を最適化します。職人技はたくさんあります。それは長い試合で、燃え尽きることなどありません」 - ラース・バク(Lars Bak)、Google
JSの面接の複雑さを知ってもらいたいので、初見でこのJSのステートメントの結果を答えてください。
console.log(2.0 == “2” == new Boolean(true) == “1”)
<<<<<<<<<<<<… >>>>>>>>>>>>>
<<<<<<<<<<<<… >>>>>>>>>>>>>
<<<<<<<<<<<<… >>>>>>>>>>>>>
(これらの行は、以下に書いてある答えを見ないように引いたものです。)
10人中9人が、falseが出力されると答えます。しかし、実際はtrueを出力します。なぜでしょう?詳細はこちらを参照してください。
JavaScriptは難しいです。面接者が上記のような質問をしてくるくらい頭が切れる人の場合、私たちにできることはあまり多くありません。では、どうすればいいのでしょう?JSの面接を有利に進めるために、これらの11の基本要素について深く学びましょう。
1)JSの関数を深く理解する
関数はJavaScriptの花です。第一級オブジェクトです。JSの関数を深く理解していないと、あなたの知識に対して深刻な警報が鳴ります。JSの関数は、通常の関数以上のものです。他の言語とは異なり、関数を変数に代入して、別の関数に引数として渡すことも、別の関数から返すこともできます。したがって、それはJSの第一級オブジェクトです。
ここで関数とは何かを説明するつもりはありませんが、いくつかあなたが驚くようなことを紹介しましょう!
console.log(square(5));
/* …… */
function square(n) { return n * n; }
このコードスニペットは25を実行します。正解!では、2つ目のコードスニペットを参照してください
console.log(square(5));
var square = function(n) {
return n * n;
}
一見すると、「これも25を出力する」と答えたい誘惑に駆られるかもしれません。不正解!正解は、最初の行にこんなものが表示されます。
TypeError: square is not a function
JavaScriptでは、関数を変数として定義すると、変数名が巻き上げられますが、JSの実行がその定義に遭遇するまでアクセスすることはできません。びっくりしませんでしたか?
大丈夫ですよ。あなたは、この構文をコードのどこかで頻繁に見たことがあるかもしれません。
var simpleLibrary = function() {
var simpleLibrary = {
a,
b,
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
}
}
return simpleLibrary;
}();
なぜ人々はこのような奇妙なことをするのでしょう?これは、グローバルスコープを汚染しない変数と関数をカプセル化した関数変数です。jQueryからLodashまでのライブラリは、この手法を使って$などを提供しています。
ここで皆さんにお伝えしたいポイントは、「関数について深く学ぶ」ということです。関数の使用方法には、ちょっとした罠がたくさんあります。Mozillaの関数に関する素晴らしい記事を読んでみてください。
2)bind、apply、callを理解する
これらの関数を、有名なライブラリで見たことがあるかもしれません。これらは、さまざまな関数に別の機能を持たせることができる、いわゆる「カリー化」を可能にします。優れたJavaScript開発者は、これらの3つの関数についていつでも教えてくれるでしょう。
基本的に、これらは、何かを達成するために振る舞いを変更する関数のプロトタイプメソッドです。JSの開発者であるChad氏によると、使い方はこのような感じです。
特定のコンテキストでその関数を後で呼び出す場合は、.bind()を使用してください。これはイベントに役立ちます。コンテキストを変更してすぐに関数を呼び出す場合は、.call()または.apply()を使用してください。
あなたを救ってくれるcall!
上記の文章が何を意味するのか見てみましょう!数学の先生があなたにライブラリを作成して提出するように求めたとします。あなたは円の面積と円周を導き出す抽象ライブラリを書きました。
var mathLib = {
pi: 3.14,
area: function(r) {
return this.pi * r * r;
},
circumference: function(r) {
return 2 * this.pi * r;
}
};
あなたは教授にライブラリを提出しました。今度はその数学ライブラリを呼び出すコードを提出しなければなりません。
mathLib.area(2);
12.56
2番目のコードサンプルを提出しているときに、あなたは「小数点以下5桁の定数piを使用すること」と書かれた教授のガイドラインを発見しました。どうしましょう!あなたは3.14159ではなく、3.14を使用してしまいました。締め切りは終わってしまったので、ライブラリを再送するチャンスはありません。締め切りが近づいた今、あなたはライブラリを送る機会がありません。そんなとき、JSのcall関数があなたを救ってくれます。このようにコードを呼び出すだけで大丈夫です。
mathLib.area.call({pi: 3.14159}, 2);
それは新しいpiの値をその場でとります。出力はこうなります。
12.56636
これで教授は喜ぶはずです!call関数は、2つの引数をとります。
・コンテキスト
・関数の引数
コンテキストとは、関数エリア内でthisキーワードを置き換えるオブジェクトです。その後、引数が関数の引数として渡されます。例えば、以下のような形です。
var cylinder = {
pi: 3.14,
volume: function(r, h) {
return this.pi * r * r * h;
}
};
callの呼び出しは以下のように行います。
cylinder.volume.call({pi: 3.14159}, 2, 6);
75.39815999999999
これらの関数引数が、コンテキストオブジェクトの後の引数として渡されるのがわかりますか?
Applyは、関数引数がリストとして渡される以外は、まったく同じです。
cylinder.volume.apply({pi: 3.14159}, [2, 6]);
75.39815999999999
callを知っているならば、applyについて知っているでしょうし、applyを知っているならば、callについて知っているでしょう。では、bindとは何でしょう?
Bindは、新しいthisを与えられた関数に結び付けます。bindの場合、関数はCallやApplyのように即座に実行されません。
var newVolume = cylinder.volume.bind({pi: 3.14159}); // This is not instant call
// After some long time, somewhere in the wild
newVolume(2,6); // Now pi is 3.14159
Bindはどのような時に使用するのでしょうか?アップデートされたコンテキストを持つ新しい関数を返す関数にコンテキストを挿入したいときです。これは、this変数がユーザーの提供した変数であることを意味します。これはJavaScriptイベントを扱う際に非常に便利です。
JavaScriptの機能を合成するには、これらの3つの関数を知っている必要があります。
3)JavaScriptのスコープ(とクロージャ)を深く理解する
JavaScriptスコープはパンドラの箱です。このたった一つの概念から、何百もの難しい面接の質問を生み出すことができます。スコープには3種類あります。
・グローバルスコープ
・ローカルスコープ/関数スコープ
・ブロックスコープ(ES6で導入)
グローバルスコープは、私たちが通常行っていることです。
x = 10;
function Foo() {
console.log(x); // Prints 10
}
Foo()
変数をローカルに定義すると、関数スコープが姿を現します。
pi = 3.14;
function circumference(radius) {
pi = 3.14159;
console.log(2 * pi * radius); // Prints "12.56636" not "12.56"
}
circumference(2);
ES16標準では、新しいブロックスコープが導入され、変数のスコープを指定された括弧ブロックに限定しています。
var a = 10;
function Foo() {
if (true) {
let a = 4;
}
alert(a); // alerts '10' because the 'let' keyword
}
Foo();
関数と条件はブロックと見なされます。上記の例では、条件文が実行されるため、4をalertします。しかし、ES6はブロック変数のスコープを破壊し、スコープはグローバルになります。
次は魔法のスコープです。クロージャを使用して行うことができます。JavaScriptのクロージャは、別の関数を返す関数です。
誰かがあなたに「文字列をとり、一度に1文字ずつ返す設計を書いてください」という質問をしたとしましょう。新しい文字列が与えられた場合、古い文字列を置き換えなければなりません。シンプルにgeneratorと呼ばれています。
function generator(input) {
var index = 0;
return {
next: function() {
if (index < input.length) {
index += 1;
return input[index - 1];
}
return "";
}
}
}
実行はこのようになります!
var mygenerator = generator("boomerang");
mygenerator.next(); // returns "b"
mygenerator.next() // returns "o"
mygenerator = generator("toon");
mygenerator.next(); // returns "t"
ここで、スコープが重要な役割を果たしています。クロージャは、別の関数を返してデータをラップする関数です。上記の文字列のgeneratorは、クロージャを修飾します。indexの値は、複数の関数呼び出し間で保持されます。定義された内部関数は、親の関数で定義された変数にアクセスできます。これは別のスコープです。第二レベルの関数で1つ以上の関数を定義した場合、それはすべての親の変数にアクセスできます。
JavaScriptのスコープに関する質問は多いです!それを徹底的に理解しましょう。
4)thisキーワードを深く理解する(グローバルスコープ、関数スコープ、オブジェクトスコープ)
JavaScriptでは、関数とオブジェクトでコードを合成します。ブラウザを使用する場合、グローバルコンテキストではウィンドウオブジェクトを参照します。あなたが今ブラウザコンソールを開いて以下の文字列を入力すると、thisはtrueと評価されます。
this === window;
プログラムのコンテキストとスコープが変わると、それに応じてその時点のthisが変わります。では、ローカルコンテキストでのthisを見てみましょう。
function Foo(){
console.log(this.a);
}
var food = {a: "Magical this"};
Foo.call(food); // food is this
この出力を予測したい誘惑に駆られているでしょう。
function Foo(){
console.log(this); // prints {}?
}
きっと不正解です。なぜならthisはここではグローバルオブジェクトだからです。親のスコープがどんなものであれ、子に継承されることに注意してください。したがって、これはウィンドウオブジェクトを出力します。説明した3つのメソッドは、実際にはthisオブジェクトを設定するために使用されているのです。
では最後のタイプを見てみましょう。オブジェクトスコープでのthisです。
var person = {
name: "Stranger",
age: 24,
get identity() {
return {who: this.name, howOld: this.age};
}
}
ここでは、変数として呼び出すことができる関数であるgetter構文を使用しました。
person.identity; // returns {who: "Stranger", howOld: 24}
この例では、thisは実際にはオブジェクトそのものを参照しています。先ほど述べたように、thisは異なる場所では異なる形で振る舞います。よく理解しておいてください。
5)オブジェクトを深く理解する(Object.freeze、Object.seal)
多くの人は、このようなオブジェクトを知っているでしょう。
var marks = {physics: 98, maths:95, chemistry: 91};
これは、キーと値のペアを格納するMapです。JavaScriptsオブジェクトには、何でも値として格納することができる特別なプロパティがあります。つまり、リスト、別のオブジェクト、関数などを値として格納することができるのです。他にも色々格納できます。
以下の方法でオブジェクトを作成することができます。
var marks = {};
var marks = new Object();
特定のオブジェクトをJSON文字列に簡単に変換することができます。また、JSONオブジェクトのstringifyメソッドとparseメソッドをそれぞれ使用して、それを元に戻すこともできます。
// returns "{"physics":98,"maths":95,"chemistry":91}"
JSON.stringify(marks);
// Get object from string
JSON.parse('{"physics":98,"maths":95,"chemistry":91}');
ではオブジェクトについて知っておくべきことは何でしょう?Object.keysを使用すると、オブジェクトを簡単に反復処理することができます。
var highScore = 0;
for (i of Object.keys(marks)) {
if (marks[i] > highScore)
highScore = marks[i];
}
Object.valuesは、オブジェクトの値のリストを返します。
オブジェクトに関する他の重要な関数は以下のとおりです。
・Object.prototype(オブジェクト)
・Object.freeze(関数)
・Object.seal(関数)
Object.prototypeは、多くのアプリケーションを持つ重要な関数を多く提供します。一部を紹介します。
Object.prototype.hasOwnPropertyは、特定のプロパティ/キーがオブジェクト内に存在するかどうかを調べるのに便利です。
marks.hasOwnProperty("physics"); // returns true
marks.hasOwnProperty("greek"); // returns false
Object.prototype.instanceofは、特定のオブジェクトが特定のプロトタイプの型であるかどうかを評価します(次のセクションで説明します。それは関数です)。
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var newCar = new Car('Honda', 'City', 2007);
console.log(newCar instanceof Car); // returns true
他にも2つの関数があります。Object.freezeは、既存のプロパティを変更できないようにオブジェクトをフリーズすることができます。
var marks = {physics: 98, maths:95, chemistry: 91};
finalizedMarks = Object.freeze(marks);
finalizedMarks["physics"] = 86; // throws error in strict mode
console.log(marks); // {physics: 98, maths: 95, chemistry: 91}
この例では、オブジェクトをフリーズした後でphysics(物理)プロパティの値を変更しようとしています。しかし、JavaScriptはそれを許可しません。特定のオブジェクトがフリーズしているかどうかを調べるには、以下のようにします。
Object.isFrozen(finalizedMarks); // returns true
Object.sealはfreezeと少し異なります。設定可能なプロパティは許可されますが、新しいプロパティの追加または削除およびプロパティは許可されません。
var marks = {physics: 98, maths:95, chemistry: 91};
Object.seal(marks);
delete marks.chemistry; // returns false as operation failed
marks.physics = 95; // Works!
marks.greek = 86; // Will not add a new property
また、以下のように、特定のオブジェクトがシールされているかどうかを確認することもできます。
Object.isSealed(marks); // returns true
グローバルオブジェクト関数には他にも多くの重要な関数/メソッドがあります。興味がある方は、こちらを参照してください。
CREDIT:原著者の許諾のもと翻訳・掲載しています。