素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: twitter: @javascripter すかいぷ:javascripter_ , javascripter あっと tsukkun.net twitterにどうぞ。

ES6のclassは巻き上げがされない

ちょっとハマったのでメモ。 例えば

alert(A);

function A() {
}

はできるが、

alert(A);
class A {

}

ができないのはなぜか。

class A {
}

class B extends A {

}

例えば、このようなコードの場合、hoisting(スコープ先端への巻き上げ)をしても問題ないが、 関数と違ってextendsはその場で評価しなければいけないことがある。 例えば、

var A = function () {

};
A.prototype.x = function () {
  return 1;
};

class B extends A {
}

alert(new B().x());

というコードの場合、BクラスはAを継承しなければいけないので、var Aで宣言されているものはhoistingされないのでclass宣言もその場で評価しないと、正常に継承することができない。

というわけで、

function A () {
}

class A {
}

は評価の順序が違い、class Aはvar A =とおなじような順序で評価されるわけである。

io.jsとES6とトランスパイラまわりの現状

最近いろいろな環境でES6まわりの環境が整いはじめたので、使っていくために下調べしておこうと思う。

ES6の実装状況であるが、ECMAScript 6 compatibility tableにまとめが載っている。

例えば、io.js (nodeのfork)のES6実装状況。stableなES6 featuresが元から有効になってる。

現在有効なのは、文字列テンプレート、for of、generators, Promise, Map, WeakMap, Setなど。

最新版をターゲットにするのであれば、上の機能は何も考えずにそのまま使うことができる。 このうち、テンプレート、for of, WeakMap以外はpolyfillが手に入るので、それらを使わなければPromise, Mapなどはfeature detectして実装されていない場合はmoduleを読み込むことで幅広いバージョンをサポートできる。

node.jsは--harmonyを有効にしなければジェネレータが使えないなど、node.jsはio.jsとはES6まわりの実装事情が違う。

io.jsでサポートされていない(classes)などの機能を使いたい、あるいは自分で書いているコードでサポートするバージョンのio.js / nodeでネイティブに実装されていない機能を使いたい場合は、トランスパイラを使う。 今一番勢いがあって機能が整ってるのはBabel.js (つい最近まで6to5という名前だった)で、クラスなどのES6の機能はもちろんsource mapsのサポートもあり、React、JSX、Flowなども使用することができる。

一般的なトランスパイラと同様で、

$ npm install --global babel
$ babel script.js

とするだけで使うことができるので、gulpなどでコンパイルするといい。

この場合、

