Open31

【JS】ガチで学びたい人のためのJavaScriptメカニズムのメモ

snunsu:<esnunsu:<e

スコープチェーン

  • スコープは内側から外側に向かって探しに行く
let globalVar = "global"; // グローバルスコープ

function exampleFunction() {
    let localVar = "local"; // ローカルスコープ

    if (true) {
        let blockVar = "block"; // ブロックスコープ
        console.log(blockVar); // "block"
    }

    // console.log(blockVar); // エラー: blockVarはここではアクセスできない

    function innerFunction() {
        console.log(localVar); // "local" - クロージャによりアクセス可能
    }

    innerFunction();
}

exampleFunction();

snunsu:<esnunsu:<e

クロージャー

意味

  • 上位階層の値や参照を保持していること

ユースケース

  • プライベート関数の実装に役立つ
  • また、動的な関数の生成にも役立つ

プライベート関数の実装

function incrementFactory(){
  // numは関数外から更新されない
  let num = 0;
  function increment(){
      console.log(num++);
  }
  return increment;
}

const increment = incrementFactory();
increment(); // 0
increment(); // 1
increment(); // 2

動的な関数の生成

function addNumberFactory(num){
  function addNumber(value){
    return num + value;
  }
  return addNumber;
}

const add5 = addNumberFactory(5);
// function addNumber(value){
//   return 5 + value;
// }
// が返ってきてる

const result = add5(10);
// function addNumber(value){
//   return 5 + 10;
// }
console.log(result); // 10
snunsu:<esnunsu:<e

即時関数

(function(){
  console.log('hey');
})();

function sayHey(){
  console.log('hey');
}

sayHey(); // と同義
(sayHey)(); // と同義
(sayHey)();

の()はグループ化の意味

グループ化

  • 数値演算の際の優先順位を上げる時に使うやつ
  • 関数に対して行えば、特に何もしないことになる
  • 関数名を使用しない時に()を使用しないとエラーになるので()を使用する
snunsu:<esnunsu:<e

let const var

  • varは使わないこと!!!!!!!!
var let const
スコープ 関数スコープ ブロックスコープ ブロックスコープ
再宣言 可能 不可能 不可能
再代入 可能 可能 不可能
初期化 不要(undefinedで初期化) 不要(undefinedで初期化) 必須
ホイスティング 変数宣言と初期化がホイスト 変数宣言がホイスト(暗黙のTDZ) 変数宣言がホイスト(暗黙のTDZ)
snunsu:<esnunsu:<e

暗黙的型変換について


function printTypeAndValue(val){
    console.log(typeof val,val);
}

let a = 0;
let b = '1' + a;
// aがbのstring型にキャスティングされる
console.log(printTypeAndValue(b)); // string 10

let c = 15 + b;
// cがbのnumber型にキャスティングされる
console.log(printTypeAndValue(c)); // number  5

let d = c - null;
// nullがcのnumber型にキャスティングされ0と解釈される
console.log(printTypeAndValue(d)); // number  5

