LoginSignup
8
5

More than 1 year has passed since last update.

javascriptをudemyで学んでみた(関数とオブジェクト編)

Last updated at Posted at 2022-08-25

udemyのガチで学びたい人のためのJavascriptメカニズムという講座を受講しまして、
自分の言葉でまとめたいなと思ったのでこの記事を書きました。

最近エンジニアになったばかりですので暖かい目で見ていただければと思います

jsを学ぶ理由

シンプルに「Reactを使ってみたいなー」と思ったからです
しかし、どうやらReactを使うにはjsの基礎がわかっていないと話にならんとのことだったので、とりあえず素のjsしっかり学ぼうと思いました。教材に触れる前の自分のレベル感としては「プログラミングの基礎はある程度わかる」「DOM操作も何となくわかる」「非同期処理はJQueryを使ってならやったことある」です

ちなみに知らなかった用語としては「スコープ」「クロージャー」「モジュール」などです
(スコープに関しては今まで意識して使ったことがないというのが正しいかもです)

Reactに必要な知識

こちらの記事を参考してに学ぶ項目を決めました
キーワードをざっと羅列するとスコープ、モジュール、非同期処理、スプレッド演算子、三項演算子、ES6クラス etc...

この教材のレベル感

正直なかなか難しいです
メモリレベルでのお話や、ブラックボックスになっている処理をあえて解き明かしたりと、
とても深い領域まで解説されています

一応講座自体は全て目は通しているのですが、とても難しくて重要度が高くないと判断した箇所に関しては、後で出てきた際に 「あ、なんか見たことある〜」 程度の理解で十分かなと思っています

ですので、「そこまではいらんかな〜」と感じたところはバリバリ端折るので許してください

jsにおける関数の挙動

関数に渡す引数が足りない場合

まずは以下のコードを見てください
この場合の実行結果はどうなるでしょうか??

qiita.js
function fn(a, b) {
    console.log(a, b);
}

fn(1); //引数が一つしかない

他の言語なら、エラーを吐いて怒られそうですがjsの場合はこのようになります

qiita.js
fn(1) // 1 undefined

なんと、、、エラーにならずundefinedが設定されます

同じ名前の関数が宣言されていた場合

それじゃあ関数fnが二つ定義されている場合はどうなるでしょうか?

qiita.js
function fn(a, b) {
    console.log(a, b);
}

function fn(a, b) {
    console.log(a, b);
}

fn(1,2);

頼むから、エラー吐いてくれ!

qiita.js
fn(1,2);//1,2

結果はなーんと、一番下に書いた関数がそのまま実行されちゃうんですね〜
ちなみに引数の数が異なっていたとしても、jsでは別の関数として認識はされません

ではどうすればいいかというと、「関数式」 を使うことでこの問題は解決できます
以下のコードを見てください

qiita.js
const fn = function(a, b) {
    console.log(a, b);
}

function fn(a, b) {
    console.log(a, b);
}

fn(1,2); // エラー! 

無事エラーを吐きました!
関数式を使うことで同様の名前を持つ関数が再宣言ができなくなります

ひと言メモ
普通に関数を定義する場合と関数式を使う場合の大きな違いは、関数の再宣言を防ぐことでそこまで大きな違いはないです
ですが、のちに出てくるアロー関数は普通に関数を定義する際と大きく異なる点があるので注意が必要です

argumentsとは?

argumentsとは 「関数コンテキストによって自動で生成されるオブジェクト」 です
簡単にいうと 「関数スコープ内で使える便利なやつ」 という認識で間違っていないと思います

argumentsは関数に渡された実引数を保持することができます

以下のコードで確認してみましょう!

qiita.js
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パラメーターを使ってみましょう!

qiita.js
function fn(...args) {
    console.log(args[0]);
}

fn(1,2); //1←(配列として値が格納される!)

こんな感じ、仮引数の前に...を取るのが特徴です

ひと言メモ
ちなみに同様に...を取るスプレッド演算子というものがあるのですが、これとは処理の内容が全然違うので注意してください
スプレッド演算子は渡された配列やオブジェクトを展開する構文です

コールバック関数

コールバック関数とは他の関数に引数として渡せる関数のことを言います

以下のコードを見てください

qiita.js
function hello(name) {
    console.log('hello ' + name);
}

function fn(cb) {
    cb();
}

fn(hello);

関数fnの引数に関数helloが渡っているのが確認できますね

ただ、この講座を受けていて自分が疑問に感じたのは 「結局コールバック関数を使うメリットてなんだ?」 ということです
わざわざ関数の引数に関数を渡してその中で実行されるってかなりの手間じゃないかと思いました