dist/*.js
src/*.es6.js

のように、ES6のコードは.es6.jsとしておくとわかりやすくて、 npm publishするときは、 dist/側はhookでインストール時に生成されるようにしておくとgit logが汚れない。

source mapsについては、io.js / node側で有効にするには

require('source-map-support').install()

とすれば良い (参考: ES6 at PayPal )。

ReactとJSXを使いたい場合は、もともとreact-toolsはharmonyオプションがあるので、一部機能だけを使えればいい場合は

<script type="text/jsx;harmony=true">

などとして書くかreact-toolsでharmonyオプションを有効にすればいいのだが、Babel (6to5) もJSXをサポートしているので、より完全なES6実装が欲しい場合はBabelを使うとよい。

そんな感じで、タイトルと比べて内容がスカスカではあるが、ここ最近、トランスパイラが急に進化してきてES6が使える環境がいきなり整ってきたような雰囲気があるので、そろそろ使い始めてもいい頃だと思う。いつのまにかブログの更新が数ヶ月空いてしまったけど生きてるので、今年も頑張ってブログを書いていきたい。

JSライブラリ/フレームワークの良い、悪いメモ

※ただのメモで、未来志向なのであまり真に受けてはいけない。

良いっぽい

React.js

い/コンポネント志向/APIの設計がいい。JSXと他のトランスパイラの組み合わせという問題はある

Promise

ネイティブに入った、誰もが使ってる

TypeScript

ES6時代でも存在意義のある言語。TypeScript互換のFacebook Flowの動向に注目

Backbone.js

ModelとEventを使う/Viewは使わなくていい

Lodash

Underscore.jsをよくしたやつ

Gulp

Gruntより良いという意味で。browserifyまわりがうまく動かない問題があってnpm runのほうがいいという噂もあるがまあ良いに分類してもいい

EventEmitter

Custom EventはDOMにくっ付いてる感があるのでロジック志向の物にはEventEmitter使ったほうがいい

6to5

Traceurとか色々あるけどSource Mapsあってライブラリ依存が少なくてregenerator使ってるあたりが良い

co

generator/Promise時代のasync

JSHint

JS直接書くならこれがいる

Browserify

クライアントサイドもrequire使うべき

jQuery

UIベタ書きせずBetter DOMとして使う

悪いっぽい

AngularJS

フレームワークがでかい、設計がおかしい

Polymer

ダーティーハックの塊 コメントで指摘があったがハックが多いのはWebComponents(特にShadow DOM)のPolyfill実装のpaltform.jsで、これはpolyfillなのでいずれ消えるそうである。ただ、ヘビーなpolyfill実装に依存しているブラウザが多いので、現時点ではあまり勧められないのも事実だと思う。

Async.js

Promiseを使う

jQuery.Deferred

正しいPromiseを使う

AMD modules

CommonJSを使う

Prototype

だいぶ前に死んだ

jQuery UI / jQuery Mobile

もう役目は終えた

JSLint

JSHintの時代になった/どうでもいいことでエラーを出しすぎる

RequireJS

でかすぎる/AMDの時代は終わった/事前にコンパイルしてくっつけるスタイルが主流

Component

npm使って困ったらbowerで良い

判断しかねる

CoffeeScript

ES6+のclass / arrow function syntaxもろもろと相性が悪い/若干役目を終えつつある感が漂ってる

KnockoutJS

MVVMよりReactJSの設計のほうが綺麗

Meteor

ロックインが激しい/思ったより普及しなかった/まだ発展中

Grunt

Gulpの時代が来てる/gulpのプラグインも埋め込めるようになったから大は小を兼ねる的な使い方もある

Traceur

全体的に重くてコンパイル後がでかい/ES6カバー率は高い

ImmutableJS

見込みはあるけどAPIがまだまだ不安定/あまり使われてない

Get mouse coordinates on Yosemite using JavaScript Automation(ターミナルからマウス座標をゲットする)

英語で調べたけどぜんぜん出てこないので英語で書く。Yosemiteでターミナルからマウス座標をゲットするコマンドはこれ。 osascript -l JavaScript -e "ObjC.import('Cocoa');console.log(JSON.stringify($.CGEventGetLocation($.CGEventCreate(null))))"

In OS X 10.10 (a.k.a. Yosemite), Apple has deprecated Ruby 1.8 along with support for "osx/cocoa" module in favour of AppleScript's AppKit and JavaScript Automation. I found it quite inconvenient since it used to be quite easy to get access to OS X's internal information such as mouse coordinates without needing any external binary files prior to Yosemite. After searching on the Internet for a while, however, I found an alternative way to do it in Terminal using JavaScript Automation introduced in Yosemite. So here's how to do it.

Firstly, open up Script Editor and switch the language from ActionScript to JavaScript. You can now write JavaScript in Script Editor, in which you have access to most of the functionality available in Action Script like Mail.inbox.messages[0].subject(). You will also get this magical ObjC bridge called $.

ObjC.import('Cocoa');
$.NSBeep();

(More information available at JavaScript for Automation Release Notes)

You can use the following code to obtain the mouse coordinates:

ObjC.import('Cocoa');
ObjC.import('stdio');

var c = $.CGEventGetLocation($.CGEventCreate($()));
$.printf('%d %d\n ', c.x, c.y);

Secondly, there's also a builtin command called osascript, which supports JavaScript as well. Type in

osascript -l JavaScript -e "ObjC.import('Cocoa');console.log(JSON.stringify($.CGEventGetLocation($.CGEventCreate(null))))"

in Terminal, and you'll get something like

{"x":920.4453125,"y":698.9140625}

You can execSync the above code and then JSON.parse it to get the mouse coordinates in Node.js. I can think of many other interesting things including creating a native app on top of JavaScript Automation, though it will not look so JavaScript-y.

To sum it up, using JavaScript Automation will become a standard now that Apple seems to have lost its interest in Ruby. I think JavaScript Automation is worth investigating because most of the knowledge can be applied to Objective C and other languages that bridge Cocoa APIs.

References:

  1. YosemiteからRuby1.8が削除されたのでAppleScriptでのウィンドウのサインズ変更の取り扱いを変更した - rcmdnk's blog
  2. JavaScript for Automation Release Notes
  3. JavaScript for OS X Automation by Example -Telerik Developer Network

array.indexOf(value) >= 0で要素が含まれてるか検索すると失敗する場合がある

indexOf(val) >= 0の話

配列に要素が含まれているかのチェックに

[1, 2, 3].indexOf(2) >= 0; // true

のようなコードを使っている場面を極めて頻繁に(それ以外を使っているのを見ることは稀なくらい)見るが、

function contains(a, v) {
  return a.indexOf(v) >= 0;
}

は厳密にはJavaScriptでは正しく動くとは限らない。上記コードのどこが間違っているのか、下記の解説を見る前に考えてみてほしい。こんなシンプルなコードにも、バグがある。

解説

containsがどのように動くべきなど明白だというのは間違いである。実は微妙な問題がたくさんある。 まず、致命的な部分をあげると、最初のナイーブな実装だと

contains([NaN], NaN) // false

となってしまう。明らかに意図した動作と異なり、バグである。 これは

NaN === NaN // false

という意味不明なJavaScriptNaNの仕様によるもので、indexOfも同様に===を使用しているので、こうなる。 訂正: コメントで指摘されたが、NaN !== NaNはほぼ全ての言語で採用されているIEEEの数値処理の仕様に基づいている(つまり不可解な仕様ではなくごく一般的な動作な)だけで、JavaScript自体の仕様上のミスではなかった。NaNまわりはハマりやすいポイントではあるので、気をつけたい。 contains関数では

contains([NaN], NaN]) // true

となるべきだ。

他にも、微妙な問題がある。 例えば、

[ ,,, ] [0] === undefined // true

であるが

0 in [ ,,, ] // false

であり、JavaScriptには、length以下に、holeと呼ばれる存在しないindexが存在する可能性があって、

[,,,].forEach(function () {alert(1); })

ではalertは一度も呼ばれない。indexOfも同様に存在しない要素をスキップするので

contains([,,,], undefined) // false

となる。これを仕様と呼ぶのかバグと呼ぶのかは考え方次第であるが、とりあえずforEachやindexOfにあわせて、holeは検索に含めないことにする。 ただし注意点として、最近はarrayのfor-inは順序が保証されているようだが

for (var i in a) {
  if (a.hasOwnProperty(i)) {

  }
}

は使ってはいけない。 なぜかというと

var a = [];
a['z'] =1;

の'z'までループしてしまうからだ。 また

for (var i = 0; i < a.length; i++) {
  for (i in a) {}
}

も同様にダメで、なぜかというと

Array.prototype[0] = 'a';

があると動作がおかしくなるからだ。というか

Array.prototype[0] = 'a';
var a = [, ];
a.indexOf('a') // 0
[,].forEach(function () { alert(1); }) // alert(1)される

のようにforEachとindexOfはプロトタイプチェーンを辿るので、この部分も難しいところであるが、今回はcontainsではプロトタイプチェーンを辿らないように実装した。

もう一点、これは余談だが、扱いの微妙な値がJavaScriptにはある。+0と-0である。一見同じようにみえる値だが、両者は厳密には異なる。

1/0 // Infinity

である一方、

1/-0 // -Infinity

であるからだ。

[-0].indexOf(+0) // 0

であるので、ここでは慣例に従って-0と+0はcontainsでは区別しないことにする。

さて、indexOfでは検索しはじめる位置を第3引数で指定できる。

[0].indexOf(0, 1) // false

となる。 もしこれに-Infinityを指定したらどうだろうか。

[0].indexOf(0, -Infinity) // 0

となり、きちんと、一瞬で答えが返ってくる。これも実装すべきだろう。

さて、おおよそ全ての値で動く実装を下記にあげる。

function contains(array, value, startIndex) {
  var hasOwnProperty = Object.prototype.hasOwnProperty;

  if (startIndex > array.length) { // for efficiency and also handling Infinity
    return false;
  } else if (startIndex === -Infinity) {
    startIndex = 0;
  } else {
    // when startIndex isn't specified make it 0
    // if startIndex is an invalid value, just make it 0 to behave like indexOf
    startIndex = +startIndex || 0;
  }

  for (var i = startIndex; i < array.length; i++) {
    // when array is sparse and value is undefined, ignore non-existent indices
    // 0 in [, ] returns true when Array.prototype[0] = 3
    // Therefore, hasOwnProperty must be used
    if (hasOwnProperty.call(array, i)) {
      var item = array[i];
      
      // when item is NaN and value isNaN
      if (item !== item && item !== value) {
        return true;
      } else if (value === item) {
        return true;
      }
    }
  }
  return false;
}

実行結果。

// regular values
contains([0, 1, 2], 0) // true
contains([0, 1, 2, 3], 0, 1 ) // false

// irregular values
contains([0], '0') // false
contains([NaN], NaN) // true
contains([,,], undefined) // false
contains([-0], +0) // true

contains([0], 0, -Infinity) // true
contains([0], 0, Infinity) // false
contains([0], 0, NaN) // true

ここまで読んで分かったと思うが、array.indexOf(val) >= 0が常に動かないことへ対処は、真面目にやろうとすると長い。長い上に他のJSのネイティブのAPIの仕様もちょっとずつおかしいので、プログラムを書く時点でNaNやInfinity, 穴のある配列を作らないように作るのがベターである。

scrollイベントが呼ばれすぎることへの対処

onscrollを監視していると大量にeventが来てパフォーマンスが劣化するというのはよく聞く話で、 underscore.jsなどでイベントをthrottleやdebounceさせて対処していることが多い。 ただ、scrollに応じてスタイルを変更させたりする場合は、0.1秒くらい遅れがあると視覚的にカクカクしがちで、仕方なくsetTimeout(fn, 0)でthrottleさせることも多いと思う。

scrollイベントは画面のフラッシュレートと同期していないので、マウスやトラックパッドの動きに応じて大量に呼ばれていて、setTimeoutも画面のフレッシュレートとは関係なく内部の実行タイマーに応じて動いている。よって、setTimeout(fn, 0)でthrottleしてもまだ無駄が多い。実験してみたところ、requestAnimationFrameを使うと少しだけマシになることがわかった。

結論から言うと、スクロールと画面の同期遅れを最小限にしたい場合はsetTimeoutではなくrequestAnimationを使ったほうがsetTimeout(fn, 0)より良くて、画面のチラツキが気にならないような場面ではsetTimeout(fn, 100)くらいを使うと良いと思う。

[Log] native: 484
[Log] throttled: 429
[Log] smartly throttled: 341

これは、下記のテストコードを適当なページで走らせて上下にスクロールさせた結果。

setInterval(function () {
  console.log("native: %d", nNative);
  console.log("throttled: %d", nThrottled);
  console.log("smartly throttled: %d", nSmartThrottled);
  
}, 1000);

var nNative = 0;

window.addEventListener('scroll', function () {
    nNative++;
}, false);

var tid;
var nThrottled = 0;
window.addEventListener('scroll', function (event) {
  clearTimeout(tid);
  tid = setTimeout(function () {
    nThrottled++;
  }, 0);
}, false);


var nSmartThrottled = 0;

function callback(e) {
  nSmartThrottled++;
}

var frameId;
function handleScroll(event) {
  cancelAnimationFrame(frameId);
  frameId = requestAnimationFrame(function () {
    callback(event);
  });
}


window.addEventListener('scroll', handleScroll, false);

forも明示的な加算も使わずに「1から100まで出力する」

1から100までforを使わずに、さらに明示的な加算も使わずに表示するもの、自分で思いついたバージョンを書いておく。非同期である。

var INTERVAL = 1000;
var d = Date.now();
var tid = setInterval(function () {
  var n = (Date.now() - d) / INTERVAL | 0;
  console.log(n);
  if (n == 100) clearInterval(tid);
}, INTERVAL);

自分の環境ではINTERVALは700まで安定的に動作した。テストを書いたので、自分の環境で正常に動作する範囲をテストしたい場合、下記のコードを使うと良い。node hundred.js>output.txtなどのようにしておいてcat output.txt|./test.zshとしてSuccessが表示されれば動いている。

#!/bin/zsh

I=1
while read -r LINE
do
  if [ $LINE -ne $I ]
  then
    echo "Failed: $LINE should be $I"
    exit 1
  fi
  I=$(($I + 1))
  if [ $LINE -eq "100" ]
  then
    break
  fi
done;

read -r LINE

if [ $LINE ]
then
  echo "Failed: Too many lines"
  exit 1
fi

echo "Success"

参考:

FacebookのReactで綺麗で高速なDOMのView操作

それほど新しい話題でもないが、FacebookReact | A JavaScript library for building user interfacesというDOMを抽象化したようなView操作のライブラリをリリースした。

Githubが出してるエディタのAtomが実験的にReactを採用したり、Instagramが採用していたり、エディタでReactのシンタックスがサポートされ始めたり、海外では実際に使われはじめていて、実際に使ってみたら思いのほか使いやすかったので、簡単に使い方を書いていこうと思う。

まず、ReactにはJSXという、htmlをJS内で書ける言語がある。オプショナルなものだが、使ったほうがコードが綺麗になるので、この記事では使っていく。下記コードはEdit fiddle - JSFiddleなどでコンパイルなしにそのままテストできるので、適当にコピーしながら確かめると理解が深まる。

/** @jsx React.DOM */