let e = d - true;
// trueがdの型にキャスティングされ1と解釈される
console.log(printTypeAndValue(e); // number  4
snunsu:<esnunsu:<e

厳格的な等価性と抽象的な等価性

function printEq(a,b){
    console.log(a === b); // 厳格的
    console.log(a ==b );  // 抽象的
}

let a = '1';
let b = 1;
let c = true;
printEq(a,b) // false true
printEq(b,c) // false true
snunsu:<esnunsu:<e

ANDとORの応用

name = name || 'Tom'; // nameがfalsyならTomが代入される

name && hello(name); // nameがtruthyなら右の関数が実行される
snunsu:<esnunsu:<e

プリミティブとオブジェクト

  • プリミティブ型の再代入は→の方向が変わるイメージ
    • コピーしたとき、参照元に影響しない
    • 参照先の値がコピー
let a = 1;
let b = a; // この時点でコピーしたのは参照先の値のみであり参照の→をコピーした訳ではない
b = 3; // コピーした値を3に変えているだけ
console.log(a); // なので1が出力される
  • オブジェクトは参照を名前付きで管理している
    • コピーしたとき、参照元にも影響する
    • 参照がコピー
let c ={
    prop: 'hello'
}
let d = c; // dにcの参照をコピーしてる
d.prop = 'bye'; // 参照先を変えられている
console.log(c,d); // { prop: 'bye' } { prop: 'bye' }
snunsu:<esnunsu:<e

分割代入

const a = {
  prop : 'hello',
  tmp : 'hey'
}

let {prop,tmp} = a;

console.log(prop);
console.log(tmp); // プロパティ名と一致させないとundefinedになる
snunsu:<esnunsu:<e

関数は 実行可能な オブジェクトである

const person = {
    name : 'Tom',
    // helloを探す
    hello : function(){
        console.log('Hello' + this.name);
    }
}
person.hello(); // personを探す
  • person内のhelloが直接呼び出されたときは、呼び出し元のオブジェクトがpersonではない
snunsu:<esnunsu:<e

直接呼び出されたとき

name = 'John';
const person = {
    name : 'Tom',
    // helloを探す
    hello : function(){
        console.log('Hello, ' + this.name);
    }
}
person.hello(); // Hello, Tom
const ref = person.hello; 
ref(); // Hello, John

の場合

function(){
  console.log('Hello, ' + this.name);
 }
  • を渡していることになる
  • なのでpersonを介しているわけではない
snunsu:<esnunsu:<e

コールバック関数

name = 'John';
const person = {
    name : 'Tom',
    // helloを探す
    hello : function(){
        console.log('Hello, ' + this.name);
    }
}
person.hello(); // Hello, Tom

function fn(ref){
  ref();
}

fn(person.hello); // Hello, John
  • これは参照コピーが起きている
snunsu:<esnunsu:<e

定義化

  • thisは
  • オブジェクトとして実行すると→呼び出しオブジェクト
  • 関数として実行すると→グローバルオブジェクト
snunsu:<esnunsu:<e

bind

  • 明示的にできる
name = 'John';
const person = {
    name : 'Tom',
    // helloを探す
    hello : function(){
        console.log('Hello, ' + this.name);
    }
}
const helloTom = person.hello.bind(person); // Hello, Tom

function fn(ref){
  ref();
}

fn(helloTom); // Hello, Tom
snunsu:<esnunsu:<e

apply

  • 実行までしてくれる
  • 第二引数は配列
  • callは配列ではなく、実引数
function a(){
  console.log('hello ' + this.name);
}
const tim = {name : 'tim'};
a.apply(tim); // hello tim
snunsu:<esnunsu:<e
  • 以下、ChatGPTによるまとめ
  • callとapplyはどちらも関数のthisを指定するために使用されます。
  • callは個別の引数をコンマ区切りで、applyは引数を配列として渡します。
  • applyは配列を引数として関数に渡す必要がある場合に特に便利です。
  • ES6以降では、applyの代わりにスプレッド演算子 (...) を使って配列を個別の引数に展開することが一般的になりました。
snunsu:<esnunsu:<e

実用例

const array = [1,2,3,4,5];
const result = Math.max.apply(null,array);
console.log(result);

下記でも可

const array = [1, 2, 3, 4, 5];
const result = Math.max(...array);
console.log(result); // 5
snunsu:<esnunsu:<e

アロー関数

// 基本系
const b = function hello(name){
  return 'hello ' + name;
}

// functionと関数名を省略できる
const c = (name) => {
  return 'hello ' + name;
}

// 波括弧とリターンも削除できる
const d = (name) =>  'hello ' + name;

console.log(b('boss')); // hello boss
console.log(c('sir')); // hello sir
console.log(d('siri')); // hello siri

snunsu:<esnunsu:<e
  • アロー関数はthisやargmentsが使えない
  • なのでthisはグローバルオブジェクトを指す
snunsu:<esnunsu:<e

prototype

function Person(name,age){
  this.name = name;
  this.age = age;
}

Person.prototype.hello = function (){
  console.log('hello , ' + this.name)
}

const bob = new Person('Bob',18);

bob.hello(); // hello , Bob
  • 関数を追加できる
  • 内部的には prototype が追加される
  • インスタンス生成時に参照だけ渡してメモリの効率化を図ってる
snunsu:<esnunsu:<e
function Person(name,age){
  this.name = name;
  this.age = age;
  return {};
}

const bob = new Person('Bob',18);
console.log(bob); // {}
function Person(name,age){
  this.name = name;
  this.age = age;
  return 1;
}

const bob = new Person('Bob',18);
console.log(bob); // Person { name: 'Bob', age: 18 }
snunsu:<esnunsu:<e

new演算子

  • newしたときreturnされる値がobjectかそれ以外かで挙動が変わる
  • objectであればobjectが返ってくる
  • それ以外はprototypeをコピー
function Person(name,age){
  this.name = name;
  this.age = age;
  return {}
}

Person.prototype.c = function(){}

function newPre(C, ...args){
  const _this = Object.create(C.prototype);
  const result = C.apply(_this, args);
  if(typeof result === "object" && result !== null){
    return result;
  }
  return _this;
}

const bob = new newPre(Person,1,2);
console.log(bob);
snunsu:<esnunsu:<e

プロトタイプチェーン

  • prototypeは上位オブジェクトまで遡って調べてくれる
  • hasOwnPropertyは自分自身のオブジェクトまでしか調べない
  • inはprototypeまで調べてくれる
function Person(name,age){
  this.name = name;
  this.age = age;
}

Person.prototype.hello = function(){
  console.log('hello,' + this.name);
}

const bob = new Person('Bob',18);
console.log(bob.hasOwnProperty('name')); // true
console.log(bob.hasOwnProperty('age')); // true
console.log(bob.hasOwnProperty('hello')); // false
console.log('name' in bob); // true
console.log('age' in bob); // true
console.log('hello' in bob); // true
snunsu:<esnunsu:<e

プロパティとディスクリプタ

const obj = {prop:0}

const descripter = Object.getOwnPropertyDescriptors(obj);
console.log(descripter);
  • vallue →値
  • configurable→設定変更可能性
  • enumerable→値の列挙可能性
  • writable→値の変更可能性
snunsu:<esnunsu:<e

チェーンメソッド

  • retuen でインスタンスを返してあげる
  • そうすることでインスタンス.ファンクションという形になるので繋げて実行できる
class Person{

  constructor(name,age){
    this.name = name;
    this.age = age;
  }

  hello(person){
    console.log(`Hello, ${this.name} `);
    return person;
  }

  show(person){
    console.log(`His age is  ${person.age} `);
    return person;
  }  
}


const bob = new Person('bob',18);
const tim = new Person('tim',20);
bob.hello(tim).show(tim);
snunsu:<esnunsu:<e

ジェネレータ

概要

  • 一時停止と再開が可能な関数
  • yieldはdoneとvalueが内包されている
  • doneは状態管理要員
  • valueは値管理要員
  • これらを外部に提供できる
  • next()を使用して状態遷移できる

活用例

フィボナッチ数列

function* fibonacci() {
  let [prev, curr] = [0, 1];
  while (true) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

const seq = fibonacci();
console.log(seq.next().value); // 1
console.log(seq.next().value); // 2
console.log(seq.next().value); // 3
console.log(seq.next().value); // 5
console.log(seq.next().value); // 8
console.log(seq.next().value); // 13

信号機の状態管理

function* trafficLight() {
  while (true) {
    console.log('Green');
    yield;
    console.log('Yellow');
    yield;
    console.log('Red');
    yield;
  }
}

const light = trafficLight();
light.next(); // Green
light.next(); // Yellow
light.next(); // Red
// これを繰り返す
  • もし、ジェネレータを使わない場合
  • こんな感じになる
  • 面倒
const trafficLight = (() => {
  const states = ['Green', 'Yellow', 'Red'];
  let current = 0;

  return {
    change: function() {
      console.log(states[current]);
      current = (current + 1) % states.length;
    },
    getCurrentState: function() {
      return states[current];
    }
  };
})();

trafficLight.change(); // Green
trafficLight.change(); // Yellow
trafficLight.change(); // Red
trafficLight.change(); // Green と繰り返す

snunsu:<esnunsu:<e

Proxy

概要

  • オブジェクトのプロパティ操作に対して独自のメソッドを作成することができる
  • setやgetはトラップ関数と言う
const targetObj = {a:0};
const handler = {
  // 第一引数はnew Proxyのfunctionの第一引数
  // 第二引数はプロパティにアクセスされた際のプロパティ名
  // 第三引数は新しい値
  // 第四引数はnew Proxy自身のインスタンス
  set:function(target,prop,value,receiver){
    console.log(`[set]:${prop}`);
    target[prop] = value;
  },
  get:function(target,prop,receiver){
    console.log(`[get]:${prop}`);
    target[prop] = value;
  }
}

const pxy = new Proxy(targetObj,handler)
pxy.a = 1; // [set]:a

使い方

値をsetしようとした時に例外を投げる

  set:function(target,prop,value,receiver){
    console.log(`[set]:${prop}`);
    throw new Error('setしないで');
  },

getして値が無かったらデフォルト値を返す

  get:function(target,prop,receiver){
    if(target.hasOwnProperty(prop)){
      return target[prop];
    }else{
      return -1;
    }
    console.log(`[get]:${prop}`);
  }
snunsu:<esnunsu:<e

Reflect

  • オブジェクトの内部メソッドにアクセスする手段を関数化できる
class C {
  constructor(a,b){
    this.a = a;
    this.b = b;
  }
}

// これらは同義
const obj1 = new C(1,2);
const obj2 = Reflect.construct(C,[1,2]);

// これらは同義
console.log('c' in obj1);
console.log(Reflect.has('obj1, 'c'));
snunsu:<esnunsu:<e

DOMと仮想DOM

<!DOCTYPE html>
<html>
<head>
    <title>My Page</title>
</head>
<body>
    <div>
        <p>Text 1</p>
        <p>Text 2</p>
    </div>
</body>
</html>

に対する

Document
|-- DOCTYPE
|-- html
    |-- head
    |   |-- title
    |       |-- #text: "My Page"
    |-- body
        |-- div
            |-- p
            |   |-- #text: "Text 1"
            |-- p
                |-- #text: "Text 2"
  • これが複製されたのが仮想DOM
  • 仮想DOMの変更を検知したらDOMに対してpatch(更新)を行う
  • 仮想DOMを使用した方がパフォーマンスが良いらしい
  • documentはwindowオブジェクトのAPI
  • window.documentが本来の形