JavaScriptメモ

Posted: January 31, 2022

参考書籍

りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅰ. 言語・環境編】 - くるみ割り書房 ft. React - BOOTH
りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅰ. 言語・環境編】 ☝🏻 2021年 9月アップデート。React 17.0 と TypeScript 4.4 に対応。フロントエンド初中級者や前の版をお持ちの方にもオススメです。くわしくは下の目次をチェック! ―――― シリーズ累計 1.8 万部を突破、BOOTH 技術書カテゴリで売上 1 位を誇る『りあクト! TypeScriptで始めるつらくないReact開発』の最新 3.1 版。今版は三部構成となっており、本書はその第一部「言語・環境編」です。 第一部は Create React App の使い方に始まり、JavaScript、関数型プログラミング、TypeScript までをカバー。今版では「フロントエンドの開発になぜ Node.js が必要なの?」「webpack は何をしてくれてるの?」「import はどうやってファイルを読み込んでるの?」のような、学習者が抱く根本的な疑問にまで分け入ってくわしく説明していきます。 前版は言語についての解説はフロントエンド開発に必要な最低限の内容でしたが、本書ではそれに加えて JavaScript の一見不可解な仕様をその歴史から読み解いたり、TypeScript がどのように型を解決するかや、型定義をフレキシブルに行う方法など、中級以上にステップアップするための、より掘り下げた内容が盛り込まれています。 なお第二部では React のコンポーネントや Hooks、第三部では React の副作用処理を扱っています。続けてご覧ください。 ▫️ https://oukayuka.booth.pm/items/2368019 (Ⅱ. React 基礎編) ▫️ https://oukayuka.booth.pm/items/2367992 (Ⅲ.

関数の書き方

// function
const plusOne = function(n){
  return n + 1;
}

// アロー関数
const addOne = (n) => {
  return n + 1;
};

// アロー関数(省略記法)
// 本文がreturn文だけのときは、return文をブロックごと省略できる
const increment = n => n + 1;

console.log(plusOne(1));
console.log(addOne(1));
console.log(increment(1));

引数の表現

// デフォルト引数
const raise = (n, m=2) => n ** m;

console.log(raise(2, 3)); //8
console.log(raise(3));    //9

// Rest parameters
// 最後の引数の前に, 「...」プレフィックスをつけることで、残りの引数を配列として受け取れる
const showNames = (a, b, ...rest) => {
  console.log(a);
  console.log(b);
  console.log(rest);
};

showNames('John', 'Jane', 'Johnny', 'Jenny', 'Julia');

// 引数の1つ目でもrest parametersは使用可能
const showAllArgs = (...args) => {
  console.log(args);
};

showAllArgs('A', 'B', 'C', 'D');

// rest parametersに名前をつけて取得できる。ただし、定義以上の数の引数は捨てられる
const sum = (i, ...[j, k, l]) => i + j + k + l;

console.log(sum(1, 2, 3, 4));    //10
console.log(sum(1, 1, 1, 1, 1)); //4

分割代入とスプレッド構文

// オブジェクトのキーや値を変数から取得する
const key = 'bar';
const baz = 65536;
const obj1 = {foo: 256, [key]: 4096, baz: baz};
console.log(obj1); //{foo: 256, bar: 4096, baz: 65536};

// プロパティのショートハンド
const obj2 = { baz };
console.log(obj2); //{baz: 65536}

// 分割代入
const [n, m] = [1, 4];
console.log(n, m); //1 4

const obj = {name: 'Kanae', age: 24};
const { name, age } = obj;
console.log(name, age); //Kanae 24

// 分割代入2
// オブジェクトのプロパティのキー名を指定して分割代入する
const response = {
  data: [
    {
      id: 1,
      name: 'Patty Rabbit',
      email: 'patty@maple.town',
    },
    {
      id: 2,
      name: 'Rolley Cocker',
      email: 'rolley@palm.town',
    },
    {
      id: 3,
      name: 'Bobby Bear',
      email: 'bobby@maple.town',
    },
  ],
};
const { data: users = [] } = response;  // キー名がなかったときに備えて[]をデフォルト設定

