udemyのガチで学びたい人のためのJavascriptメカニズムという講座を受講しまして、
自分の言葉でまとめたいなと思ったのでこの記事を書きました。
最近エンジニアになったばかりですので暖かい目で見ていただければと思います
jsを学ぶ理由
シンプルに「Reactを使ってみたいなー」と思ったからです
しかし、どうやらReactを使うにはjsの基礎がわかっていないと話にならんとのことだったので、とりあえず素のjsしっかり学ぼうと思いました。教材に触れる前の自分のレベル感としては「プログラミングの基礎はある程度わかる」「DOM操作も何となくわかる」「非同期処理はJQueryを使ってならやったことある」です
ちなみに知らなかった用語としては「スコープ」「クロージャー」「モジュール」などです
(スコープに関しては今まで意識して使ったことがないというのが正しいかもです)
Reactに必要な知識
こちらの記事を参考してに学ぶ項目を決めました
キーワードをざっと羅列するとスコープ、モジュール、非同期処理、スプレッド演算子、三項演算子、ES6クラス etc...
この教材のレベル感
正直なかなか難しいです
メモリレベルでのお話や、ブラックボックスになっている処理をあえて解き明かしたりと、
とても深い領域まで解説されています
一応講座自体は全て目は通しているのですが、とても難しくて重要度が高くないと判断した箇所に関しては、後で出てきた際に 「あ、なんか見たことある〜」 程度の理解で十分かなと思っています
ですので、「そこまではいらんかな〜」と感じたところはバリバリ端折るので許してください
jsにおける関数の挙動
関数に渡す引数が足りない場合
まずは以下のコードを見てください
この場合の実行結果はどうなるでしょうか??
function fn(a, b) {
console.log(a, b);
}
fn(1); //引数が一つしかない
他の言語なら、エラーを吐いて怒られそうですがjsの場合はこのようになります
fn(1) // 1 undefined
なんと、、、エラーにならずundefinedが設定されます
同じ名前の関数が宣言されていた場合
それじゃあ関数fnが二つ定義されている場合はどうなるでしょうか?
function fn(a, b) {
console.log(a, b);
}
function fn(a, b) {
console.log(a, b);
}
fn(1,2);
頼むから、エラー吐いてくれ!
fn(1,2);//1,2
結果はなーんと、一番下に書いた関数がそのまま実行されちゃうんですね〜
ちなみに引数の数が異なっていたとしても、jsでは別の関数として認識はされません
ではどうすればいいかというと、「関数式」 を使うことでこの問題は解決できます
以下のコードを見てください
const fn = function(a, b) {
console.log(a, b);
}
function fn(a, b) {
console.log(a, b);
}
fn(1,2); // エラー!
無事エラーを吐きました!
関数式を使うことで同様の名前を持つ関数が再宣言ができなくなります
ひと言メモ
普通に関数を定義する場合と関数式を使う場合の大きな違いは、関数の再宣言を防ぐことでそこまで大きな違いはないです
ですが、のちに出てくるアロー関数は普通に関数を定義する際と大きく異なる点があるので注意が必要です
argumentsとは?
argumentsとは 「関数コンテキストによって自動で生成されるオブジェクト」 です
簡単にいうと 「関数スコープ内で使える便利なやつ」 という認識で間違っていないと思います
argumentsは関数に渡された実引数を保持することができます
以下のコードで確認してみましょう!
function fn() {
const a = arguments[0];
const b = arguments[1];
console.log(arguments);
console.log(a,b);
}
fn(1,2);
fnに引数1と2が渡っているのにも関わらず、仮引数が空欄のままです
ですが、jsは指定した実引数をargumentsの内部に自動で格納してくれます
これは渡される引数の数が決まってない場合に使われるそうです
とても便利ですね
しかし!!!!!
実は最近だとargumentsはあまり使われなくなっています
その理由は大きく2つあり
・argumentsは配列と比べて扱いずらい
・ES6でRestパラメーターというものが導入された
からです
Restパラメータは不特定多数の引数を配列として格納してくれるので、
わざわざ扱いずらいargumentsを採用しなくなったという背景があります
では、Restパラメーターを使ってみましょう!
function fn(...args) {
console.log(args[0]);
}
fn(1,2); //1←(配列として値が格納される!)
こんな感じ、仮引数の前に...を取るのが特徴です
ひと言メモ
ちなみに同様に...を取るスプレッド演算子というものがあるのですが、これとは処理の内容が全然違うので注意してください
スプレッド演算子は渡された配列やオブジェクトを展開する構文です
コールバック関数
コールバック関数とは他の関数に引数として渡せる関数のことを言います
以下のコードを見てください
function hello(name) {
console.log('hello ' + name);
}
function fn(cb) {
cb();
}
fn(hello);
関数fnの引数に関数helloが渡っているのが確認できますね
ただ、この講座を受けていて自分が疑問に感じたのは 「結局コールバック関数を使うメリットてなんだ?」 ということです
わざわざ関数の引数に関数を渡してその中で実行されるってかなりの手間じゃないかと思いました
講座の中では 「汎用性が高まる」 という説明がありましたが 「なんのこっちゃい」と思ってしまい、いまいちピンと来ませんでした
そこで色々わかりやすそうな記事や動画を探した結果、自分はこちらの動画でコールバック関数のメリットを理解しました!
震えるほどわかりやすいので、スッキリしたい方はどうぞご覧ください〜
7分ほどなので、そこそこ太めのパスタ茹で上がる頃には終わってます
this
thisとは呼び出し元のオブジェクトへの参照を保持するキーワードです
オブジェクト指向プログラミングをしたことある方なら、一度は使ったことがあると思います
こちらも、argumentsと同様に関数コンテキスト内で自動生成されるオブジェクトの一つになります
特筆すべき特徴が2つあり
・実行コンテキストによって参照先が変わる
・呼びだし元のオブジェクトの参照を保持する
まあ、日本語だとなんのことかいまいちピンとこないので、とりあえずコード見て確認しましょう
const person = {
name: 'Tom',
hello: function() {
console.log('Hello ' + this.name);
}
}
person.hello(); //Hello Tom
上記の例は、オブジェクトのメソッドとして関数が実行された場合です
答えはHello Tomになります
thisは呼び出し元のオブジェクトを参照するので、この場合はhello関数のレキシカルスコープであるpersonオブジェクト内のnameプロパティの値を取得します
そして次にこちらの例を見てください
簡単に説明すると、hello関数を定数に一度代入してそれを実行しているだけです
window.name = 'John';
const person = {
name: 'Tom',
hello: function() {
console.log('Hello ' + this.name);
}
}
const ref = person.hello;
ref();// Hello John
結果はなーんと、関数をコピーしただけなのにグローバルスコープの値を取得してしまってますね〜
そう、関数として実行される場合 thisはグローバルオブジェクトを参照するという特徴を持ってしまうのです
まとめるとこうです
・オブジェクトのメソッドとして実行される場合、thisは呼び出し元のオブジェクトを参照する
・関数として実行される場合、thisはグローバルオブジェクトを参照する
なぜこのような違いが生まれるのか
内部の処理まで興味ない方は飛ばしても大丈夫です!
メソッドをコピーすると、helloが参照している関数の参照がコピーされます(赤矢印)
そして、コピーされた関数に対してrefから参照が通ることになります
つまり、refを実行する際は personオブジェクトを経由していない のでthisはグローバルオブジェクトへの参照を保持することになってしまう
その結果として、windowオブジェクトのnameの値を取得することになります
bindとcall apply
先ほどのセクションでthisの挙動は理解できたと思いますが、「いや〜関数をコピーしたとしてもオブジェクトのプロパティ使いたいな〜、グローバルスコープに同様のプロパティを追加するのやだな〜、なんかいい方法ないかな〜」 と思う方がいるかもしれません
いい方法あるんですよ!!!
thisの参照先や引数を束縛する方法が
そのキーワードがbind、call、applyです
まずはbindの動きを確認していきましょう!
ちなみに、下記の例ではコールバック関数を使用していますが、
コールバック関数も関数をコピーして実行した際とthisの参照先は変わらないので問題ありません
window.name = 'John';
const person = {
name: 'Tom',
hello: function() {
console.log('Hello ' + this.name);
}
}
const helloTom = person.hello.bind(person)
function fn(ref) {
ref();
}
fn(helloTom);//Hello Tom
第一引数にthisが参照するオブジェクトを指定するだけです
これだけで、実行結果がHello Tomとなります
簡単ですね
ちなみに、bindは第2引数に値を指定してそれを束縛することもできます
もちろんcallとapplyも同様のことが可能です
call、applyとは何が違うの?
bindとcall、applyの違いはすぐに実行されるかどうかだけです
先ほどのbindを用いたコードでは、一度helloTomという定数に格納してそれを実行していました
callとapplyは使うとすぐに関数が実行されます
ちなみに、callとapplyの違いはというと束縛する引数が配列かそうでないかという違いなだけです
callはbindと同様に値を指定し、applyは配列を指定します
では、コードを見て確認しましょう
function a(name, name1) {
console.log('hello ' + name + ' ' + name1);
}
const tim = {name: 'Tim'};
a.call(tim, 'Tim', 'Bob'); //hello Tim
a.apply(tim, ['Tim', 'Bob']); //hello Tim
確かに、callは文字列が渡されておりapplyには配列が渡されています
結果はどちらも同じですね
アロー関数
アロー関数とはES6で導入された、無名関数を記述しやすくした省略記法です
アロー関数に関してはさまざまな書き方があるので、とりあえずそれらをみてみましょう
//関数宣言
function a(name) {
return 'hello ' + name;
}
console.log(a('Tom')); //hello Tom
//関数式
const b = function(name){
return 'hello ' + name;
}
console.log(b('Tom')); //hello Tom
//引数を持つアロー関数その1
const c = (name) => {
return 'hello ' + name;
}
console.log(c('Tom')); //hello Tom
//引数を持つアロー関数その2
const d = name => 'hello ' + name;
console.log(d('Tom')); //hello Tom
//引数を持たないアロー関数その3
const e = () => 'hello ';
console.log(e()); //hello
//引数を持たないアロー関数その4
const f = _ => 'hello ';
console.log(f()); //hello
アロー関数と一口に言っても、いろいろな書き方があるんだないうことがわかりますね
ただ、初めにこれをみた自分の率直な意見は 「うわ〜可読性下がりそう、、、」 でした
みんなjsに精通しているような開発現場でないかぎり、アロー関数を省略しまくるのはちょっとどうなんだと個人的には思ってしまいました
それとも、現場レベルになるとこの省略は案外普通レベルなことなのでしょうか??
アロー関数の注意点
アロー関数が普通の関数宣言と異なる点があり、それはアロー関数はthisの値がセットされないことです
window.name = 'John';
const person = {
name: 'Tom',
hello: () => {
console.log('Hello ' + this.name);
}
}
person.hello(); //Hello John
上記のコードは、普通に考えるとメソッドを実行しているのでpersonオブジェクトの'Tom'を参照すると考えると思います
しかし、アロー関数はそのコンテキスト内ではthisを取らないのでスコープチェーンをたどり上のグローバルコンテキストを参照することになリます
従って、windowオブジェクトのnameプロパティの値が参照されるという流れになります
つまり、「アロー関数ではレキシカルスコープのthisが参照される」 ということですね!
終わりに
今回は関数とオブジェクト編として学習したことをまとめました
次回は非同期処理についてまとめたいと思います
最後に、クロージャーとアロー関数を組み合わせたコードを見て終わりにしましょう
//通常の状態
function addNumberFactory(num) {
function addNumber(value) {
return num + value;
}
return addNumber;
}
//アロー関数で省略した状態
const addNumberFactory = num => value => num + value;
お疲れ様でした〜