#akai

floatcloud

JavaScript Design Patterns

Writing code that’s expressive, encapsulated & structured

1. Module Pattern

Interchangeable single-parts of a larger system that can be easily re-used.

Stepping stone: IIFE

Immediately invoked function expressions (or self-executing anonymous functions)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function() {
    // code to be immediately invoked
}()); // Crockford recommends this way

(function() {
    // code to be immediately invoked
})(); // This is just as valid

(function(window, document, undefined) {
    //code to be immediately invoked
})(this, this.document);

(function(global, undefined) {
    //code to be immediately invoked
})(this);

Simulate privacy

The typical module pattern is where immediately invoked function expressions (IIFEs) use execution context to create ‘privacy’. Here, objects are returned instead of functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var basketModule = (function() {
    var basket = []; //private
    return { //exposed to public
        addItem: function(values) {
            basket.push(values);
        },
        getItemCount: function() {
            return basket.length;
        },
        getTotal: function() {
            var q = this.getItemCount(), p = 0;
            while (q--) {
                p += basket[q].price;
            }
            return p;
        }
    }
}());
  • In the pattern, variables declared are only available inside the module.
  • Variables de ned within the returning object are available to everyone
  • This allows us to simulate privacy

Sample usage

Inside the module, you’ll notice we return an object. This gets automatically assigned to basketModule so that you can interact with it as follows:

1
2
3
4
5
6
7
8
9
10
11
12
//basketModule is an object with properties which can also be methodsbas
basketModule.addItem({item: 'bread',price: 0.5});
basketModule.addItem({item: 'butter',price: 0.3});

console.log(basketModule.getItemCount());
console.log(basketModule.getTotal());

//however, the following will not work:
// (undefined as not inside the returned object)
console.log(basketModule.basket);
//(only exists within the module scope)
console.log(basket);

Module Pattern: jQuery

In the following example, a library function is defined which declares a new library and automatically binds up the init function to document.ready when new libraries (ie. modules) are created.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function library(module) {
    $(function() {
        if (module.init) {
            module.init();
        }
    });
    return module;
}

var myLibrary = library(function() {
    return {
        init: function() {
        /*implementation*/
        }
    };
}());

AMD + jQuery

A very simple AMD module using jQuery and the color plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
define(["jquery", "jquery.color", "jquery.bounce"], function($) {
    //the jquery.color and jquery.bounce plugins have been loaded
    // not exposed to other modules
    $(function() {
        $('#container')
            .animate({'backgroundColor': '#ccc'}, 500)
            .bounce();
    });
    // exposed to other modules
    return function () {
        // your modules functionality
    };
});

AMD/UMD jQuery Plugin

Recommended by Require.js author James Burke

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function(root, factory) {
    if (typeof exports === 'object') {
        // Node/CommonJS
        factory(require('jquery'));
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Use a named plugin in case this
        // file is loaded outside an AMD loader,
        // but an AMD loader lives on the page.
        define('myPlugin', ['jquery'], factory);
    } else {
        // Browser globals
        factory(root.jQuery);
    }
}(this, function($) {
    $.fn.myPlugin = function() {
    };
}));

CommonJS Modules

They basically contain two parts: an exports object that contains the objects a module wishes to expose and a require function that modules can use to import the exports of other modules

1
2
3
4
5
6
7
8
9
10
11
/* here we achieve compatibility with AMD and CommonJS
using some boilerplate around the CommonJS module format*/
(function(define){
    define(function(require,exports){
        /*module contents*/
        var dep1 = require("foo");
        var dep2 = require("bar");
         exports.hello = function(){...};
         exports.world = function(){...};
    });
})(typeof define == "function" ? define : function(factory) { factory(require, exports) });

2. Facade Pattern

Convenient, high-level interfaces to larger bodies of code that hide underlying complexity

Facade Implementation

A higher-level facade is provided to our underlying module, without directly exposing methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var module = (function() {
    var _private = {
        i: 5,
        get: function() {
            console.log('current value:' + this.i);
        },
        set: function(val) {
            this.i = val;
        },
        run: function() {
            console.log('running');
        },
        jump: function() {
            console.log('jumping');
        }};
    return {
        facade: function(args) {
            _private.set(args.val);
            _private.get();
            if (args.run) {
                _private.run();
            }
        }}
}());
module.facade({run: true,val: 10}); //outputs current value: 10, running

3. Mediator Pattern

Encapsulates how disparate modules interact with each other by acting as an intermediary

Mediator Implementation

One possible implementation, exposing publish and subscribe capabilities.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var mediator = (function() {
    var subscribe = function(channel, fn) {
        if (!mediator.channels[channel]) mediator.channels[channel] = [];
        mediator.channels[channel].push({ context: this, callback: fn });
        return this;
    },

    publish = function(channel) {
        if (!mediator.channels[channel]) return false;
        var args = Array.prototype.slice.call(arguments, 1);
        for (var i = 0, l = mediator.channels[channel].length; i < l; i++) {
            var subscription = mediator.channels[channel][i];
            subscription.callback.apply(subscription.context, args);
        }
        return this;
    };

    return {
        channels: {},
        publish: publish,
        subscribe: subscribe,
        installTo: function(obj) {
            obj.subscribe = subscribe;
            obj.publish = publish;
        }
    };
}());

