Node.js
イベントループによる並行処理
Node.jsの第一の特徴としてあげられるのは、並行処理をマルチスレッドではなくイベントループによって実現するというものです。イベントループは単一のスレッド(シングルスレッド)で動作するため、マルチスレッドのようなリクエスト数の増大に伴う問題が起きづらくなっています。
イベントループは実行すべきタスクをキューに積み、これを1つづつ取り出してシングルスレッドで実行していきます。逐次処理のようですが、これで並行処理を実現できるのは、ここでのタスクが一連の処理をI/Oの発生するタイミングを境に分割した物だからです。プログラムはI/O実行時にその完了後のタスクを指定し、実際にI/Oが完了すると指定されたタスクがキューに追加されます。
Ecmascript, Web標準
JavaScriptの言語使用はECMAScript(ECMA Internationalという団体に属する技術いんかいのTC39での議論で標準化する)標準によって規定されます。また、ブラウザ環境におけるJavaScriptのAPI仕様を定めるものとして、Web標準がある。これはHTML, CSS, JavaScriptなどのブラウザ関連の技術について、ブラウザ館での使用を統一するための標準です。
ECMAScript 標準
ECMAScript標準のバージョンは2015年にリリースされたES2015以降、西暦であらわされるようになりました。ES2015はもともとES6として標準化が進められていたが、以前のバージョンとの期間が空きすぎてしまったために年次リリースができるように改められました。この結果、ES6と呼ばれていた物は最終的にES2015としてリリースされ、以降ES2016, ES2017, ES2018,...ES2020と続いている。
javascriptの基本
変数宣言
変数宣言をするには、[const, let]を用います。
- const: 宣言した変数にには値の再割り当てができない(定数)
- let: 再割り当てが可能
この両者の特徴として、ブロックのスコープ無ことと、同一スコープないで同じ名前の変数を複数同時に宣言できないことがあげられます。
> const foo = 'foo'
undefined
> let bar = 'bar'
undefined
> foo = 'foo2'
Uncaught TypeError: Assignment to constant variable.
> bar = 'bar2'
'bar2'
constとletはES2015で導入されましたが、それまではJavaScriptで変数宣言する際は"var"を使用していました。今でも使用可能ですが、すこー部が関数や同じなあ絵の変数を繰り返し宣言可能なことなど、わかりづらく不具合の原因となるので使用しない方が良い。
関数
javascriptで関数を定義する方法はいくつかあります。
- 名前をつけて宣言する(他の言語と同様に定義)
> function add1(a, b) {return a + b}
undefined
- 関数式で関数を生成
> const add2 = function(a, b) { return a + b }
undefined
- 関数式でも関数に名前をつけられる
> const add3 = function addFn(a, b) { return a + b }
undefined
- アロー関数での定義(ES2015で導入)
// アロー関数式
> const add4 = (a, b) => { return a + b }
undefined
// アロー関数式の省力記法({}を省略するとreturnなしで値が返される)
> const add5 = (a, b) => a + b
undefined
// パラメータが一つならパラメータを囲む()も省力可能
> const addOne = a => a + 1
undefined
関数宣言で作る関数はモジュール内でホスティングされ。宣言前に参照できるが、関数式で作成する関すは作る前に参照するとエラーになります。
console.log(add6(1, 2))
console.log(add7(1, 2))
function add6 (a, b) {
return a + b
}
const add7 = (a, b) => a + b
>>>
// 関数宣言で作成したadd6は宣言前に参照可能
3
// 関数式で作成したadd7は作る前に参照するとエラーになる
Uncaught ReferenceError: Cannot access 'add7' before initialization
オブジェクト
今回は、オブジェクトリテラルから生成されるようなプレーンなオブジェクトについて説明します。
// オブジェクトリテラルによるプレーンなオブジェクトの生成
> const obj1 = { propA: 1, propB: 2 }
undefined
// プロパティ名を指定して取得(ドット記法)
> obj1.propA
1
// プロパティ名を指定して取得(ブランケット記法)
> obj1['propA']
1
// プロパティの追加
> obj1.propC = 3
3
> obj1
{ propA: 1, propB: 2, propC: 3 }
// プロパティの削除
> delete obj1.propC
true
> obj1
{ propA: 1, propB: 2 }
プロパティの追加、削除をイミュータブルに行うには、スプレッド構文、レスト構文に使います。
'use strict'
const print = console.log
const obj1 = { propA: 1, propB: 2 }
// スプレッド構文でexecurteを追加
const obj2 = { ...obj1, propC: 3 }
// 元のオブジェクトは不変
print('obj1', obj1)
// 新しく生成されたオブジェクトにキーが追加される
print('obj2', obj2)
# レスト構文でexecuteを削除
const { propA, ...obj3 } = obj2
// 元のオブジェクトは不変
print('obj2', obj2)
// 新しく生成されたオブジェクトにキーが追加される
print('obj3', obj3)
>>>
obj1 { propA: 1, propB: 2 }
obj2 { propA: 1, propB: 2, propC: 3 }
obj2 { propA: 1, propB: 2, propC: 3 }
obj3 { propB: 2, propC: 3 }
プロパティの値を取得、設定する際に関数を実行するgetter, setterという機能もあります。
'use strict'
const print = console.log
const price = {
value: 100,
get withTax() {
return Math.floor(this.value * 1.1)
},
set withTax(withTax) {
this.value = Math.ceil(withTax / 1.1)
}
}
// getterから値を取得
print(price.withTax) // >>> 110
// setterで値を設定
price.withTax = 333
// getterで取得される値が更新されることを確認
print(price.withTax) // >>> 333
// setterにより正しく値が設定されることを確認
print(price.value) // >>> 303
配列
配列リテラルで初期化します。
'use strict'
const print = console.log
// 配列リテラルで初期化
const arr1 = ['foo', 'bar']
print('arr1.length', arr1.length) // arr1.length 2
// 指定したインデックス要素を取得
print('arr1[1]', arr1[1]) // arr1[1] bar
// 指定した要素のインデックスを取得(存在しない場合:-1)
print(arr1.indexOf('bar')) // 1
// 要素が配列に含まれるかどうか
print(arr1.includes('bar')) // true
// 前要素を引数に指定した文字列を結合
print(arr1.join('-')) // foo-bar
配列の末尾への要素の追加、削除はそれぞれ push(), pop()を使います。同様に配列の先頭への要素の追加、削除はそれぞれunshift(), shift()を使います。
'use strict'
const print = console.log
const arr1 = ['foo', 'bar']
// 末尾に要素を追加
arr1.push('baz')
print('arr1', arr1) // arr1 [ 'foo', 'bar', 'baz' ]
// 末尾に要素を複数追加
arr1.push('a', 'b', 'c')
print('arr1', arr1) // arr1 [ 'foo', 'bar', 'baz', 'a', 'b', 'c' ]
// 末尾の要素を削除
arr1.pop()
print(arr1) // [ 'foo', 'bar', 'baz', 'a', 'b' ]
// 先頭に要素を追加
arr1.unshift('qux')
print(arr1) // [ 'qux', 'foo', 'bar', 'baz', 'a', 'b' ]
// 先頭の要素を複数追加
arr1.unshift('d', 'e', 'f')
print(arr1)
/*
[
'd', 'e', 'f',
'qux', 'foo', 'bar',
'baz', 'a', 'b'
]
*/
// 先頭要素を削除
arr1.shift()
print(arr1)
/*
[
'e', 'f', 'qux',
'foo', 'bar', 'baz',
'a', 'b'
]
*/
要素の追加、削除をイミュータブルに行うには、オブジェクトの場合と同様スプレッド構文、レスト構文を使います。
'use strict'
const print = console.log
const arr2 = ['foo', 'bar', 'baz']
// スプレッド構文で先頭と末尾に要素を追加
const arr3 = ['a', ...arr2, 'b', 'c']
print('arr3', arr3) // arr3 [ 'a', 'foo', 'bar', 'baz', 'b', 'c' ]
// 末尾の配列はそのまま
print('arr2', arr2) // arr2 [ 'foo', 'bar', 'baz' ]
// レスト構文で先頭の要素を削除
const [head1, head2, ...arr4] = arr2
print('arr4', arr4) // arr4 [ 'baz' ]
// 末尾の配列はそのまま
print('arr2', arr2) // arr2 [ 'foo', 'bar', 'baz' ]
配列のレスト構文では、レスト要素が配列の最後でならなければならないという制約があります。配列から要素を削除する際に、これが不都合になる場合はレスト構文の代わりに配列の部分配列を返す'slice()'というメソッドを使います。
'use strict'
const print = console.log
const arr2 = ['foo', 'bar', 'baz']
// 切り出したい最初の要素のインデックスと最後の要素の次のインデックスを指定して部分配列を取得する
print(arr2.slice(0, 2)) // [ 'foo', 'bar' ]
// インデックスに負の値を指定すると配列の最後から数えたいんでくすとして扱われる
print(arr2.slice(0, -1)) // [ 'foo', 'bar' ]
// 第2引数を小着すると、配列の最後まで切り出す意味になる
print(arr2.slice(2)) // [ 'baz' ]
// 第一引数も第2引数も省略すると、配列の最初から最後までコピーする
print(arr2.slice()) // [ 'foo', 'bar', 'baz' ]
配列に対する反服処理はfor文またはfor...of文で記述できます。
'use strict'
const print = console.log
const arr2 = ['foo', 'bar', 'baz']
for(const e of arr2) {
print(e)
}
>>>
foo
bar
baz
forやfor...ofを使わずに配列の持つメソッドを使って反復処理を実行すつ処理もあります。そのようなメソッドは引数として反復処理の内容を記述した完酢を受け取り、その処理を各要素に適用します。どのメソッドもイミュータブルで、元の配列を変更しません。また、全てのメソッドでコールバック関数が受け取るパラメータは共通で、配列の要素、その要素のインデックス、捜査対象の配列の3つをこの順番で受け取ります。
'use strict'
const arr2 = ['foo', 'bar', 'baz']
// forEach(): 各要素にコールバック関数の処理を適用し、戻り値はない
arr2.forEach(console.log)
// map(): 各要素をコールバック関数の戻り値に置き換えた配列を返す
console.log(arr2.map(e => e + e))
// filter(): コールバック関数が真の値を返す要素の身を含む配列を返す
arr2.filter(e => e.startsWith('b'))
// finc(): コールバック関数が真の値を返す最初の要素を返す
arr2.find(e => e.startsWith('b'))
>>>
foo 0 [ 'foo', 'bar', 'baz' ]
bar 1 [ 'foo', 'bar', 'baz' ]
baz 2 [ 'foo', 'bar', 'baz' ]
[ 'foofoo', 'barbar', 'bazbaz' ]
find()はコールバック関数が一度でもtrueを返せば結果が確定するため、その時点で反復処理を終了します。
クラス
JavaScriptのクラスは、オブジェクト思考言語の経験のある方なら直感的に理解できると思います。privateなメンバーは名前の先頭に#をつけるという、特殊になっています。
'use strict'
class Foo {
// privateフィールド
#privateField = 1
// publicフィールド
publicField = 2
// staticなprivateフィールド
static #staticPrivateField = 3
// staticなpublicフィールド
static staticPublicField = 4
constructor(parameter) {
this.filedInitializedInConstructor = parameter
console.log('Foo constructor')
}
// privateなgetter
get #computed() {
return this.publicField * 2
}
// privateなsetter
get computed() {
return this.#computed
}
set #computed(value) {
this.publicField = value / 2
}
set computed(value) {
this.#computed = value
}
#privateMethod() {
return this.#privateField
}
publicMethod() {
return this.#privateField
}
static #staticPrivateMethod() {
return this.#privateField
}
static staticPublicMethod() {
return this.staticPublicField
}
}
const fooInstance = new Foo(100)
console.log(fooInstance.#privateField) // SyntaxError: Private field '#privateField' must be declared in an enclosing class
console.log(fooInstance.publicField) // 2
console.log(fooInstance.filedInitializedInConstructor) // 100
console.log(fooInstance.#compute) // SyntaxError: Private field '#compute' must be declared in an enclosing class
console.log(fooInstance.computed) // 4
fooInstance.#computed = 10 // SyntaxError: Private field '#computed' must be declared in an enclosing class
fooInstance.computed = 10
console.log(fooInstance.computed) // 10
console.log(fooInstance.publicMethod()) // 1
console.log(Foo.#staticPrivateField) // SyntaxError: Private field '#staticPrivateField' must be declared in an enclosing class
console.log(Foo.staticPublicMethod()) // 4
継承
class Bar extends Foo {
constructor(parameter) {
super(parameter)
this.subClassPublicField = 100
console.log('Bar constructor')
}
publicMethod() {
return super.publicMethod() * this.subClassPublicField
}
}
const barInstance = new Bar(100)
console.log(barInstance.publicField) // 2
console.log(barInstance.subClassPublicField) // 100
console.log(barInstance.subClassMethod()) // TypeError: barInstance.subClassMethod is not a function
console.log(Bar.staticPublicField) // 4
console.log(Bar.staticPublicMethod()) // 4
等価性
JavaScriptには等価性の評価のための演算子として"==="と"=="が存在しますが、常に"==="を使うようにしてください。"==="による等価性の評価は厳密で結果が容易に予測できますが、"=="はそうではないため、利用するとコードの可読性を著しく下げてしまう。
0 === '' // > false
0 == '' // > true
// プリミティブの場合、同じリテラルで表現されるあたい同士の場合はtrueになる
1 === 1 // > true
// オブジェクト同士を比較する場合は構造が同じだけで別々のオブジェクトなので、falseになる
{ 'foo': 1 } === { 'foo': 1 } // > false
commonJs モジュール
modele.ecportsとrequire()
CommonJSモジュールを使う場合、それぞれのJavaScriptファイルが個別のCommonJSモジュールとして扱われます。CommonJSモジュールは、モジュールレベルのスコープでNode.jsが自動的に割り当てるmoduleという変数のexportsプロパティ(module.exports)を通して、外部に関数や変数を後悔します。一方外部モジュールをロードする際には同じくモジュールスコープで割り当てられるrequire()という関数を使います。
module.exports.add = (a, b) => a + b
module.exports.subscribe = (a, b) => a - b
const math = require('./math')
const result = math.add(1, 2)
console.log(result)
>>>
3
__filenameと__dirname
- __filename: ファイル名の絶対パス
- __dirname: ディレクトリ名の絶対パス
strictモード
ファイルの先頭に 'use strict'
と記述すると、そのファイルないでstrictモードが有効になります。strictモードは安全でパフォーマンスに優れたコードの記述を促すためES5で導入されたJavaScriptのモードです。特徴としてはいかがあげられる。
- 無効な処理や意図せぬ結果を引き起こしうる処理をエラーにする
- JavaScriptエンジンによる最適化を阻害する機能を制限または向こうにする
- ECMAScriptの新しいバージョンへの移行を容易にするため、将来使われうる識別子を予約語として変数等の名前に使えないようにし、将来違う意味を持ちうる構文をエラーにする。
'use strict'
let myString = 'あいうえお'
mystring = 'かきくけこ'
console.log*mystring
>>>
mystring = 'かきくけこ'
ReferenceError: mystring is not defined