「改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで」を読んでいる③

5.5.2章まで読んだ。知らない文法だけメモ。知らない要素ばっかりになってきた。

「改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで」を読んでいる① - チョキチョキかにさん

「改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで」を読んでいる② - チョキチョキかにさん

クラス

var Class = function(){};
var c = new Class();
console.log(c);

Functionオブジェクトをクラスとして使う。

初期化

let Student = function(name, age){
  this.name = name;
  this.age = age;
  this.getName = function(){
    return this.name;
  };
};

let student = new Student("takashi", 10);
console.log(student.getName());

あくまでメソッドは関数オブジェクトのメンバなので後から追加もできる。

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

let student = new Student("takashi", 10);
student.getName = function(){
  return this.name;
};

console.log(student.getName());

追加されたメソッドはインスタンスに対して追加されてるので別のStudentインスタンスにはない。

あくまでFunctionクラスなので、コンストラクタではなく関数として呼ばれてしまう可能性がある。

let Class = function(val){
        if(!(this instanceof Class)){
            return new Class(val);
      }
      this.val = val;
      this.getVal = () => val;
}

let c1 = new Class("a");
let c2 = Class("b"); // コンストラクタを呼び出せていないが、instanceofで判定してコンストラクタ経由でClassオブジェクトを返している。
console.log(c1.getVal());
console.log(c2.getVal());

this

thisの参照先は条件によって変わる。 コンストラクタの場合は生成したインスタンス自体。メソッドの場合はレシーバーオブジェクト。

call/apply

Functionオブジェクトのメンバで、その関数自体を呼び出す。thatに渡された値をthisとして参照できる。

prototype

メソッドはFunctionオブジェクトのフィールドであり、つまりコンストラクタを呼び出すときにFunctionオブジェクトが組み立てらる。 ので、インスタンスを作るたびに同じFunctionが毎回生成されてしまう。

同じオブジェクトの全てのインスタンスが同じprototypeへの参照を持っており、ここにFunctionオブジェクトを定義すると、Functiojnオブジェクトの初期化が無駄に複数回行われなくなる。

let Class = function(){}

Class.prototype.print = () => {
  console.log("call");
}
c1 = new Class();
c1.print();

インスタンスとprototypeに同じ名前のフィールドがあると、インスタンスが優先される。

オブジェクトリテラルでprototypeに対してメソッドを定義しするとこうなってスッキリする。

let Class = function(){}

Class.prototype = {
  print : () => console.log("call"),
  getDog : () => "dog"
}
c1 = new Class();
c1.print();
console.log(c1.getDog())

staticなプロパティ/メソッド

クラス自体に定義する。

let Class = function(){}
Class.print = () => console.log("ok");
Class.print();

プロトタイプチェーン

let Parent = function(){};
Parent.prototype = {
  print : () => console.log("Parent"),
  sleep : () => console.log("sleep")
}

let Child = function(){};
Child.prototype = new Parent(); // ChildのprototypeにParentを指定することで継承を表現
Child.prototype.print = () => console.log("Child");

c = new Child();
p = new Parent();
c.print();
p.print();
c.sleep();
p.sleep();

JavaScriptのプロトタイプチェーンはインスタンスが作成された時点で決まるので、インスタンス生成後に元クラスのprototypeを差し替えて継承元を変えても、インスタンスの挙動は変わらない。

型の判定方法

  • constructorプロパティで取得したコンストラクタと、型を比較
    • 継承してると親のコンストラクタが返って来ちゃう
  • instanceofでコンストラクタと比較
    • 継承元と比較してもtrue
  • isPrototypeOfでプロトタイプを比較

privateフィールド

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty これ使えばクロージャで表現しなくてもいいような気がする?

class命令

class Student {
  constructor(name, age){
    this.name = name;
    this.age = age;
  }
  
  print(){
    console.log(this.name + ":" + this.age)
  }
}

let takashi = new Student("takashi", 10);
let takeshi = new Student("takeshi", 80);
takashi.print();
takeshi.print();

アクセス修飾子はない。静的メソッドの宣言のためにstatic修飾子が使える。

class Super{
    constructor(val) {
    this.val = val;
  }
  print1(){console.log("Super");}
  print2(){console.log("Super");}
}

class Child extends Super{
    constructor(val1, val2) {
    super(val1);
    this.val2 = val2;
  }

    print1(){console.log("Child");}
}

let s = new Super(1);
let c = new Child(2,3);
s.print1(); // Super
s.print2(); // Super
c.print1(); // Child
c.print2(); // Super(Override)

console.log(c.val); // 2(superで親クラスのコンストラクタが呼ばれている)
console.log(c.val2); // 3

Computed property

let i = 1;
let a = {['yes' + i] : 100}
console.log(a); // {yes1: 100}

自前でのイテレーターの実装

class Test{
  constructor(max){
    this.max = max
  }
  [Symbol.iterator](){
    let value = 0;
    let that = this;
    return {
      next() {
        if (value > that.max) { // nextメソッドの内部ではthisの領域が変わってしまうので、thisではなくthatに格納した値を利用している。
          return {done:true}
        }
        return  {value:value++, done:false}
      }
    }
  }
}

let test = new Test(10);
for (let i of test){
  console.log(i);
}

Generator

yieldで値が返却(返却といっていいのか?)した後、そこで一旦停止するが次のループでまたyieldの行から処理が再開する。

function* aho(){
  let count = 0;
  while(true){
    count++
    if(count % 3 == 0){
      yield count + "!!!!!!!!!";
      continue;
    }
    if (count > 10) {break;}
    yield count;
  }
}

for(let val of aho()){
  console.log(val)
}

Proxy

オブジェクトをラップして値の参照とか更新とか初期化とか関数の呼び出しに干渉できる。

class Test {
  constructor(greeting) {
    this.greeting = greeting;
  }
}

let test = new Test("fuck");
var testProxy = new Proxy(test, {
  get: function(target, prop) {
    if(target.greeting == "fuck"){
      return "hello beautiful world";
    }
    
    return target.greeting;
  }
});
console.log(testProxy.greeting);

Testクラスのオブジェクトのgreetingフィールドがfuckだったらhello beautiful worldに差し替えてる。 用途がよくわからん。ライブラリのHTTPクライアントに呼び出し元のログの機構を組み込みたい時とかかな。