Example

Usage of the implementation from the last slide.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Pub/sub on a centralized mediator

mediator.name = "tim";
mediator.subscribe('nameChange', function(arg) {
    console.log(this.name);
    this.name = arg;
    console.log(this.name);
});

mediator.publish('nameChange', 'david'); //tim, david

//Pub/sub via third party mediator

var obj = {name: 'sam'};
mediator.installTo(obj);
obj.subscribe('nameChange', function(arg) {
    console.log(this.name);
    this.name = arg;
    console.log(this.name);
});

obj.publish('nameChange', 'john'); //sam, john

Form http://www.slideshare.net/AddyOsmani/futureproofing-your-javascript-apps-compact-edition

Installing Rails on Mac OS X

  1. Run software update

  2. Form Mac App Store download & install XCode

  3. Install Homebrew

    1
    
    /usr/bin/ruby -e "$(curl -fsSL https://raw.github.com/gist/323731)"
    
  4. Install Git

    1
    
    brew install git
    
  5. Install rbenv & ruby-build

    1
    2
    
    brew install rbenv
    brew install ruby-build
    
  6. Install Ruby

    1
    2
    
    rbenv install 1.9.2-p290
    rbenv global 1.9.2-p290
    
  7. Install MySQL

    1
    
    brew install mysql
    
  8. Install Rails

    1
    
    gem install rails --no-rdoc --no-ri
    
  9. Enjoy!

用 Rbenv 来管理 Ruby 的安装

如果你 Ruby 开发者,应该知道用 RVM 来安装/管理 Ruby 版本,同时也能用它的 gemset 功能来管理各个工程的 gems。

最近,37SignalsSam Stephenson 也创建了一个类似的软件,叫做 rbenv。不过它功能非常简单,简单到只是用来管理 Ruby 版本,连安装的功能也没提供。

注意:rbenv 和 RVM 是不兼容的,所以安装 rbenv 之前要先把 RVM 卸载了。

1 安装

1.1 安装 rbenv

rbenv 的源代码托管在 GitHub 下,安装只需要简单的 clone 下来就可以来。

把 rbenv clone 到 ~/.rbenv 下:

1
git clone https://github.com/sstephenson/rbenv ~/.rbenv

然后让 shell 加载 rbenv:

1
2
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

如果用 Zsh,就用 ~/.zshrc 替换 ~/.bash_profile

重启 shell, 或者运行 exec $SHELL,就可以开始用 rbenv 了。

1.2 安装 ruby-build

由于 rbenv 本身并不能用来安装 Ruby,为了方便我们还需要用到 ruby-build,它的安装也非常简单:

1
2
3
git clone git://github.com/sstephenson/ruby-build.git
cd ruby-build
./install.sh

1.3 安装 Ruby

安装好 ruby-build 后,就可以用简单的一条命令来安装 Ruby:

1
ruby-build 1.9.3-p0 ~/.rbenv/versions/1.9.3-p0

注意:Ruby 需要安装在 ~/.rbenv/versions/

同时 ruby-build 还提供了一个 rbenv install 命令给 rbenv,所以上面的命令可以变成:

1
rbenv install 1.9.3-p0

2 rbenv 的常用命令

rbenv 提供了很多命令,这里列几个常用的:

2.1 rbenv global

来用设置 Ruby 的全局版本。

上面安装好 Ruby 后,还需要运行一下这条命令:

1
rbenv global 1.9.3-p0

这样默认就会用 1.9.3-p0 了。但如果当前目录下有 .rbenv-version 文件,就会用文件里显示的版本。

2.2 rbenv local

1
rbenv local 1.9.2-p290

会在当前目录下生成 .rbenv-version 文件,此文件会覆盖 rbenv global 设定。

如果想取消的话,可以这样:

1
rbenv local --unset

2.3 rbenv versions

显示所有版本,前面加 * 的为当前激活的版本。

1
rbenv versions

2.4 rbenv rehash

每当安装新的 Ruby 版本,或 gem 都要运行一下,不然有可能会出现新安装的不起作用的现象:

1
rbenv rehash

2.5 其它

当然还有其它命令,具体可以用 rbenv help 查看。

1
rbenv help

3 最后

虽然 rbenv 提供的功能非常少,但对我来说者正是我需要的,less is more,其它的功能我根本不需要。

喜欢用 RVM gemset 的人,可以安装 rbenv-gemset 插件来实现同样的功能。但还是用 Bundler 来管理应用依赖吧。

更新

如果你有安装 Homebrew 的话,可以用以下命令来安装 rbenv 和 ruby-build:

1
2
3
brew update
brew install rbenv
brew install ruby-build