JavaScriptの勉強中:その3 Statements / Objects

続きー。全然終わらん。でも、面白い。

bufferings.hatenablog.com

## Ch. 5 Statements

### 5.3.3 switch

  • Switch 文は Java と同じで case を抜けるのに break; が必要。
  • case のマッチには == じゃなくて === が使われる。良かった。

### 5.4.4 for/of

  • ES6で導入された
  • iterable なオブジェクトをループする。配列、文字列、セット、マップなど。

配列の場合

const data = [1,2,3,4,5];
let sum = 0;
for (const element of data) {
    sum += element;
}
console.log(sum); // => 15

オブジェクトはデフォルトでは iterable ではないけど、 Object.keys() Object.values() または Object.entries() を使えばループできる:

const o = { x: 1, y: 2, z:3 };
let pairs = "";
for (const [k, v] of Object.entries(o)) {
    pairs += k + v;
}
console.log(pairs); // => "x1y2z3"

前に書いたけど文字列も iterable だから for/of ループで1文字ずつ回せる。サロゲートペアのやつも2つに分かれたりせずに、1文字として処理される。

### for/await

  • ES2018で "asynchronous iterator" が導入されて for/await でループできる
async function() {
   for await (num of asyncIterable) {
     console.log(num);
   }
}

### 5.4.5 for/in

  • for/of は ES6 で導入されたけど for/in は昔からある
  • オブジェクトのプロパティをループするための文
  • あんまり使わないほうが良さそう。 for/of で考えるようにしといたらいいのかな。

### 5.5.6 Throw

  • Errorクラスだけじゃなくて、なんでも投げれるのか。おー。

### 5.5.7 try/catch/finally

  • try/catch/finally 自体は特に違和感ない
  • ES2019 からは catch でパラメーターの指定を省略できるようになった
try {
    return JSON.parse(s);
} catch {
    return undefined;
}

### 5.6.1 with

  • strict mode では使えない。覚えなくて良さそう。

### 5.6.2 debugger

  • IDE使ってたらあんまり使わなさそうかなー。

## Ch. 6 Objects

### 6.2 Creating Objects

オブジェクトを生成する方法は3つある。

  • オブジェクトリテラルを使う
  • new オペレーターを使う
  • Object.create() を使う
// オブジェクトリテラル
let point = { x: 0, y: 0 };

// new
let date = new Date();

// Object.create()
let obj = Object.create({x: 1, y: 2});

### Object.create()

  • 第一引数に渡したオブジェクトが新しいオブジェクトのプロトタイプになる。
  • 第一引数に null を渡すとプロトタイプを持たないオブジェクトが生成できる。
  • 普通の空オブジェクト( {} とか new Object() と同じもの)を作る場合はこんな風にやる:
let emptyObject = Object.create(Object.prototype);
  • 用法の一つとしてあるのは、あるオブジェクトを引数として渡すときに、想定外(でも悪意のない)変更から守るために利用したりする:
let o = { x: "don't change this value" };
library.function(Object.create(o));

プロトタイプの仕組みって面白いなー。

6.3 Querying and Setting Properties

  • プロパティの値を get/set するには、ドット( . )または角括弧( [ ] )オペレーターを使用する
  • ドットの場合は、右側のプロパティ名はシンプルな識別子じゃないといけない
  • 角括弧の場合は、括弧の中身は文字列として評価される式、もしくは文字列や Symbol に変換することができる値でなければならない
let author = book.author;
let title = book["main title"];

book.edition = 7;
book["main title"] = "ECMAScript";

角括弧の方は実行時にプロパティ名を評価できるから柔軟そうだな。けど ES6 以降は、そういうことしたいときには Map を使えば良さそう。

### 6.3.2 Inheritance

  • オブジェクトに対するプロパティの値を取得したいときは、そのプロパティが own properties になければ prototype チェーンを探しに行く
> let obj1 = {x: 1};
undefined
> let obj2 = Object.create(obj1);
undefined
> obj2.y = 2
2
> let obj3 = Object.create(obj2);
undefined
> obj3
{}
> obj3.x + obj3.y
3
  • プロパティに値をアサインするときにも prototype チェーンを見に行くけど、これはそのプロパティに対するアサインができるかどうかを確認するためだけ。
  • もし、その継承したプロパティが読み取り専用だったら、アサインは許可されない。
  • もし、アサイン可能だったとしても、 prototype を変更することはなくて、オリジナルのオブジェクトにプロパティをセットする。
// さっきの続きで
> obj3.y = 5;
5
> obj3
{ y: 5 }
> obj2
{ y: 2 }
  • もし指定されたプロパティに対して継承元が setter メソッドを持っている場合はその setter が呼びだされる。
  • でも this は呼び出し元のオブジェクトになるから、結局 prototype は変更されない。
> let obj1 = {x: 1, get sample(){return this.x}, set sample(v){this.x = v}}
undefined
> let obj2 = Object.create(obj1);
undefined
> obj2.sample
1
> obj2
{}
> obj2.sample = 5
5
> obj2
{ x: 5 }