console.log(users);
//[
//  { id: 1, name: 'Patty Rabbit', email: 'patty@maple.town' },
//  { id: 2, name: 'Rolley Cocker', email: 'rolley@palm.town' },
//  { id: 3, name: 'Bobby Bear', email: 'bobby@maple.town' }
//]

// スプレッド構文
// 配列やオブジェクトの前に...をつけることで中身を展開する
// Rest parametersとやってることは同じ
const arr1 = ['A', 'B', 'C'];
const arr2 = [...arr1, 'D', 'E'];
console.log(arr2); //[['A', 'B', 'C', 'D', 'E']

const obj1 = { a: 1, b: 2, c: 3, d: 4 };
const obj2 = { ...obj1, d: 99, e: 5 }; //同じキーのものは上書きされる
console.log(obj2); //{ a: 1, b: 2, c: 3, d: 99, e: 5 }

const user = {
  id: 1,
  name: 'Patty Rabbit',
  email: 'patty@maple.town',
  age: 8,
};
const { id, ...userWithoutId } = user;
console.log(id, userWithoutId);
// 1 { name: 'Patty Rabbit', email: 'patty@maple.town', age: 8 }

オブジェクトのマージとコピー

オブジェクト型の値は変数に代入しただけだと、参照渡しになり実態は共有されたままので、コピーするときには別の方法を考える必要がある

const original = { a: 1, b: 2, c: 3 };

const copy = { ...original }; // オブジェクトのコピーにスプレッド構文を使う
console.log(copy);
console.log(copy === original); // false プロパティは同じでもアドレスの違う別物

const assigned = { ...original, ...{ c: 10, d: 50}, d: 100 };
console.log(assigned); // { a: 1, b: 2, c: 10 , d: 100}
console.log(original); // { a: 1, b: 2, c: 3 }

上記はシャローコピー(コピーされるオブジェクトの深さが1段階までしか有効でない)ので、プロパティの値がさらに配列やオブジェクトでネストされている場合はそれらの値はコピーしない

const patty = {
  name: 'Patty Rabbit',
  email: 'patty@maple.town',
  address: { town: 'Maple Town' },
};

const rolley = { ...patty, name: 'Rolley Cocker' };
rolley.email = 'rolley@palm.town';
rolley.address.town = 'Palm Town';

console.log(patty);
// {
//   name: 'Patty Rabbit',
//   email: 'patty@maple.town',
//   address: { town: 'Palm Town' }
// }

// addressはオブジェクトなので、参照がコピーされているだけとなる。

// 一旦文字列として展開して、JSONパースするという技もあるが、
// プロパティにDateオブジェクトや、関数、undefinedが入ってるとうまくいかない
// コピーしてプロパティを書き換えるという場面があまりないので、
// 基本はシャローコピーをしつつどういう危険性があるかを認識しておく

ショートサーキット効果

// &&や||を使って右辺の評価を左辺の評価に委ねる
// ||は左辺がfalsyな値だと右辺に渡される
// &&は左辺がtruthyな値だと右辺に渡される
const hello = undefined || null || 0 || NaN || '' || 'Hello!';
const chao = ' ' && 100 && [] && {} && 'Chao!';

true && console.log('1.', hello); // 1. Hello!
false && console.log('2.', hello); // 
true || console.log('3.', chao); //
false || console.log('4.', chao); // 4. Chao!

Nullish CoalescingとOptional Chaining

const users = [
  {
    name: 'Patty Rabbit',
    address: {
      town: 'Maple Town',
    },
  },
  {
    name: 'Rolley Cocker',
    address: {},
  },
  null,
];

for (u of users) {
  const user = u ?? { name: '(Somebody)' };
  const town = user?.address?.town ?? '(Somebody)';
  console.log(`${user.name} lives in ${town}`);
}

// ??(Nullish Coalescing) はSQLでいうところのcoalesceと同じ。左辺がNULLかundefinedのときに右辺にいく
// ?.(Optional Chainning)使うと、各階層でプロパティがなくtype errorが発生してもundefinedで返ってくる

