中上級者になるためのJavaScript【知識編】

  • 2260
    いいね
  • 10
    コメント

【News】電子書籍化しました!


この記事は、敷居が低いもののなかなか中上級に進めず困っているJavaScript学習者の方を対象としています。よりJavaScriptに対する理解を深める際に気を付けるべき事柄、知っておくべきキーワードの提供をゴールとします。

「クロージャーについてもっと知りたい!」「カリー化なるものがあるのか、知らなかった!」といったきっかけになれば幸いです。

JavaScriptは書ける人が多く、ベストプラクティスが整ってあるものの、逆に間口が広すぎてコピペで済ませてしまったり(場合によってはしょうがないことですが)基礎を学ぶ機会がなくなんとなく現場に出てしまったりすることがありますので、ぜひこの機会にJavaScriptを復習してみてください。

(注)自分もまだまだJavaScript勉強中の身ですので、間違いや足りない部分があればぜひ教えてください:smile:! 
特に、記事内で紹介している「ベストプラクティス」などは、情報が古いか、誤って学習しているおそれがありますので、これ以上誤ったコードを広めてしまう前にもツッコミくだされば嬉しいです!

Outline

  • 1. Basic Tips
    • 1-1. グローバル変数を乱用しない
    • 1-2. forループを極める
    • 1-3. 暗黙の型変換を避ける
  • 2. オブジェクト
    • 2-1. 名前空間のイロハ
    • 2-2. パブリック/プライベートを意識する
    • 2-3. モジュールパターンについて知る
  • 3. 関数
    • 3-1. 関数のイロハ
    • 3-2. コールバック
    • 3-3. クロージャー
    • 3-4. 即時関数
    • 3-5. カリー化
  • 4. DOM
    • 4-1. DOMで避けるべきこと
    • 4-2. 高速化・最適化のためのTips

1. Basic Tips

Index

  • 1-1. グローバル変数を乱用しない
  • 1-2. forループを極める
  • 1-3. 暗黙の型変換を避ける

1-1. グローバル変数を乱用しない

JavaScriptでは「グローバル汚染」という言葉を耳にするほど、グローバル変数による名前空間の汚染が問題となることがあります。グローバル変数やスコープをそもそもわかっていない場合、もしくは言語的に正しい文法はかけているが、グローバル変数を多用することによるデメリットを理解していない場合があります。

グローバル汚染の悪影響

グローバル空間を特に理由のない変数定義で汚染してしまうと、

  • サードパーティプラグインを読み込んだ時の変数の衝突
  • チームメンバーが書いたコードとの名前衝突
  • 昔書いた自分のコードで使った変数との衝突

が起こってしまうことがあります。したがって、以下の方法を使ってグローバル変数を使わないための工夫をする必要があります。

対策:varを忘れない

JavaScriptでは、varを使わずに定義した変数はグローバル変数として走査されます。


function speakOut(){
  // global variable
  global = "Hello from global";

  // local variable
  var local = "Hello from local";

  console.log(global);
  console.log(local);
}

speakOut();

console.log(global);
console.log(local); // -> これだけエラーをはく


対策:名前空間を利用する


// object for name space
var myApp = {};

myApp.name = "My First JavaScript App";

対策:クロージャを利用する


(function(){
  maybe_global = "Hello from global?"; // varを忘れるとグローバル
  var local = "Hello from local";

})();

console.log(maybe_global);
console.log(local);

ベストプラクティス:単独varパターン

以下のように、必ず関数の先頭でvar文を書くことによって、誤ってローカル変数をvarを付け忘れて宣言してしまうことを避ける事ができます。

  • 習慣付けによってローカル変数のvarつけ忘れを防ぎやすい
  • ローカル変数や関数自体の全体的な可読性があがる
  • コード量が少なくて済む

function login(){
    var userId,
        userPasswd,
        date,
        sessionId,
        errorMessage = [],
        status = {};

    ...

}

1-2. forループを極める

for文を速くする

一般的なforループは以下のとおりです。


for ( var i = 0; i < myarray.length; i++){
    console.log(i);
}

