Clean Javascript

Preview:

DESCRIPTION

SaCSS vol.29( http://atnd.org/events/20768 ) での発表資料です。

Citation preview

Clean JavaScript

佐藤 竜之介(Ryunosuke SATO)Sapporo.js

SaCSS vol.29 - 2011.11.26

http://www.flickr.com/photos/hoshikowolrd/5151171470/

提供

Sapporo.js

Community for people who like JavaScript.

Sapporo.js

http://sapporojs.org

Sapporo.js

Now learning

I’m a programmer

佐藤竜之介

@tricknotes

よろしくお願いします

Clean JavaScript

佐藤 竜之介(Ryunosuke SATO)Sapporo.js

SaCSS vol.29 - 2011.11.26

http://www.flickr.com/photos/hoshikowolrd/5151171470/

今日のテーマ

良いコード

とは?

コードだけが良いのではなく、良い振る舞いを行えるのが良いコードである

よく出てくる問題に対応するための法則性

良い習慣

どういうときにどうするのが良いコードにつながっていくか

対象者

普段 JavaScript を書いているひと良いコードを書きたいと思っているひと良いコードに興味があるひと

普段 JavaScript を書いているひと良いコードを書きたいと思っているひと良いコードに興味があるひと

about JavaScript

Works in everywhere!

interesting!

http://www.flickr.com/photos/peco-sunnyday/2752403413/

I like

JavaScript is most popular!

https://github.com/languages

but...

difficult

http://www.flickr.com/photos/maynard/6105929170/

Now training...

??How to face the difficulty

良い習慣

装備を整えて、挑む準備が必要

Oops... :(

http://www.flickr.com/photos/cholovak/4500742434/

some ideas

DOMwith

point

HTML と JavaScript をいかに分けるか

画面 ロジック

HTML JavaScript

画面 ロジック

HTML JavaScript

here!

practice

problem

どこで JavaScript のイベントが設定されているのかわかりづらい

イベントは外側から - Attach events from outer -

solution

HTML の 属性として設定するのをやめてJavaScript からのみイベントを設定する

イベントは外側から - Attach events from outer -

<a onclick=”showMessage()”>Click me</a>

code smell

イベントは外側から - Attach events from outer -

<a id=”showMessage”>Click me</a>

var a = document.getElementById(‘showMessage’);a.addEventListener(‘click’, showMessage);

イベントは外側から - Attach events from outer -

improvement

HTML の idやclassに依存したコードになっていて、画面を修正するたびに動かなくなる部分がある

セレクタからの分離 - Separate from Selector -

problem

HTML の id や class に極力依存しない設計にする

セレクタからの分離 - Separate from Selector -

solution

function showPhoto(photoId) { var photo = document.getElementById(‘photo-’ + photoId); // do ...}

セレクタからの分離 - Separate from Selector -

function showPhoto(photoId) { var photo = document.getElementById(‘photo-’ + photoId); // do ...}

セレクタからの分離 - Separate from Selector -

code smell

var showPhoto = function(element) { // do ...}

var photo = document.getElementById(‘photo-12’);showPhoto(photo);

セレクタからの分離 - Separate from Selector -

improvement

モジュール化 - modulized functions -

ファイルの数が多く、探したい関数がなかなか見つけられない

problem

ファイル名を表すモジュールにまとめる

モジュール化 - modulized functions -

solution

// users.jsvar showUserName = function() { // do somethig}

var showUserAge = function(){ // do somethig}

var validateUserForm = function() { // do somethig}

モジュール化 - modulized functions -

code smell

// users.jsvar showUserName = function() { // do somethig}

var showUserAge = function(){ // do somethig}

var validateUserForm = function() { // do somethig}

global

モジュール化 - modulized functions -

// users.jsvar users = {};

users.showName = function () { // do somethig}

users.showAge = function (){ // do somethig}

users.validateForm = function () { // do somethig}

global

モジュール化 - modulized functions -

// users.js(function(global) { global.users = {};

users.showName = function () { // do somethig }

// ...)(this);

global

モジュール化 - modulized functions -

improvement

JavaScript のイベントで、DOM の元々の動作を上書きしたい

振る舞いの上書き - overwrite behavior -

problem

DOM が元々持っている属性を利用する足りなければ、DOM に属性を追加する

振る舞いの上書き - overwrite behavior -

solution

振る舞いの上書き - overwrite behavior -

// using jQuery

$(‘a#showDialog’).click(function() { $.ajax(‘/about’, { success: function(html) { // do something... } });});

code smell

// using jQuery

$(‘a#showDialog’).click(function() { $.ajax($(this).attr(‘href’), { success: function(html) { // do something... } });});

振る舞いの上書き - overwrite behavior -

improvement

コールバック関数を深く設定してしまっていて、一つの処理が大きくなってしまっている。同じ変数があちこちにちらばっていて、

処理をわけるのが困難である

浅いスコープ - shallow scope -

problem

変数が、2段以上の function のスコープをまたがないようにする

→引数や、 this で渡すようにする

浅いスコープ - shallow scope -

solution

// using jQuery

var $elements = $(‘a#remote’);$elements.click(function() { var url = $(this).attr(‘href’); $.ajax(url, { success: function(html) { var text = $(html).text(); $element.text(text); } }); return false;});

浅いスコープ - shallow scope -

// using jQuery

var $elements = $(‘a#remote’);$elements.click(function() { var url = $(this).attr(‘href’); $.ajax(url, { success: function(html) { var text = $(html).text(); $element.text(text); } }); return false;});

deep

浅いスコープ - shallow scope -

code smell

// using jQuery

var $elements = $(‘a#remote’);$elements.click(function() { var url = $(this).attr(‘href’); $.ajax(url, { success: function(html) { var text = $(html).text(); $(this).text(text); }, context: this }); return false;});

浅いスコープ - shallow scope -

improvement

DOMをモデルオブジェクトへ - DOM to model -

DOM にいくつかのフラグを設定したり、まとめて操作を行いたい部分がある

problem

DOM をラップするオブジェクトを作成し、一連の処理をメソッドにする

DOMをモデルオブジェクトへ - DOM to model -

solution

var element = document.getElementById(‘message’);element.addEventListener(‘click’, function() { // this == element if (this.getAttribute(‘data-is-checked’)) { this.setAttribute(‘data-is-checked’, true); this.innerText = ‘clicked!’; }});

DOMをモデルオブジェクトへ - DOM to model -

code smell

var domToModel = function(element, Model) { var method, name, object, parent, proto; model = Object.create(element); proto = Model.prototype; for (name in proto) { method = proto[name]; model[name] = method; } Model.apply(model); return model;};

var CustomElement = function() {};CustomElement.prototype.showText = function() { if (!this.getAttribute(‘data-is-checked’)) { this.setAttribute(‘data-is-checked’, true); this.innerText = ‘clicked!’; }};

var element = document.getElementById(‘message’);var model = domToModel(element, CustomElement);model.addEventListener(‘click’, function() { model.showText();});

DOMをモデルオブジェクトへ - DOM to model -

var element = document.getElementById(‘message’);var model = domToModel(element, CustomElement);model.addEventListener(‘click’, function() { model.showText();});

DOMをモデルオブジェクトへ - DOM to model -

var domToModel = function(element, Model) { var method, name, object, parent, proto; model = Object.create(element); proto = Model.prototype; for (name in proto) { method = proto[name]; model[name] = method; } Model.apply(model); return model;};

var CustomElement = function() {};CustomElement.prototype.showText = function() { if (!this.getAttribute(‘data-is-checked’)) { this.setAttribute(‘data-is-checked’, true); this.innerText = ‘clicked!’; }};

var element = document.getElementById(‘message’);var model = domToModel(element, CustomElement);model.addEventListener(‘click’, function() { model.showText();});

DOMをモデルオブジェクトへ - DOM to model -

improvement

var domToModel = function(element, Model) { var method, name, object, parent, proto; model = Object.create(element); proto = Model.prototype; for (name in proto) { method = proto[name]; model[name] = method; } Model.apply(model); return model;};

var CustomElement = function() {};CustomElement.prototype.showText = function() { if (!this.getAttribute(‘data-is-checked’)) { this.setAttribute(‘data-is-checked’, true); this.innerText = ‘clicked!’; }};

画面とロジックの分離 - separate logic with view -

ロジックと画面への情報更新が強く結びついていて、どちらか片方だけの変更が困難である

problem

ロジックと画面への情報更新を分割し、イベントを通じて変更を通知する

solution画面とロジックの分離 - separate logic with view -

function createTimer() { var timerId; var startTimer = function(millisec) { timerId = setTimeout(function() { $(‘.timeout’).text(‘finished’); }, millisec); } var stopTimer = function() { clearTimeout(timerId); } return { startTimer: startTimer, stopTimer: stopTimer }}

画面とロジックの分離 - separate logic with view -

function createTimer() { var timerId; var startTimer = function(millisec) { timerId = setTimeout(function() { $(‘.timeout’).text(‘finished’); }, millisec); } var stopTimer = function() { clearTimeout(timerId); } return { startTimer: startTimer, stopTimer: stopTimer }}

画面とロジックの分離 - separate logic with view -

view

code smell

function Timer(millisec) { this. millisec = millisec; this.callbacks = []; this.timerId = null;}

Timer.prototype.afterFinish = function(callback) { return this.callbacks.push(callback);};

Timer.prototype.start = function() { var callbacks = this.callbacks; this.timerId = setTimeout(function() { var callback, i, length; for (i = 0, length = callbacks.length; i < length; i++) { callback = callbacks[i]; callback(); } }, this. millisec);};

Timer.prototype.stop = function() { clearTimeout(this.timerId);}

var timer = new Timer(1000);timer.afterFinish(function() { $('.timer .message').text('Finished!!');});timer.start();

画面とロジックの分離 - separate logic with view -

var timer = new Timer(1000);timer.afterFinish(function() { $('.timer .message').text('Finished!!');});timer.start();

model

view

function Timer(millisec) { this. millisec = millisec; this.callbacks = []; this.timerId = null;}

Timer.prototype.afterFinish = function(callback) { return this.callbacks.push(callback);};

Timer.prototype.start = function() { var callbacks = this.callbacks; this.timerId = setTimeout(function() { var callback, i, length; for (i = 0, length = callbacks.length; i < length; i++) { callback = callbacks[i]; callback(); } }, this. millisec);};

Timer.prototype.stop = function() { clearTimeout(this.timerId);}

画面とロジックの分離 - separate logic with view -

improvement

A tiny fraction

独立しているわけではない相互に関連している

others...

Pure JavaScript

Application

http://www.sproutcore.com/

良いコードを書くためには

http://www.flickr.com/photos/bonguri/4610536789/

interest

reading

Good text

writing

http://www.flickr.com/photos/tsukacyi/4848685561/

Shall we learn about

Clean JavaScript?

Recommended