Giter VIP home page Giter VIP logo

prototype-chain's Introduction

JavaScript の プロトタイプチェーン について理解する

目的:JavaScriptの基本言語仕様について理解を深める

近年、フロントエンド開発ができるエンジニアが少しずつ増えてきています

フレームワーク(Vue, React)の書き方は知っているけれど、Javascript の基本的な言語仕様についてはあまり知らない人が増えているので

そんな人と、そうでもない人(=つまり全員)に向けたのおさらい

また、フロントエンドエンジニアのプロパーとして知っておくべき内容

プロトタイプってなに

Chromeのコンソールで以下を実行してみてください

var object = {
  name: 'inoko'
};

オブジェクトをリテラル(直書き)で生成するだけです

そのオブジェクトを参照してみてください

__proto__ というオブジェクトがなぜか生成されています

フロントエンドの開発経験があるエンジニアなら、一度は見たことがある光景でしょう

👇 展開してみた

いろんなメソッドを持っていますが、見覚えはありませんか?

例えば hasOwnProperty とか toString とか。

この __proto__ オブジェクトが、プロトタイプです

一体どこから来たの・・?

継承(extends)されて、やって来ました

継承元は?

window.Object.prototype

確認のため、比較してみる

同じですね

この window.Object は JavaScript の標準ビルトインオブジェクトになっています

MDN によると、継承の仕組みは以下のように説明されています

JavaScript におけるすべてのオブジェクトは Object に由来します。 すべてのオブジェクトは Object.prototype からメソッドとプロパティを継承しています

つまりJavaScriptでは、生成されたオブジェクトすべてにおいて

window.Object.prototype から継承されたプロトタイプを __proto__ という名前で持ちます

Array や String, Number の場合はどうなの?

実際にやってみましょう

まずは、生成して

var array = ['a', 'b', 'c'];

参照して、展開してみる

__proto__ いた

forEach pop push など、配列を扱うときによく使う method が並んでいますね

勘のいい人はわかると思いますが

Array は window.Array.prototype というオブジェクトを継承しています

String や Number も同様です

このように

すべてのJavaScriptオブジェクトには、もう一つの別のオブジェクトが関連付けられています

この もう一つの別のオブジェクトを、プロトタイプと呼びます

言い換えると

すべてのJavaScriptオブジェクトは、プロトタイプを持ちます

注)null の場合を除く

以下の場合は、プロトタイプを持ちません

var nullObject = Object.create(null);

ここで質問

例えば、こんな処理をしたことはありませんか?

var array = ['a', 'b', 'c'];
array.push('d');

array という変数には、 push というプロパティやメソッドを定義してないのに、どうして使えるのでしょう??🤔

ここで プロトタイプチェーン が出てきます

プロトタイプチェーン

以下を実行すると

array.push('d');

array という変数に push というプロパティやメソッドがないかどうか調べる

  array には ['a', 'b', 'c'] という値しか代入していないので、もちろんないです

② なかった場合、__proto__ が参照する先を調べる

 ここで、配列のプロトタイプの継承元である window.Array.prototype の先を見に行きます

 今回の例では、ここで push が見つかり、実行されます

 ちなみに、

__proto__ になかった場合、さらに遡って __proto__ の先へ先へと、永遠と調べ続ける

__proto__ が存在しなくなるまで探したが見つからない場合、 undefined を返す

 このように、__proto__ は鎖のように連鎖的に繋がっています

 この プロトタイプの連鎖のことをプロトタイプチェーン と呼びます

 もし、何らかのプロパティやメソッドが参照できなかった場合は、プロトタイプチェーンの最終リンクで見つからなかった、という事になります

 何かの値を参照するときは毎回、 プロトタイプチェーンを遡っています

他のパターン

以下の場合を考えてみましょう

var object = {
  age: 20
};
console.log(object.name); // undefined

① 変数 objectname が存在するかどうか調べる

 ないので、次は

