[Javascript] prototypejsのArray関数hack問題の対応策

2016年9月16日

Javascript テクノロジー プログラミング

JSのライブラリは本当に便利なものである一報、内部処理についてはブラックボックス化している事がほとんどで、実際に泣かされてきたWEBエンジニアも数多くいる事から、業界では経験の積まれたネイティブ派と、若くてイケイケだが苦い思いをしたことが無いライブラリ派に分かれているようである。 多くの技術書が、ライブラリ仕様を推奨している背景には、面倒くさい処理をわざわざ書かなくてもいい煩わしさからの開放もあるのだが、苦い経験をしたことがないエンジニアにとっては、付属する便利でトリッキーな関数を使いたくて仕方がなくなるわけである。 今回は「prototypejs」で実際に経験した苦い思い出とそれの解決方法をメモしておくことにする。

PrototypejsのArray問題

prototypejsを導入しているサイトで配列を定義すると、配列内に勝手に別の要素を仕込まれてしまうという、コーディングの常識では考えられないお作法が存在する。 実はprototypejsだけなく、その他にもこの手のグローバル関数hackを行なっているライブラリはいくつかあるようだ・・・ 知らずに使うと確実に怪我をするので、大怪我にならないために、対応法を身につけておこう。

具体的なArray-Hackとは?

実際に見てみよう。 prototypejsは本家サイトから最新版をDL(※2016.9.11時点ではvar 1.7.3) http://prototypejs.org/download/ これをhtmlファイルで読み込んでブラウザのコンソール画面で実験。 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"/> <title>Prototype.js - sample</title> <script type="text/javascript" src="prototype.js"></script> </head> <body> <h1>Prototype.js-Sample</h1> </body> </html>

配列オブジェクトを作成

var a = new Array('aa','bb');

配列数を見てみる

a.length; > 2 この時点ではなんの問題もない・・・

配列を順に取り出す処理を行なってみると

for (var i in a) console.log(i); > 0 > 1 > each > eachSlice > all > any > collect > detect > findAll > elect > grep > include > member > inGroupsOf > inject > invoke > max > min > partition > pluck > reject > sortBy > toArray > zip > size > inspect > _reverse > _each > clear > first > last > compact > flatten > without > uniq > intersect > clone ビックリ仰天丸!!! まったくいらんことしまくってくれてますね!!!

見解

JSの配列はobjectableなことをいい事に、好き放題関数を埋め込んでくれてます。 実はこれを知らずに配列データを作成して、内容を順番に取り出すようなツールを作っていて、たまたまそのツールをprototypejsが導入されているサイトで実行したら、結果が食い違ってきてしまいます。 多くの場合、JSエラーが発生してツールが停止してしまいますけどね・・・

対応法その1

実はこれを回避する為の簡単なコーディング方法があります。 それは var a = new Array("aa","bb"); ↓↓↓ var a = ["aa","bb"]; このように、わざわざnew宣言をしなくても、簡単に配列が作れるJSの方式を利用して、より簡潔に書くことでprototypejsの呪縛から開放されるのです。

結果

var b = {0:"aa",1:"bb"}; console.log("count:"+b.length); for(var i in b)console.log(i); > count:undefined > 0 > 1 ただし、見て分かる通り、lengthで取得できる値が取得できなくなります。 察しのいい人は気がつくかもしれませんが、配列のpush関数も使えなくなります。 b.push("cc"); > VM504:1 Uncaught TypeError: b.push is not a function(…)

対応法その2

for..in..文を使わない。 具体的には、for..in..文を使うとobject型の内装する要素を全て拾ってしまいます。 以下の様な取得を行うことで回避する方法があります。 var c = new Array("aa","bb"); console.log("count:" + c.length); for(var i=0; i<c .length; i++){ console.log(i +":"+ c[i]); } > count:2 > 0:aa > 1:bb 結果は納得の行く内容になっています。 実は本来は、配列の要素取得にfor..in..は使ってはいけなかったんでしょうね。 そしてpush関数も使えるようになります。 c.push("cc"); console.log("count:" + c.length); for(var i=0; i<c .length; i++){ console.log(i +":"+ c[i]); } > count:3 > 0:aa > 1:bb > 2:cc

対応法その3

JSバージョンが最新でいいのであれば、書き方をES6対応にしてしまいましょう。 var d = new Array("aa","bb"); d.each(function(obj , id){ console.log(id +":"+ obj[id]); }); > 0:a > 1:b phpで言うところのforeachですね。 この書き方になれたほうがいいかもしれません。 でも、古いブラウザではJSエラーになるので、対応ブラウザに気をつけながら使いましょう。

ブラウザの昔話

一昔前にはどの企業サイトも「prototypejs」が使われていた時代がありました。 それはまだIE6が主流で、GoogleChromeブラウザも存在しておらず、Firefox、Opera、SafariがIEに追いつけ追い越せと言わんばかりに、拡張機能重視でWEBレンダリング技術を向上させていっている時代。 一報IEブラウザについては、シェア1位をいいことに、W3Cの仕様を無視し、独自路線での機能拡張を次々と行い、結果、唯一仕様が定まらない扱いづらいブラウザというレッテルが貼られ、WEBエンジニア泣かせのブラウザとして定着してしまいました。 元々のデフォルトグローバル関数がショボかったこともありますが、基本関数の書き換えは絶対に行なってはいけません。 数多く大手のサイトのJSを見てきましたが、大企業のサイトでそのサイト内だけだからということで、色々なグローバル関数を書き換えているような企業もあります。 もちろん内部にレギュレーションが存在するのかもしれませんが、今やSaasツールは、大手企業でも大規模に導入されるほどの提供状態になっている。 こうした時に、大企業のレギュレーションなど知るはずもないSaas提供会社は、その会社への導入時にJSプログラムのコンフリクトに悩まされることでしょう。 WEBブラウザを利用するプログラム群に関しては、W3C規定と基本関数は手を入れないようにするルールを守ってもらいたいものである。

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。