Nicholas Zakas著『ハイパフォーマンスJavaScript』(O'Reilly)
によると、配列や配列に似たオブジェクトに対してforループを処理する場合、以下のように配列の長さを一度変数にキャッシュしておく方法を紹介しています。これによって、毎回配列の長さを取得する必要がなくなります。

同著によれば、Safari 3で2倍、IE7で190倍速度が違うとのこと。個人的に開発してきた小規模のサービスでこの違いを大きく実感することはあまりありませんが、このようなTipsがあるのを知っておけば、大規模サービス開発の際になにかしら役に立つかもしれません。


for ( var i = 0, len = myarray.length; i < len; i++){
    console.log(i);
}

また、ループ内でDOM要素を追加・削除するなど長さが変わってしまうような場合は
var文を外に出して宣言しておけば大丈夫です。


function myFunc(){

    var i = 0,
        len,
        myarray = [];

    //...

    for ( i = 0, len = myarray.length; i < len; i++){
        // ...
    }

}


for文をもっと速くする

ここから先は少しテクニックに走りすぎている感もありますが、
コンペや、速度が命のネットワーキングあたりを書いているときなどでは
知っておくといいことがあるかもしれません。

以下のように、変数の数を減らし、カウントアップ方式からカウントダウン方式に
することによってより高速化を望めます。ただ、これはどの言語でも言えることですが、
速度を求めすぎたコードは可読性が下がりがちになるので、そのプロダクトに
求められるものを考えながらトレードオフで使い分ければいいかと思います。


var i, 
    myarray = [];

for ( i = myarray.length; i--;){
    //...
}

1-3. 暗黙の型変換を避ける

まず質問ですが、以下の条件式の違いを説明できますか?
また、どちらが実行され、どちらが実行されないか、分かりますか?


var zero = 0;

// pattern A

if(zero === false){
    // ...
}

// pattern B

if(zero == false){
    // ...
}

=====は違います。
もちろん!==!=も違います。

JavaScriptでは変数を比較するときに暗黙の型変換が行われます。
===!==では、比較する際に値と式の型の両方をチェックしますが、
==!=では値しかチェックしません。

したがって、さきほどの例では
pattern Aは実行されず、
pattern Bは実行されます。

変数比較の際には、なるべく
===!==を使うようにしましょう。

2. オブジェクト

Index

  • 2-1. 名前空間のイロハ
  • 2-2. パブリック/プライベートを意識する
  • 2-3. モジュールパターンについて知る
  • 2-4. サンドボックスパターンとは

2-1. 名前空間のイロハ

名前空間は、中規模以上のアプリを書くにあたって必須のパターンです。
JavaScriptでは、グローバルスコープを汚染することを防ぐ代わりに、
唯一のアプリケーションのグローバルオブジェクトを作成することが主流です。

また、プロトタイプチェーンを用いることによって、
モジュールコンテナを作成することもできます。


var MYAPP = {};

MYAPP.name = "My First SPA";
MYAPP.data = "2015/06/25";

MYAPP.Update = function(){
  ...
};
MYAPP.Delete = function(){
  ...
};

// オブジェクトのコンテナ
MYAPP.modules = {}; 

MYAPP.modules.name = "My First SPA's first module";
MYAPP.modules.data = {
  1 : "test",
  2 : "test2",
  3 : "test3"
};

ベストプラクティス:名前空間

なお、名前空間を作成する際には、サードパーティプラグインの読み込みや
逆にプラグインとして公開して使ってもらう場合に、この唯一のグローバルオブジェクトが
競合してしまう可能性がないとも言い切れません。

したがって、以下のような定義をします。


if (typeof MYAPP === "undefined"){
  var MYAPP = {};
}

そして、これをもっと短くした以下の書き方もあります。


var MYAPP = MYAPP || {};
var MYAPP.module = MYAPP.module || {};

ベストプラクティス:『JavaScript パターン』における名前空間

JavaScriptが提供している名前空間のメソッドなどはないため、
おのおのが自分の最善と思える名前空間の定義をしており、
先人の方々の例が「JavaScript 名前空間」などとググるとたくさん
参考にできます。

『JavaScriptパターン』(O'Reilly)
のp91-92にあるnamespace()メソッドは非常に示唆に富んでいるので、ここで紹介
したいとおもいます。作者のStoyan Stefanovさんの説明は非常にわかりやすいので、ここでわからなかったらぜひ原著を見てみてください。

まず、いかのように名前空間を準備したいとします。


var MYAPP = {
  modules: {
    moduleA: {},
    moduleB: {}
  }
};

これを、たとえば以下のようなnamespace()メソッドで定義できるようにしましょう。


MYAPP.namespace('MYAPP.modules.moduleA');
MYAPP.namespace('MYAPP.modules.moduleB');

また、ここで紹介されているメソッドは、以下のような宣言方法にも対応しています。


// 戻り値をローカル変数に代入する
var module2 = MYAPP.namespace('MYAPP.modules.module2');
module2 === MYAPP.modules.module2; // true

// 先頭のMYAPPを省略
MYAPP.namespace('modules.module51');

// 長い名前空間
MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property');

では、実装の中身を見ていきましょう。


var MYAPP = MYAPP || {};

MYAPP.namespace = function(ns_string){
  var parts = ns_string.split('.'), // . で区切った配列
      parent = MYAPP, // グローバルオブジェクトのアプリ名
      i;

  // 先頭のグローバルを取り除く
  if ( parts[0] === "MYAPP"){
    parts = parts.slice(1); // 先頭を削除
  }

  for ( i = 0; i < parts.length; i += 1){
    // プロパティが存在しなければ作成する
    if ( typeof parent[parts[i]] === "undefined"){
      parent[parts[i]] = {}; // モジュールのオブジェクト生成
    }
    parent = parent[parts[i]];
  }
};

2-2. パブリック/プライベートを意識する

まず、次のコードを見てください。
User()関数は、userオブジェクトをプライベートメンバに
持っており、容易にIDやemailなどが変更されないようにしたい。

また、プライベートメンバにアクセスできるgetUser()メソッドを準備しました。

(注)これはアンチパターンです。真似しないでください。


function User(){
  // プライベートメンバ
  var userInfo = {
    userId: "user01",
    userEmail: "user01@gmail.com",
    userName: "David Brown"
  };

  // パブリック関数
  this.getUserInfo = function(){
    return userInfo;
  };
}

この設計には問題が有ります。
以下の呼び出し方を見てみてください。


var user = new User(),
    userInfo = user.getUserInfo();

userInfo.email = "evil@gmail.com";
userInfo.nickname = "Hello, world!";

console.dir(user.getUserInfo());

このようにすることによって、本来はプライベートメソッドである
userInfoの中身が書き換えられてしまいました。

このような振る舞いを避けるために、プライベートにしておきたいオブジェクトや配列に対する参照を渡さないようにするほかありません。

たとえばこの場合、userIdとuserNameだけを渡すメソッドを作成したりすれば良いでしょう。


function User(){
  // プライベートメンバ
  var userInfo = {
    userId: "user01",
    userEmail: "user01@gmail.com",
    userName: "David Brown"
  };

  // パブリック関数
  // * すべての秘匿情報を返さない(必要な物だけ返す)
  this.getUserId = function(){
    return userInfo["userId"];
  }
}

2-3. モジュールパターンについて知る

モジュールパターンは、

  • 名前空間
  • プライベートメンバとパブリックメンバの使い分け
  • 即時関数(3-4で説明)

などを組み合わせて作る、コード作成のためのパターンです。モジュールパターンを適用することによって、機能別にコードを分割することができます。さらに、各機能ごとにコードをまとめることによって、機能の拡張・削除・リファクタリングが容易になります。また、導入も簡単なので、結構広く使われていると思います。

以下の順で説明していきます。

  • モジュールパターン(1):名前空間の準備
  • モジュールパターン(2):モジュールの定義
  • モジュールパターン(3):モジュールを使う
  • モジュールパターン(4):モジュールの公開

モジュールパターン(1):名前空間の準備

まずは名前空間を準備します。


var MYAPP = MYAPP || {
  util: {
    math: {}
  },
  data: {
    int: {}
  }
};

モジュールパターン(2):モジュールの定義

次に、モジュールを定義します。

ここで、プライバシーが必要なときに、即時関数が活用できます。


MYAPP.util.math = (function(){
  // ここに処理を書いていきます。
}());

たとえば、ここでは加減乗除のメソッドを提供しているとしましょう。


MYAPP.util.math = (function(){
  return {
    add: function(x, y){
      return x + y;
    },
    minus: function(x, y){
      // xよりyが大きい時は「x-y」を、そうでない場合は「y-x」を返します
      return x > y ? x - y : y - x;
    },
    multiply: function(x, y){
      return x * y;
    },
    divide: function(x, y){
      // yが0のとき「NaN(Not a number)」を返します
      return y !== 0 ? x / y : "NaN";
    }
  };
}());

このように、即時関数が提供するプライベートスコープを使うことで、
プライベートのプロパティとメソッドを必要に応じて宣言することができます。

モジュールパターン(3):モジュールを使う

最後に、この即時関数が返すオブジェクトを指定します。
(1),(2)のコードをまとめてかきます。


// (1)名前空間の準備
var MYAPP = MYAPP || {
  util: {
    math: {}
  },
  data: {
    int: {}
  }
};

// (2)モジュールの定義
MYAPP.util.math = (function(){


  return {
    add: function(x, y){
      return x + y;
    },
    minus: function(x, y){
      // xよりyが大きい時は「x-y」を、そうでない場合は「y-x」を返します
      return x > y ? x - y : y - x;
    },
    multiply: function(x, y){
      return x * y;
    },
    divide: function(x, y){
      // yが0のとき「NaN(Not a number)」を返します
      return y !== 0 ? x / y : "NaN";
    }
  };
}());

// (3)モジュールを使う
MYAPP.util.math.add(4,5);
MYAPP.util.math.minus(4,5);
MYAPP.util.math.multiply(4,5);
MYAPP.util.math.divide(4,5);

モジュールパターン(4):モジュールの公開

それでは、今まで書いたコードを

  • まずadd,minusなどのメソッドをプライベートにする
  • 続いて、パブリックAPIを開示する(返すメソッドを指定する)

ことで、簡便なモジュールパターンの例の紹介を締めくくりたいと思います。


// (1)名前空間の準備
var MYAPP = MYAPP || {
  util: {
    math: {}
  },
  data: {
    int: {}
  }
};

// (2)モジュールの定義
MYAPP.util.math = (function(){

  // これらメソッドをプライベートメンバとして定義
  add = function(x, y){
    return x + y;
  },
  minus = function(x, y){
    // xよりyが大きい時は「x-y」を、そうでない場合は「y-x」を返します
    return x > y ? x - y : y - x;
  },
  multiply = function(x, y){
    return x * y;
  },
  divide = function(x, y){
    // yが0のとき「NaN(Not a number)」を返します
    return y !== 0 ? x / y : "NaN";
  }

  // public API
  return {
    add: add,
    minus: minus,
    multiply: multiply,
    divide: divide
  };
}());

// (3)モジュールを使う
MYAPP.util.math.add(4,5);
MYAPP.util.math.minus(4,5);
MYAPP.util.math.multiply(4,5);
MYAPP.util.math.divide(4,5);

以上でモジュールパターンの説明を終わりたいと思います。

3. 関数

Index

  • 3-1. 関数のイロハ
  • 3-2. コールバック
  • 3-3. クロージャー
  • 3-4. 即時関数
  • 3-5. カリー化

3-1. 関数のイロハ

JavaScriptでは、関数もオブジェクトです。

「何をいまさら」と思った方は、章を読み進めるかほかの記事をぜひ読んでください(笑)
「え、そうなの」と思った方は、ここで、JavaScriptの関数の基礎について復習しておきましょう。

JavaScriptの関数はスコープを提供します。また、宣言の仕方にもいくつか種類があります。

名前付き関数式


var myfunc = function myfunc(){
  //...
};

無名関数

名前付き関数の名前が省略可能であるため、以下のように書くこともできます。


var myfunc = function(){
  //...
};

基本的に、名前付き関数にも無名関数にも違いはありません。
唯一の違いは、 「この関数オブジェクトのnameプロパティが空文字列になる」 点です。

関数宣言

無名関数と関数宣言との違いは、var myfunc=によって変数名に代入しているかどうかの違いです。
また、セミコロンは関数宣言では必要ありませんが、無名関数は必要です。


function myfunc(){
  // ...
}

3-2. コールバック

JavaScriptでは、関数もオブジェクトです。
オブジェクトということは、関数の引数として渡すことができます。
ということはつまり、関数の引数に関数を指定することができます。
これが「コールバック」の仕組みです。

基本的な使い方は簡単で、以下のように引数に関数を指定するだけです。なお、引数に入れる関数にかっこをつけないように気を付けてください。括弧があると関数は実行されてしまいます。実行されるタイミングは、呼び出し元の関数内部でコントロールするべきです。


function sayHello(callback){
  //...
  callback();
  //...
}

function getName(){
  //...
}

sayHello(getName);

参考リンク

今回は詳しく掘り下げていませんが、このコールバックの仕組みを多用してしまうと、いわゆる「コールバック地獄」に陥ってしまいます。下記のKDKTNさんのリンクは非常にわかりやすく、ストーリー調になっていて読みやすくもあるのでおすすめです。

3-3. クロージャー

クロージャーはJavaScriptの難関の一つと思われがちですが、JavaScriptをマスターするためには避けては通れない場所です。個人的には、スコープの概念さえきちんと理解していれば、そのスコープを管理するための非常に便利なツールとして目に映る日が来るはずです(きっとw)。なので、もしスコープとか名前空間がいまいち。。。って人は、先にここらへんを勉強するといいかもです。

ただ、説明したり、言語がしたりするのは難しいかもしれませんが、クロージャーを作って、使うのは全然難しくはありません。ぜひ、苦手意識や先入観を一旦忘れて学んでみてください!


function User(){
    // private member
    var name = "Jason";

    // public member
    this.getName = function(){
        return name;
    };
}

var user01 = new User();

// 直接プライベートメンバにはアクセス出来ない
console.log(user01.name); // -> undefined

// パブリックメソッド経由でプライベートメンバにアクセスする
console.log(user01.getName()); // -> "Jason"

ここで重要な事は、
「コード内のプライベートメンバ(ここではname)には、外側から参照できない」
そして
「コード内のプライベートメンバ(ここではname)を参照するには、パブリックメソッド(ここではgetName)を経由するしかない」

ということです。これによって、プライベートな変数として
定義することができたり、変数事のスコープを自由自在に操れるようになります。

クロージャーをいつ使うのか

artgearさんが私が今までクロージャを理解できなかった理由というブログで書いていますが、最初は自分も全く同じ気持でした。

「どういう時にクロージャを使えばいいのか」
が分かりませんでした。言いかえると友人がなんて言って悩んでいる時に
「そう言う時はクロージャを使うといいよ」
と言ってあげればいいのか。

例えばプログラムの勉強を始めた友人が
「これと同じ処理もう何回も書いてるんだよ。コピペばっかりしてる気がする」
と言って悩んでいたら
「そこを関数にすればいいんじゃない?」
って教えてあげますよね。

これと同じように友人が
「○○○○○○なんだよ、うまい方法ない?」
と質問して来た時に
「そういう時はクロージャを使えばいいよ」
と答えてあげるべき○○○○○○は何なのか。

クロージャーを使えるタイミングはたくさんあります(自分がまだまだ知らないタイミングもきっとたくさんあると思います)が、とりあえず、ひとつ覚えておきましょう。それは、

「メソッドを 1 つだけ持つオブジェクトを使いたくなるような状況ならば、どんな時でもクロージャを使う事ができる」 という時です。

詳しくは、MDNに書いてあります。本記事では、テキストサイズを調整する際に、クロージャーを用いています。


function makeSizer(size){
    return function(){
        document.body.style.fontSize = size + 'px';
    }
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

(詳細はMDNによるJSFiddleより)

参考リンク

説明がとてもわかりやすい、秀逸な記事ふたつを紹介します。

3-4. 即時関数

即時関数は、使い方と理解は簡単です。「関数を作成した直後に実行される関数」です。以上。

。。。

ですが問題は、その「使いどころ」です。即時関数自体は知っていても、「いつ使うのか」、「なんのために使うのか」 を知らなければその真価を発揮させることはできません。

即時関数は、 「一回だけしか必要ない処理」 をするために使われます。具体的には

  • ページが読み込まれたときの初期化処理
  • オブジェクトの生成
  • DOM要素へのイベントハンドラの設定

などです。これらの処理は一回しか実行されないので、再利用可能な名前付き関数を作る理由は全くありません。むしろそうしてしまうと、グローバルスコープを汚染してしまいます。そう、即時関数とは、 「一回だけ必要な処理を、グローバルスコープを汚染せずに実行する方法」 なのです。

具体的には、ブックマークレットなどでよく使われます。仕事を効率化させるような便利なブックマークレットが多いので、以下のリンクを参考にしてください。

以下が代表的な即時関数です。


(function() {
  //...
}());

ちなみに、即時関数は以下の書き方もできますが、JSLintでは上記のやり方が推奨されています。


(function(){
  //...
})();

基本的な使い方

即時関数に引数を渡す

以下のように即時関数に引数を渡すことができます。


(function(firstname, lastname){
  console.log("Hello, " + firstname + lastname + "!");
}("David", "Brown"));

『JavaScriptパターン』の引用ですが、一般には、即時関数の引数にはグローバルオブジェクトを渡します。なぜならば、そうるすことによってwindowを使わずに関数の内部からアクセスできるからとのこと。


(function (global) {

  // globalを介してグローバルオブジェクトにアクセス

}(this));

参考リンク

3-5. カリー化

JavaScriptには、「部分適用」や「カリー化」といった考え方が存在します。
ココらへんは定義がわかりづらかったり、筆者自身も完璧に理解できているとは言いがたい部分も
ありますので、ざっくりと全体像でもお伝えできればと思います。

関数の適用、部分適用、カリー化の順番でお話していきます。

関数の適用とは

そもそも、関数プログラミング言語においては、関数は「呼び出されるもの」というより、「適用されるもの」と捉えるほうが正確です。JavaScriptにはFunction.prototype.apply()メソッドがあります。

つまり、関数の呼び出しはapply()メソッドの糖衣構文(シンタックスシュガー)である、ということです。実際にapply()を使ってみます。

一つ目の引数には、関数の内部でthisに束縛されるオブジェクト、
二つ目の引数には、引数の配列で、関数の内部で使用したい値の配列を渡します。


var multiply = function (x, y){
    return x * y;
};

multiply.apply(null, [4, 5]); // -> 20


一つ目の引数が異なる場合を紹介します。
名前空間myAppを用意し、その中のmultiply()を適用します。


var myApp = {
    add: function(x, y){
        return x + y;
    },

    multiply: function(x, y){
        return x * y;
    }
};

// 普通に呼び出す
myApp.add(4, 5);
myApp.multiply(4, 5);

// apply()で適用する
myApp.add.apply(myApp, [4, 5]);
myApp.multiply.apply(myApp, [4, 5]);

ここまでで、 「関数は実は呼び出すものではなく適用されるもの」 ということがわかったでしょうか?次のフェーズに進みます。

部分適用とは

関数の呼び出しには、実際には 「関数に引数の配列を適用する」 ことがわかりました。これがわかれば次は簡単です。「部分適用」とは、単純に、 「引数を全てではなく一部だけ渡す」 ことを指します。

改めて、2つの引数をとるmultiply()メソッドについて考えてみます。以下の例では、完全適用、部分適用の二パターンを紹介します。


// 完全適用
var multiply = function(x, y){
    return x * y;
};

multiply.apply(null, [4, 5]);

// 部分適用
var partialMultiply = function(y){
    return multiply(4, y);
}

partialMultiply(5); // -> 20

部分適用では、第一引数を固定しています。
部分適用によって、partialMultiply()という新しい別の関数が得られました。
そしてその関数は別の引数で呼ぶことができます。

カリー化とは

カリー化された関数に引数を渡すだけで、別の関数を簡単に定義できる。それがカリー化の醍醐味です。

部分適用とカリー化の違いは、

  • カリー化: 関数を引数1つずつに分割して適用させる
  • 部分適用: 一部の引数を固定して新しい関数を作り出すこと

です。もっというと、
「関数に部分適用を理解させ処理させること」 です。

先程の例では、以下のようにmultiply()メソッドの第一引数を「4」に固定していました。これは部分適用です。しかし、これでは汎用性が足りません。


// 部分適用
var partialMultiply = function(y){
    return multiply(4, y);
}

partialMultiply(5); // -> 20

これをカリー化で表現すると、以下のようになります。


function multiply(x, y){
    if(typeof y === "undefined"){ //部分適用
        return function(y){
            return x * y;
        };
    }
    //完全適用
    return x * y;
}

multiply(4)(5);

カリー化の特徴は、関数を呼び出す際に、引数をひとつずつ渡すようになっている
点です。これによって、「複数の引数を必要とする関数を、1つの引数の関数の定義だけで
同じ機能をもつ関数」に書き換えることが出来ました。

次に、引数が2つ以上の場合でも汎用的に使えるよう、multiply()
書き換えてみましょう。


function createCurry(func){
    var slice = Array.prototype.slice,
        stored_args = slice.call(arguments, 1);

    return function(){
        var new_args = slice.call(arguments);
        args = stored_args.concat(new_args);
        return func.apply(null, args);  
    };
}

出典: 『JavaScriptパターン』

このカリー化を使って、テストをしてみましょう。


function multiply(x, y){
    return x * y;
}

// pattern A
var newMultiply = createCurry(multiply, 5);
newMultiply(4);

// pattern B
createCurry(multiply, 5)(4);

また、変換関数createCurry()のパラメータは複数でも使用可能です。
カリー化も複数段階で使うことができます。


function calcVolume(x, y, z){
    return x * y * z;
}

// 複数の引数
createCurry(calcVolume, 5)(2, 3); // 30
createCurry(calcVolume, 5, 2)(3); // 30

// 2段階のカリー化
var calcVolumeWithWidthFive = createCurry(calcVolume, 5);
calcVolumeWithWidthFive( 2, 3 );
var calcVolumeWithWidthFiveAndHeightTwo = createCurry(calcVolumeWithWidthFive, 2);
calcVolumeWithWidthFiveAndHeightTwo( 3 );

参考リンク

特に一つ目の、KDKTNさんの食べられないほうのカリー化入門は非常にわかりやすいので、一読をオススメいたします。

4. DOM

Index

  • 4-1. DOMで避けるべきこと
  • 4-2. 高速化・最適化のためのTips
  • 4-3. イベント処理

(注)この章はjQueryも含みます

4-1. DOMで避けるべきこと

  • DOMアクセスのループは避ける
    • 一度変数に格納してから
  • 可能ならばセレクタAPIを使う
  • DOMはid属性で活用
  • DOM生成の回数はなるべく減らす

DOMアクセスのループは避ける

アンチパターン

for (var i = 0; i < 1000; i += 1){
    $('#button01').innerHTML += i + ", ";
}

ベストプラクティス

var i, content = "";
for ( i = 0; i < 1000; i += 1){
    content += 9 + ",";
}

// 最後にDOMにアクセス
$('#button01').innerHTML += content;

可能ならばセレクタAPIを使う

ネイティブのquerySelector()querySelectorAll()
自分でDOMメソッドを使って選択を実行するより速い。


document.querySelector("ui .column");
document.querySelectorAll("#main .col-12");

DOMはid属性で指定

セレクタAPIを使わず自分で選択するときには
クラス名よりid名のほうがパフォーマンスが高い。

これについては簡単なので詳細省略。

DOM生成の回数はなるべく減らす

アンチパターン

$('#button01').css(...);
$('#button01').animate(...);

このように同じ要素に対してDOM操作を繰り返す場合
一旦変数に格納して行う。

ベストプラクティス

var btn = $('#button01');
btn.css(...);
btn.animate(...);

4-2. 高速化・最適化のためのTips

Stylesheet > JavaScriptの順で読み込む

  • scriptタグを読み込んでいる間ページはブロックされてしまう
  • 下部で読み込むことによって、ページ内要素がレンダリングされてからscriptが読み込まれるので、ユーザは真っ白ページで数秒待たされるということがない
  • 特に画面描画に関係ないJSは