概要
Modern JavaScriptをイチから学び直すという題材でセミナーした時の資料
当資料ではchromeのdevelopper toolだけを使ってJavaScriptの基本を説明する。
基本の振り返り
実行環境
chrome Developper toolで基本操作可能。
chromeを開き、 F12
コンソールを開く
var, const, let
変数の宣言。旧石器時代では var
を使っていた。
var hoge = 'aa'
var
は再代入できてしまう困った君なので
var i = 1
i += 1
i = 'a'
i += 1
//-> 'a1'
なんてことになってしまう。
もちろん、こんな書き方は通常しないけど i
がグローバル変数で
複数の関数から参照されていたりすると、うっかり書き換えてしまう可能性はある。
var i = 1
i += 3
function fuga() {
i += 1
}
function fugafuga() {
i = 'a'
}
fuga()
fugafuga()
fuga()
こういう悲劇がつながることから、現代では var
は完全に封印する。
var
を書いたら負け。
代わりに const
を使う
const i = 1
i = 2 // error
i = 'a' // error
ちなみに現代ではどうしても再代入したい場合に let
というものも用意されている。
let j = 1
j = 2
j = 'a'
しかし、うまく書けば let
を一切使わなくてもコーディングできるので
let
も書いたら負けと考えるべき
セミコロン
セミコロン文化は死にました(諸説あり)
一切つけなくてOK。邪魔だし。
int, string, bool
JavaScriptは動的型付言語なので、変数定義の時に型宣言は不要。
const aa = 'aa'
const bb = 1
const isAlive = true
三項演算子
if
文も使うけど、三項演算子の方がよりスマートにかける
const name = 'tom'
if (name === 'tom') {
console.log('tomです')
} else {
console.log('tomじゃない')
}
console.log(
(name === 'tom') ? 'tomです' : 'tomじゃない'
)
// 三項演算子を使うと判定の結果を戻り値として返せるので
// 上記のように関数の中で条件分岐して結果を即座に渡すということができる
三項演算子を連ねることで switch
文の代わりとしても使える。
三項演算子の方が汎用的でgood
const month = 2
console.log(
(month === 1)
? 'January'
: (month === 2)
? 'February'
: (month === 3)
? 'March'
: 'Other'
)
値入ってる?
ぬるぽ対応。JavaScriptは非常に簡単かつ強力な判定の仕方ができる
const value1 = undefined
const value2 = null
const value3 = 'hoge'
console.log((value1) ? true : false) // => false
console.log((value2) ? true : false) // => false
console.log((value3) ? true : false) // => true
// 判定にかけるだけで、値が入ってたらtrue, 入ってなかったらfalseになる
例えば関数のパラメタで値がない場合は処理しないという時は以下のように書くことができる。
const join = (val1, val2) => {
if (!val1) return
if (!val2) return
return val1+val2
}
オブジェクト
JavaScriptでいうオブジェクトは、オブジェクト思考的なオブジェクトでなく
ディクショナリーとして機能するものと理解した方が早い。
{}
というリテラルで作成することができる。ブレース。
これだけ覚えておけばいい。
中はkeyとvalueを詰める。
const obj = {}
obj.id = 1
// => { id: 1 }
ちなみにJavaScriptでのオブジェクトは第1級変数なので
intとかstringとかと同じ扱いができる。
ということはオブジェクトを関数の引数に渡したり、関数の戻り値をオブジェクトにもできる。
これを利用すると、関数へ不定数の引数を渡したりすることができる。
const obj2 = {
name: 'hoge',
age: 18,
}
function hoge(obj) {
console.log(obj.name)
console.log(obj.age)
obj.isAlive = true
return obj
}
hoge(obj2)
Array
配列。JavaScriptではリテラルで []
で作れる。ブラケット。
配列の数とか定義する必要なし。
配列の中の型を定義する必要なし。
配列の中に文字とか数字とかオブジェクトとかつめても問題ない。
取り出すのは要素のインデックスを指定するのが超基本。
ただ後述するが、そんなとり方はかっこ悪いのであんまりしない
const arr = ['a', 1, { name: 'text'} ]
console.log(arr[0])
console.log(arr[1])
console.log(arr[2])
Spread Separator
JavaScriptはSpread Separatorという文明を手に入れた。
オブジェクトの中身や配列の中身をビョーンと一気に展開するというテク。
...
というリテラルで動作する。
これは非常に便利で多用する。必須で覚える。
オブジェクトの階層を変えたいなーと言う場合に簡単にできる
const user = {
name: 'kana momonogi',
age: 23,
blood: 'AB',
height: 153,
weight: 40,
size: {
B: 80,
W: 54,
H: 80,
}
}
const {
size,
...other
} = user
const edited_user = {
...other,
...size,
}
console.log(edited_user)
もとのObjectを壊すことなく、キーを追加したオブジェクトを作るなんてこともできる
const user = {
name: 'kana momonogi',
age: 23,
blood: 'AB',
height: 153,
weight: 40,
size: {
B: 80,
W: 54,
H: 80,
}
}
const edited_user = {
...user,
shoe_size: 23.5,
}
// user.shoe_size = 23.5 でもできるが、これだとuserオブジェクトを変更してしまう
// 基本的にもらった変数は変更しないように作ることで他への影響を減らすことになる(immutableということ)
console.log(edited_user)
オブジェクトとオブジェクトをくっつける(merge)する場合も簡単
const user = {
name: 'kana momonogi',
age: 23,
blood: 'AB',
height: 153,
weight: 40,
size: {
B: 80,
W: 54,
H: 80,
}
}
const additional_data = {
shoe_size: 23.5,
cup: 'E'
}
const edited_user = {
...user,
...additional_data // 同一キーがあると上書き。それ以外は追加。
}
console.log(edited_user)
オブジェクトの中の必要な部分だけ取り出すことも可能
const user = {
name: 'kana momonogi',
age: 23,
blood: 'AB',
height: 153,
weight: 40,
size: {
B: 80,
W: 54,
H: 80,
}
}
const {
name,
...other
} = user // user objectから指定したキーの値だけ取り出す
const {
size,
} = other
const {
B,
W,
} = size
console.log(name)
console.log(B)
console.log(W)
また、SpreadSeparatorはオブジェクトだけでなく配列にも使うことができる。
const arr = [1, 2, 3, 4, 5]
const added_arr = [...arr, 6] // こうすると元のarrを壊すことなく、要素を追加したarrを作ることができる
console.log(added_arr)
文字列内の変数展開
これも多用する。
const age = 23
const str = `私は${age}歳です。`
console.log(str)
ちなみに ${}
の中は変数展開をする。
そしてJavaScriptでは関数も変数。
三項演算子も利用できる。
const age = undefined
const str = `私は${(age) ? age : '??'}歳です`
console.log(str)
ループ使わない
配列を回す場合、ループといえば旧石器時代では for
が定番だった。
for
も使えないこともないが、どうしても変数の再代入が発生することが多いのと
だいたい配列の要素分全部回すことばかりなのでわざわざ要素を指定する必要性が少なかったりと
あまりいいループとはいえないことが多い。
そんな問題点は for
の代わりに map
を使うことで解消できる。
map
は要素分すべて回し、その結果を別の配列として返却することができる。
これを使うと、元の配列を壊すことなく、加工した配列を作ることができる。
const arr = [1, 2, 3, 4, 5]
// 旧石器時代
for (let i = 0; i < arr.length; i++) { // あ、let出た
arr[i] = arr[i] * 2 // 元のarrを破壊的に変更している
}
console.log(arr)
// 現代
const arr2 = [1, 2, 3, 4, 5]
const result = arr2.map(elem => { // mapの中は関数。mapは関数の引数に1つずつ要素を入れてくる
const edited_elem = elem * 2 // もらった1要素に対する編集の処理
return edited_elem // returnで値を返すと、その要素が編集後の配列の1要素になる
})
// この処理においてはarr2は何も変更されていない←重要
console.log(result)
// さらに省略表記を使うとこうなる
const arr3 = [1, 2, 3, 4, 5]
const result2 = arr3.map(elem => elem * 2)
console.log(result2)
mapは10個の要素の配列を渡すと、必ず10個の要素の配列を返却する。
(インプットと同数の要素しか返却できない)
じゃあ、10個の要素のうち、該当する要素だけを返したい場合は? filter
を使う
const arr = [1, 2, 3, 4, 5]
const result = arr.filter(elem => {
return (elem % 2 !== 0) // return でtrueを返すと返却対象。falseを返すと除外される
})
console.log(result) // => [1, 3, 5]
// 省略して書くとこうなる
const result2 = arr.filter(elem => (elem % 2 !== 0))
console.log(result2)
filter
と map
は組み合わせることができる
const arr = [1, 2, 3, 4, 5]
const result =
arr
.filter(elem => (elem % 2 !== 0))
.map(elem => elem * 2)
console.log(result) // => [2, 6, 10]
関数
旧石器時代で function
と呼ばれていたもの。
現代では function
は書いたら負け。代わりに () => {}
を使う。??。
() => {}
はアロー関数と呼ばれ、とにかく function
より優れているとだけ覚えておけばいい。
基本形は以下になる
// 旧石器時代
function tweet(name, age) {
console.log(`私の名前は${name}。${age}歳。よろしくね`)
}
tweet('kana momonogi', 23)
// 現代
const tweet2 = (name, age) => {
console.log(`私の名前は${name}。${age}歳。よろしくね`)
}
tweet('kana momonogi', 23)
なお、アロー関数は基本形から省略形に変更することができる。
省略形を使うことができる場合、極力省略形を使う方がカッコイイとされる。
// 基本形
const tweet3 = (name) => {
console.log(`私は${name}です`)
}
// 引数が1つの場合、パーレンを省略することができる
const tweet4 = name => {
console.log(`私は${name}です`)
}
// 更にブロック内が1行の場合、ブレースを省略することができる
const tweet5 = name => console.log(`私は${name}です`)
// 引数がいらない関数の場合は逆にパーレンが必要になる
const tweet6 = () => console.log(`引数がない関数`)
また、戻り値を返す関数の場合は以下のようになる
// 基本形
const tweet7 = name => {
return `私は${name}です`
}
console.log(tweet7('kana momonogi'))
// ブロック内が1行で実行結果をそのまま返す場合はブレースとreturnを省略できる
const tweet8 = name => `私は${name}です`
console.log(tweet8('kana momonogi'))
第一級変数の関数
旧石器時代からJavaScriptの関数は第一級変数。
ということはintやstringの仲間。
ということは、引数にできたり、関数の戻り値にすることができる。
JAVA等他言語でもできるけど、案外活用されてない事が多い。
慣れてないので難しく感じることがあるが、とても便利で多用する。
これができるようになるとコーディングが1段カッコよくなる。
例えば関数の引数に関数を渡すのは以下のようになる
const writeLog = text => console.log(`LOG: ${text}`)
const ajax = (param, callback) => {
console.log('execute')
console.log(param)
callback('finish')
}
ajax({ id: 1, name: 'hoge'} , writeLog) // 関数を渡す場合、`()`をつけないところがポイント
また、関数の戻り値が関数は以下のようになる
const basic_method = name => console.log(`I'm ${name}`)
// 関数をもらって、パワーアップした関数を返す
const wrap = func => {
const plus_method = name => {
func(name)
console.log('thank you')
}
return plus_method
}
const execute_method = wrap(basic_method)
execute_method('kana momonogi')
ちなみに上記も省略形でかけるので省略すると以下のようになる
const basic_method = name => console.log(`I'm ${name}`)
const wrap = func =>
name => {
func(name)
console.log('thank you')
}
const execute_method = wrap(basic_method)
execute_method('kana momonogi')
パラメタ・戻り値にobjectやarray
関数の引数にobjectやarrayを渡すことができる。また戻り値も同様。
これを活用すると引数・戻り値の数を固定しない柔軟なやりとりができる。
const find = param => {
const {
min_age,
max_age,
gender,
} = param || {}
const data = [
{ age: 57, gender: 'male', name: 'tom'},
{ age: 48, gender: 'female', name: 'julia'},
{ age: 23, gender: 'female', name: 'kana'},
]
return data
.filter(row =>
(min_age)
? (row.age >= min_age)
: true
.filter(row =>
(max_age)
? (row.age <= max_age)
: true
)
.filter(row =>
(gender)
? (row.gender === gender)
: true
)
)
}
console.log(
find({
min_age: 45,
max_age: 60,
})
)
メソッドチェーン
関数の戻り値に関数をセットすると関数の連発をすることができる。
これをメソッドチェーンという。
例えばこんな感じ
const wrap = value => {
const addOne = () => wrap(++value)
const double = () => wrap(value * 2)
const power = () => wrap(value * value)
const getResult = () => value
return {
addOne,
double,
power,
getResult,
}
}
console.log(
wrap(1)
.addOne()
.double()
.power()
.getResult()
)
ネットを漁ってprototypeを使わず関数型のメソッドチェーンのサンプルを探したけど
いいのがなかったので自分で作ってみたところ思いの外いい出来でビックリした
非同期処理
JavaScriptの処理は同期的なものと非同期的なものがある。
同期的:ソース見たまま、上から順に処理されてく。
非同期的:上から処理していくが、間に非同期処理があると非同期をキックだけして結果を待たずに次を実行していく。
よくある ajax
処理は非同期的。
サーバにアクセスすると、サーバからの結果を待たずしてjavascriptの処理は進んでいく。
この性質をよく理解して、結果が戻ってきたらXXをするという記述をしないと思った結果にならない。
const load = param => {
setTimeout(() => {}, 5000) // 非同期のイメージ。setTimeoutは非同期の関数
console.log('after setTimeout')
}
console.log('start')
load()
console.log('after load')
上記の処理だと5秒待ってから after setTimeout
を期待したいところだが
実際には一瞬ですべて実行されてしまう。
非同期の処理はキックだけして結果を待ってくれない為。
これを待ってから処理させるにはこのように書く
const load = param => {
setTimeout(() => { console.log('after setTimeout')}, 5000)
}
console.log('start')
load()
console.log('after load')
setTimeoutの第1引数は関数を指定するが、この関数はsetTimeoutで待った後、
自動的に実行されるようになっている。
こういうのをコールバック関数という。
一見、これでOKそうに見える。そして流行した。
そしたらコールバック地獄という疫病が流行った。
例えば、サーバにデータを問い合わせて、その結果を受けてまたサーバにアクセスする場合、
callbackの中でさらにcallbackを呼んでいくことになる。
これが無限の繋がりをみせてコールバックなのかなんなのかもわからなくなることを
コールバック地獄と呼んでいる。
こんな具合である
const load = param => {
setTimeout(() => {
console.log('load')
load2(param)
}, 2000)
}
const load2 = param => {
setTimeout(() => {
console.log('load2')
load3(param)
}, 2000)
}
const load3 = param => {
setTimeout(() => {
console.log('load3')
}, 2000)
}
console.log('start')
load()
console.log('after load')
非同期処理との戦いは旧石器時代からずっと行われ、様々な解決策が生まれてきた。
そこで生まれたのが Promise
で、それを使いやすくしたのが async
await
である
非同期処理は Promise
という関数で括ってコールバックを定型的に書くようになった。
Promise
の連鎖を簡単に、ネストしないように書くため、 async
await
が生まれた。
上記の例を書き直すとこうなる
// setTimeoutのcallbackの部分にPromiseのresolveを入れることでPromise化できる
const customSetTimeout = ms =>
new Promise(resolve => setTimeout(resolve, ms))
// 非同期を待つ処理を持つ関数を定義する時は必ず async 指定する
const load = async param => {
await customSetTimeout(2000) // awaitと書くとそれ以降に書いたコードは非同期後に実行される
console.log('load1') // awaitの処理が終わってから実行される
await customSetTimeout(2000)
console.log('load2')
await customSetTimeout(2000)
console.log('load3')
}
console.log('start')
load()
console.log('after load')
なにがなんだかわからない?
非同期との戦いの歴史が長すぎるのと、その仕組が非常に複雑であるので
簡単に説明することができない。すまない。
それくらい非同期処理は厄介なもの(だった)
今は async
await
の書き方を覚えて、 await
の後ろは非同期を待ってから処理される
とだけ覚えておいてほしい。