JavaScriptの基礎知識【初心者向け】

# 概要
N番煎じネタ、あまり踏み込んだネタもやらず、クライアントAPIやNodeにしかないものによらない、初心者向けの一般的なネタのみにする予定

## 基礎編
### [var, let, const](https://qiita.com/niisan-tokyo/items/bacb5ed98ea10831eeba)
|   | スコープ | 代入など   |
|:-:|:---:|:---:|
| var | 関数スコープ | 代入・再宣言ともに**可** |
| let | ブロックスコープ | 代入**可**/再宣言**不可** |
| const | ブロックスコープ | 代入・再宣言ともに**不可**　|
varは関数スコープなので同一関数内だと同じ名前は再代入になって書き換えられる、これは思わぬバグをうむのでvarの使用は推奨されない。
`+=`、`++`なども代入になるために`const`に使用してしまうとエラー(TypeError)になる。
`const`は代入もできないため宣言時に値を確定させないとエラー(SyntaxError)になる。

```js:error.js
const i=0;
i++; // TypeError
const undef; // SyntaxError
const undef2=undefined; // 実はOK
```
#### スコープの優先
同じ名前の変数はより狭いスコープのものが優先される。


```js:scope.js
const c=0;
var v=0;
{
    const c=1; // スコープの優先で再宣言にならない
    var v=1; // ブロックスコープで縛られないので外のvを書き換える
    console.log(c, v); // 1, 1
}
console.log(c, v); // 0, 1 //　外にあるcなので0
```
スコープの優先度はバグを作りやすいので注意が必要。

### [プリミティブ型とObject型](https://developer.mozilla.org/ja/docs/Web/JavaScript/Data_structures)
JavaScriptではプリミティブ型以外はすべてObject型になります。プリミティブ型はimmutable(変更不可)[^immutable]でObject型で参照型です(後述)

#### プリミティブ型
- null
- undefined
- 真偽値
- 文字列
- 数値
- シンボル (es2015で追加)

の6個です。

#### constなObject型
JavaScriptの`const`は代入不可であるが変更可である。

#### 参照型としてのObject型
Object型は参照型であり関数の引き渡しは参照渡しである。

```js:Object.js
const obj={ a: 'hoge', b: 'hoge' };
obj.b='fuga'; // 変更可能
const swap=(a)=>{
   const tmp=a.a;
   a.a=a.b;
   a.b=tmp;
}
swap(obj); // objは参照渡しなので呼び出し元も変更される

console.log(obj); // { a: 'fuga', b: 'hoge' }
```
このようにJavaScriptでは変更されないObjectを作ることはできないので、プログラマーがObjectの状態に常に気にする必要がある。

#### Classは糖衣構文
糖衣構文だから使うな、ではなく**糖衣構文あることを理解して使うべし**ということを強調しておく。
JavaScriptのClassはObjectでクラスを実現するための糖衣構文なのでプロパティは編集可能である。
また、Class自身は`function`でインスタンス化された変数は`object`である。

```js:Class.js
const MyClass=class{}
const myClass=new MyClass();

myClass.hoge='hoge';

console.log(typeof MyClass); // function
console.log(typeof myClass); // object
```

