# KT#0013-yoshiki JavaScript++ ## 内容 JavaScript についてなんとなく疑問に思ったことを調べてまとめてみる ## 前提 **JavaScript** である。Javascript ではない。 ## バージョン管理ツール Node.js 自体のバージョン管理ツールで nodebrew とか nodevn とか nvm とかあって違いがよくわからんかったので調べてみた。 地味にいろいろある。 - [tj/n (n)](https://github.com/tj/n) - [nvm](https://github.com/nvm-sh/nvm) - [nodenv](https://github.com/nodenv/nodenv) - [nodebrew](https://github.com/hokaccha/nodebrew) マジでどれも良さそう。 `tj/n` or `nvm` が開発が盛んで良さげな気がする。 anyenv というバージョン管理ツールまとめて管理ツールが `nodenv` をサポートしているから、自分は `nodenv` を使っている。 ## yarn と npm どっちも Node.js のパッケージ管理ツール。 ぶっちゃけ違いがわからんかったので調べた。 なんとなく想像がつくが、npm の方が古い (リリース 2010 年)。yarn は 2016 年に Facebook によりリリースされた。2020/7 に yarn 2 なるものがリリースされたらしい。ただ、コミュニティで叩かれまくっており現在は利用非推奨。 人気をみると yarn の勢いが強め。 https://www.npmtrends.com/npm-vs-yarn 大きな違いはロックファイル (プロジェクト内で利用される依存関係の正確なバージョンを示したファイル)。yarn は yarn.lock で npm は package-lock.json。ロックファイルが微妙に違うので yarn と npm を同じプロジェクトで混ぜて使うと、バージョンがぐちゃぐちゃになるかもしれないので非推奨。 yarn の方がパフォーマンスが良いらしい ### 検証 適当な EC2 インスタンスを検証環境とする ``` AMI: ami-0992fc94ca0f1415a (Amazon Linux 2) インスタンスタイプ: t2.medium 対象のプロジェクト: 山田育英会の applicant ``` npm ``` [ec2-user@ip-172-31-45-42 ~]$ npm --version 7.5.4 [ec2-user@ip-172-31-45-42 applicant]$ time npm install real 0m49.814s user 0m52.098s sys 0m4.560s ``` yarn ``` [ec2-user@ip-172-31-33-109 applicant]$ yarn --version 1.22.10 [ec2-user@ip-172-31-33-109 applicant]$ time yarn install real 0m44.116s user 0m37.361s sys 0m11.069s ``` 確かに微妙に早い 昔は yarn の方がセキュリティ的によいと言われていた。npm にはパッケージの脆弱性を気にせずインストールしていたらしい。最近は npm にも脆弱なパッケージをインストールする際に警告がでるので、あまり変わらん。 yarn の登場によって npm が開発頑張りまくって、yarn の性能にほぼ追いついているらしい。 なので、もはやどっちを採用するかは好み。 ## 名前 元の名前は Mocha その後に LiveScript という名前になった。JavaScript の開発者である Brendan Eich の雇い主が当時流行っていた Java に似せるよう命令して JavaScript になった。 JavaScript という名前自体は Netscape という当時 JS を提供していた会社の持ち物。それを Mozila foundation が引き継いで、ECMAScript になっている。なので、本来は JavaScript という名前すら正しい呼び名じゃない。 (シャーペンとかホッチキスみたいな感じ) ES5 対応とかの ES は ECMASCript。ES は大体毎年更新される。 ## var 使うなってなんで? **fuck point 1.** 再宣言可能 var で宣言すると、同じ変数名で再度代入できる ```js > var a = 1; undefined > var a = 2; undefined > let b = 1; undefined > let b = 2 Uncaught SyntaxError: Identifier 'b' has already been declared ``` **fuck point 2.** スコープなんじゃそりゃ スコープが直感的じゃない ``` > var a = 1 undefined > if (true) { var a = 2; console.log(a); } 2 undefined > console.log(a); 2 ``` わかりにくいけど、関数毎のスコープになっている。関数に属していないとグローバルスコープになる。 なんとなく、ブロック単位のスコープでしょと思うと嫌な目にあう。 ``` > var a = 1; undefined > f = () => { var a = 2; console.log(a); } > console.log(a); 1 undefined > f() 2 ``` ## JS の型 基本的にプリミティブ型とオブジェクト型に大別されると思って良い。 ### プリミティブ型 - Boolean 型 - Number 型 - BigInt 型 (使う機会少ない、対応環境が少ないので) - String 型 - Symbol 型 - Null 型 (つまりただの null) - Undefined 型 (他の言語と異なり null と明確に区別してる) Boolean は false, 0, NaN(Not a Number), '', null, undefined を falthy と判定する。 それ以外は truthy となる。 **[疑問]** いやいや、プリミティブとは言いつつも文字列 (String 型) には length が使えるし、数字には toString 関数があるやんけ、何がプリミティブじゃい、オブジェクトやないか。 **[回答]** 本当にプリミティブ型は用意されているものの、実際にアクセスすると対応するラッパーオブジェクトに変換されている。 ```js > testA = 'aaa'; > testB = new String('aaa'); > testA.length; 3 > testB.length; 3 > testA === testB; false > testA === testB.valueOf(); true ``` まぁこれのおかげで、何も意識しなくても便利な関数が利用できている訳だし、厳密性を犠牲にして便利になっているという感じ。 ### オブジェクト型 プリミティブ型以外w。ちなみに JSON で知られる連想配列とか map は狭義のオブジェクト。 これらの狭義のオブジェクトは広義のオブジェクト型を元に作成されている。 ## JavaScript のクラスとプロトタイプベースのオブジェクト指向 厳密にはクラスじゃなくて関数。 ```js > class A { constructor(name){ this.name = name; } } undefined > typeof A 'function' ``` オブジェクト指向にはクラスベースとプロトタイプベースがある。クラスベースでは宣言したクラスは実体をもたない。プロトタイプベースは宣言したクラスが、実態のあるオブジェクトをプロトタイプとして次のオブジェクトが継承される。コンストラクタ関数が、このプロトタイプを元に新しいインスタンスを返す関数なので、クラス A はタイプが関数となる。 上記のクラス宣言は以下の記述と同義。 (class 構文は以下のような書き方が面倒だから省略して書ける記法って感じ、こういうの *シンタックスシュガー* って言うらしい) ```js function A(name) { this.name = name; return this; } ``` プロトタイプベースだと後からクラスにメンバ変数、メソッド増やしたりできる。 ```js > class A { constructor(name){ this.name = name; } } undefined > let instanceA = new A('instanceA'); // ← この時点では methodA という関数は定義されていない undefined > A.prototype.methodA = function() {console.log(`I am ${this.name}`)}; // ← 関数 A が生成するプロトタイプに methodA を追加 [Function (anonymous)] > instanceA.methodA(); // ← instanceA の継承元プロトタイプに加えた変更が反映されている I am instanceA ``` ## オブジェクトのコピー JavaScript やっててあるあるな問題 ```js let original = {a: 1, b: 2, c: 3}; let copy = original; original.a = 100; console.log(copy); // { a: 100, b: 2, c: 3 } ``` `Object.assign()` 関数を使うと一応回避できるように見える。`Object.assign()` は第一引数のオブジェクトに、第二引数のオブジェクトのプロパティを割り当てていく関数。 ```js let original = {a: 1, b: 2, c: 3}; const copy = Object.assign({}, original); original.a = 100; console.log(copy); // { a: 1, b: 2, c: 3 } console.log(original); // { a: 100, b: 2, c: 3 } ``` スプレッド構文を使ってもできる ```js let original = { a: 1, b: 2, c: 3 }; const copy = {... original}; original.a = 100; console.log(copy) //{ a: 1, b: 2, c: 3 } ``` これでいけると思ったら悲しいことになる ```js let original = { a: 1, b: {b1: 2, b2: 3}, c: 4 }; const copy = {...original}; original.b.b1 = 200; console.log(copy); // { a: 1, b: { b1: 200, b2: 3 }, c: 4 } ``` コピーが 1 階層しかされない、こういうのシャローコピーって言うらしい 一回、`JSON.stringify()` で文字列にしてパースしなおすという荒技がある。 (ただ、これですら Date オブジェクトとかは綺麗に変更されなくて困る) ```js let original = {a: 1, b: {b1: 2, b2: 3}, c: 4}; const copy = JSON.parse(JSON.stringify(original)); original.b.b1 = 200; console.log(copy); // { a: 1, b: { b1: 2, b2: 3 }, c: 4 } ``` この辺は外部ライブラリに頼るしかない ## this 大体以下のパターンしかない ### 1. new 演算子をつけてコンストラクタ関数を呼び出した時 → 新規作成されたオブジェクト 先の話と関連があるが、new 演算子をつけるとその関数のプロトタイプオブジェクトをコピーしてオブジェクトを作って、それを関数に this として渡している 関数が this で終わってなかったら勝手に `return this` をしている ``` > const dump = function () { console.log('`this` is', this); }; > const obj = new dump(); `this` is dump {} > obj dump {} > dump.prototype dump {} ``` obj と dump.prototype は同一のオブジェクト型になっていることがわかる 前述の通り別に Class 定義してなくても new できる ### 2. メソッド内で実行された時 → その所属するオブジェクト ```js > const foo = { name: 'Foo Object', dump() { console.log(this); }, }; > foo.dump(); { name: 'Foo Object', dump: [Function: dump] } ``` dump 関数は foo 配下にいるので this すると foo オブジェクトがそのまま返ってくる ### 非 strict モード時 1・2以外の関数 → グローバルオブジェクト キングオブゴミ仕様 何もしてない時にも this は存在し続けている ```js > this <ref *1> Object [global] { global: [Circular *1], clearInterval: [Function: clearInterval], clearTimeout: [Function: clearTimeout], setInterval: [Function: setInterval], setTimeout: [Function: setTimeout] { [Symbol(nodejs.util.promisify.custom)]: [Function (anonymous)] }, queueMicrotask: [Function: queueMicrotask], clearImmediate: [Function: clearImmediate], setImmediate: [Function: setImmediate] { [Symbol(nodejs.util.promisify.custom)]: [Function (anonymous)] } } ``` node だと global オブジェクト、ブラウザ環境だと window オブジェクトを指す ### strict モード時 → undefined 実質 `'use strict'` って何って説明 strict モードないだと以下のような動きになる *実行環境がないのでイメージ用のサンプルコード、実際に以下のコードを実行できる環境はほとんどない* ```js > class A { constructor(name){ this.name = name; } } > A('test'); > this <ref *1> Object [global] { global: [Circular *1], clearInterval: [Function: clearInterval], clearTimeout: [Function: clearTimeout], setInterval: [Function: setInterval], … name: 'test' ``` new は実行した関数にプロトタイプをコピーしたオブジェクトを this として渡して最後に this を返してくれる処理 new つけずに実行すると、this は global オブジェクトなので、`this.name = name` は global オブジェクトに name を追加する処理になる 気づかない間に global 汚染をしちゃう そこで、今は strict モードが用意されており、こういう処理が止められる ```js > A('test'); Uncaught TypeError: Class constructor A cannot be invoked without 'new' ``` よくある this 何を指しとるんじゃ問題はみんな JS に精通していると思うので省略 ## 細かい便利記法 ### オブジェクト型の中に関数入れるやつ オブジェクトの中に関数を入れるやつ ```js const foo = { bar: 'bar', baz: function () { console.log('I am foo') } } ``` こんな感じで簡単に書ける ```js const foo = { bar: 'bar', baz() { console.log('I am foo') } } ``` ### オブジェクト内のキー名の変数展開 オブジェクト内部のキー名に [<変数>] とすることで、キー名がその変数になってくれる ```js Type ".help" for more information. > const keyName = 'bar'; > const obj1 = { foo: 256, [keyName]: 4096, baz: 'test' }; > obj1 { foo: 256, bar: 4096, baz: 'test' } ``` ### ショートハンド {<変数名>} とすることで、{"<変数名>": <値>} のオブジェクトが作れる ```js > const foo = 111; > const obj2 = { foo }; > obj2 { foo: 111 } ``` ### 分割代入 オブジェクトから、{<キー名1>, <キー名2>} = <オブジェクト変数> とすることで、そのキー名を変数として値を取り出せる ```js > const obj = { name: 'yoshiki', age: 25 }; > const { name, age } = obj; > name 'yoshiki' > age 25 ``` ### スプレッド構文 `...<オブジェクト変数>` とすることで、そのプロパティを展開する 同一オブジェクトに同じキー名があると上書きしてくれる ```js > const obj1 = { a:1, b:2, c:3, d:4 }; > const obj2 = { ...obj1, d: 99, e: 55 }; > obj2 { a: 1, b: 2, c: 3, d: 99, e: 55 } ``` 以下みたいな使い方で、id だけ抜粋した userWithoudId 変数を作ることもできる ```js > const user = { id: 1, name: 'yoshiki', email: 'yoshiki@amazon.com', age: 25 }; > const {id, ...userWithoudId} = user; > id 1 > userWithoudId; { name: 'yoshiki', email: 'yoshiki@amazon.com', age: 25 } ``` ### Nullish Coalescing ?? と書くことで、左側が `null` なら、右側が評価されるやつ ```js > a = 1; > b = null null > new_a = a ?? 'a is null'; 1 > new_b = b ?? 'b is null'; 'b is null' ``` OR 演算子でかけることもあるけど、 `null` を評価するならこっちの方がスマートにかけそう ### Optional Chaining `?.` と書くことで、オブジェクトの上位階層が null でも途中で undefined だったら例外なく処理が完了する ```js > const obj = {}; > obj.a > obj.a.b; Uncaught TypeError: Cannot read property 'b' of undefined > obj.a?.b; undefined ``` ## まとめ なんでキモがられているのかなんとなくわかった気がした いろいろ更新されて便利な記法が増えていっている、これからも頑張ってほしい TypeScript も勉強していきたい
{"metaMigratedAt":"2023-06-15T20:11:34.975Z","metaMigratedFrom":"Content","title":"KT#0013-yoshiki JavaScript++","breaks":true,"contributors":"[{\"id\":\"4af5dd16-098f-44c1-bc14-a63a21ab4417\",\"add\":9347,\"del\":71}]"}
    220 views
   Owned this note