Help us understand the problem. What is going on with this article?

JSについて本気出して考えてみた

はじめに

こんにちはばーんです
今回はJavaScriptの言語仕様について書いていきます。

JavaScriptの勉強会参加と(https://x-hack.connpass.com/)
その際に勧められた書籍を読んで、自分で得た知識を整理していきます。

なるべく言語仕様書のように堅苦しくなく説明していくよう心掛けますw

今回の対象者

【JavaScriptは殆どオブジェクトである】という言葉の意味が理解できていない方

  • そもそもオブジェクトって何?
  • これってメソッド?プロパティ?
  • console.log("hello world") が "hello world" を表示するまでに何をしているのか?

といったことを解決していきたいと思います。

ゴール

【JavaScriptは殆どオブジェクトである】が理解できる

具体的には下記の内容を理解できればOKだと思っています。

①「そもそもオブジェクトがどういうものか?理解する」

②「JavaScriptがどういうもので構成されているか知る」

③「どういう流れで普段自分たちが②を意識せずに使えているか知る」

第1章: オブジェクト / プロパティ / メソッド って何?

前段の通りJavaScriptは殆どがオブジェクトです。

関数も配列も
※3章で掘り下げます

オブジェクトとは「名前と値を持つプロパティを格納するコンテナ」です。
例えば下記のようなオブジェクトがあった場合は、

const baan = {
  age: 31,
  gender: "man",
  sayHello: function () {
    console.log("こんにちは!");
  },
};

オブジェクト / プロパティ / メソッドを下記のように分類できます。

const baan = {
  age: 31,
  // ageという名前(キー)と31という値を持つプロパティです
  gender: "man",
  // genderという名前(キー)とmanという値を持つプロパティです
  sayHello: function () {
    console.log("こんにちは!");
  },
  // sayHelloという名前(キー)と値に関数を持つプロパティです
  // このプロパティの値はメソッドとなります
};
console.log(baan);
// 出力: { age: 31, gender: 'man', sayHello: [Function: sayHello] }
// baan は3つのプロパティを持ったオブジェクトです
baan.sayHello();
// 出力: こんにちは!

自分自身もオブジェクト、プロパティ、メソッドは混乱していましたが、このように明示できます。

オブジェクトのプロパティへのアクセス方法

どのようにオブジェクトを使用するか?ということです。

オブジェクト名 + ドット + プロパティ名(名前またはキー)でアクセスできます。
ちなみに関数の場合は()をつけることで実行されます。

const baan = {
  age: 31,
  gender: "man",
  sayHello: function () {
    console.log("こんにちは!");
  },
};
console.log(baan.age);
// 出力: 31

console.log(baan.gender);
// 出力: man

baan.sayHello();
// 出力: こんにちは!
baan.sayHello;
// 何も出力されません (sayHelloにアクセスしていますが括弧演算子がついていないので)

※ドット記法とブラケット記法がありますが、話が逸れるのでブラケット記法については書きません。

Point: console.log()は関数です

※正確には関数ではなくconsoleオブジェクトの中のメソッドの1つですが、伝わりやすくする為上記のような表記としています。

当時JS完全に理解した状態だった自分は、関数と聞くと

function () {
  "hogehoge"
};

みたいなモノを想像していました。
しかし、初学者にとっても馴染みの深いconsole.log()が関数だったなんて!と衝撃を覚えました。

Screen Shot 2020-08-04 at 14.29.49.png

このようにchromeのDevtoolにconsole.logを入力すると

ƒ log() { [native code] }

と表記されます。 ƒ はfunctionのことですね^^

第1章まとめ

  • オブジェクトはただの箱
  • 中に0個以上のプロパティを持っている
  • 使う時はオブジェクト名とキーをドットで繋げる
  • 関数を値に入れるとメソッドになる

このことを念頭に読み進めて頂けるとm_ _m

第2章: JSの構成要素を知る

まずここでは、JavaScriptがどのような要素で構成されているか?を見ていきます。

結論から言うとJavaScriptはプリミティブ値とネイティブオブジェクト(コンストラクタ)で構成されています。

プリミティブ

原始的な、基本の みたいな意味ですね。
JavaScriptでは数字、文字列、真偽値などが当てはまります。
(https://developer.mozilla.org/ja/docs/Glossary/Primitive)

ネイティブオブジェクト(コンストラクタ)

はい。こちらがややこしいので詳しく書いていきます。

コンストラクタ(関数)

そもそもコンストラクタ(関数)とは、
new演算子を使って実行されるとオブジェクトを生成する関数です。

ちょっと待って下さい。ブラウザバックしないで欲しい。

// ①最初にこれを実行するとエラーが出ます
console.log(baan);
const baan = new Object();
// 出力: ReferenceError: Cannot access 'baan' before initialization
// ②次にこちらを実行して下さい
const baan = new Object();
console.log(baan);
// 出力: {}

当然といえばそうですが、①はbaanを定義していないので構文エラーが出ます。
②はnew演算子を使って実行しているので空のオブジェクトが生成されています。
これを普段見慣れているような形にすると、

// ③普段見かける形
const baan = new Object();
baan.age = 31;
baan.gender = "man";
console.log(baan);
// 出力: { age: 31, gender: 'man' }

このObject()の部分がコンストラクタ関数です

ネイティブオブジェクト(コンストラクタ)

そして、原始的に用意されているネイティブオブジェクトは9つです。

Number()
String()
Boolean()
Object()
Array()
Function()
Date()
RegExp()
Error()

これ以外を宣言するとエラーが出ます。

// 無作法に新しいコンストラクタはできません
const baan = new Person();
baan.age = 31;
baan.gender = "man";
console.log(baan);
// 出力: ReferenceError: Person is not defined

ちなみにPersonを使用したい場合は、

const Person = function Person(age, gender, sayHello) {
  this.age = age;
  this.gender = gender;
  this.sayHello = sayHello;
  this.sayHello = function () {
    return sayHello;
  };
};

const baan = new Person(31, "man", "こんにちは!");
console.log(baan);
// 出力: Person { age: 31, gender: 'man', sayHello: [Function (anonymous)] }
console.log(baan.sayHello());
// 出力: こんにちは!

このようにコンストラクタ関数を宣言すればOKです!
これで想定通りの振る舞いになりました^^

2章まとめ

  • JavaScriptはプリミティブ値とネイティブオブジェクト(コンストラクタ)で構成されている
  • コンストラクタをnewすることでオブジェクトが生成される

第3章: 殆どがオブジェクトである理由を知る

ここではネイティブオブジェクト(コンストラクタ)から、どのようにして普段使っているオブジェクトになっているのかを掘り下げていきます。
その為には1章の冒頭部分を掘り下げていきます。

前段の通りJavaScriptは殆どがオブジェクトです。
関数も配列も

関数オブジェクトについて

関数もオブジェクトです。なので値としてセットできます。
例えば関数の引数であったり、メソッドとして。

const plusFunc = function (x, y) {
  return x + y;
};
const subtractFunc = function (x, y) {
  return x - y;
};
console.log(plusFunc(5, subtractFunc(4, 1)));
// plusFuncの引数は2つあります。この場合数値の5とsubtractFuncという関数です
// 今回subtractFuncは引数に4と1を持っており結果として3を返します。なので
// 出力: 8 (5 + 3なので)

※メソッドについては1章で触れているので省略します

また、ネイティブオブジェクト(コンストラクタ)についてですが、

const myFunction = new Function("x", "y", "return x + y");
console.log(myFunction);
// [Function: anonymous]
console.log(myFunction(1, 3));
// 出力: 4

const result = myFunction(2, 3);
console.log(result);
// 出力: 5

このようにnewすることで、無名関数を生成しています。
もう少しわかりやすくする為chromeのDevtoolを使います。

Screen Shot 2020-08-04 at 17.09.59.png

無名関数が生成されていますね^^

※ただし、Functionコンストラクタによる関数の生成は推奨されません。これは、JSエンジンによる最適化を妨げたり、他の問題を引き起こしたりする場合があるためです。

配列オブジェクトについて

こちらも関数同様にネイティブオブジェクト(コンストラクタ)をnewしてみます。

// ①ネイティブオブジェクト(コンストラクタ)を使用した配列の生成
const myArr = new Array(1, "test");
console.log(typeof myArr);
console.log(myArr[0]);
console.log(myArr[1]);

// ②配列リテラルを使用した配列の生成
const myArray = [1, "test"];
console.log(typeof myArray);
console.log(myArray[0]);
console.log(myArray[1]);
// myArrもmyArrayも出力は全て同じです
// 出力: object / 1 / test

普段私たちは②を使用していると思います。
が、内部的にはJavaScriptがよしなにやってくれているだけで、配列の生成は①で行われています。

このように普段使用している関数も配列もオブジェクトです。
そして、それらはネイティブオブジェクト(コンストラクタ)より生成されています。

なので「JavaScriptは殆どオブジェクト」なのです。

ちょっとまってプリミティブ値は?

2章まとめ

  • JavaScriptはプリミティブ値とネイティブオブジェクト(コンストラクタ)で構成されている

ここで述べているプリミティブ値はどーなんの?
という話ですが、プリミティブ値はオブジェクトでラップされる時があります。
つまり、擬似的にオブジェクトのように振る舞います。

const num = 1
console.log(num.toString());
console.log((1).toString());
// 出力: "1"

本来1(数値)はプリミティブ値なのでtoStringのプロパティを持ちませんが、プリミティブ値のプロパティにアクセスする際に、オブジェクトでラップされるのでこのような表現になります。

3章まとめ

つまり1〜3章をまとめて
「JavaScriptは殆どオブジェクト」なのです。

番外編: グローバルオブジェクト / プロトタイプチェーンを知る

自分自身が衝撃を受けた項目があるので、最後に番外編として追記させて頂きます。

グローバルオブジェクト

私たちがconsole.log()を使用する時、consoleオブジェクトについてはそれほど考えないと思います。
ただ、それでもchromeのDevtoolにconsole.logを入力すると使えてしまいます。

それはつまり、JavaScriptがよしなにやってくれてます。
具体的にブラウザは、グローバルオブジェクトとしてwindowオブジェクトを持っています。
※正確にはブラウザを立ち上げたクライアントPCのメモリ上に、グローバルオブジェクトを展開しています

そして、 console はオブジェクトであり、 window のプロパティです。 log はメソッドです。

①まずchromeのDevtoolにwindowを入力します
Screen Shot 2020-08-04 at 19.19.39.png

②windowのプロパティを見てみると大量のプロパティが設定されてあります
Screen Shot 2020-08-04 at 19.21.44.png

③その中にconsoleとlogがありましたね!
Screen Shot 2020-08-04 at 19.22.48.png

// いつも書いてる形
console.log("hoge")

// こちらでも同じ結果が出力される
window.console.log("hoge")

ちなみにブラウザ上なのでwindow.console.log("hoge") でも動きますが、Node.jsではエラーが出ます(ReferenceError: window is not defined)
グローバルオブジェクトが違うので。外面は同じですが内面は違う動きをしています。

プロトタイプチェーン

const myArr = [1, 2];
console.log(myArr);
// 出力: [ 1, 2 ]

const result = myArr.join("");
console.log(result);
// 出力: 12

普段私たちは配列を操作する時にこのようにメソッドを使用します(今回は join)。
このような時にmyArrにjoinというプロパティを定義しましたか?

していません。ですが、実際に私たちは明示的にjoinを定義しなくても使えています。
それにはJavaScriptのプロトタイプチェーンが関係しています。

まずプロトタイプチェーンを文面だけで説明すると、

ネイティブオブジェクトコンストラクタ関数(Object, Function, Arrayなど)はオブジェクトを生成する際にprototypeプロパティを継承させます。

待って諦めないで聞いて欲しい。スクロールはもう少し我慢して欲しい。
配列を例に説明していきます。

まず、配列を作る時、普段は

const myArr = [1, 2];

と宣言します。
これは、第3章>配列オブジェクトで記載している通り

const myArr = new Array(1, 2);

と同意です。つまり、ネイティブオブジェクトコンストラクタ関数である Array()をnewしてインスタンス(この場合myArr)を生成しています。
そして、その際にprototypeというプロパティを継承させています。

prototypeにはjoinというメソッドがあります
Screen Shot 2020-08-04 at 20.16.51.png

なので、私たちは明示しなくともjoinというメソッドが使えるのです。

プロトタイプチェーンは今回の場合であれば、myArrにjoinメソッドがないと判断するとエラーを返すのではなく遡って確認しにいきます。

※最終的にはObject()まで確認しにいきます

自分はプロトタイプチェーンを知ってから格段におまじないみたいなコードが減りました。
何故エラーが出るか?どういう仕組みで?が理解できたので。

簡単に言うとprototypeでリンクしているというイメージでいいかと思います。ブラウザでは__proto__という表記のものもあります。

補足

何点か補足事項があります。

今回書けなかった話

  • thisとは?
  • classとは?
  • インターフェースとは?
  • 予約語とは?

この辺りは次回以降にまとめていこうと思います。

書籍について

開眼!JavaScriptという本です。
とても楽しく読める言語仕様についての本でした^^
ただし、恐らく勉強始めてすぐにこれに出会っていても自分は理解できなかったと思います。

できればJavaScriptで何か動かしてから購入をおすすめします。
(道中呪文みたいな文章結構あるので)

質問 / ツッコミに関して

この記事に関して細部の認識はずれている可能性があります。
技術的なマサカリwは大歓迎ですので、何かあれば遠慮なくコメント頂けると幸いです。

参考サイト

参考にさせていただきましたm_ _m
ありがとうございました^^

https://developer.mozilla.org/ja/docs/Web/JavaScript
https://nodejs.org/api/console.html
https://harakotan.hatenablog.jp/entry/2015/05/17/004707
https://www.tweeeety.blog/entries/2014/02/05

さいごに

今回自分がこの記事を書くきっかけになったのはxhackさんの勉強会がきっかけでした。

参加していなければ理解が浅いまま進んでいたと思います。
この場を借りてお礼申し上げます。ありがとうございましたm_ _m

それではまた^o^/

baan_nasebanaru
2019.11~ITに興味を持ち学習開始。現在は副業としてWebサイト制作やシステム開発に携わっています。React中心でお仕事を受けています。 転職活動開始しました。 わくわく会というコミュニティにも所属しており初学者の方のサポートや、案件の紹介をしています。もちろん無料のコミュニティなので、興味のある方は是非お声掛けください^^ (TwitterのDMが1番反応しやすいです!)
https://baan.work/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away