講座の中では 「汎用性が高まる」 という説明がありましたが 「なんのこっちゃい」と思ってしまい、いまいちピンと来ませんでした

そこで色々わかりやすそうな記事や動画を探した結果、自分はこちらの動画でコールバック関数のメリットを理解しました!

震えるほどわかりやすいので、スッキリしたい方はどうぞご覧ください〜
7分ほどなので、そこそこ太めのパスタ茹で上がる頃には終わってます

this

thisとは呼び出し元のオブジェクトへの参照を保持するキーワードです
オブジェクト指向プログラミングをしたことある方なら、一度は使ったことがあると思います

こちらも、argumentsと同様に関数コンテキスト内で自動生成されるオブジェクトの一つになります
特筆すべき特徴が2つあり

・実行コンテキストによって参照先が変わる
呼びだし元のオブジェクトの参照を保持する

まあ、日本語だとなんのことかいまいちピンとこないので、とりあえずコード見て確認しましょう

qiita.js
const person = {
    name: 'Tom',
    hello: function() {
        console.log('Hello ' + this.name);
    }
}
person.hello(); //Hello Tom

上記の例は、オブジェクトのメソッドとして関数が実行された場合です
答えはHello Tomになります

thisは呼び出し元のオブジェクトを参照するので、この場合はhello関数のレキシカルスコープであるpersonオブジェクト内のnameプロパティの値を取得します

そして次にこちらの例を見てください
簡単に説明すると、hello関数を定数に一度代入してそれを実行しているだけです

qiita.js
window.name = 'John';

const person = {
    name: 'Tom',
    hello: function() {
        console.log('Hello ' + this.name);
    }
}

const ref = person.hello;
ref();// Hello John

結果はなーんと、関数をコピーしただけなのにグローバルスコープの値を取得してしまってますね〜

そう、関数として実行される場合 thisはグローバルオブジェクトを参照するという特徴を持ってしまうのです

まとめるとこうです
オブジェクトのメソッドとして実行される場合、thisは呼び出し元のオブジェクトを参照する
関数として実行される場合、thisはグローバルオブジェクトを参照する

なぜこのような違いが生まれるのか

内部の処理まで興味ない方は飛ばしても大丈夫です!

こちらは、メモリ空間内の参照の関係を図式化したものです
qiita.png

メソッドをコピーすると、helloが参照している関数の参照がコピーされます(赤矢印)
そして、コピーされた関数に対してrefから参照が通ることになります

つまり、refを実行する際は personオブジェクトを経由していない のでthisはグローバルオブジェクトへの参照を保持することになってしまう
その結果として、windowオブジェクトのnameの値を取得することになります

bindとcall apply

先ほどのセクションでthisの挙動は理解できたと思いますが、「いや〜関数をコピーしたとしてもオブジェクトのプロパティ使いたいな〜、グローバルスコープに同様のプロパティを追加するのやだな〜、なんかいい方法ないかな〜」 と思う方がいるかもしれません

いい方法あるんですよ!!!
thisの参照先や引数を束縛する方法が

そのキーワードがbind、call、applyです

まずはbindの動きを確認していきましょう!

ちなみに、下記の例ではコールバック関数を使用していますが、
コールバック関数も関数をコピーして実行した際とthisの参照先は変わらないので問題ありません

qiita.js
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は配列を指定します

では、コードを見て確認しましょう

qiita.js
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で導入された、無名関数を記述しやすくした省略記法です

アロー関数に関してはさまざまな書き方があるので、とりあえずそれらをみてみましょう

qiita.js
//関数宣言
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の値がセットされないことです

qiita.js
window.name = 'John';

const person = {
    name: 'Tom',
    hello: () => {
        console.log('Hello ' + this.name);
    }
}
person.hello(); //Hello John

上記のコードは、普通に考えるとメソッドを実行しているのでpersonオブジェクトの'Tom'を参照すると考えると思います

しかし、アロー関数はそのコンテキスト内ではthisを取らないのでスコープチェーンをたどり上のグローバルコンテキストを参照することになリます
従って、windowオブジェクトのnameプロパティの値が参照されるという流れになります

つまり、「アロー関数ではレキシカルスコープのthisが参照される」 ということですね!

終わりに

今回は関数とオブジェクト編として学習したことをまとめました
次回は非同期処理についてまとめたいと思います

最後に、クロージャーとアロー関数を組み合わせたコードを見て終わりにしましょう

qiita.js
//通常の状態
function addNumberFactory(num) {
    function addNumber(value) {
        return num + value;
    }
    return addNumber;
}

//アロー関数で省略した状態
const addNumberFactory = num => value => num + value;

お疲れ様でした〜

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5