たくろぐ!

世界一のチラ裏

開眼JavaScriptをまとめました

はじめに

あほ~い、宝鐘海賊団クルー56513号のたくです。

昔買って読んだ同書のまとめを書いていこうと思います。

JavaScriptの言語仕様を理解して設計思想をつかむための本だと思っている!

注意点としては、ES5(ES2009)準拠のため、ブロックスコープの概念がなかったり、一部古い表現になってます!

今読むとややこしいのですが、JSの進化がわかるのでそれはそれでよかったと思ったり!

では早速始めてみるぞ。

目次

1章 JavaScriptオブジェクト

1.1 オブジェクトの生成

1.2 コンストラクタ関数はオブジェクトインスタンスを構築して返す

1.3 ネイティブ / ビルトインオブジェクトのコンストラク

1.4 ユーザ定義の(ネイティブではない)コンストラクタ関数

1.5 new演算子とコンストラクタ関数でインスタンス生成

1.6 リテラルを使って値を生成

1.7 プリミティブ型(基本型)の値

1.8 null、undefined、'string'、10、true、falseはオブジェクトではなくプリミティブ型

1.9 プリミティブ値はどのように保存・複製されるか

1.10 プリミティブ値は、値そのものを比較

1.11 プリミティブ型の文字列、数値、真偽値はオブジェクトのように扱うとオブジェクトのようにふるまう

1.12 オブジェクト(複合型の値)

1.13 オブジェクトはどのように保存・複製されるか

1.14 オブジェクトは同値判定に参照を使用

1.15 オブジェクトは動的なプロパティを持つ

1.16 typeof 演算子の戻り値

1.17 動的プロパティはミュータブル(可変)オブジェクトを可能にする

1.18 すべてのコンストラクタのインスタンスはコンストラクタ関数にポイントするconstructorプロパティを持つ

1.19 instatanceof演算子を使ってコンストラクタ関数を特定する

1.20 インスタンスは自身のプロパティを持つことができる(インスタンスプロパティ)

1.21 「JavaScriptオブジェクト」と「Object()オブジェクト」の意味

2章 オブジェクトとプロパティを扱う

2.1 オブジェクトはすべての値をプロパティとして格納できる

2.2 オブジェクトと配列でツリー構造や多次元配列を実現

2.3 ドット記法とブラケット記法でオブジェクトのプロパティにアクセス

2.4 delete演算子でプロパティを削除する

2.5 プロパティへの参照はどのように解決されるのか

2.6 hasOwnProperty()を使って、プロパティがプロトタイプチェーン経由ではないことを確認

2.7 in演算子を使って、オブジェクトを保持しているかどうかを確認する

2.8 for-inループを使って、プロパティを列挙する

2.9 ホストオブジェクト

2.10 Underscore.jsを使ってオブジェクトを拡張する

3章 オブジェクト(Object())

3.1 Object()オブジェクト

3.2 Object()のパラメータ

3.3 Object()が持つプロパティとメソッド

3.4 Object()のインスタンスのプロパティとメソッド(String.prototypeのプロパティ)

3.5 Object()オブジェクトをオブジェクトリテラルで生成する

3.6 すべてのオブジェクトはObject.prototypeから継承する

4章 関数(Function())

4.1 Function()オブジェクト

4.2 Funciton()のパラメータ

4.3 Function()が持つプロパティとメソッド

4.4 Function()のインスタンスのプロパティとメソッド(Function.prototypeのプロパティ)

4.5 関数は常に値を返す

4.6 JavaScriptの関数は第一級関数

4.7 関数に引数を渡す

4.8 thisとargumentsはすべての関数本体で利用できる

4.9 arguments.callee

4.10 関数インスタンスのlengthプロパティとarguments.length

4.11 引数を再定義

4.12 関数の実行を中断

4.13 関数の定義方法(宣言、式、もしくはコンストラクタ)

4.14 関数の実行方法(関数、メソッド、コンストラクタ、もしくはcall()とapply())

4.15 無名関数

4.16 関数式を即時実行

4.17 無名関数を即時実行

4.18 関数は入れ子可能

4.19 関数を関数に渡し、関数から関数を返す

4.20 関数宣言を使って、定義される前に関数を実行する(関数の巻き上げ)