var div = <div>aaa</div>;

@jsxというノーテーションは、トランスパイラが

var div = React.DOM.div(null, 'aaa');

という風に変換するために必要になるimport文で、必ず必要になる魔法のようなもの。 ここで作成したdivは、実際のDOMとは違いバーチャルDOMと呼ばれるもので、

React.renderComponent(div, document.body);

のようにすると、document.bodyに、必要になった時のみdivが構築される。 必要になった時というのは、Reactは管理化にあるDOMの中身を全て把握していて

for (var i = 0; i < 100; i++) {
  React.renderComponent(div, document.body);
}

としても、実際のDOM構築やHTMLのDOM Treeの変更は1度しか行われないということである。 アプリの作成を行ってプロファイリングをすると、DOMの変更がボトルネックになっている場合が多いので、この方式は速度のでるアプリを開発するのに非常に有効である。

Reactでは、React.createClassを使って、カスタムエレメントのようなものを作成し、Viewをコンポネント化できる。

/** @jsx React.DOM */
var App = React.createClass({
    render: function() {
        return <div>aaa</div>;
    }
});

var app = <App />;
React.renderComponent(app, document.body);

このように、独自のカスタムエレメント風のものを生成することができる。基本的に要素はimmutableになっており、同様であるとみなされるものはReact内で再利用されたり、バーチャルDOMのdiffアルゴリズムに基づいて、自動的に再配置される。 よって、要素の変更時に全部一旦消して挿入し直したり、あまり考えずに自然な形で書いても、メモリを大量に消費する心配はだいぶ少なくなる。

