ECMA-262 5th edition で導入された Object.defineProperty を使い、属性を指定してプロパティを定義する
ECMAScript *1 において、プロパティとは名前と値のペアを意味します。 より詳しく言うと、名前が付けられたプロパティは、その状態を表す属性 (Property Attribute) *2 の集合を値として持っています。
ECMA-262 5th edition では、この属性の値を指定してプロパティを定義できるように、Object.defineProperty
というメソッドが導入されました。 この記事では、Object.defineProperty
メソッドを使い、属性を指定してプロパティを定義する方法を説明します。
なお、ECMA-262 5th edition を実装している JavaScript 処理系はまだ多くありません。 Firefox 4 などの一部のブラウザなどでしか実行できません。 また、サンプルコード中で、結果を出力するための print
関数を使用していますが、適当に出力用の関数に置き換えてください。
プロパティの属性
プロパティにはそれぞれ属性があり、読み出し可能かどうかや for-in ループ中にそのプロパティが現れるかどうかが決定されます。 ECMAScript の 5th edition で導入された accessor property *3 と、昔ながらの data property とでは異なる属性を持ちます。 それぞれのプロパティがどのような属性を持つのかを次に説明します。
data property が持つ属性
属性名 | 意味 |
---|---|
[[Value]] | プロパティの値。 |
[[Writable]] | 内部関数 [[Put]] *4 によって [[Value]] の値を書き換えられるかどうか。 true なら書き換え可能。 |
[[Enumerable]] | true か false 。 true なら for-in ループ中に値が現れる。 false なら現れない。 |
[[Configurable]] | true か false 。 false ならプロパティを delete したり、data property に変化させたり、プロパティの属性 ([[Value]] を除く) を変更したりできない。 |
accessor property が持つ属性
属性名 | 意味 |
---|---|
[[Get]] | Undefined か関数。 プロパティの値が参照されたときに呼び出される。 |
[[Set]] | Undefined か関数。 プロパティへの値の代入が行われるときに呼び出される。 |
[[Enumerable]] | true か false 。 true なら for-in ループ中に値が現れる。 false なら現れない。 |
[[Configurable]] | true か false 。 false ならプロパティを delete したり、data property に変化させたり、プロパティの属性を変更したりできない。 |
Object.defineProperty
メソッドを使用してプロパティを定義する
さて、普通にプロパティを定義しただけでは、これらの属性を指定することはできません。 これらの属性を指定してプロパティを定義するには、Object.defineProperty
メソッドを使用します。 このメソッドは引数を 3 つとり、第 1 引数がプロパティを設定する対象となるオブジェクト、第 2 引数が定義するプロパティの名前、第 3 引数がプロパティの属性を指定するためのオブジェクト、となります。 第 3 引数は、プロパティで属性を指定します。 例えば、[[Value]] が 10
で、[[Enumerable]] が true
である新しいプロパティを作る場合は、{ value: 3, enumerable: true }
というオブジェクトを渡すことになります。
新しいプロパティが accessor property になるか data property になるかは、第 3 引数の内容によって決まります。 第 3 引数として渡すオブジェクトに "value" や "writable" という名前のプロパティがあればそれは data property になりますし、"set" や "get" という名前のプロパティがあればそれは accessor property になります。 なお、"value" も "set" も持っているようなオブジェクトを引数として渡すとエラーが発生します。
var obj = {}; // 書き込み不可能な新しい data property を定義する Object.defineProperty( obj, "test", { value: 200, writable: false } ); // 新しいプロパティを参照 print( obj.test ); // 200 // 書き込みしてみる obj.test = 300; // しかし、書き込みは反映されず、200 のまま print( obj.test ); // 200
第 3 引数に渡すオブジェクトのプロパティは、一部 (または全部) 省略できます。 省略した際のデフォルトの値は次のとおりです。
属性名 | デフォルト値 |
---|---|
[[Get]] | Undefined |
[[Set]] | Undefined |
[[Value]] | Undefined |
[[Writable]] | false |
[[Enumerable]] | false |
[[Configurable]] | false |
既存のオブジェクトに accessor property を定義する
前の記事 で述べた accessor property を既存のオブジェクトに定義するためにも Object.defineProperty
を使います。
例として、値の代入を行う際に、値の範囲をチェックするような accessor property を定義してみます。 ここでは、accessor property が値を格納するための変数 (value
) を、クロージャを使って確保しています。
var obj = {}; // accessor property を定義 Object.defineProperty( obj, "prop1", (function() { var value; return { set: function( v ) { if( 0 <= v && v <= 100 ) { value = v } else { throw new Error( "値は 0 から 100 までの範囲で指定してください" ) } }, get: function() { return value } } } )() ); // accessor property を使う obj.prop1 = 10; // 値の設定 print( obj.prop1 ); // 値の読み出し obj.prop1 = 1000 // 期待されない値を設定しようとすると例外が投げられる
まとめ
- プロパティは属性を持っている
- ECMAScript 5th edition から導入された
Object.defineProperty
メソッドを使えば、属性を指定してプロパティを定義できる - 第 3 引数に渡すオブジェクトのプロパティによって data property になるか accessor property になるかが決められる
Object.defineProperty
が使える JavaScript 処理系はまだ多くない
JavaScript に新しく導入された accessor property について
ECMAScript の 5th edition では、新しく accessor property (アクセサプロパティ) というものが導入されました。 本記事は、この accessor property について説明します。 accessor property に対応している JavaScript 処理系はまだ多くないと思いますが、少なくとも Firefox 4 と Internet Explorer 9、Safari 5 は対応しているようです。
互換性の問題があるので一般の web サイト上で使うにはまだ時期が早いですが、Firefox 4 向けの拡張機能を作るときなど、特定の JavaScript 処理系でのみ動かすコードを書く場合には使っていくといいかもしれません。
accessor (アクセサ) とは
JavaScript では、オブジェクトはプロパティを持っています。 普通は、以下のように単に名前を指定して代入したり参照したりして使用します ((本記事では、出力用の関数として print
を使用します。 Rhino では最初から print
関数がありますが、ない場合は各自定義してください。))。
var obj = {}; obj.prop1 = "aaaa"; // prop1 という名前のプロパティに値を代入 print( obj.prop1 ); // prop1 という名前のプロパティの値を参照
しかし、上のような方法だと、予期しない値が設定される可能性があります。 プロパティに値を代入する際に値が期待するものかどうかのチェックをしたい場合はどうしたらいいのでしょうか? 1 つの方法としては setter/getter 関数を使用する、というものがあります。
var obj = {}; (function(o) { // クロージャにより, 外部からアクセスできない変数 (value) を保持する var value; o.setProp1 = function set(v) { if( 0 <= v && v <= 100 ) { value = v } else { throw new Error( "値は 0 から 100 までの範囲で指定してください" ) } }; o.getProp1 = function get() { return value }; })(obj); obj.setProp1( 10 ); // 値の設定 obj.getProp1(); // 値の読み出し obj.setProp1( 1000 ); // 期待されない値を設定しようとすると例外が投げられる
Java などでは、このような getter/setter を使用する方法が一般に使われています。 一方、Ruby などには accessor と呼ばれるもの *1 があり、代入文形式でプロパティの設定や読み出しを書いて、内部的には getter/setter 関数を呼ぶ、ということができます。 以下は Ruby の例です。
class C # 値の設定をするための属性 def prop1=(v) raise "値は 0 から 100 までの範囲で指定してください" unless 0 <= v and v <= 100 @prop1 = v end # 値の読み出しをするための属性 def prop1; @prop1 end end obj = C.new obj.prop1 = 10 # 値の設定 obj.prop1 # 値の読み出し obj.prop1 = 1000 # 期待されない値を設定しようとすると例外が投げられる
obj.prop1 = 1000
という風に代入文の形式をしていますが、実際には関数 (メソッド) が呼び出されており、値が期待するものかどうかのチェックを行うなどの処理が可能です。 関数形式ではなく単純な代入文や参照のような形式でコードを書き、getter/setter を使う方法と同じように値のチェックができる、というのが accessor です。
ECMAScript *2 にも、5th edition で accessor が導入されましたので、新しい JavaScript 処理系ではこのような accessor が使用できるようになっています。 このような accessor としてのプロパティを、JavaScript では accessor property と呼びます。
ちなみに、今までから使われていたようなプロパティは data property と呼ばれます。
accessor property の定義方法
accessor property を定義するには、オブジェクトリテラルの中に以下のように書きます。 以下では、propertyName という名前の accessor property を設定しています。
// オブジェクトリテラル var obj = { // プロパティの読み出し用 get propertyName() { /* 関数本体 */ }, // プロパティの設定用 set propertyName(v) { /* 関数本体 */ } }
この方法では、すでに存在するオブジェクトに accessor property を追加することはできません。 すでに存在するオブジェクトに accessor property を追加する場合は、Object.defineProperty
メソッドなどを使用する必要があります。 Object.defineProperty
メソッドについては 次の記事 を参照してください。
継承した accessor property への代入は?
継承された accessor property と同じ名前のプロパティへの代入文は、新しくプロパティが作られるのか、それとも継承したプロパティの [[Set]] 属性の関数が呼び出されるのか、という疑問が出てくるかもしれません。 結論から言うと、継承された accessor property への値の代入は、継承されたプロパティの [[Set]] 属性が使用されます。
var obj = (function() { // クロージャにより, 外部からアクセスできない変数 (_prop1) を保持する var _prop1 = 10; return { // accessor property を定義 get prop1() { return _prop1 }, set prop1(v) { if( 0 <= v && v <= 100 ) { _prop1 = v } else { throw new Error( "値は 0 から 100 までの範囲で指定してください" ) } }, // ただの data property prop2: 20 }; })(); // Object.create は、第 1 引数を prototype に持つ新しいオブジェクトを生成する // すなわち sub は obj を継承する var sub = Object.create( obj ); // 継承したプロパティの参照 print( sub.prop1 + ", " + obj.prop1 ); // 10, 10 print( sub.prop2 + ", " + obj.prop2 ); // 20, 20 // 派生オブジェクトの同名のプロパティに値を代入してみる sub.prop1 = 50; sub.prop2 = 40; // 継承したプロパティの参照 print( sub.prop1 + ", " + obj.prop1 ); // 50, 50 print( sub.prop2 + ", " + obj.prop2 ); // 40, 20
data property と比較するとわかりやすいですが、継承元が同名のプロパティを持っていても、それが data property であれば、プロパティへの代入文は自分自身に新しくプロパティを作ります。 よって、継承元には影響を与えません。
一方、継承元が同名の accessor property を持っている場合は、その accessor property が使われますので、継承元にも影響を与えます。
まとめ
- ECMAScript 5th edition で、accessor property が導入された
- オブジェクトリテラル内で関数定義をするように accessor property の定義ができる
- 既に存在するオブジェクトに accessor property を新たに定義するには Object.defineProperty メソッドを使う
- accessor property が使える JavaScript 処理系はまだ多くない
*1:Ruby では 「属性」 (attribute) と呼ばれています。
*2:JavaScript やそれに類似の言語の統一的な言語仕様
JavaScript におけるクラスの作成と継承
JavaScript はプロトタイプベースのオブジェクト指向言語であり、クラスという概念を持っていない *1、というのはよく言われることですが、new
演算子とコンストラクタとプロトタイプを使うことでクラスのようなものを作ってインスタンス化することが可能です。 さらに、プロトタイプを使ってクラスの継承も実現できます。
この記事では、JavaScript においてクラスベースの言語と似たようなことをする方法について説明します。
用語
まずは用語の説明をします。 ECMAScript (5th edition) *2 に書かれているものを訳しました。
- コンストラクタ (constructor)
- オブジェクトを生成し、初期化する関数オブジェクト
- プロトタイプ (prototype)
- 別のオブジェクトに共有されたプロパティを提供するオブジェクト
- プロパティ (property)
- オブジェクトの一部で、名前と値が関連付けられたもの。 オブジェクト
obj
に対してプロパティ name にアクセスするにはobj.name
とする - 自身のプロパティ (own property)
- あるオブジェクト自身に直接含まれているプロパティ
- 継承したプロパティ (inherited property)
- あるオブジェクト自身のプロパティではないが、そのオブジェクトのプロトタイプに含まれているプロパティ
オブジェクトは内部にプロトタイプへの参照を 1 つ持っています。 オブジェクトのプロパティが参照されたとき、そのオブジェクト自身がそのプロパティをもっていない場合に、プロトタイプのプロパティが調べられ、もしプロトタイプがそのプロパティを持っていればそれが返されます。 この仕組みを利用して、クラスやクラスの継承といったものの真似事ができます。
方針
以下のような方針でクラスベースのオブジェクト指向の真似事をします。
クラスの定義とインスタンス化
まずは継承を使わず、単一のクラスを作ってみます。 new
演算子をコンストラクタに作用させると、新しいオブジェクトが作られて、コンストラクタが実行されます。 このとき、コンストラクタ内の this
は新しく作られたオブジェクトを参照します。
とりあえず、コンストラクタとして適当に関数を作ってみます。 コンストラクタ内の this
のプロパティとして値を設定すると、それが新しいオブジェクト自身のプロパティとなります。 方針のところで述べたように、インスタンス変数はオブジェクト自身のプロパティとして設定したいので、コンストラクタ内でインスタンス変数を設定します。
// コンストラクタを定義 // コンストラクタの名前はクラス名にしておくと良い. // クラス名の最初の文字を大文字にすることでクラスであることをわかりやすくする var Class = function( var1 ) { // インスタンス変数の設定 this.var1 = var1; // コンストラクタ関数は戻り値を明示しなくて良い }; // インスタンス化してみる // new 演算子をコンストラクタに作用させることでインスタンス化する var c1 = new Class( 100 ); // インスタンス変数がちゃんと設定されている alert( c1.var1 ); // "100"
次に、プロトタイプにメソッドの追加を行います。 new
演算子をコンストラクタに作用させたとき、コンストラクタの prototype
プロパティの参照先が新しく作られるオブジェクトのプロトタイプとなります。 つまり、メソッドはコンストラクタの prototype
プロパティに追加します。
// 上の続き // メソッドを定義 Class.prototype.view = function() { // this は新しく生成されるオブジェクト (インスタンス) を参照する alert( "Class クラスのインスタンス [var1 : " + this.var1 ); }; // メソッドを実行してみる // インスタンス化した後に追加したメソッドも実行できる c1.view();
まとめると次のようになります。
// コンストラクタを定義する var Class = function( args ) { // コンストラクタ関数内でインスタンス変数の設定 /* ... */ }; // コンストラクタの prototype プロパティのプロパティとして // インスタンスメソッドを定義する Class.prototype.methodName1 = function( args ) { /* ... */ }; Class.prototype.methodName2 = function( args ) { /* ... */ };
クラスの継承
次にクラスの継承について説明します。
クラスの継承を実現するために、以下のことを行う必要があります。
インスタンス変数の初期化は、派生クラスのコンストラクタから親クラスのコンストラクタを明示的に呼び出すことで実現します。 メソッドの継承は、親クラスのコンストラクタの prototype
プロパティの参照先オブジェクトをプロトタイプとして持つオブジェクトを派生クラスのコンストラクタの prototype
プロパティに設定することで実現します。 そのためのメソッドを Function.prototype
に追加します。
// 継承のためのメソッド // baseConstructor は親クラスのコンストラクタ Function.prototype.extend = function( baseConstructor ) { // 親クラスのコンストラクタの prototype プロパティの参照先オブジェクトを // プロトタイプとして持つオブジェクトを作る var F = function(){}; F.prototype = baseConstructor.prototype; // 派生クラスのコンストラクタの prototype プロパティに設定 this.prototype = new F(); // ここでは互換性を考えて上の方法をとっているが // Object.create が使えるなら // this.prototype = Object.create( baseConstructor.prototype ); // でよい // コンストラクタを示すプロパティを設定 this.prototype.constructor = this; this.baseConstructor = baseConstructor; return this; };
あとは上で定義した Function.prototype.extend
メソッドを使うだけです。 注意点としては、派生クラスから親クラスのコンストラクタを呼び出すことを忘れないようにしなければいけない、ということです。 Function.prototype.extend
メソッドを使うことで、親クラスのコンストラクタは派生クラスのコンストラクタの baseConstructor
プロパティで参照できるので、SubClass.baseConstructor
で取得しています。 さらに、親のコンストラクタ内で this
がインスタンスを指すように、Function.prototype.apply
メソッドを使用しています。
// 親クラスの定義 var BaseClass = function BaseClass( args ) { /* ... */ }; BaseClass.prototype.method = function() { /* ... */ }; // 派生クラスの定義 var SubClass = function SubClass( args, args2 ) { // 親クラスのコンストラクタを明示的に呼び出す (引数は適当に指定) SubClass.baseConstructor.apply( this, [args] ); // インスタンス変数の定義など /* ... */ }.extend( BaseClass ); // BaseClass を継承 SubClass.prototype.method = function() { /* ... */ };
これで継承もできるようになりました。
JavaScript でクラスベースの書き方をする意義について
とまあ長々と説明してきましたが、JavaScript であえてクラスベースの書き方をする意義があるのか、というとあまりないような気はします。 もちろん、同じメソッドを持っていて値だけが異なるオブジェクトを大量に作りたい、というようなときにはクラスベースでの書き方をするといいと思います。
しかし、他のクラスベースの言語 (Java とか Ruby とか) に慣れているから、という理由だけで JavaScript でもクラスベースの書き方をするのはやめておいた方がいいでしょう。 JavaScript では、他の方法を使って簡単にオブジェクトの生成ができます。 なんでもかんでもクラスベースの書き方にする、というのではなく、必要に応じてクラスベースの書き方を使ってください。
*1:ECMAScript には Class という言葉が出てきますが、ここでいうクラスとはまた別です。 ここでは、ユーザーが定義可能な型としてのクラスのことを言っています。
*2:JavaScript やそれに類する言語の統一仕様
書評: 『JavaScript: The Good Parts ― 「良いパーツ」 によるベストプラクティス』 Douglas Crockford 著, 水野 貴明 訳
JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス
- 作者: Douglas Crockford,水野貴明
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/12/22
- メディア: 大型本
- 購入: 94人 クリック: 1,643回
- この商品を含むブログ (190件) を見る
Amazon.co.jp でのレビューで高評価だったのでちょっと読んでみようかなぁ、と思って購入しました。 読んでみると思ったほど良い内容ではなかったので、ちょっと気になった部分を書いておきます。
初心者向けにしてはやや難あり?
本書の 「はじめに」 には次のように書かれています。
本書は JavaScript というプログラム言語についての本であり、偶然か、はたまた好奇心のためか、初めて JavaScript の世界へ飛び込もうとしているプログラマに向けて書かれている。 また本書は、すでに JavaScript を使っていて、この言語により精通したい初級プログラマに向けて書かれた本でもある。
つまり、本書は JavaScript の初心者向けの本ということです。 私も本書を読んで、内容的には初心者向けの書籍だと感じました。
しかし、「こうすべきではない」 という筆者の主張が強く、初心者向けにしてはやや難があるように感じました。 絶対に守るべきことを強く主張するだけならまだ良いのですが、本書には必ずしも良いとは言い切れない主張も多かったので、そういう主張を読者が鵜呑みにしてしまう可能性があるのはちょっと問題だと思いました。 まあ主張が強すぎるだけで内容的には悪くはないので、「こういう主張もあるんだなー」 という程度に読むのであれば初心者にもいいかと思います。
細かい部分について、Kanasansoft Web Lab. さんが下記記事にて色々突っ込んでいるのでご覧ください。
特に問題があるのは継承について
私が、本書で特に問題があると感じたのは 「継承」 について書かれた章です。
本書では、下記のような継承方法を 「疑似クラス型」 の継承 *1 と呼んでいます。
// ベースとなる疑似クラス var Base = function Base( value ) { this.fBase = value; }; Base.prototype.doBase = function doBase() { /* 何らかの処理 */ }; // Base を継承した疑似的なサブクラス var Sub = function Sub( value ) { this.fSub = value; }; Sub.prototype = new Base(); Sub.prototype.doSub = function doSub() { /* 何らかの処理 */ }; // インスタンス化 var b = new Base( "ベース" ); var s = new Sub( "サブ" );
疑似クラス型の継承だと、必要以上に深く複雑な階層構造を構築するコードを書く必要があって、さらにプライベート領域 (オブジェクトの外部から参照できない変数やメソッド) を持てないため良くない、というのが本書の主張です。
その解決策として、下記のようにオブジェクトを生成する関数を使って継承を実現する方法 (本書では 「関数型」 と呼ばれていました) が記されていました。
// このサンプルコードは上のサンプルコードと等価なわけではない // Base 相当のオブジェクトを生成する関数 var createBase = function createBase( spec ) { // spec がプライベートな領域 // that が新しく生成したオブジェクト var that = {}; that.doBase = function doBase() { // プライベートな領域の値を返す return spec.name; }; return that; }; // Sub 相当のオブジェクトを生成する関数 var createSub = function createSub( spec ) { spec.subInfo = "sub!"; // Base 相当のオブジェクトを生成 var that = createBase( spec ); that.doSub = function doSub() { // プライベートな領域の値を返す return spec.subInfo; }; // ... // メソッドの追加などをする // ... return that; }; // オブジェクトの取得 var b = createBase( { name: "ベース" } ); var s = createSub( { name: "サブ" } );
要は 「クロージャでプライベート領域が実現できるし、こう書くことで疑似クラス型の手法よりもすっきりとする」 というのが筆者の主張なわけです。
しかしながら、プライベート領域を実現しようと思ったら疑似クラス型の方法でも可能ですし、個人的には関数型の手法よりも疑似クラス型の手法の方がすっきりしているように思います。 また、(プライベート領域を実現しない場合の) 疑似クラス型であれば、メソッドはそれぞれのオブジェクトで共有されるため、メモリ領域は少なくて済みます。 (関数型の場合は各オブジェクトにメソッドが追加されるので、メモリ領域が多く必要。)
私が本書で特に気になったのはこの部分と、あとは new
演算子を使うべきではない、という主張です。 継承の方法も new
演算子を使うべきかどうかも、必ずしも筆者の考えが正解だとは言えないと思うのですが、筆者の主張が強くやや独善的であるように感じました。
おまけ: よりよい疑似クラス型の継承方法
上では、コンストラクタ関数の prototype
メソッドに、継承したいオブジェクトをインスタンス化したものを設定していました。 しかし、単にそうするだけではサブクラスの constructor
プロパティがベースクラスを指す、というような問題があります。 また、プロトタイプオブジェクトのプロパティだけでなく、インスタンス化されたオブジェクトにのみ存在するプロパティも継承してしまいます。
// 上の疑似クラスのサンプルの続き b.constructor // => Base s.constructor // => Base // 本当は Sub になってほしい
そこで、次のように継承用のメソッドを定義します。
Function.prototype.extend = function extend( aBaseConstructor ) { var T = function() {}; T.prototype = aBaseConstructor.prototype; this.prototype = new T(); this.prototype.constructor = this; this.baseConstructor = aBaseConstructor; return this; };
こうしておくと、extend
メソッドを使うだけで継承ができます。 さらに、ベースクラスのコンストラクタの呼び出しも簡単にできるようになります。
var Base = function Base( base ) { this.baseValue = base; }; Base.prototype.doBase = function doBase() { return this.baseValue; }; var Sub = function Sub( base, sub ) { // ベースクラスのコンストラクタ呼び出し Sub.baseConstructor.apply( this, [base] ); // サブクラスにおけるプロパティの設定 this.subValue = sub; }.extend( Base ); // 継承 Sub.prototype.doSub = function doSub() { return this.subValue; }; // インスタンス化 var b = new Base( "aaa" ); var s = new Sub( "aaa", "bbb" );
詳しい説明については JavaScript におけるクラスの作成と継承 の記事をご覧ください。
*1:ちなみに、ここで 「疑似クラス型の継承」 として紹介している継承の方法は 「疑似クラス型の継承」 の方法として最善だとは言えないと思います。 後でさらに良い方法を示します。
JavaScript で OAuth 認証を行う方法
最近、twitter クライアントを Firefox 拡張機能として作ったりしているのですが、twitter では認証方式として OAuth が採用されているため、JavaScript で OAuth 認証を行う必要があります。 ここでは、JavaScript の OAuth ライブラリを用いて OAuth 認証を行う方法を説明します。
OAuth 自体の詳しい説明などは OAuth の公式サイト をご覧ください。
OAuth ライブラリの入手
Google code に OAuth 関連ライブラリのプロジェクトサイト があります。 ここで、John Kristian さん作の JavaScript 用のライブラリも公開されています。
Revision 1249 の段階では、ライブラリとして使用するのに必要なファイルは以下の 2 つのファイルです。 これらのファイルをダウンロードしてください。
- oauth.js
- sha1.js
OAuth ライブラリの使用方法
さて、OAuth ライブラリをどのように使うかの例を示します。 より多くの例が JavaScript 用 oauth ライブラリのリポジトリ中 にありますので、もっと多くの例を見たい方はそちらをご覧ください。
GET メソッドの場合
以下に GET メソッドの場合の例を示します。 強調表示をしている部分は、特に OAuth に関係しているところです。 デフォルトで使用されるシグニチャメソッドは HMAC-SHA1 です。
// 既に oauth.js や sha1.js はロードしているとする// GET メソッドで接続する先の URL
var action = "http://example.com/oauth"
// 渡すパラメータ
var parameters = [
[ "screen_name", "XXXXXX" ],
[ "since_id", "XXXXXX" ],
];
// consumer key などの設定
var accessor = {
consumerKey : "XXXXXX",
consumerSecret : "XXXXXX",
token : "XXXXXX",
tokenSecret : "XXXXXX"
};
// メッセージオブジェクトの生成
var message = { method: "GET", action: action, parameters: parameters };
// パラメータを含む URL の取得
var url = OAuth.addToURL( message.action, message.parameters );
// OAuth 認証のためのパラメータ等の補完 (パラメータを含む URL の取得の後にすること)
OAuth.completeRequest( message, accessor );
// XMLHttpRequest オブジェクトを使用しての通信
var req = new XMLHttpRequest();
req.onreadystatechange = function receiveRequestToken() {
if( req.readyState == 4 ) {
if(req.status == 200) {
// 成功時の処理
} else {
// 失敗時の処理
}
}
}
req.open( message.method, url, true );
// Authorization ヘッダの設定 (getAuthorizationHeader の第 1 引数は realm)
var realm = "";
req.setRequestHeader( "Authorization", OAuth.getAuthorizationHeader(realm, message.parameters) );
req.send();
POST メソッドの場合
以下に POST メソッドの場合の例を示します。 基本的には GET メソッドの場合と同じですね。 GET メソッドの場合はパラメータを URL に含める一方 POST メソッドの場合はパラメータリクエスト本文として送信するので、その違いに起因する変更が必要となります。
// 既に oauth.js や sha1.js はロードしているとする// POST メソッドで接続する先の URL
var action = "http://example.com/oauth"
// 渡すパラメータ
var parameters = [
[ "screen_name", "XXXXXX" ],
[ "since_id", "XXXXXX" ],
];
// consumer key などの設定
var accessor = {
consumerKey : "XXXXXX",
consumerSecret : "XXXXXX",
token : "XXXXXX",
tokenSecret : "XXXXXX"
};
// メッセージオブジェクトの生成
var message = { method: "POST", action: action, parameters: parameters };
// リクエスト本文の生成
var requestBody = OAuth.formEncode( message.parameters );
// OAuth 認証のためのパラメータ等の補完 (リクエスト本文の生成の後にすること)
OAuth.completeRequest( message, accessor );
// XMLHttpRequest オブジェクトを使用しての通信
var req = new XMLHttpRequest();
req.onreadystatechange = function receiveRequestToken() {
if( req.readyState == 4 ) {
if(req.status == 200) {
// 成功時の処理
} else {
// 失敗時の処理
}
}
}
req.open( message.method, message.action, true );
// Authorization ヘッダの設定 (getAuthorizationHeader の第 1 引数は realm)
var realm = "";
req.setRequestHeader( "Authorization", OAuth.getAuthorizationHeader(realm, message.parameters) );
req.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );
req.send( requestBody );
OAuth ライブラリのライセンスについて
OAuth ライブラリのライセンスは Apache License, Version 2.0 です。 使用の際はライセンスの内容を確認した上で使用してください。
JavaScript での OAuth 認証の使いどころ
今のところ XMLHttpRequest を使ったクロスドメイン通信は一般的ではありません *1 ので、JavaScript での OAuth 認証はあまり使いどころがないような気はします。 Firefox 拡張機能をはじめとする、各種ブラウザの拡張での使用が一番多いのではないでしょうか。
Firefox 拡張機能の中でこの oauth ライブラリを使用する場合は、グローバルな名前空間を汚さないように mozIJSSubScriptLoader を使ってロードするようにすれば良いでしょう。
*1:XMLHttpRequest Level 2 ではこの機能が盛り込まれる予定です。
JavaScript の this キーワードは何を指すのか - コールバック関数内では this を使ってはいけない
this
キーワードの値の決定され方について説明していますが、より全般的な this
キーワードの決まり方について別の記事を書きました。 合わせて参照してください: JavaScript の this キーワードに結びつけられる値はどのように決定されるのか (言語仕様の説明) - ひだまりソケットは壊れないJavaScript における this
キーワードは、簡単なように見えて、その実 JavaScript 初心者にとって落とし穴になりやすいものです。 探せば this
キーワードに関する解説はいくらでもありますが、基本に戻って ECMA-262 *1 を参照しながら解説してみたいと思います *2。
this
キーワードの落とし穴 - コールバック関数がうまく動かない
this
キーワードがどのように落とし穴になりやすいのか考えてみます。 まず、普通に this
キーワードを使ってプログラムを書いてみます。
var obj = {};
obj.message = "メッセージ!!";
obj.callback = function() {
alert( this.message );
};
obj.callback(); // "メッセージ!!" と書かれたアラートが表示される
これを実行すると、コメント中に書かれているように "メッセージ!!" と書かれたアラートが表示されます。 obj.callback
の関数中の this
が obj
を指しており、this.message
が obj.message
と同一であるためです。 これは誰もが納得するでしょう。 問題は、obj.callback
をコールバック関数として setTimeout
や addEventListener
に渡したときに発生します。
// obj は上のサンプルと同じ
setTimeout( obj.callback, 1000 );
// 1000ms 後に "メッセージ!!" と書かれたアラートが表示され――ない!!
これを実行すると、約 1000ms 後に "メッセージ!!" と書かれたアラートが表示される、と思ってしまうかもしれませんが、実際には "undefined" と書かれたアラートが表示されてしまいます *3。
同じ関数を呼び出しているはずなのに、obj.callback()
の形式で呼び出すと期待通りの動作をして、コールバック関数として呼び出すと期待通りの動作をしない。 これが this
キーワードの落とし穴です。
というわけで this
キーワードについて ECMA-262 (Edition 5) で調べてみる
そこで、this
キーワードが何を指すかがどのように決定されるのかを、ECMA-262 (Edition 5) を参照しながら説明します。
JavaScript や JScript の標準として ECMAScript というものがあり、その公式文書が ECMA-262 です。 ここでは Edition 5 (PDF) を参照しながら説明したいと思います。
関数呼び出し
関数呼び出しについては、11.4.3 節に述べられています。 関数呼び出しの形式は MemberExpression Arguments というもので、MemberExpression は変数だったり関数式だったりします。 Arguments は括弧でくくられた引数のリストです。 関数呼び出し時の手順 (11.4.3 節に書かれている内容を訳したもの) を以下に示します。
- 1. MemberExpression の実行結果を ref とする
- 2. GetValue(ref) の結果を func とする
- 3. Arguments の実行結果を argList とする
- 4. Type(func) が Object でなければ TypeError 例外を発生させる
- 5. IsCallable(func) が false なら TypeError 例外を発生させる
- 6. Type(ref) が Reference の場合:
- a. IsPropertyReference(ref) が真の場合:
- i. GetBase(ref) の結果を thisValue とする
- b. そうでない場合 (ref の base は Environment Record):
- i. GetBase(ref) のメソッド ImplicitThisValue を呼び出した結果を thisValue
- 7. そうでない場合 (Type(ref) が Reference でない):
- a. thisValue は undefined
- 8. this の値として thisValue を、引数リストとして argList を提供して func の内部メソッド [[Call]] を呼び出し、その結果を返す
よくわかんないかも知れませんが、
obj.function_name()
というように、obj のプロパティとして関数を参照して関数呼び出しを行った場合 (6.a の場合)、またはwith( obj ) {
function_name(); // function_name は obj のプロパティ
}
という形で with
文を使って関数を参照して関数呼び出しを行った場合 (6.b の特殊な場合)、thisValue は obj
となります。 一方でvar function = function() { ... };
function();
のように、関数を参照している局所変数を使って関数呼び出しを行った場合 (6.b の場合)、または(function() { ... })();
というように関数式で作成した関数をそのまま呼び出すような場合 (7 の場合)、thisValue は undefined
となります。thisValue を決定した後、内部関数 [[Call]] が呼び出されます。
内部関数 [[Call]] の呼び出し
内部関数 [[Call]] については、13.2.1 節に書かれています。 this
の値は 10.4.3 節で説明されていると書かれています。
this
の値の決定
というわけで 10.4.3 節を見てみます。 10.4.3 節は、コードの実行ステップが関数に入るときの処理について書かれており、this
の値についても述べられています。 this
に関する部分は、以下のとおりです。
- 1. If the function code is strict code, set the ThisBinding to thisArg.
- 2. Else if thisArg is null or undefined, set the ThisBinding to the global object.
- 3. Else if Type(thisArg) is not Object, set the ThisBinding to ToObject(thisArg).
- 4. Else set the ThisBinding to thisArg.
thisArg というのが、thisValue のことを意味しており、ThisBinding というのが this
キーワードに結び付けられるものを表しています。
まず、strict モードの場合、this
は thisArg そのままになります。 thisArg が undefined
や null
でもそのままです。
ただ、strict モードで実行することはあまりないと思います。 strict モードでなく、thisArg が null
または undefined
の場合、this
はグローバルオブジェクトを指すことになります。 それ以外の場合、thisArg そのもの (プリミティブ型ならばそれをオブジェクト化したもの) を指すことになります。
this
キーワードの決定方法についてのまとめ
重要なのは this
キーワードは関数がどのようにして呼び出されたかによって決まる ということです。 基本的には、
- オブジェクト (またはプリミティブ型) のプロパティから参照されている関数を呼び出した場合は
this
はそのオブジェクトを指す - それ以外の場合、
this
はグローバルオブジェクト (strict モードでない場合)
です。
そんなわけでコールバック関数内では this
を使わないようにすべし
さて、ここまでくるとなぜコールバック関数として渡した関数の中の this
は期待通りのオブジェクトを指さないのかがわかったと思います。 なんらかのプロパティから参照されている関数を別の関数に渡したとき、渡した先の関数内ではもはやそれはただの局所変数から参照されているだけの関数になってしまうからです。
function do_func( func ) {
func(); // 渡された関数 func は, もはや局所変数から参照されているだけ
}
do_func( obj.func ); // obj のプロパティ func から参照されている関数を渡しても
// それは obj のプロパティから参照されていることは
// 渡した先の関数ではわからない
この問題への対処法はまあ色々とあるかと思いますが、一番簡単でわかりやすいのは コールバック関数として別の関数に渡す関数の中では this
を使わない というものだと思います。 理解してしまえばなんてことはないですが、JavaScript を使い始めた最初のうちはついついこの落とし穴にはまってしまうことが多いと思いますので気をつけてください。
補足
この記事ではコールバック関数の中における this
キーワードについて注目しましたが、コールバック関数として別の関数に渡したときだけでなく、別のオブジェクトのプロパティに代入したときにも this
の指すものは変わってきます。
var obj1 = { message: "this is obj1!!" };
var obj2 = {};
obj1.show_msg = function() { alert( this.message ); };
obj2.show_msg = obj1.show_msgobj1.show_msg(); // "this is obj1!!" というアラート
obj2.show_msg(); // "undefined" というアラート
ただ、こっちはそれなりにわかりやすいので悩むことはあんまりないと思います。 コールバック関数として別の関数に渡したときに悩む人は多い気がしますので、この記事ではコールバック関数中の this
キーワードに注目しました。
Firefox アドオン中での文字コードの変更方法 (XPCOM を JavaScript から使用する)
Firefox のアドオン (拡張機能等) を開発する際に、文字コードを変更する必要がでてくることもあるかと思います。 ここでは、JavaScript のコード中の文字列の文字コードを XPCOM *1 を使用して変更する方法について記します。
ここに書いてある方法はあくまで Firefox アドオン中で実行可能な方法であり、通常の web ページ内の JavaScript などでは使用できません。 文字コードの変換はアドオンから外部のプログラムなどに文字情報を送る際などに必要になると思います。
nsIScriptableUnicodeConverter
使用するのは nsIScriptableUnicodeConverter です。 これを使用することで JavaScript 上の通常の文字列 *2 を各種文字コードでエンコードされた文字列に変換したり、逆に各種文字コードでエンコードされた文字列を Unicode 文字列に変換したりできます。
nsIScriptableUnicodeConverter を使用するために、まずインスタンスを取得します。
var unicodeConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
次に、変換対象の文字コードを設定します。 次の例では Shift_JIS を文字コードとして設定しています。
try {
unicodeConverter.charset = "Shift_JIS";
} catch(e) {
// 設定しようとした文字コードがサポートされていない場合は例外発生
// 例外処理
}
最後に文字コードの変換を行います。 次の例では文字列 "テスト文字列" を Shift_JIS に変換し、そのあとで逆の変換をしています。
var str = "テスト文字列";
// 文字列 str を Shift_JIS (unicodeConverter.charset に設定した文字コード) に変換する
// 変換後、Funish メソッドを使用し, その返り値を変換後の文字列の末尾に繋げなければならない
var sjisStr = unicodeConverter.ConvertFromUnicode( str ) + unicodeConverter.Finish();
// 変換後の文字列をまた Unicode 文字列に戻す
var ucStr = unicodeConverter.ConvertToUnicode( sjisStr );
これで文字コードの変換ができます。 nsIScriptableUnicodeConverter は他にもメソッドをいくつか持っているので、詳しくは MDC のドキュメントを読んでください。
*1:XPCOM とは Mozilla プロジェクトにおいて開発されているクロスプラットフォームなコンポーネント技術で、JavaScript からもそのコンポーネントを利用できます。