object.__proto__ が参照する先を調べる

 オブジェクトのプロトタイプは window.Object.prototype でしたね

 こちらにも、 window.Object.prototype.name は存在しません

 ですので、さらに遡ろうとします

object.__proto__.__proto__ があるか調べる

object.__proto__.__proto__ が存在しないので undefined を返す

プロトタイプチェーンは実際にどこで使われているか

ES6 が登場して、JavaScriptがClass構文に対応しましたが

ES6 に対応していないブラウザ(IEとか)や、Class構文が登場する前は、どのように実装されていたのでしょうか?

Babel で Class構文をコンパイルしてみる

以下のシンプルな Animal ClassBabel に通してみます

/**
  Animal クラス
  **/
class Animal {
  constructor(name) {
    this.name = name;
  }

  walk() {
    console.log('walk method.');
  }

  eat() {
    console.log('eat method.');
  }
}

// Animal Class から dogインスタンス を作る
var dog = new Animal('John');
dog.walk(); // walk method.
dog.eat(); // eat method.

⬇︎ Babel を通す

"use strict";

function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }

function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

/**
  Animal クラス
  **/
var Animal =
/*#__PURE__*/
function () {
  function Animal(name) {
    _classCallCheck(this, Animal);

    this.name = name;
  }

  _createClass(Animal, [{
    key: "walk",
    value: function walk() {
      console.log('walk method.');
    }
  }, {
    key: "eat",
    value: function eat() {
      console.log('eat method.');
    }
  }]);

  return Animal;
}(); // Animal Class から dogインスタンス を作る


var dog = new Animal('John');
dog.walk(); // walk method.

dog.eat(); // eat method.

今回の説明に不要そうなメソッドは取り除いてみます

/**
 * プロパティを定義する
 */
function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) {
      descriptor.writable = true;
    }
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

/**
 * クラスを作る
 */
function _createClass(Constructor, protoProps) {
  if (protoProps) {
    _defineProperties(Constructor.prototype, protoProps);
  }
  return Constructor;
}

/**
  Animal クラス
  **/
var Animal =
/*#__PURE__*/
function () {
  function Animal(name) {
    this.name = name;
  }

  _createClass(Animal, [{
    key: "walk",
    value: function walk() {
      console.log('walk method.');
    }
  }, {
    key: "eat",
    value: function eat() {
      console.log('eat method.');
    }
  }]);

  return Animal;
}();

// Animal Class から dogインスタンス を作る
var dog = new Animal('John');
dog.walk(); // walk method.
dog.eat(); // eat method.

みてみると、 prototype を使っていますね

さらに、読みやすいように簡略化してコメントを書いてみます

/**
  Animal クラス
  **/
var Animal = function () {
  function Animal(name) {
    this.name = name;
  }

  // walk メソッドを定義
  Animal.prototype.walk = function() {
    console.log('walk method.');
  }
  // eat メソッドを定義
  Animal.prototype.eat = function() {
    console.log('eat method.');
  }

  return Animal;
}();

// Animal Class から dogインスタンス を作る
var dog = new Animal('John');
dog.walk(); // walk method.
dog.eat(); // eat method.

この状態が、JavaScriptのES5以前の仕様でクラスを実現しようとした時の書き方です

ES6 の Class構文 もあくまでラッパーなので、内部的にブラウザはこのように解釈しています

注目すべきは walk,eat メソッドを prototype のメンバーに持たせている というところです

ここで、new したあとの dogインスタンス の中身を見てみましょう

eat walk メソッドはそれぞれ

dog.__proto__.eat dog.__proto__.walk プロトタイプに定義されています

以下を実行したときは、プロトタイプチェーンを遡り、dog.__proto__.walk を実行することになります

dog.walk(); // walk method.

なぜメソッドを prototype のメンバーに持たせる必要があるか

仮に、prototype ではなく 以下のように実装するとします