カスタム要素には、属性として文字列やオブジェクトを渡すことができ、これはクラス内でthis.propsとしてアクセスできる。これはReactのページのサンプルからとったもの。

/** @jsx React.DOM */

var Hello = React.createClass({
    render: function() {
        return <div>Hello {this.props.name}</div>;
    }
});
 
React.renderComponent(<Hello name="World" />, document.body);

作成時に渡されるpropsは変更されないという想定がある。

ステート管理をしたい場合、別のオブジェクトであるthis.stateが専用に用意されており、getInitialStateメソッドで最初のthis.stateを生成することができ、その後はthis.setStateを利用してステート管理を行うことで、自動的にViewの更新が行われるようになる。ところで、JSXの属性は普通のHTMLの属性と違い、値は任意のJSオブジェクトを取ることができるので、

onClick={ this.onClick }

などで関数を渡してイベント登録を行うことになる。 ステート管理があるコードは

/** @jsx React.DOM */

var Hello = React.createClass({
    getInitialState: function () {
      return { name: "not clicked"  };
    },

    onClick: function () {
      this.setState( {name: "clicked" });
    },

    render: function() {
        return <div onClick={ this.onClick } >{this.state.name}</div>;
    }
});

React.renderComponent(<Hello />, document.body);

このような形になる。外部から後でView内のstateを変更したい場合は(MVCのように設計したい場合、modelからviewを変更できる必要がある)、 一般に、Modelを作成し、それを属性として渡す。ReactにはModelは付属しないので、Backboneや、好きなものを利用する。主動で作成するとこのような形になる。