[^immutable]: [Primitive (プリミティブ)(MDN)](https://developer.mozilla.org/ja/docs/Glossary/Primitive)

## 等価演算子
### プリミティブ型
プリミティブ型は値が等しいかどうかの判定をする。`==`は型変換をするが、`===`は型変換をしない厳密な判定になる。
JavaScriptの型変換では思わぬ値に変換される場合があるので`==`は使わないことを**強く**推奨する。
(非存在を判定するための`if( val==null ) console.log(var, 'は存在しません');`は数少ない例外の一つ)

### 参照型(オブジェクト)
オブジェクトは参照値が等しいか調べる[^ref]ためオブジェクトの中の値は一切関係ない。
中身を見るような`equal`関数を作っても以下のように中身がオブジェクトとプリミティブなのかによって変わる。

```js:equalObj.js
const obj1={ a: 'hoge', b: 'fuga' };
const obj2={ a: 'hoge', b: 'fuga' };
const obj3=obj1;

const equal=(obj1, obj2)=>{
    if( Object.keys(obj1).length!==Object.keys(obj2).length ) return false;
    for( const [ key, val] of Object.entries(obj1) ){
        if( obj1[key]!==obj2[key] ) return false;
    }
    return true;
}

console.log('obj1 == obj2 ', obj1==obj2); // false
console.log('obj1 == obj3 ', obj1==obj3); // true
console.log('equal(obj1, obj2) ', equal(obj1, obj2)); // true

const arr1=[ obj1, obj2 ];
const arr2=[ { ...obj1 }, { ...obj2 } ]; // スプレッド構文による代入
const arr3=[ obj1, obj2 ];

console.log('arr1 == arr2 ', arr1==arr2); // false
console.log('arr1 == arr3 ', arr1==arr3); // true
console.log('equal(arr1, arr2) ', equal(arr1, arr2)); // false 中身のオブジェクトの参照値は違う
console.log('equal(arr1, arr3) ', equal(arr1, arr3)); // true 
```
このようにJavaScriptでオブジェクトの比較をするには型によって再帰をするなどの処置が必要で非常に注意が必要である。

[^ref]: プリミティブの参照はJavaScriptでは触れないためプリミティブ型の参照値を比べるはできない。

## 型判定
### [typeof演算子](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/typeof)

```js:typeof.js
console.log(typeof undefined); // undefined
console.log(typeof null);      // object
console.log(typeof 0);         // number
console.log(typeof 'hello');   // string
console.log(typeof true);      // boolean
console.log(typeof Symbol());  // symbol
const arrFunc=()=>{};
console.log(typeof arrFunc);   // function
console.log(typeof function(){}); // function
console.log(typeof {});           // object
console.log(typeof []);           // object
```
**`null`は`"object"`である**
プリミティブに対しては関数のみ`"function"`がありアロー関数も`"function"`になる。

### [Array.isArray(value)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
配列かどうか判定する、`typeof`では配列かオブジェクトかは判定できないのでコチラを使う。

### [Object.prototype.toString.call()](https://qiita.com/amamamaou/items/ef0b797156b324bb4ef3)
組み込み型に対して内部プロパティ`[[Class]]`を使って判定する方法`[object [[Class]] ]`の形で帰ってくるので適当に切り取って結果を得る

```js:toString_call.js
onst getType=(obj)=>{ return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase(); };

console.log(getType(undefined)); // undefined
console.log(getType(null));      // null

console.log(getType(function(){}));       // function
console.log(getType(function * (){}));    // generatorfunction
console.log(getType(async function(){})); // asyncfunction
console.log(getType(new Map()));          // map
console.log(getType(class{}));            // object
```
`toLowerCase`を用いて小文字にしている。`function*`や`async function`もきちんと判定できる。
独自実装のクラスはObjectになる。

### 独自クラスの判定
`Object.constructor`もしくは[instanceof](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/instanceof)を使う

### JavaScriptの数値関係の判定
#### NaN判定
**[グローバルなNaN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/isNaN)は使わない。[Number.isNaN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN)を使う**。

#### InfinityはNaNに含まれない
有限な数かは[Number.isFinite](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite)を使う。

#### [Number.isInterger](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger)
JavaScriptには整数と実数の区別がないためこれらの判定は`Number.isInterger`を使う


#### [Number.isSafeInterger](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger)
数値には分解能である数以上は連続の整数にならなくなるその限界の数の範囲内であるかを返す。
限界の整数値は[Number.MAX/MIN_SAFE_INTERGER](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)で定義されている。

### 型判定のまとめ
JavaScriptで完全で安全な型判定をするのは難しい。その場で必要な分だけするようにしよう。そのためにもいくつかの典型的な方法は知っておいたほうがいいだろう。

## [スプレッド構文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)と[残余パラメーター](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/rest_parameters)
両方共、`...`です。使う場面によって呼び名が変わりますがそのほうが機能が分かりやすいので是非、別のものとして覚えてください[^three_dot]。
スプレッド構文は配列リテラル[^spread2018]や関数呼び出しで使った場合で配列を展開して連続のパラメーターにします。`Math.sum(...arr)`や`Math.min(...arr)`のように使います。
レストパラメーターは関数定義の仮引数に使うことで残りのパラメーターを配列のようにまとめます。

```js:rest_parameters.js
const echo=(...args)=>{ console.log(args); }

echo(); // [] から配列
echo('a', 'b'); // [ 'a', 'b' ]
echo({ x: 1, y: 2}); // [ { x: 1, y: 2} ]
```
のように配列でラップしたようになります。
[^three_dot]: [「スプレッド演算子」という表現を使わない](https://qiita.com/hoo-chan/items/784f103e4d26747c89f0)
[^spread2018]: ES2018からはオブジェクトリテラルでも使える。

## [分割代入](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
`const [ a, b, c ]=[...elem ];`や`const {a, b}={ a: 1, b: 2}`の様な形式で受けられる。undefinedの場合の既定値も設定できる。
オブジェクトの方は`import/export`等でよく使われる。


## 文字列操作
### [String.trim()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/trim)
前方と後方にある空白文字を削除する。忘れがちだけどブラウザからユーザーインプットを受ける時に意外と使える。

### [String.split(separator, limit=undefined)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/split)
`separator`(文字列/正規表現)で分割する、separatorは**削除される**。

```js
const arr=' 1, 2, 3, 4, '.split(',')
console.log(arr) //[ ' 1', ' 2', ' 3', ' 4', ' ' ]
```
数値に変換するには`Array.map(callback)`を使う。

### [String.replace(regexp|substr, newSubstr|function)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/replace)
第一引数は(文字列/正規表現)、第二引数で関数による操作ができる。
第一引数が文字列の場合、**最初の文字**だけが置換される

#### replaceAll的なもの[^replaceAll]
[^replaceAll]: [【JavaScript】検索文字列を全置換する方法【replaceAll】](http://marycore.jp/prog/js/replace-all/)
正規表現を使ってグローバルフラッグ`g`をつける

```js:replaceAll_regExp.js
"a b c".replace(/ /g, '-')  // "a-b-c"
```
split-joinイディオム

```hs:replaceAll_split_join.js
"a b c".split(' ').join('-')  // "a-b-c"
```
joinはArrayについたメソッドである

## String/Arrayに共通するメソッド
### includes(elem, position=0)
[String版](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/includes)、[Array版](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)booleanを返す。Array版は第二引数に負の数を設定することが可能。

### [slice(start, end=this.length)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)
第一引数は負の場合は最後からの要素を取り出す、第二引数は省略可(最後まで)

## Arrayについたメソッド
Arrayには破壊的メソッドと非破壊的メソッドがある。Objectが参照渡しのため破壊的メソッドを呼ぶと関数の外の配列にも影響する。

```js:destruction.js
const add=(arr)=>{
    arr=arr.concat([ 'new elem' ]); // 新しい配列を作ってarrに代入
    console.log('add arr =', arr);
}

const add2=(arr)=>{
    arr.push('new elem'); // arrに要素を追加
    console.log('add2 arr =', arr);
}

const arr=[ 'hello', 'world' ];
add(arr); // add arr = [ 'hello', 'world', 'new elem' ]
console.log(arr); // [ 'hello', 'world' ] 外のarrは変わらない
add2(arr); // add2 arr = [ 'hello', 'world', 'new elem' ]
console.log(arr); // [ 'hello', 'world', 'new elem' ]　外のarrにも要素が追加される
```
### [Array.concat([ ...elem ])](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/concat)
引数は**配列**で2つの配列を繋げて**新しく**構築された配列を返す。

### [Array.splice(index, howMany, [ ...elem ])](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)(**破壊的**)
spliceは継ぎ合わせるの意味でindexからhowManyを取り除き[ ...elem ]の配列を入れる。
howManyは省略可でその場合はindexから最後まで取り除かえる。
[ ...elem ]が省略された場合は何もたされない、sliceは新しく構築したものを返すがspliceは破壊的ヴァージョンとしても使える。

### [Array.push([ ...elem ])](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/push)(**破壊的**)
[...elem]を末尾に足すメソッド、足すので配列の要素数は増える。戻り値は新しいArray.length。

### [Array.pop()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/pop)(**破壊的**)
末尾の要素を取り出だすメソッド、取り出すので配列の要素は減る。空の場合はundefinedが帰る。

### [Array.shift()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/shift)(**破壊的**)
先頭の要素を取り出だすメソッド、取り出すので配列の要素は減る。空の場合はundefinedが変帰る。

### [Array.unshift([ ...elem ])](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift)(**破壊的**)
[...elem]を配列の先頭に足すメソッド、足すので配列の要素数は増える。戻り値は新しいArray.length

### [Array.some(function)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/some)
`function(elem){ return (true/false); }`を満たす要素があるかないかを返すメソッド。
`Array.includes(elem)`は値を渡すがsomeは関数を渡せる。

### [Array.every(function)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/every)
`function(elem){ return (true/false); }`を**すべての要素が**満たすかどうかを返すメソッド

### [Array.filter(function)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
`function(elem, index, self){ return (true/false) }`でフィルターされた部分集合、元の配列より小さい配列、を返す。
functionの引数は省略可能(以下の関数プログラミング的メソッドも同様)

### [Array.map(function)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
`function(elem, index, self){ return any; }`による写像、元の配列と同じlengthを持つ配列、を返す。

### [Array.reduce(function, initialValue)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)
`function(accumulator, value, index, self){ return any; }`(accumulatorは一つ前に返された値)、で作られるある値を返す。
functionの引数が`filter、map`と違うので注意。

# あとがき
普段、記事はストックばっかり増えてまとまらないのでアドベントカレンダーに参加して期限を切ってだそうと思いました。
最初はもっとよく使う小技集にするつもりでしたが、書いてるうちに基礎的知識になりました。基礎的知識集という意味ではまだまだ足りないものも多いですが力尽きたのでこれで出します、ちょくちょく追加するかもしれません。
明日は、@wshito さんの記事です。