thisの中身の4つのパターン

1.new 演算子をつけて呼び出したとき: 新規生成されるオブジェクト

const dump = function(){ console.log('`this` is', this); };
const obj = new dump();
//`this` is dump {}

obj !== dump.prototype
//true

2.メソッドとして実行されたとき: その所属するオブジェクト

const foo = {
  name: 'Foo Object',
  dump() {
    console.log(this);
  },
};

foo.dump(); // { name: 'Foo Object', dump: [Function: dump] }

3.上記1,2以外の関数[非 Strict モード]: グローバルオブジェクト

JavaScriptでは、thisが実際にはグローバル変数である。メソッドではない関数、およびnew演算子をつけずに実行される関数は、グローバルオブジェクトがthisとして引き渡される。

4.上記1,2以外の関数[Strict モード]:undefined

const Person = function(name) { this.name = name; return this; };
Person('somebody');

<ref *1> Object [global] {
  global: [Circular *1],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  },},
  name: 'somebody'
}
> name
'somebody'

// グローバル汚染が起きる
// use strict をファイルの先頭行や関数の最初につけることで、これは防止できる
const Person = function(name) { 'use strict'; this.name = name; return this; };
Person('somebody');

Uncaught TypeError: Cannot set properties of undefined (setting 'name')
    at Person (REPL1:1:57)

this 理解の補助

JavaScript の this を理解する多分一番分かりやすい説明 - Qiita
JavaScript の this は、(他のプログラム言語から見ると) ちょっと面白い挙動に見えることがあります。 先日、この this の挙動について、会社の同僚が説明してくれたのですが、これまで聞いた説明の中で一番分かりやすいと感じたので、頑張って日本語で説明してみます。 分かりにくかったら、多分それは私の技量不足。 function が基準スコープになるのがまず一点。 その function をどう呼ぶかで変わるのかがもう一点。 それを踏まえて...... this は function を呼んだ時の . の前についているオブジェクトを指している と理解できるというのが、同僚の説明でした。 . が省略された場合はグローバルオブジェクトになります (non-strict モード時)。 strict モードでは undefined になります。(@ryo511 さん、ご指摘感謝) これだけだとナンノコッチャかもしれないので、具体例を...... this の内容をコンソールに表示するだけの、なんてことない関数です。 これをブラウザ上で呼び出すと...... のようになります。 この関数 test を特定のオブジェクトに結びつけます。 逆に、あるオブジェクトに結びついているものをグローバルスコープで呼び出すこともできます。 test() は obj の関数 test を呼び出していることになりますが、呼び出し時に .

callとapply

//call や applyを使うとオブジェクトを指定することができる

function test() {
    console.log(this)
}
var obj = { name: "obj" }
test() // => Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
test.call(obj) // => {name: "obj"}

test.call(obj)はobj.test()に等しい(ただし、この例ではobjにtestのメソッドは追加されない)

コンストラクタ

var obj = new function() {
    this.name = "obj"
    console.log(this) // => {name: "obj"}
}

// new を使うと、新規にオブジェクトを作成して、それに対して、 
// call を使って function を呼び出して、関数内部で return this するような動作になります。

// 上記は以下と同じ動作
var obj = function() {
    this.name = "obj"
    console.log(this) // => {name: "obj"}
    return this
}.call({})

bind

// 強制的にオブジェクトを結びつける
function test() {
    console.log(this)
}
var obj = { name: "obj" }
var check = test.bind(obj)
check() // => {name: "obj"}

// check呼び出しには.がついてないがobjがbindされているので、this=objとなる

Export

import { ONE, TWO as ZWEI } from './constants.js';
export const plus = (n, m = ONE) => n + m;
const times = (n, m = ZWEI) => n * m;

// デフォルトエクスポート。名前なしエクスポートで、名前は読み込む側で任意設定
// デフォルトエクスポートは1モジュール(1ファイル)につき1回まで
export default times;