/** @jsx React.DOM */

var Hello = React.createClass({
    componentWillMount: function () {
      // 本当は複数登録しても変にならないようにイベントとかを使う
      this.props.model.onTextUpdate = function () {
        // setStateを使ってもいいがオブジェクトが重複するのでmodelを使う時は主動でアップデートを指示するのがベスト
        this.forceUpdate();
      }.bind(this);
      
      
    },
    render: function() {
        return <div>{ this.props.model.getText() }</div>;
    }
});


var helloModel = {
  text_: "hi",
  onTextUpdate: function () {},
  setText: function (text) {
    this.text_ = text;
    this.onTextUpdate();
  },
  getText: function () {
    return this.text_;
  }
};

React.renderComponent(<Hello model={helloModel} />, document.body);


setTimeout(function () {
  helloModel.setText("dynamically updated outside the view!");
}, 1000);

基本的にはこれが全てで、componentDidMountメソッドの定義などView挿入時などのフックAPIReact | Working With the Browserにのっている。

一見独自のSyntaxがあったりして利用が難しいように見えるが、実はAPI自体は極めてシンプルで、このシンタックスを利用しなかったとしても、plainなJSからも普通に簡単に使える。競合であるWelcome - Polymerなどはpolyfillとしての性質や未来志向が強く、実際にサイトで使おうとするとライブラリが巨大かつReactに存在するようなバーチャルDOM管理などはあまりないので、今使うとしたらReactだと思う。

