初めに
JavaScript、ES5までの基礎概念勉強ノートです。この勉強ノートの作成は、独学でオンライン授業を受けたとき書いた個人の感想やメモ、VScodeで検証を行い注釈を入れてたものを、復習と振り返りのつもりで日本語で書いてまとめてみました。
ノートは主にMDNかいくつの文章を参照して自分の解釈を入れたものですので、間違いがあればご指摘していただければ幸いです。
#誤解によって誤った自己解釈や間違いだらけのノートにでもなり得ます。
#日本語も英語も母語ではないんですが、練習のために書いています。
Array
let score = [1, 3, 5, 10, 100]
console.log(score.length) // 5
console.log(score[score.length]) // undefined // array's index start at 0
console.log(score[score.length - 1]) // 100
score[score.length] = 1000
console.log(score[score.length - 1]) // 1000
score.push(10000)
console.log(score.length) // 7
console.log(score[score.length - 1]) // 10000
score.push(0.3)
score.push(20)
console.log(score)
// [
// 1, 3, 5,
// 10, 100, 1000,
// 10000, 0.3, 20
// ]
let score2 = []
score2[10] = 100
score2.push(1000)
score2.push(0.3)
console.log(score2, score2.length)
// [ < 10 empty items >, 100, 1000, 0.3 ]13
Array(配列):性質の一致したもしくは近いデータを格納して共通操作のためによく使われている。indexは 0 からなので、最後のindexは length - 1 です。
Object
// object
// key-value pair
const students = []
const peter = {
name: 'Peter',
score: [80, 75, 90],
address: 'Tokyo',
phone: '010101110',
family: {
father: 'Mick',
mather: 'Mary'
}
}
console.log(typeof peter) // object
console.log(peter.name) // Peter
console.log(peter['name']) // Peter
students.push(peter)
console.log(students.length) // 1
console.log(students[0].name) // Peter
console.log(students[0]['name']) // Peter
const key = 'family'
console.log(students[0][key])
// { father: 'Mick', mather: 'Mary' }
console.log(peter.family.mather) // Mary
console.log(students[0]['family']['father']) // Mick
console.log(peter.score[0]) // 80
console.log(students[0].score[1]) // 75
// object can include anything like String, Number, Boolean, Array, Object, Function.
Object(オブジェクト):キーバリュー型で構成している。基本的に何でも入れることができます。
function 概念編
// return value
function add1(a, b) {
return a + b
}
console.log(add1(1, 2)) // 3
// callback case1
function add2(a, b) {
console.log(a + b)
}
add2(1, 2) // 3
// callback case2
function add3(a, b) {
return a + b
}
add3(1, 2) // nothing happened, function just be done and return value, but there is nothing to catch the value
// put function into variable and declare
var add4 = function (a, b) { // anonymous function
return a + b
}
// take function like variable to put another function into itself
// it can be anonymous function or not
var hello = function () { // anonymous function
console.log('hellow, wrold!')
}
function exec(fn) {
fn()
}
exec(hello) // hellow, wrold!
// it also can put function straightly
exec(function () { // anonymous function
console.log(123)
})
// note: function and anonymous function's difference is readability when we debug
function(関数式):オブジェクト指向言語の学びに一番コアの部分とも言えます。functionは配られた引数を使い、もしくは単純に呼び出され自分のスコープの中で動作をして値を返します。しかし値を返すにはreturn使わなければなりません。何をreturnするかを指定しない場合は、undefinedというデフォルト値を返します。
変数にassignするのもできる。(変数はとにかくものを保管するボックスのイメージ)
関数式の引数(argument)は何でも入れられます。関数式それぞれ自分のスコープ(作用する領域)を持っているので、関数式中のに取り入れた引数は、関数式の()で宣言した変数名と使わないと動作しません(これもまたassignに関わります)。JSファイルを動かす環境もmain()という関数で実行しています。
function 実践編
function returnObjectXY(x, y) {
return {
answerX: x * 2,
answerY: y * 3
}
}
console.log(returnObjectXY(2, 3))
// { answerX: 4, answerY: 9 }
returnObjectXY(2, 3) // nothing can catch value
// generateArray(3) => [1, 2, 3]
// generateArray(10) => [1, 2, 3, ..., 10]
function generateArray(n) {
let result = []
for (let i = 1; i <= n; i++) {
result.push(i)
}
return result
}
console.log(generateArray(3)) // [ 1, 2, 3 ]
console.log(generateArray(10))
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
console.log(generateArray(0)) // []
// ===
function logOdd(num) {
if (num % 2) {
console.log(num)
}
}
function print1to100() {
for (let i = 1; i <= 100; i++) {
logOdd(i)
}
}
print1to100()
// function declare
// function hello() {
// console.log('hello')
// }
const hello = function () {
console.log('hello')
}
function print(any) {
console.log(any)
}
print('hello') // hello // hello was a string argument for print(any)
print(hello) // [Function: hello] // print variable called hello, it's a function
print(hello())
// hello
// undefined // variable hello didn't return anything, so it return undefined
/*
const hello = function () {
return 'hello'
}
function print(any) {
console.log(any)
}
print(hello()) // hello
*/
// call another function in function
function callFunction(any) {
any()
}
callFunction(hello) // hello
// [1, 2, 3] => [2, 4, 6]
function transform(arr, doubleFunction) {
let result = []
for (let i = 0; i < arr.length; i++) {
result.push(doubleFunction(arr[i]))
// result.push(doubleFunction(arr))
}
return result
}
function double(x) {
return x * 2
}
const arr = [1, 2, 3]
// const arr = 10 // push only effect array
// console.log(transform(arr, double)) // [2, 4, 6]
// console.log(transform([1, 2, 3], function (x) {
// return x * 3
// })
// )
// [ 3, 6, 9 ]
// ES6 arrow function
console.log(transform([1, 2, 3], x => x * 3))
// [ 3, 6, 9 ]
// arguments
function add() {
console.log(arguments) //[Arguments] { '0': 2, '1': 3 } // array-like // object
console.log(arguments[0], arguments[1]) // 2, 3
return arguments[0] + arguments[1]
}
console.log(add(2, 3)) // 5
ES6 arrow function:ES5ではなくES6範疇の話ですが、すごく可読性が上がる書き方です。ここの書き方は関数式内returnだけだったので一番簡潔化にできました。
arguments:メソッドではないんですが、functionの中でargumentsを使ったら、ここで使われた引数がわかる。ソースコードが長くなると読みづらくもなるので、引数として何を使ったかすぐわかります。
== vs. ===
// JavaScript is Weakly type language.
var str = 'hello' + 'world' // 'helloworld'
var number = 10
number = number + '' // 10+''='10'
// number.toString()
console.log(str) // 'helloworld'
console.log(number === '10') // true
// Difference between '==' and '==='
console.log(1 == 1) // true
console.log(1 == '1') // true
console.log(1 === '1') // false
// So we shall always use '===' to check boolean
console.log(1 === 1) // true
console.log('hello' === 'hello') // true
// But if it's not Primitive type, even use '==', it won't be true
console.log([1] == [1]) // false // by reference? by sharing
console.log({ a: 1 } == { a: 1 }) // false // by reference? by sharing
==(等価演算子):truthy/falsy value(疑似真偽値)でもイコール
===(厳密等価演算子):値、または真偽値(true/false)完全一致がイコール
!==(不等価演算子): not be equal to this value
!===(厳密不等価演算子): not be equal to this equal
(if条件式では勝手に疑似真偽値として変換し、真偽値と検証して動作してくれますが、ほかの場合では比較運算子は元の意味で検証を行います。)
pass by value
//pass by value: copy value and pass (address is different)
function swap(a, b) {
let temp = a
a = b
b = temp
console.log('a, b:', a, b)
}
const number1 = 10
const number2 = 20
console.log(number1, number2) // 10, 20
swap(number1, number2) // a, b: 20, 10
console.log(number1, number2) // 10, 20
// Primitive type: String, Number, Boolean, BigInt, Symbol, undefined
console.log('hello' === 'hello') // true
console.log(1 === 1) // true
console.log(true === true) // true
console.log(false === false) // true
var hugeInteger = BigInt(9007199254740991)
var hugeString = BigInt('9007199254740991')
console.log(hugeInteger === hugeString) // true
console.log(Symbol('aaa') === Symbol('aaa')) // false
var sym1 = Symbol('bbb')
var sym2 = sym1
console.log(sym2 === sym1) // true
console.log(undefined === undefined) // true
// null: null
console.log(null === null) // true
primitive(プリミティブ):言語実装の最下層からコピーされ表現されます。immutable(変更できません)。
Symbol:一意のものを持っている性質であるため、中身が同じでもSymbolとSymbolの間絶対イコールしない。しかし参照位置が同じで渡されたら完全一致になる。(by sharing*)
pass by reference? by sharing?
// by reference means it refers to an address on the computer
var a = {
name: 'Peter'
}
var b = a
b.name = 'Mick'
console.log(a.name) // Mick
// So we can replace it by another value
// When Variables refer to the same address, it always been true
console.log(a == b) // true
console.log(a === b) // true
console.log(a) // { name: 'Mick' }
console.log(b) // { name: 'Mick' }
// but does it really pass by reference?
function addValue(obj) {
obj.number++
console.log(obj)
}
const a = {
number: 10
}
console.log(a) // { number: 10 }
addValue(a) // { number: 11 }
console.log(a) // { number: 11 }
// they refer to the same address, but...
// if pass a new address, it refer to the new address
function addValue(obj) {
obj = {
number: 100
}
console.log(obj)
}
const a = {
number: 10
}
console.log(a) // { number: 10 }
addValue(a) // { number: 100 }
console.log(a) // { number: 10 }
// note: JavaScript only 'pass by value', or 'pass by sharing'
// --> if it passes by reference, it should also change a's value outside, because they're the same address in the computer
// but it's just sharing the address, so another variable can reassign a new address to put a new object
// Object type: Array, Object, function
// Array
var a = [1]
var b = [1]
var c = b
console.log(a == b) // false
console.log(b === c) // true
// Object
var d = {
a: 1
}
var e = {
a: 1
}
var f = e
console.log(d == e) // false
console.log(e === f) // true
// function has special case
// function case1
function compare1() {
return 1
}
function compare2() {
return 1
}
console.log(compare1() === compare2()) // true
// case1 return the same value, so it's true
// function case2
function compare3() {
console.log('string')
}
function compare4() {
console.log('string')
}
console.log(compare3() === compare4()) // true
// it looks like case1, but case2 is something strange
// function case3
function compare5() {
console.log(123)
}
function compare6() {
console.log('456')
}
console.log(compare5() === compare6()) // true
// case2 & case3: when we use console.log, even though value was not the same, it always be true.
// function case4
function compare7() {
return '123'
}
function compare8() {
return '456'
}
console.log(compare7() == compare8()) // false
// function case5
function compare9() {
return 'ccc'
}
function compare10() {
console.log('ccc')
}
console.log(compare9() == compare10()) // false
// compare10() return undefined
「pass by reference」、「pass by sharing」というキーワードでいろんな文章を読んでから自分の納得できるようにまとめてみました。C言語には「pass by reference」同じ参照位置に指してデータをアクセスする一方、JSのオブジェクトタイプには類似した性質を持っていながら新たな別のアドレスを配ることもできて、しかしスコープが違えば一時的に限定的にアドレスのシェアみたいな、なので「pass by sharing」という概念を捉えました。
配列、オブジェクトでは中身同じでも基本的にイコールしない。
変数が同じ参照位置に指定している場合は、イコールになる。
functionはちょっとおかしなケースがあります。
→ returnで返す値が同じなら、イコール。
→ console.log()を使うと出力内容が違ってても、イコール。
ほかの場合は絶対イコールしない。
assignment operators
// let a = 0
// a += 1 // a = a +1
// console.log(a) // 1
// a++
// console.log(a) // 2
// a -= 1
// console.log(a) // 1
// a--
// console.log(a) // 0
// // ..., etc.
// a++ vs. ++a
let a = 0
// console.log(a++ && 10) // 0
// // after execution, a = a + 1
// console.log('a:', a) // a: 1
console.log(++a && 10) // 10
// before execution, a = a + 1
console.log('a:', a) // a: 1
assignment operators(代入運算子):「=」はassign(与える、配属する)。数学の方程式で使うのと全然違う意味です。left-handにある変数はボックス、right-handにあるのは配る(振る)値(value)です。
logical operators & falsy value
// +, -, *, /, %(剰余) // arithmetic operators(算術演算子)
// ||(or), &&(and), !(not) // logical operators(論理運算子)
// ||(or)
console.log(true || true) // true
console.log(false || true) // true
console.log(10 || 20) // 10
console.log(0 || 20) // 20 // 0 is falsy value
// always return first true or truthy value
// &&(and)
console.log(true && 1) // 1
console.log(true && false) // false
console.log(10 && 20) // 20
console.log(false && 10) // false
console.log(0 && false) // 0
// if first value is true, always return second value
// if first value is false, always return first value
// !(not)
console.log(!true) // false
console.log(!false) // true
// // false
// false
// // falsy value
// 0
// -0
// 0n (BigInt)
// ''
// null
// undefined
// NaN
// if it's not false or falsy value, it would be true or truthy value
logical operators(論理運算子):&& 論理積と || 論理和のshort-circuit(短絡)性質をうまく使えば、比較的に欲しい結果(または値)が導かれる。
falsy value(偽値):個人的に気を付けなければいけない部分です。
false と falsy value の使い分けは最初分からなかった。if条件式で使えば自動的にBoolean(真理値)に変換され、ある種で規定している値だけが入られるように、スクリプティング攻撃の防御もできます。
ただ両者自体が違っていて、comparison operators(比較運算子)の 厳密等価(===) を使うとき気を付けないとだめなんです。
null:お探しの値が存在しません。(そのもの自体がありませんから)
undefined:お探しの値がどこにあったかわかりません。(あったけど宣言されていないか、確かに実行したが何の値を返すか指定していません(functionのreturnとか))
(bit) shift operators
// binary bits
// 0100 = 2^2 = 4
// 1000 = 2^3 = 8
// 1001 = 2^3 + 2^0 = 9
// <<(left shift) -> *2
console.log(10 << 1) // 20
console.log(10 << 3) // 80
console.log(1 << 10) // 1024
console.log(1 << 31 - 1) // 1073741824
console.log(1 << 30) // 1073741824
// >>(right shift) -> /2
console.log(1024 >> 1) // 512
console.log(512 >> 3) // 64
console.log(80 >> 3) // 10
// rounding
console.log(9 >> 1) // 4
console.log(13 >> 1) // 6
// note: compare with *2 and /2, shift operators are lower performance logically
(bit) shift operators(ビットシフト運算子): binary(二進数)を前提にしている運算子です。
左側:十進法の数字
右側:二進数を基数にした指数(べき乗数)
<<(左シフト):右から詰める(2の指数と掛け算)
>>(右シフト):左から詰める(2の指数と割り算)
32位ビットを超えたら詰め方向によってビットが破棄される。
bitwise operators
// a &(AND) b
// if the first value is true, always return the second value
// if the first value is false, always return the first value
// 1010 -> 10
// 1111 -> 15
// ----
// 1010 -> 10
// true false true false
console.log(10 & 15) // 10
// note1: a & 1 -> 1, means a is an odd number
// note2: a & 1 -> 0, means a is an even number
// note3: a % 2 -> 0, even number
// note4: a % 2 -> 1, odd number
// a |(OR) b
// always return first true or truthy value
// 1010 -> 10
// 1111 -> 15
// ----
// 1111 -> 15
console.log(10 | 15) // 15
// a ^(XOR) b
// if they're the same as each other, return 0
// if not, return 1
// 1010 -> 10
// 1111 -> 15
// ----
// 0101 -> 5
console.log(10 ^ 15) // 5
// ~(NOT)a
// 0 -> 1
// 1 -> 0
// 1111 -> 15 // 00000000000000000000000000001111
// ----
// 0000 -> -16 // 11111111111111111111111111110000
console.log(~15) // -16
bitwise operators(ビット演算子):十進法の数字をビットに換算してから扱う論理演算子です。
二進数の計算が慣れてないからJSの勉強に一番大変な部分でした。特に ~(NOT、ビット否定) では、各ビットを 0⇔1 に変換して、最上位のビットが 0 だと負数を返し、1 だと正数を返す。
ビット否定の運算は -(x + 1) に代入して出力値を計算できますが、-1 と 2^32 - 1 のいずれは 0 になります。
ternary operators
// condition ? A : B
// if condition is true, return A
// if not, return B
// console.log(10 > 5 ? 'bigger' : 'smaller') // bigger
// random 1~100
let score = Math.floor((Math.random() * 100)) + 1
console.log(score)
let message = score >= 60 ? 'pass' : 'fail'
console.log(message)
score = 100
// poor readability
message = score >= 60 ? (score === 100 ? 'No.1' : 'pass') : 'fail'
console.log(message) // No.1
// note: be careful of nested structure
ternary operators(条件運算子/三項運算子):
condition ? a : b (条件に合ってる?合ってるならaを返す。そうでなければbを返す。)
返す値の中でもまた三項運算子が利用できます。ただ入れ子構造に気を付けた方がいいです。
Primitive - immutable values & Object - Accessor methods / Mutator methods
// Primitive values: immutable values
let a = 'hello'
// a.toUpperCase() // return String in a new address
// a.split('') // return Array in a new address
// console.log(a) // hello
// String can not be changed
// a = 'world' // reassign
// console.log(a) // world
// you can catch returned value by variable
// a = a.toUpperCase()
// console.log(a) // HELLO
//
let b = a.split(' ')
console.log(a) // hello
console.log(b) // [ 'hello' ]
// Object
// Accessor methods
// indexOf() (return a new array)
let arr = [1, 2, 3]
arr.indexOf(2)
console.log(arr) // [ 1, 2, 3 ]
arr = arr.indexOf(2)
console.log(arr) // 1
// Mutator methods
// push() (change original array)
let arr1 = [1, 2, 3]
arr1.push(4)
console.log(arr1) // [ 1, 2, 3, 4 ]
// if you write like this
arr1 = arr1.push(4)
console.log(arr1) // 5 // push is a function, you print the returned value(new length of the array)
// -> [ 1, 2, 3, 4, 4]
//
let arr2 = [0, 1, 2, 3, 4, 5, 6]
arr2 = arr2.push('fdsfe')
console.log(arr2) // 8
//
// reverse() (change original array, and return new array)
let arr3 = [1, 2, 3]
// arr3.reverse()
// console.log(arr3) // [ 3, 2, 1 ]
arr3 = arr3.reverse()
console.log(arr3) // [ 3, 2, 1 ]
// note: using Mutator methods should be mindful of the return value
Primitive - immutable values:値として不変とう性質を持っているので、メソッドを使っても変わりなく存在するということです。概念としてよくreassign(再配置)と間違われます。
Object - Accessor methods / Mutator methods:オブジェクト型に使う両種類のメソッドです。値をアクセスだけ(元の状態を変えない)と値を直接操作する(元の状態を変える)方法です。
感想と参考資料
今回は入門編に一部の話をまとめてみました。振り返りながら、この中で一番大変なのはやはり二進数の換算、理論の計算など、直感ですぐわかるようになるまでまだまだ時間がかかりそうです。
次回はタイプ検査(typeof)、if条件式、ループ(do while, while, for loop)、Arrayメソッド、Stringメソッド、Numberメソッドをまとめていきたいと思います。