/**
  Animal クラス
  **/
var Animal = function () {
  function Animal(name) {
    this.name = name;

    this.walk = function() {
      console.log('walk method.');
    }

    this.eat = function() {
      console.log('eat method.');
    }
  }

  return Animal;
}();

// Animal Class から dogインスタンス を作る
var dog = new Animal('John');
dog.walk(); // walk method.
dog.eat(); // eat method.

thisAnimal を指すので、プロトタイプに値を持たせるのではなく

Animal.walk のように Animal の直下にメソッドを定義します

この状態でも、得られる結果は先ほどと、同様です

ここで、 new された後の dogインスタンス の中身を見てみましょう

dog.__proto__.eat ではなく、new した dog の直下に定義されていますね

ここで、以下のように大量に new した場合を考えましょう

var dog = new Animal('John');
var cat = new Animal('Emma');
var rabbit = new Animal('Olivia');
var tiger = new Animal('Ava');
var pig = new Animal('Isabella');
var squirrel = new Animal('Sophia');
var horse = new Animal('Charlotte');
// ...more

この場合、 walk eat メソッドが、new するたびにメモリ空間を消費することになります

もし prototype を使っていた場合は、継承元のメソッドのみでしかメモリを消費しません

つまり メモリの無駄遣いを防ぐために、プロトタイプ、プロトタイプチェーンの仕組みが使われています

(おまけ) なぜ for in は使うとき注意すべきか

例えば、window.Object の持つメソッドを拡張したい場合、以下のようなメソッドを window.Object のプロトタイプに追加していたとします

Object.prototype.hogeFunction = function() {
  console.log('hoge');
};

この状態で、以下のようなオブジェクトを for in で参照すると・・

var object = {
  name: 'yamada',
  age: 20
};

for(var key in object) {
  console.log('key: ', key);
}

// 結果
// key:  name
// key:  age
// key:  hogeFunction

このように、Object.prototype に定義した hogeFunction まで参照してしまいます

プログラマの意図した動作と違う動きをしてしまうので、for in を使うときは注意が必要です

むしろ、本当に for in でないと実現できない実装ではない限り、使わなくて良いと思います

どうすべきか

  1. for inhasOwnProperty を使う

prototype ではない値のみ参照する

for(var key in object) {
  if (object.hasOwnProperty(key)) {
    console.log('key: ', key);
  }
}

// 結果
// key:  name
// key:  age
  1. lodash を使う(おすすめ)

array にも object にも forEach が使えるようになっている

上記のプロトタイプの仕組みを理解した上で、 lodash を使うのがおすすめ

ちなみに、lodash の forEach のコードを見るとオブジェクトのキーの数だけ while で処理している

while (++index < length) {
  if (iteratee(iterable[index], index, iterable) === false) {
    break
  }
}

Lodash github forEach (baseEach)

まとめ

  • すべてのJavaScriptオブジェクトは、プロトタイプを持つ

  • 何かの値を参照するときは毎回、プロトタイプチェーンを遡っている

  • Class構文も内部的にはプロトタイプ、プロトタイプチェーンの仕組みを使われている、余分なメモリを消費しないため

参考

prototypeをもたないオブジェクト https://qiita.com/yoshiwatanabe/items/338705a20e8475e4057e

JavaScriptはオブジェクトについて参照渡しだなんて、信じない https://qiita.com/mozisan/items/1b9d4bf5a1bb341dd354

JavaScript のデータ型とデータ構造 https://developer.mozilla.org/ja/docs/Web/JavaScript/Data_structures#Strings

C言語 文字列 https://www.cc.kyoto-su.ac.jp/~yamada/programming/string.html

Babel https://babeljs.io/

Class構文 https://html5experts.jp/takazudo/17355/

プリミティブ型とオブジェクト https://murashun.jp/blog/20190401-08.html

prototype-chain's People

Watchers

James Cloos avatar Masataka Umamichi avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.