ここでカバーされていない部分はReact | Getting Startedを読むと良い。CSSのプロパティなどもHTML属性と同様に変更を保持してくれたり、もう少し機能がある。Polymerと違いscoped styleなどはないが、別に不便だとは思わない。CSSはclassNameを変更してcssファイルでやるのが管理する上で楽だと思うので、自分はあまり使っていない。

setTimeout, setIntervalを乗っ取って爆速にする

setTimeout() vs ハッカー、仁義なき戦いによると

function isNativeFunction(func, name)
{
    for (var o in func) {
        if (o === "toString") return false;
    }
    var match = func.toString().match(/^function (\S+)\(\)\s*{\s*\[native code\]\s*}$/);
    return (match && match[1] === name);
}

setInterval = function(){};
isNativeFunction(setInterval, 'setInterval'); // false

でsetIntervalが偽装されているか調べられると書いてあるが、そんなことはない。

自分が普段使っているブラウザはSafariなので他のブラウザではsetIntervalが上書きできないから下記コードは無効だが、setIntervalを書き換えたいと思う時など特定のときしかありえないので、Safariを使えばいいと思う

ほとんど全てのチェックを素通りするフェイクのsetIntervalの書き方をここに載せる(ネイティブかどうかを判別する方法を発見したら教えて欲しい)。ここではsetIntervalだけ上書きしているが、少しコードを追加すればsetTimeoutも上書きできる。

このコードを使うと、具体的には、setInterval(f, 1000);とすると、setInterval(f, 1)となり、スクリプト等でdocument-on時に走らせると全てが爆速になる。

(function () {

var nativeSetInterval = setInterval;
var nativeToString = Function.prototype.toString;

// ネイティブはfunction () {であるがsetInterval.length = 1で、Object.definePropertyで上書き不可能なので仮の引数を入れてごまかす
// この関数のtoString()については後から偽装するので大丈夫
var fakeSetInterval = function setInterval(_) {
  var fn = arguments[0];
  var ms = arguments[1];
  return nativeSetInterval.call(this, fn, ms / 1000);
}

// setInterval.toString()の結果を偽装 DontEnum等もネイティブ同様になるようにする
Object.defineProperty(Function.prototype, 'toString', {
  configurable: true,
  enumerable: false,
  value: function toString() {
    if (this == fakeSetInterval) {
      return 'function setInterval() {\n    [native code]\n}';
    }
    
    if (this == Function.prototype) {
      // Function.prototype.toString()対策
      return 'function () {\n    [native code]\n}';
    }
    
    // Function.prototype.toString.toString()対策
    if (this == Function.prototype.toString) {
      return 'function toString() {\n    [native code]\n}';
    }

    // 偽装する必要のないものは元のtoStringを呼ぶ
    return nativeToString.apply(this, arguments);
  },
  writable: true
});

// 改造済みのsetIntervalを注入

Object.defineProperty(Window.prototype, 'setInterval', {
  configurable: true,
  enumerable: true,
  value: fakeSetInterval,
  writable: true
});

})();

