読者です 読者をやめる 読者になる 読者になる

あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

型とかオブジェクトとか基本的なことを理解する

JavaScript

今日は、kanazawa.js v1.2です。

もうちょっとしたらしゃべることになるんですけど、大変申し訳ないことに、下書きはなんとか出来上がりつつあるものの、文章を要約して、スライドにまとめる時間はなさそうなので、この記事をプレゼン資料にしちゃうことにします。(あと味的には、今までに書いた記事と被る箇所があります。)

自己紹介

大部分省略。

JavaScriptが好きです。

ブラウザを開きながら、Firebugでその場で弄って遊んでみたりできるし、関数がとても強力なところが好きです。堅苦しくない仕様がとても肌に合ってると思ってます。

最近は、プログラミングが好きというよりも、プログラミング言語が好きなんだと気付き始めてます。

オブジェクトという言葉の定義を確認する

関連するデータを束ね、代入、演算、手続き(関数やメソッドなど)を介した受け渡しといった操作の対象にでき、またメッセージの受け手になれる実体

  • サイ本

名前の付けられた値を集めたもの

JavaScript的には後者の方がしっくりくるかも。

データ型

JavaScriptのデータ型には以下のものがあります。

  • 数値
  • 文字列
  • 論理値
  • null
  • undefined
  • オブジェクト

数値、文字列、論理値、null、undefinedがプリミティブ値、それ以外がオブジェクトというように2つに分類できます。

プリミティブ値はプロパティを持ちません。

データ型を調べる

typeof演算子が使えます。

typeof 'hoge'
// "string"
typeof 30
// "number"
typeof true
// "boolean"
typeof undefined
// "undefined"
typeof new String('hoge')
// "object"
typeof new Number(30)
// "object"

ここまではだいたい合ってますが、以下は気持ち悪い。

typeof function() {}
// "function"
typeof null
// "object"

Function型というのはデータ型としては定義されてませんし、nullがObject型というものよくわかりませんが、仕様です。

typeof演算子について補足

ちなみに、typeofは演算子なので、+ 'hoge'とか - 10とかの仲間です。

typeof"hoge"とか、区切りが判断できるものであれば、くっつけても呼び出せたりします。

typeof('hoge')とか、typeof(true)とか関数っぽく呼び出せるのは、括弧が関数の引数を指定する括弧じゃなくて、グループ化演算子とみなされ括弧内の値が式として評価されるためです。

グループ化演算子は括弧内の値を常に式として評価させることができるので、好きです。

プリミティブ値とオブジェクト

数値、文字列、論理値の3つのプリミティブ値は、コンテキストによってプロパティを使うことができます。

文字列の場合
var name = 'taiju';

taijuという文字列にnameという名前を付けました。

文字列はプリミティブ値なので、プロパティはないはずです。

name.length // 5
name.replace('u', 'uu'); // taijuu

でもプロパティが参照できちゃいます。

このようにプリミティブ値の中でも数値、文字列、Booleanの3つはコンテキストによって、プロパティを参照できます。

name.length // 5

nameは文字列ですが、この場合、lengthプロパティを参照することが期待されているコンテキストです。

この場合、イメージ的には以下のような感じになります。

(new String(name)).length;

この時に使い捨てで呼び出されるStringオブジェクトをラッパーオブジェクトと呼びます。

プリミティブ値とオブジェクトふたたび

代入や関数の引数にした時に値渡しになるか、参照渡しになるかの違いがあります。

プリミティブ値の場合

代入
var a = '1,2,3';
var b = a;
b = '10,2,3';
console.log(a); // 1,2,3
関数の引数
var a = "1,2,3";
var x10 = function(str) {
  var tmpa = str.split(',');
  for (var i = 0, l = tmpa.length; i < l; i++) {
    tmpa[i] = tmpa[i] * 10;
  }
  str = tmpa.join(',');
  return str;
};
x10(a);
console.log(a); // 1,2,3

プリミティブ値は、値渡しになります。

値渡しなので、関数内で、引数に渡された値(ここではString)を変更しても、元となった値は変更されていません。

オブジェクトの場合

代入
var a = [1,2,3];
var b = a;
b[0] = 10;
console.log(a); // [10,2,3]
関数の引数
var a = [1,2,3];
var x10 = function(ary) {
  for (var i = 0, l = ary.length; i < l; i++) {
    ary[i] = ary[i] * 10;
  }
  return ary;
};
x10(a);
console.log(a); // [10,20,30]

オブジェクトは、参照渡しになります。
参照渡しなので、関数内で、引数に渡された値(ここではArray)を変更すると、元となった値も変更されています。

オブジェクト入門

JavaScriptには組み込みのオブジェクトが複数あります。

  • Objectオブジェクト
  • Functionオブジェクト
  • Arrayオブジェクト
  • Stringオブジェクト
  • Booleanオブジェクト
  • Numberオブジェクト
  • Mathオブジェクト
  • Dateオブジェクト
  • RegExpオブジェクト
  • Errorオブジェクト

JavaScriptをブラウザで使用する場合は、グローバルオブジェクトはwindowになります。

トップレベルで、console.log(this)することでも確認できます。