4.21 関数は自身を呼ぶことができる(再帰

5章 グローバルオブジェクト

5.1 グローバルオブジェクト

5.2 グローバル関数

5.3 グローバルプロパティ、グローバル変数

5.4 グローバルオブジェクトにアクセスする

5.5 グローバルオブジェクトは暗黙的に参照される

6章 this

6.1 thisとは何か、およびthisは何を参照するか

6.2 thisの値はどのように決められるのか?

6.3 入れ子関数内では、thisはグローバルオブジェクトを参照する

6.4 入れ子関数内でthisを見失う問題をスコープチェーンを使って回避する

6.5 call()やapply()を使ってthisの値をコントロールする

6.6 thisキーワードをユーザ定義のコンストラクタ関数で使う

6.7 プロトタイプメソッド内のthisは生成されるインスタンスを参照する

7章 スコープとクロージャ

7.1 JavaScriptのスコープ

7.2 JavaScriptブロックスコープを持たない

7.3 関数内で変数を定義する際は常にvarを使い、スコープの穴を避ける

7.4 スコープチェーン(静的スコープ)

7.5 スコープチェーンの検索は最初に発見した値を返す(マスキング)

7.6 スコープは関数実行時ではなく関数定義時に決められる

7.7 クロージャはスコープチェーンによって生成される

7.8 グローバルスコープで宣言されている関数もクロージャである

8章 関数のprototypeチェーン

8.1 プロトタイプチェーン

8.2 なぜprototypeプロパティが重要なのか

8.3 protptypeプロパティはすべてのFunction()インスタンスに自動的に付与される

8.4 prototypeプロパティのデフォルト値はObject()オブジェクト

8.5 コンストラクタ関数から生成されたインスタンスはそのコンストラクタのprototypeプロパティにリンクする

8.6 プロトタイプチェーンの終着点はObject.prototype

8.7 プロトタイプチェーンは最初に見つけたプロパティを返す

8.8 prototypeプロパティに新しいオブジェクトを設定するとデフォルトのconstructorプロパティを失う

8.9 プロトタイプからプロパティを継承するインスタンスは常に最新の値を取得

8.10 prototypeプロパティを新しいオブジェクトに置き換えた場合、過去のインスタンスは更新しない

8.11 ユーザ定義コンストラクタもプロトタイプ継承を行うことができる

8.12 継承チェーンを生成する

9章 配列(Array())

9.1 Array()オブジェクト

9.2 Array()パラメータ

9.3 Array()のプロパティとメソッド

9.4 Array()のインスタンスのプロパティとメソッド(Array.prototypeのプロパティ)

9.5 配列を生成する

9.6 要素の追加・変更

9.7 lengthとインデックス

9.8 指定した数の要素を持った配列を生成する

9.9 配列のlengthを設定して要素の追加・削除

9.10 配列を配列に格納(多次元配列)

9.11 配列の要素を前から・後からループする

10章 String()

10.1 String()オブジェクト

10.2 String()で文字列オブジェクトを生成、そして値を文字列に変換

10.3 String()のプロパティとメソッド

10.4 String()のインスタンスのプロパティとメソッド(String.prototypeのプロパティ)

11章 Number()

11.1 NUmber()オブジェクト

11.2 整数と小数

11.3 Number()に引数を渡して数値に変換

11.4 Number()のプロパティとメソッド

11.5 Number()のインスタンスのプロパティとメソッド(Number.prototypeのプロパティ)

12章 Boolean()

12.1 Boolean()オブジェクト

12.2 Boolean()のパラメータ

12.3 Boolean()のプロパティとメソッド

12.4 Boolean()のインスタンスのプロパティとメソッド(Boolean.prototypeのプロパティ)

12.5 真偽値オブジェクトは格納されている値がfalseであってもtrueと評価される

12.6 いくつかの値はfalse、それ以外はすべてtrueに変換

13章 プリミティブ型文字列、数値、真偽値

13.1 プリミティブ値は、プロパティにアクセスされるとオブジェクトに変換される

13.2 文字列、数値、真偽値は常にプリミティブ型を使う

14章 null

14.1 null

14.2 typeof nullは'Object'を返す

15章 undefined

15.1 undefined

15.2 JavaScript ECMA-262 Edition 3以降ではundefined変数が存在

16章 Math 関数

16.1 ビルトインMathオブジェクト

16.2 Mathが持つプロパティとメソッド

16.3 Mathはコンストラクタ関数ではない

16.4 Mathオブジェクトは変更・削除不可

要約

1章 JavaScriptオブジェクト

オブジェクトとは名前と値を持つプロパティを格納するコンテナにすぎない。

JavaScriptでのメソッドは、オブジェクト上で動作するよう意図した「関数オブジェクトを持ったプロパティ」と定義される。

var cody = new Object(); //Object()コンストラクタ関数と呼ぶ
(中略)
cody.gender = 'male';
cody.getGender = function() { // これがメソッド。オブジェクト内関数。
  return cody.gender;
};
console.log(cody.getGender());
// Person型の関数を定義する
var Person = function(gender) {
  this.gender = gender;
  this.getGender = function() {
    return this.gender;
  };
};

// 上記を呼んでcodyを作る
// コンストラクタ関数で作るよりも汎用性が高く、一般的
var cody = new Person('male');
console.log(cody.getGender());
wrapped_str = new String('foo'); // String {'foo'}
constructor_called_str = String('foo'); // 'foo'
literal_str = 'foo'; // 'foo'

文字列、数値、真偽値は、newすると各プリミティブをラップするオブジェクトが生成される。

// 数値オブジェクト、数値リテラル
var myNumber = new Number(23);
console.log(myNumber); // Number(23)
typeof myNumber // 'object'
  
var myNumberWithoutNew = Number(23);
console.log(myNumberWithoutNew) // 23
typeof myNumberWithoutNew // 'number'  

var myNumberLiteral = 23;
console.log(myNumberLiteral) // 23
typeof myNumberLiteral // 'number'  
  
// オブジェクト、オブジェクトリテラル
myObject = new Object();
console.log(myObject); // {}
typeof myObject // 'object'
  
myObjectWithoutNew = Object();
console.log(myObjectWithoutNew); // {}
typeof myObjectWithoutNew // 'object'
  
var myObjectLiteral = {};
console.log(myObjectLiteral); // {}
typeof myObjectLiteral // 'object'

リテラルはプリミティブ型が宣言されるたびに作成されるので、同値判定がtrueになる。

newを使ってオブジェクトを作成すると、参照がコピーされるため、同値判定はfalseになり、等値判定はtrueになる。

オブジェクト同士では、同じオブジェクトを参照している場合に同値判定がtrueになる。

var price1 = 10;
var price2 = 10;
var price3 = new Number(10);
var price4 = price3;

console.log(price1 === price2); // true

console.log(price1 === price3); // false

console.log(price4 === price3); // true

price4 = 10;

console.log(price4 === price3); // false

console.log(price4 == price3); // true

typeofでは結果に一貫性がないため、以下の例外のみを覚える。

// プリミティブ値のnull
var myNull = null;
console.log(typeof myNull); // object
  
// プリミティブ値のundefined(参考)
var myUndefined = undefined;
console.log(typeof myUndefined) // undefined
  
// newを使用したFunctionオブジェクト
var myFunction = new Function('x', 'y', 'return x * y');
console.log(typeof myFunction); // function

// newを使用したNumberオブジェクト(参考)
var myNumber = new Number(23);
console.log(typeof myNumber); // object

4章 関数(Function())

関数の引数は関数内で仮引数の変数とargumentsで上書きが可能。

var myFunction = function(foo) {
    arguments[0] = 'xyz';
    console.log(foo, arguments[0]);
    foo = 'foo';
    console.log(foo, arguments[0]);

    var arg1 = arguments[1];
    arguments[1] = '123';
    console.log(arg1, arguments[1]);
}

myFunction('abc');  
  
// xyz xyz
// foo foo
// undefined '123' 

関数の定義方法は以下の3つ(関数式を2つに分けると4つ)。

// 関数コンストラクタ
var addConstructor = new Function('x', 'y', 'return x + y');
  
// 関数宣言(function文)
function addStatement(x, y) {
    return x + y;
}
  
// 関数式
var addExpression = function(x, y) {
    return x + y;
}

// 関数式(関数名指定:関数内で関数自身を指定する場合に使用。4.21節を参照。)
var addNamedExpression = function addNamedExpression(x, y) {
    return x + y;
};

// すべて4を出力
console.log(addConstructor(2, 2), addStatement(2, 2), addExpression(2, 2), addNamedExpression(2, 2));

call()とapply()で関数を実行する。

var greet = {
    runGreet: function() {
        console.log(this.name, arguments[0], arguments[1]);
    }
}

var cody = { name: 'cody' };
var lisa = { name: 'lisa' };

greet.runGreet.call(cody, 'foo', 'bar'); // cody foo bar
greet.runGreet.apply(lisa, ['foo', 'bar']); // lisa foo bar

5章 グローバルオブジェクト

var演算子を伴って変数を宣言する場合、その変数はグローバル変数となり、var演算子を使用しない場合はグローバルプロパティとして宣言されます。

グローバル変数とグローバルプロパティの間には、以下のようなわずかな違いが発生します。

グローバルコンテクストでvarを使って変数を宣言すると(例:var x = 1;)。グローバル変数の宣言となります。(と同時に、グローバルプロパティを生成します)。

対して、場所を問わず x = 1; (varなし)を実行すると、JavaScriptはまずスコープチェーンをたどってxを検索し、xが存在すれば1の代入を試みます。

しかし、スコープチェーンを検索してもxが見つからない場合、この記述はグローバルスコープにxプロパティを追加します(グローバルプロパティの生成)。

var演算子を使って変数を宣言する際、JavaScriptはDontDelete属性(ECMAScriptで定義されている内部属性)を持ったプロパティを追加します。

対して、var演算子を使わずグローバルプロパティを新たに追加する場合、そのプロパティにはDontDelete属性を付与しません。

結果として、グローバル変数はdelete演算子を使って削除することができず、グローバルプロパティはdeleteで削除することができます。

以下の例では、ブラウザ環境で実行したところ、var演算子なしでは構文エラーとなった。

グローバル変数はdelete演算子で削除できないことを覚える。

// グローバル変数
var a = 10;
  
// グローバルプロパティ
b = 100; // Uncaught TypeError: Assignment to constant variable.  
  
delete a; // 削除できない
// delete b;

console.log(a); // 10
// console.log(b);

6章 this

var myObject = {
    func1: function() {
        console.log(this); // myObject
        var func2 = function() {
            console.log(this); // window - これ以降の入れ子内ではthisはグローバルオブジェクトを参照
            var func3 = function() {
                console.log(this); // window
            }();
        }();
    }
}
  
myObject.func1();
var foo = {
    func1: function(bar) {
        bar(); // windowが返る
        console.log(this); // fooが返る
    }
}

foo.func1(function() {console.log(this)}); // ここのレキシカルスコープでthisが評価される

以下は本書の内容ではないですが、理解を深められるので覚えておくといいと思います。

function a() {
    return this;
}

a(); // Windowオブジェクト  
const b = function() {
    return this;
}

b(); // Windowオブジェクト  
function c() {
    'use strict'
    return this;
}
c(); // undefined  

ES6以降のアロー演算子(関数を作る)も覚えましょう。

let d = {
    firstName: "Ui",
    sayHi() {
        let arrow = () => {
            console.log(this);
        };
        arrow();
    }
}

 d.sayHi(); // {firstName: 'Ui', sayHi: ƒ}  
let e = {
    firstName: "Ui",
    sayHi() {
        let no_arrow = function() {
            console.log(this);
        };
        no_arrow();
    }
}

e.sayHi(); // Windowオブジェクト  

7章 スコープとクロージャ

JavaScriptでは、コードが実行されるコンテクストにスコープが割り当てられます。

スコープには、グローバルスコープ、ローカルスコープ(「関数スコープ」と呼ばれることもあります)、そしてevalスコープの3つのタイプが存在します。

※ES6でブロックスコープが追加になったため、要注意!!!

var logElementNumber = function() {
    var funcArray = [];
    var i;
    for(i = 0; i < 3; i++) {
        funcArray[i] = function() { console.log(i); }; // クロージャとしてlogElementNumber関数のローカル変数iの参照を持つ
    }
    return funcArray;
}();

まとめ

本書にも書いてあるが、これを理解したからと言って何ができるというのは特にないと思います。

言語の文法やイディオムなんかはJavaScript Primerを読んだ方がよっぽどためになると思います。(そこまで言うか!)

ただし、言語仕様を理解することでその仕様となった経緯や背景がわかり、今後どうなっていくのかを想定しやすくなります。

また冒頭でも言及したのですが、本書は2013年に翻訳されたもので、原著はさらに古く、

JSがES5(ES2009)準拠というのもあり、だいぶ古いJSを対象としているのもハードルが高い気もしています。

JavaScriptは今もECMAScriptで標準化が進んでいて、これからも細かい仕様は変わる可能性があります。

今回は私が知らなかったことや、注意が必要なことを中心にまとめたので実際に読んでみてここで書いていないことも

たくさんあるのでぜひ本書を手に取って読んで感想を聞かせてほしいです。

今読む本じゃないだろ!という正論パンチをお待ちしております!!

参考にした記事

thisには8つのコンテクストがある→めっちゃわかりやすくておすすめ!

その他

JavaScriptのTips

パラメータを変更させない

Rubyと違ってJSはプリミティブ型は値渡し、

オブジェクト型(Array、Object、Functionとか)は参照渡しなので注意。

let colors = ["red", "orange", "yellow", "green"];

let warmColors = remove(colors, 3); // ["red", "orange", "yellow"]

function remove(array, index) {
  array.splice(index); // arrayのindexまでを取得して新しいarrayに更新する
  return array;
}

console.log(colors); // 参照されている変数も変更されて、["red", "orange", "yellow"]になる
console.log(warmColors); // ["red", "orange", "yellow"]はわかる