このコードを実行すると冒頭で引用したisNativeFunction(setInterval, 'setInterval') がtrueを返す。唯一考えられる対策はiframeなどの外部ページからFunction.prototype.toStringを取ってきてそれを使うことだが、

var toString = iframe.contentWindow.Function.prototype.toString;
toString.call(setInterval) == 'function setInterval() {\n  [native code]\n}';

このコードもiframe内で、ユーザスクリプトでdocument-start時にFunction.prototype.toStringを書き換えてしまえばいいので、意味がない。クライアント側で実行するコードは、全てユーザのコントロール下にあると思って書かないと、脆弱性の原因になるハッカーの勝利である。

引用元のサイトにあるように、ダウンロードサイトの待ち時間やゲームなどで、どうしても時間によるチェックをいれたい場合、サーバ側でタイマーを使って、待ち時間が経過するまではAPIがエラーを返すように作るしかないと思う。

Project Parfaitで尋常じゃない速さでpsdファイルからウェブサイトを組み立てる

Project Parfaitとは、adobeが開発してるβ版の、ウェブサイト作成支援のサービス。これを使うと、.psdのデザインファイルからHTMLを書き起こすのが異常に高速になるので、オススメしておこうと思う。

具体的には、psdファイルをアップロードすると、psdファイルの要素がレイヤー別に選択できるようになる。わかりやすいように画像で実例を出すと(一部塗りつぶしてあるが)

f:id:javascripter:20140803173442p:plain

このようになる。

あとは、Copy Textをクリックするとクリップボードに文字が選択されるので、HTMLファイルを書きながら

<li><a href="#">{テキストをペーストする}</a></li>

のように書いて、その後、Copy CSSをクリックし

li {
  /*CSSをペーストする*/
}

のように、貼り付けて上記の画像の場合はfont-weightがおかしいのでboldに手動で直して、widthやheightも不要なので消す。box-shadowやtext-shadowなどもそのまま再現されるので、手動で色々と考えて調整する必要がほぼなくなる。

あとは、要素の左上の絶対座標のx, yと、要素のサイズが同時に表示されているので、隣接する要素をクリックしたりして、計算機を片手にmarginやpaddingを算出して、手動でfloatなどを使ったりしていろいろ配置をいじれば完成。psdファイルは解像度が高めに作ってある場合が多いので、px座標が大きめになりがちであるが、気にせずにそのまま計算してcssに入れるとよい。 少し邪道だが、最後に

body {
  zoom: 0.8;
}

などで調整すればいいのである。あるいはそれが認められない場合は一通り仕上げてから、ツールで全ての数値を0.8かけて置換する。

psd内の画像をクリックすると選択した画像がダウンロードリストに入るので、psdファイルから画像を切り出すのも、非常に簡単。

CSS Spriteなどにしたい場合は後でまとめる必要があるが、それも後でツールを使えば良い。一般的なサイトをhtmlに起こす場合はProject Parfaitはかなりオススメである。

最近、WebサイトやWebサービスの制作の仕事を引き受けはじめて、色々と試行錯誤しはじめたのだが、いろいろ手探りにやりながらも、4〜5時間で10ページくらい作れた。その他、Webデザイン系だと、MacについてるDigital Color Meterなども常に起動しておくと便利。小規模なサイトはjQuery+プラグイン+自分で少し書いて、アプリや大規模なものはフレームワーク(AngularJSやReactJS、Backbone.jsなど)を使ってがっつり仕上げるというスタイルが、作業スピードを爆速にするためにいいのではないかと思う。試行錯誤して効率を上げていきたい。