グローバルオブジェクトのwindowの中には、普段よく扱うdocumentプロパティやscreenプロパティなど、ECMAScriptの仕様外のオブジェクトが多数追加されています。console.dir(window)して、下層のプロパティをいろいろ調べていくと楽しいです。

windowオブジェクトについて、おまけ

windowオブジェクトはwindowプロパティを持っています。windowプロパティはwindowオブジェクト自身を指したプロパティです。

なので、documentをwindow.documentと書くことができるし、var a; window.a と書くこともできます。厳密には違いますが、グローバル変数やトップレベルの関数などはwindowオブジェクトのプロパティと考えることできるかもしれません。

オブジェクトの扱い方

基本的には、new演算子を用いて、コンストラクタ関数を呼び出し、そのオブジェクトのインスタンスを生成して利用します。

例えば、Dateオブジェクトの場合、new演算子を用いて、コンストラクタ関数を呼び出すと、インスタンスが生成されます。

この場合、Dateオブジェクトのプロパティを利用することができます。

new演算子を用いずに呼び出すと、単純な関数として呼び出すと、日付を表した文字列になります。

この場合、Stringオブジェクトのプロパティは利用できますが、Dateオブジェクトのプロパティは利用できません。

var d1 = new Date()
var d2 = Date()

d1.getFullYear() // 2011
d2.getFullYear() // TypeError

リテラル

とは言え、なんでもかんでもnewするのはメンドクサイということもあって、いくつかのオブジェクトは生成するためのリテラル表記が許されています。

// Array
[1,2,3]

// Object
{ name: 'taiju' }

// Regexp
/hoge/

オリジナルのオブジェクト(型)を作る

JavaScriptではオリジナルのオブジェクト(型)を作ることができます。

var obj = {
  hoge: 'fuga'
};

これはあくまで、Objectオブジェクトのインスタンスを作っているだけで、ここで言うオリジナルのオブジェクトとは違います。

先ほど説明したように、new演算子を用いてコンストラクタ関数を呼び出し、インスタンスを生成できるようなオブジェクトの作り方です。

new演算子でコンストラクタ関数を呼べる値は一定の条件を満たしたオブジェクトです。

  1. オブジェクト型である
  2. 内部Constructメソッドを実装している
参考

新しいオブジェクト型を作るので、1.はクリアしていると仮定して、2.の内部コンストラクトメソッドを実装するオブジェクトを作るためには、Functionオブジェクトを作るという方法しかありません。(たぶん)

var myName = function() {
  this._name = '';
  this.getName = function() {
    return this._name;
  };
  this.setName = function(name) {
    this._name = name;
  };
};

これでオリジナルの myName オブジェクトが作れました。 new myName() でインスタンスを生成できます。

var mn = new myName();
mn instanceof myName; // true

関数オブジェクトは特殊で、prototypeプロバティというプロパティを持ちます。この辺りはまた違う機会にでも話します。

インスタンスの生成っておいしいの?

JavaScriptのObjectオブジェクトはなんでもデータを突っ込めるので、これにプロパティを詰め込んで使っていけばいいような気がしてきますが、Objectオブジェクトは参照渡しであるという特徴があるので、コピーして使いまわしたり、安全に運用するのが難しいです。

var myName = {
  _name: '',
  getName: function() {
    return this._name;
  },
  setName: function(name) {
    this._name = name;
  }
};

先程のmyNameをObjectオブジェクトで作ったバージョンです。

var a = myName;
var b = myName;
a.setName('taiju');

console.log(b.getName()); // taiju

参照渡しなので、bの値を変更するとaの値も変わってしまいます。使い回しし辛いし、予期せぬ箇所で値が変更されていてバグの元になりかねないですね。JavaScriptはあらゆるタイミングで、あらゆる値を書き換えられますから。

インスタンス生成バージョンで確認してみます。

var myName = function() {
  this._name = '';
  this.getName = function() {
    return this._name;
  };
  this.setName = function(name) {
    this._name = name;
  };
};

var a = new myName();
var b = new myName();
a.setName('taiju');

console.log(b.getName()); // (an empty string)

安全にコピーして使い回せますね。

JavaScriptはもともと、プロトタイプベースオブジェクト指向プログラミング言語という分類で、オブジェクトをコピーしまくってプログラミングしていくスタイルが自然です。その際、このインスタンスを生成するという機能は重要性が高いと思います。

まとめ

JavaScriptはほとんどのデータがオブジェクトです。プリミティブ値でさえ、コンテキストによってラッパーオブジェクトを経由してプロパティを参照できます。

オブジェクトにはあらゆる値をプロパティに設定できます。関数を設定すれば、メソッドと呼ばれますし、さらにオブジェクトを設定すれば、オブジェクトの階層構造ができあがります。

自分でオブジェクトを生成したい場合、コンストラクタ関数を用意すると便利です。今回は紹介していませんが、もれなくプロトタイプチェーンという継承機構も利用できますし、使い回ししやすく、安全です。

JavaScriptはオブジェクトの作るのも自由、後から書き換えるのも自由。かなり緩い仕様なので、consoleなどを使って、いろいろ試行錯誤しながら弄っていくのが一番勉強になると思います。