そっちが更新されるんかい!という気持ち。

### 6.4 Deleting Properties

  • delete オペレーターを使うとオブジェクトからプロパティを削除することができる。
  • 削除するのは own properties のみ。継承してるやつは削除できない。したければ prototype オブジェクトから削除しないといけない。
> let obj1 = {x: 1}
undefined
> let obj2 = Object.create(obj1)
undefined
> obj2.x = 2
2
> obj2
{ x: 2 }
> delete obj2.x
true
> obj2.x
1
> delete obj2.x
true
> obj2.x
1
> delete obj1.x
true
> obj2.x
undefined

### 6.5 Testing Properties

オブジェクトがプロパティを持っているかどうかをチェックする際には以下の方法がある:

  • in
  • hasOwnProperty()
  • propertyIsEnumerable()
  • 単純に呼び出してみる

in は own property または継承したプロパティがあれば true を返す

> let obj1 = {x: 1}
undefined
> let obj2 = Object.create(obj1)
undefined
> obj2.y = 2
2
> "x" in obj2
true
> "y" in obj2
true
> "toString" in obj2
true

hasOwnProperty() は own property の場合だけ true

> obj2.hasOwnProperty("x")
false
> obj2.hasOwnProperty("y")
true
> obj2.hasOwnProperty("toString")
false

propertyIsEnumerable() は own property かつ、そのプロパティが列挙可能の場合だけ true

> obj2.propertyIsEnumerable("y")
true

> Object.prototype.hasOwnProperty("toString")
true
> Object.prototype.propertyIsEnumerable("toString")
false

基本的には in を使うなら、直接プロパティにアクセスしてみて !== undefined をすればいい。 undefined のプロパティを定義してる場合は in じゃないと区別つかないけど。

### 6.7 Extending Objects

プロパティをあるオブジェクトから別のオブジェクトにコピーする場合には、こんな感じでかける:

> let target = {x:1,y:2}, source = {y:3,z:4}
undefined
> for(let key of Object.keys(source)) {
... target[key] = source[key];
... }
4
> target
{ x: 1, y: 3, z: 4 }

けど、よくある処理なので、ES6で Object.assign() が導入された

> let obj1={x:1,y:2}, obj2={y:3,z:4}
undefined
> Object.assign(obj1, obj2)
{ x: 1, y: 3, z: 4 }
> obj1
{ x: 1, y: 3, z: 4 }

もし、副作用したくなかったら、こんな感じでできる

> let obj1={x:1,y:2}, obj2={y:3,z:4}
undefined
> let obj3 = Object.assign({}, obj1, obj2)
undefined
> obj3
{ x: 1, y: 3, z: 4 }
> obj1
{ x: 1, y: 2 }

後で出てくる spread operator (ES2018) を使うともっとシンプルにかける

> let obj1={x:1,y:2}, obj2={y:3,z:4}
undefined
> let obj3 = {...obj1, ...obj2}
undefined
> obj3
{ x: 1, y: 3, z: 4 }
> obj1
{ x: 1, y: 2 }

### 6.10 Extended Object Literal Syntax

オブジェクトリテラルシンタックスについて。

(ES6) Shorthand Properties:

let x = 1, y = 2;

let obj1 = {
    x: x,
    y: y
}

// プロパティ名と変数名が同じ場合は、こう書ける
let obj2 = {
    x,
    y
}

(ES6) Computed Property Names:

// プロパティ名を実行時に指定できる。Symbol も利用できる。
let obj = {
    [PROPERTY_NAME]: 1,
    [computePropertyName()]: 2
}

(ES6) Shorthand Methods:

// こういうやつを
let square = {
    area: function() { return this.side * this.side; },
    side: 10
}

// functionを省略してこういう風にかける
let square = {
    area() { return this.side * this.side; },
    side: 10
}

// こういうのもOK
let weirdMethods = {
    "method with spaces"(x) { return x + 1; },
    [METHOD_NAME](x) { return x + 2; },
    [symbol](x) { return x + 3; },
}

### 6.10.4 Spread Operator

  • ES2018
  • オブジェクトリテラルの中で ... を使って、オブジェクトのプロパティをコピーできる。さっきやったやつ。
  • 複数のオブジェクトが同じプロパティ名を持ってる場合は、右側の方の値になる
> let obj1 = {x:1, y:2}
undefined
> let obj2 = {y:3, z:4}
undefined
> {...obj1, ...obj2}
{ x: 1, y: 3, z: 4 }
> {...obj2, ...obj1}
{ y: 2, z: 4, x: 1 }
  • Spread operator は own property だけを展開することに注意。継承した property は展開しない
> let obj = Object.create({x:1})
undefined
> let p = {...obj}
undefined
> p.x
undefined

### 6.10.6 Property Getters and Setters

  • GetterとSetterはES5
let obj = {
    x: 10,

    get sample() { return this.x; },
    set sample(x) { this.x = x; } 
}

setter を定義しなかったら read-only になる

let obj2 = {
    x: 10,

    get sample() { return this.x; },
}

ここまでで1/3くらいかな。