JavaScript基礎概念勉強ノート6

# 初めに
今回はES6一部の基礎概念を勉強ノートとしてまとめてみました。


# Scope in JavaScript
基本的に以下の四種類があります。
- Global Scope：どこでもアクセスできる。
- Function Scope：関数式内だけ。
- Block Scope：ブロック（｛｝）内だけ。
- Module Scope：モジュール内だけ。
参考資料はこちら↓
[function - What is the scope of variables in JavaScript? - Starck Overflow](https://stackoverflow.com/questions/500431/what-is-the-scope-of-variables-in-javascript)
[javascript - ES6 module scope - Starck Overflow](https://stackoverflow.com/questions/30287977/es6-module-scope)

## `let` & `const` - Block Scope
```js
// different between let and const
// declared by const means variable can not be changed or covered
const a = 1
a = 2
console.log(a)
// TypeError: Assignment to constant variable.

// declared by let means variable can be changed or covered
let b = 1
b = 2
console.log(b) // 2

// const and let only exist in "block scope"
// block scope in global, it can be accessed from any scope
const score4 = 60
if (score4 >= 60) {
  console.log(score4) // 60 
}
console.log(score4) // 60

// block scope: {}, it only exist its {}
const score5 = 60
if (score5 >= 60) {
  let text = '合格'
  console.log(text) // 合格
}
// console.log(text) // ReferenceError: text is not defined
```
`let` と `const`は**Block Scope**だけ持っていて、その中に生きている。**Global Scope**に宣言されたらどこでもアクセスできるようになりますが、**Global Scope**を持っているわけではなく、実際**Global Scope**というのはmain()（JSの本体）という関数式内で一番上の**Block Scope**（｛｝）と言ってもいいぐらいです。
一番上にある**Global Scope**に対して、**Local Scope**という言葉もよく使われる。
**Global Scope**で宣言された変数、どこの**Local Scope**からでもアクセスできる。（`let` & `const` & `var` 共通）



## `var`
```js
// var has function scope (and global scope)
// global scope
var score1 = 60
if (score1 >= 60) {
  console.log('pass') // pass
}

// function scope? "if...else" is not a function
if (score2 >= 60) { // 1. can not find "score2", break out
  var score2 = 60
  console.log('pass')
}
console.log(score2) // undefined // "if...else" doesn't execute

// var in block scope means global variable declaration
const score6 = 60
if (score6 >= 60) {
  var text = '合格' // "if...else" is not function, so it's a global variable declaration
  console.log(text) // 合格
}
console.log(text) // 合格

//
function pass(n) {
  const score6 = 60
  if (score6 >= 60) {
    var text = '合格'
    console.log(text) // 合格
  }
  console.log(text) // 合格
}

pass()
// console.log(text) // text is not defined

```
正直なところ、`var`を使用するのはおすすめしません。
`var`は**Function Scope**だけ持っていて、さきほど書いたようにJSファイルを動かせるというのは、main()関数式を呼び出すような感覚で、`var`が一番上にある**Global Scope**（main()という**Function Scope**）で変数宣言をしても問題ありませんが、それ以外の**Block Scope**や**Module Scope**など、JSを書く過程のなか不注意による問題が出てくるかもしれません。

例えば、`if`のような**Block Scope**、その中に宣言したら`if`条件が満たされてない状態で`if`が実行されず、**Block Scope**のなかの値がアクセスできず見つからないことになった。（**undefined**：あるはずの値が見つかりません）
また、特定の**Function Scope**内の**Block Scope**ではなく、Globalで**Block Scope**に宣言するというのは、main()という**Function Scope**の変数宣言、つまり変数が**Global Variable**になってしまいます。

`var`宣言は生きる範疇が広すぎる（main()**Function Scope**）のが問題、変数をreassignすることもできるので、コードが長ければ長いほど値の不一致性／不安定性が高まる。

## Undeclared variable
```js
// always remember to declare variable
score3 = 60 // 1. if we don't declare it, it would be a global variable
if (score3 >= 60) { // 2.
  // var score3 // it's not related to be declared by var or not
  console.log('pass') // pass // 3.
}

// doesn't work
// var score // 1.
// if (score >= 60) { // 2. score didn't be assigned any value, break out
//   score = 60
//   console.log('pass')
// }
```
宣言を忘れたら、変数が強制的にグローバル変数に転換されてしまいます。
（ほかの議題も触れているので、後日補足していきたいと思います。）

# Template literals
```js
// ES5 string joint
let str1 = '' + '\n' +
  'abcd' + '\n' +
  'abcde'
console.log(str1)
/*

abcd
abcde
*/

function sayHi(name) {
  console.log('Hello, ' + name + ' now is ' + new Date())
}
sayHi('Nick') // Hello, Nick now is Fri Apr 22 2022 10:31:38


// ES6 string joint
let str2 = `
Hello,
Nick
`
console.log(str2)
/*

Hello,
Nick
*/

function hello(name) {
  console.log(`Hello, ${name} now is ${new Date()}`)
  // console.log(`Hello, ${name.toUpperCase()} now is ${new Date()}`)
}
hello('Nick')
hello('Mary')
// Hello, Nick now is Fri Apr 22 2022 10: 43: 08
// Hello, Mary now is Fri Apr 22 2022 10: 43: 08
```
Web開発のとき、よくテンプレート文字列が使用しています。
`${}`のなかは、変数や関数など入れることが可能で、`console.log()`で出力か、値として`return`もできる。

# Destructuring assignment
```js
// Array
// ES5
const arr = [1, 2, 3, 4]
// let first = arr[0]
// let second = arr[1]
// let third = arr[2]
// let fourth = arr[3]

// console.log(second, third) // 2 3

// ES6 destructuring assignment
let [first, second, third, fourth] = arr
console.log(second, third) // 2 3

let [one] = arr
console.log(one) // 1
let [ichi, ni] = arr
console.log(ichi, ni) // 1 2

// note: variables in left-hand side, corresponding object or array in right-hand side (the source variable)
// note: extract required array elements into distinct variables
```
自分からみれば、分割代入構文というのは要素自身を変数として宣言し、`right-hand side`は**値のアクセス元**を指定するみたいな作法です。
配列は比較的に簡単で、インデックスの位置が合えば変数名が自由にネーミングしてもよいのです。

しかし下のようにオブジェクトには気を付けないといけないところがあります。

```js
// Object
// ES5
const obj = {
  name: 'Nick',
  age: 30,
  address: 'America',
  family: {
    Father: 'Mick',
    Mather: 'Mary'
  }
}
// let name = obj.name
// let age = obj.age
// let address = obj.address
// console.log(address) // America

// ES6 destructuring assignment
// const { name, age, address } = obj
// console.log(address) // America

// const { name, age, add } = obj
// console.log(add) // undefined

// note: be careful that the object is corresponding to the key
```
まず、変数名はオブジェクトの`key`と同じでなければなりません。key-value pairなので理解しやすいと思います。
```js
// let { family } = obj
// console.log(family) // { father: 'Mick' }
// let { father } = family
// console.log(father) // Mick
let { family: { Father, Mather } } = obj
console.log(Father) // Mick
console.log(Mather) // Mary
console.log(family) // family is not defined

// note: 'family' seems like a tree name, not a variable
```
全部ではなく、オブジェクトにある`key`、一つか複数を取り出すことも可能です。そしてなによりも、ツリーみたいにその下にある値を取得することもできる。（**値のアクセス元**をちゃんと指定していれば）
```js
// destructuring assignment in function
// before
// function test(obj) {
//   console.log(obj.a) // 1
// }

// test({
//   a: 1,
//   b: 2
// })

// after
function test({ a, b }) {
  console.log(a) // 1
  console.log(b) // 2
}

test({
  a: 1,
  b: 2
})
```
関数式の引数（argument）も分割代入構文ができる。従来のドット法で`key`を指定するより、もっとわかりやすくなっていると思います。
分割代入構文に続いて、`import`と`export`の話も少し触れていきたいと思います。
# `import` & `export`
```js:es5_module.exports
// ES5
function add(a, b) {
  return a + b
}
module.exports = add
```
```js:es5_require()
const add = require('./utils') // .js省略可
console.log(add(3, 5)) // 8
```
`module.exports`と`require()`の書き方が少し古いけれど、この書き方では馴染みがあってどんな環境でも使えてやりやすい方法だと思います。
実際、`Node.js`環境で`import`と`export`という新しい書き方にするには、事前作業が必要です。
まずは`$ npm init`で`package.json`ファイルを作って、そのなかに
```json:package.json 
{
  "type": "module"
}
```
入れるだけ。
参考資料はこちら↓
[javascript - "Uncaught SyntaxError: Cannot use import statement outside a module" when importing ECMAScript 6 - Stack Overflow](https://stackoverflow.com/questions/58211880/uncaught-syntaxerror-cannot-use-import-statement-outside-a-module-when-import)

あともう一つ大事なポイントがあります。
**`import`...`from`... 使うとき、リソースの拡張子は省略不可です**

```js:utils.js
// ES6
// example1
export function add(a, b) {
  return a + b
}
export const PI = 3.14

// example2
function add(a, b) {
  return a + b
}
const PI = 3.14
export {
  add,
  PI
}
```
```js:import.js
// ES6
// example1 & example2
import { add, PI } from './utils.js' // Node.js環境では.js省略不可
console.log(add(3, 5)) // 8
console.log(PI) // 3.14

// if we use node.js to run it, it will show SyntaxError
// SyntaxError: Cannot use import statement outside a module

/* solution for node.js:
add
{
  "type": "module"
}
into package.json 
(Solution for the script tag in HTML file, please check the Reference)

Reference: https://stackoverflow.com/questions/58211880/uncaught-syntaxerror-cannot-use-import-statement-outside-a-module-when-import
*/

/* important memo:
you should always use the full file name, like 'utils.js' to import,
otherwise, it will still show 'Error [ERR_MODULE_NOT_FOUND]'

Reference: https://stackoverflow.com/questions/61291633/expressjs-is-return-error-err-module-not-found-if-i-import-the-file-without-j
*/
```
example1 & example2のように、書き方としては一個ずつで`export`しても、オブジェクトで`export`しても、`import`したらfunction名、変数名そのまま使えるようになる。（もちろん名前を変えることも可能　⇒　example3に参照）

```js:utils.js
// example3
function add(a, b) {
  return a + b
}
const PI = 3.14
export {
  add as addFunction, // change export alias
  PI
}
```
utils.jsファイルが大きくなり、変数と関数が混じっている状態で`import`してもよくわからないことがよくあります。なので`export`するときalias（別名）として、`as`を使って関数式なら**Function**か**Fn**などつければ読みやすくなりますね。

```js:import.js
// ES6
// example3
import { addFunction, PI } from './utils.js'
console.log(addFunction(3, 5)) // 8
console.log(PI) // 3.14

import { addFunction as a, PI } from './utils.js' // change import alias
console.log(a(3, 5)) // 8
console.log(PI) // 3.14

// for all
import * as utils from './utils.js' // * usually means anything
console.log(utils.addFunction(3, 5)) // 8
console.log(utils.PI) // 3.14
```
もちろん`import`もその別名として導入しなければならない。もう一度名前を変えたいなら、`import`のとき、`as`を使って別名を書いていればいいです。
また、**for all**のところみたい、一個ずつより全部を導入してからドット法で指定していくのが一番よく使う方法です。

```js:utils.js
// example4
export default function add(a, b) {
  return a + b
}
export const PI = 3.14
note: use 'export default' => import add from './utils.js'
```
```js:import.js
// example4
import add, { PI } from './utils.js'
console.log(add(3, 5)) // 8
console.log(PI) // 3.14
```
ここでは少し`named exports`と`default export`の違いを書きたいと思います。
`named exports`はモジュール内は0個以上存在することが可能に対し、
`default export`は1個だけ指定することができる。
つまり`default export`はこのモジュール内一番重要な要素として扱われることで、
なくても大丈夫が、あるとしたら1個限定です。
その代わりに`named exports`何個存在しても構いません。

example1 ~ example3 の書き方は`named exports`
example4 の書き方は`default export`

# Default parameters
```js
function repeat(str, times) {
  console.log(times) // undefined
  return str.repeat(times)
}
console.log(repeat('abc')) // *empty string*

// if we don't give any value to parameters, it will be undefined
// "undefined" means it should be a value but can't be found
// "null" means this value doesn't exist

// use default parameters
function repeat1(str, times = 5) {
  console.log(times) // 5
  return str.repeat(times)
}

console.log(repeat1('abc')) // abcabcabcabcabc
// even though we didn't give any value as an argument, it always keep default value itself

function repeat2(str = 'hello', times = 5) {
  return str.repeat(times)
}
console.log(repeat2()) // hellohellohellohellohello
```
デフォルト引数は、引数がundefinedだったとき自動的にデフォルト値を渡すパラメータです。
上の例のようにとてもわかりやすいと思います。
しかし分割代入構文と一緒に使うなら、気を付けないといけないところがいくつあります。
```js
// use with destructuring assignment
const obj = {
  b: 2
}
// const { a, b } = obj
// console.log(a, b) // undefined 2

// const { a = 123, b } = obj
// console.log(a, b) // 123 2

const obj1 = {
  a: 1
}
const { a = 123, b = 'hello' } = obj1
console.log(a, b) // 1 hello

/*
note: when we use destructuring assignment which puts the default value inside, 
if the original object had value itself, it won't be changed.
*/
// note: default value only show up when it was undefined state
```
もともと値が存在している場合は、デフォルト値は使われない。

```js
// note: but you should be careful the original object didn't be changed
const obj2 = {
  d: 2
}
const { c = 123, d = 'hello' } = obj2
console.log(c, d) // 123 2
console.log(obj2) // { d: 2 }
console.log(obj2.c) // undefined
/*
if you try to access the default value, 
it is always undefined (for example, "c" didn't exist in obj2),
so default parameters are only a temporary value when we call.
"c" didn't exist, and we didn't assign any value to "c".
*/
```
また、デフォルト値が設定したとしても元のオブジェクトは変えられません。
もちろん、そのデフォルト値をアクセスしようとしても見つかりません。

# Spread operator
```js
let arr = [1, 2, 3]
let arr2 = [4, 5, 6, arr]
console.log(arr2) // [ 4, 5, 6, [ 1, 2, 3 ] ]

// use spread operator
let arr3 = [4, 5, 6, ...arr]
console.log(arr3) // [ 4, 5, 6, 1, 2, 3 ]
arr3 = [4, ...arr, 5, 6]
console.log(arr3) // [ 4, 1, 2, 3, 5, 6 ]

function add(a, b, c) {
  return a + b + c
}
console.log(add(arr)) // 1,2,3undefinedundefined
// note: a = arr, b = undefined, c = undefined

console.log(add(...arr)) // 6
console.log(add(...arr2)) // 15 // a = 4, b = 5, c = 6
console.log(add(...arr3)) // 7　// a = 4, b = 1, c = 2
```
スプレッド構文は、中身の要素を展開するパラメータです。
しかし上の例のように、展開すると言っても要素全部入れられるとは言えない。
関数の要求する引数を越えたら無視される。

```js
let obj1 = {
  a: 1,
  b: 2
}
let obj2 = {
  d: 4
}
let obj3 = {
  ...obj1,
  c: 3,
  ...obj2
}
console.log(obj3) // { a: 1, b: 2, c: 3, d: 4 }
```
オブジェクトの中でも要素展開できますが、
```js
let obj4 = {
  a: 1,
  b: 2
}
// let obj5 = {
//   ...obj4,
//   b: 3
// }
// console.log(obj5) // { a: 1, b: 3 }
// note: when it's the same as the key, it will be replaced by latter

let obj5 = {
  b: 3,
  ...obj4
}
console.log(obj5) // { b: 2, a: 1 }
```
同じ`key`の要素が後のものに上書きされてしまいます。

また、オブジェクト型の厳密等価（===）も、
```js
// copy an array
// let arr4 = [1, 2, 3]
// let arr5 = arr4
// console.log(arr4 === arr5) // true
// they refer to the same address, it wasn't a copy

let arr4 = [1, 2, 3]
let arr5 = [...arr4]
console.log(arr4 === arr5) // false
// because spread operator, arr5 take value from arr4, not address

let nestedArray = [4]
let arr6 = [1, 2, 3, nestedArray]
let arr7 = [...arr6]
console.log(arr6) // [ 1, 2, 3, [ 4 ] ]
console.log(arr7) // [ 1, 2, 3, [ 4 ] ]
console.log(arr6 === arr7) // false

// but if the element was an assigned array,
console.log(arr6[3] === arr7[3]) // true
```
展開されたのが同じ要素であれば、どこに展開されても厳密等価が`true`になります。

```js
// if we chang the value (by new address)
nestedArray = [7, 8, 9]
console.log(arr6) // [ 1, 2, 3, [ 4 ] ]
console.log(arr7) // [ 1, 2, 3, [ 4 ] ]
// at this time, nestedArray in arr6 & arr7 was still assigned by old address
// passing by sharing

// but if you reassign it 
arr6 = [1, 2, 3, nestedArray]
arr7 = [...arr6]
console.log(arr6) // [ 1, 2, 3, [ 7, 8, 9 ] ]
console.log(arr7) // [ 1, 2, 3, [ 7, 8, 9 ] ]
```
`arr6`も`arr7`も、もう一度`nestedArray`をreassignする前、古いアドレスを記憶していて、`nestedArray`の中身がどう変更されても変わらず`nestedArray`の古いアドレスの値を記憶している。
オブジェクトにも同じ状況が起こる。
```js
// copy an object
// let obj6 = {
//   a: 1,
//   b: 2
// }
// let obj7 = obj6
// console.log(obj6, obj7, obj6 === obj7)
// { a: 1, b: 2 } { a: 1, b: 2 } true

let obj6 = {
  a: 1,
  b: 2
}
let obj7 = {
  ...obj6
}
console.log(obj6, obj7, obj6 === obj7)
// { a: 1, b: 2 } { a: 1, b: 2 } false

obj6 = {
  a: 3,
  b: 4
}
console.log(obj7) // { a: 1, b: 2 } // old address

obj7 = {
  ...obj6
}
console.log(obj7) // { a: 3, b: 4 }
```

# rest parameters
```js
// difference between spread operator
// Array
let arr = [1, 2, 3]
let arr2 = [...arr, 4] // spread operator
console.log(arr2) // [ 1, 2, 3, 4 ]

// rest parameters (with destructuring assignment)
let arr3 = [1, 2, 3, 4, 5]
let [first, ...rest] = arr3
console.log(first) // 1
console.log(rest) // [ 2, 3, 4, 5 ]
// put the rest of elements in arr3 into variable "rest"

// let [one, ...rest, last] = arr3
// console.log(rest) // SyntaxError: Rest element must be last element
```
残余引数は残りの要素を梱包して、一つの変数に入れるような感じのパラメータです。
残りの要素への方法なので、一番下のように書いたら**SyntaxError**が出てしまいます。
オブジェクトなら、
```js
// Object
// let obj1 = {
//   a: 1,
//   b: 2,
//   c: 3
// }
// let { a, ...obj2 } = obj1
// console.log(a, obj2) // 1 { b: 2, c: 3 }

let obj3 = {
  a: 1,
  b: 2
}
let obj4 = {
  ...obj3, // spread operator
  c: 3
}
let { a, ...restObject } = obj4
console.log(a, restObject) // 1 { b: 2, c: 3 }
console.log(obj4) // { a: 1, b: 2, c: 3 }

let { ...restAll } = obj4
console.log(restAll) // { a: 1, b: 2, c: 3 }
console.log(obj4)  // { a: 1, b: 2, c: 3 }
```
`obj4`のように`spread operator`で要素を展開して、`rest parameters`で要素をまとめることも可能です。
ただ一つ注意すべきところがあります。分割代入構文と一緒に使用するとき、
`a`の出力は1、
`restObject`の出力は`{ b: 2, c: 3 }`です。
配列の例でも、
```js
let arr3 = [1, 2, 3, 4, 5]
let [first, ...rest] = arr3
console.log(first) // 1
console.log(rest) // [ 2, 3, 4, 5 ]
console.log(arr3) // [ 1, 2, 3, 4, 5 ]
```
つまり残余引数は、要素を梱包するのは今にいる環境のデータ形態と関連している。
とすると、こんな感じになるかな。
```js
const restObject = {
  b:2
  c:3
}
const rest = [2, 3, 4, 5]

let {a, ...restObject} = obj4 // 要素展開
let {first, ...rest} = arr3 // 要素展開
```
`spread operator`と`rest parameters`は使い方が分けられて使っているが、本質は一緒です。

## argumentsとの違い
```js
// function
function add(...args) { // args is array
  console.log(typeof args) // object
  console.log(args) // [ 1, 2 ]
  console.log(arguments) // [Arguments] { '0': 1, '1': 2 }
  console.log(arguments[0], arguments[1]) // 1 2
  return args[0] + args[1]
}
console.log(add(1, 2)) // 3
/*
note: "args" looks like arguments, the difference is
"arguments" is an array-like object, then "...args" is a real array
*/
```
functionのなかで引数たちのボックスとしてのargumentsとの違いは、
`spread operator`は本当のオブジェクトとして要素を扱っている。（この点は`rest parameters`も）

# Arrow function
```js
let arr = [1, 2, 3, 4, 5]
// console.log(
//   arr
//     .filter(function (value) {
//       return value > 1
//     })
//     .map(function (value) {
//       return value * 2
//     })
// )
// [ 4, 6, 8, 10 ]

// console.log(
//   arr
//     .filter((value) => {
//       return value > 1
//     })
//     .map((value) => {
//       return value * 2
//     })
// )

// console.log(
//   arr
//     .filter(value => {
//       return value > 1
//     })
//     .map(value => {
//       return value * 2
//     })
// )

console.log(
  arr
    .filter(value => value > 1)
    .map(value => value * 2)
)

// note: it can be written by one line when the function only returns a value
// memo: Reserved Word 'this'
```
アロー関数式は伝統の書き方より可読性が高くなり、書きやすく読みやすくなります。

# 参考資料のまとめ
[function - What is the scope of variables in JavaScript? - Starck Overflow](https://stackoverflow.com/questions/500431/what-is-the-scope-of-variables-in-javascript)
[javascript - ES6 module scope - Starck Overflow](https://stackoverflow.com/questions/30287977/es6-module-scope)
[javascript - "Uncaught SyntaxError: Cannot use import statement outside a module" when importing ECMAScript 6 - Stack Overflow](https://stackoverflow.com/questions/58211880/uncaught-syntaxerror-cannot-use-import-statement-outside-a-module-when-import)

# 感想
今回はES6ではかなり基礎レベルの概念をまとめました。
ほかには非同期処理、静的プロパティ・メソッド等々がありますが、引き続き頑張っていきたいと思います。