Chapter 2. Essentials
■Writing Maintainable Code
- Time to relearn and understand the problem
- Time to understand the code that is supposed to solve the problem
どちらにしても書かれたコードを理解する時間を減らすことはとても重要で、レガシーコードをメンテしてるより新しいものをいじってたほうが楽しいでしょ。
その場しのぎで書かれたコードは後で問題が起きることがよくあるので、メンテナンス性に優れたコードを書くことが成功するアプリケーションにつながるのです。
メンテナンス性に優れたコードとは
- Is readable
- Is consistent
- Is predictable
- Looks as if it was written by the same person
- Is documented
■Minimizing Globals
The Problem with Globals
グローバルな名前空間は衝突することがあります。Webページでは開発者以外によって書かれたコードが動いてることもよくあるためグローバル空間は意識する必要があります。
- サードパーティなJavaScriptライブラリ
- 広告スクリプト
- アクセス解析スクリプト
- Wigetsやバッチやボタンなど
Side Effects When Forgetting var
varをつけて作られたものはvariableなのでdeleteできません。逆にvarをつけずに作られたものはvariableではなくグローバルのプロパティであるのでdeleteすることが可能です。
// define three globals
var global_var = 1;
global_novar = 2; // antipattern
(function () {
global_fromfunc = 3; // antipattern
}());
// attempt to delete
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true
// test the deletion
typeof global_var; // "number"
typeof global_novar; // "undefined"
typeof global_fromfunc; // "undefined"
variableはdeleteできないけど、プロパティはdeleteできるAccess to the Global Object
Webページだとグローバルオブジェクトはwindowだが、他の実行環境だと異なる場合もある。そのため、グローバルオブジェクトを取得しようした場合には次のように書ける。
var global = (function () {
return this;
}());
このときのthisはグローバルオブジェクトを示している。JavaScriptが実行される環境に左右されないコードを書くためには必要となるかもしれない。Single var Pattern
一つのvarだけで複数の変数を宣言するパターン
function func() {
var a = 1,
b = 2,
sum = a + b,
myobject = {},
i,
j;
// function body...
}
利点としては- 一カ所に変数をまとめて置ける
- 宣言される前に変数が参照されてエラーになるのを防げる
- 宣言された変数を覚えやすい、見やすい
- コードを小さくするのに役立つ
下のように、var a,bとしたときJavaScriptでは左から順番に実行していくのでelを宣言して次の,でelを利用した変数を宣言することができる。
function updateElement() {
var el = document.getElementById("result"),
style = el.style;
// do something with el and style...
}
Hoisting: A Problem with Scattered vars
関数内でvarを宣言したとき、まるで関数の先頭で宣言されたような振る舞いはhoistingという名前で知られています。
// antipattern
myname = "global"; // global variable
function func() {
alert(myname); // "undefined" <= ここでundefinedになってしまってる
var myname = "local";
alert(myname); // "local"
}
func();
このプログラムの実際の動作は以下と同じ
myname = "global"; // global variable
function func() {
var myname; // same as -> var myname = undefined;
alert(myname); // "undefined"
myname = "local";
alert(myname); // "local"
}
func();
このような動作になっているのはコードの扱いに2つのステージが存在しているからです。1つめはvarやfunctionなどの宣言を作るステージ
2つめはコードを実行するステージ
そのため実行ステージより前に変数の宣言ステージがあるため、実行時にコードの並び順とは異なった結果になることがあります。
=> single varパターンだとこういうのに遭遇しにくいよってことでもある
■for Loops
forループを書く際に、以下のように書いてしまうとループごとにlengthプロパティへアクセスしてしまい低速になる。
for (var i = 0; i < myarray.length; i++) {
// do something with myarray[i]
}
特にこれがHTMLCollectionオブジェクトである顕著になる。なので、最初にlengthの値をとっておくのが定石
function looper() {
var i = 0,
max,
myarray = [];
// ...
for (i = 0, max = myarray.length; i < max; i++) {
// do something with myarray[i]
}
}
さらにここから最適化することができる。- maxという変数を使わない
- 0へのカウントダウン方式に変える
だけどこれはノードリストについてのみ使うもので、通常の配列だとfalseなものがくると強制的に終わってしまう。
var i, myarray = [];
for (i = myarray.length; i--;) {
// do something with myarray[i]
}
// whileなら
var myarray = [],
i = myarray.length;
while (i--) {
// do something with myarray[i]
}
for-in Loops
for-inループによる列挙。prototype汚染されてる時にhasOwnPropertyでフィルタする方法
// the object
var man = {
hands: 2,
legs: 2,
heads: 1
};
// cloneというメソッドがObjectにprototype拡張されている
if (typeof Object.prototype.clone === "undefined") {
Object.prototype.clone = function () {};
}
// antipattern:
// clone: function() というもの列挙されてしまう
for (var i in man) {
console.log(i, ":", man[i]);
}
// for-in loop
for (var i in man) {
if (man.hasOwnProperty(i)) { // filter
console.log(i, ":", man[i]);
}
}
// => 違う書き方
// Object.prototype.hasOwnPropertyとcallを使う
var i,
hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
if (hasOwn.call(man, i)) { // filter
console.log(i, ":", man[i]);
}
}
Object.prototype.hasOwnPropertyとcallを使う方法は初めてみたかも■(Not) Augmenting Built-in Prototypes
Object(),Array(),Function()のようなBuilt-inコンストラクタのprototype拡張は避けるべきである。理由は上のfor-in列挙の時に困ることがあるから。追加しないことがベストではあるが、次の条件をクリアしたときにはBuilt-inなものも拡張していいかもしれない
- 将来のECMAScriptで実装される予定であるBuilt-inメソッド
- そのカスタムプロパティ、メソッドがまだ存在していないことを確認できた
- チームに対して追加したことを伝える、ドキュメント化する
if (typeof Object.protoype.myMethod !== "function") {
Object.protoype.myMethod = function () {
// implementation...
};
}
■switch Pattern
switchパターンを使うときは以下のことに気をつける。- caseとswitchを並べる
- caseごとにインデントする
- caseの最後はbreak;で終わる
- (故意的にbreakさせたい時以外は)fall-throughsを避ける。(breakがないときに次のcaseへ移行するやり方を避ける)
- switchの最後は必ずdefault:を作る。
var inspect_me = 0,
result = '';
switch (inspect_me) {
case 0:
result = "zero";
break;
case 1:
result = "one";
break;
default:
result = "unknown";
}
■Avoiding Implied Typecasting
暗黙のキャスト変換は避けるべきである。要はいつも === か !== で比較するべきである。
var zero = 0;
if (zero === false) {
// not executing because zero is 0, not false
}
// antipattern
if (zero == false) {
// this block is executed...
}
加えて、typeofなどstringを返すと決まっているものについて===ではなく==を使っても問題はないのでは?という人たちもいますが、JSLintでは厳密比較演算子===を使えと警告が出る。ここで言いたいのは、一貫性を持ったコードを書くことが読みやすいコードへつながるということ。
Avoiding eval()
“eval() is evil.”evalを使う事はユーザー入力に対してのセキュリティ問題が伴います。
JSONのレスポンスをパースする時は ネイティブのJSON.parse()かライブラリを使うべきである。
またsetInterval(), setTimeout(), Function()などのstringから関数を実行できるものも避けるべきである。(普通に関数名を指定するとかした方がいい)
new Function()はeval()とアプローチが似ていますが、evalを使うよりはnew Function()を使った方がよいでしょう。どのような違いがあるかというとnew Function() でvarを使い変数を宣言した際にそれが自動的にグローバルにはならないという違いがあります。
console.log(typeof un); // "undefined"
console.log(typeof deux); // "undefined"
console.log(typeof trois); // "undefined"
var jsstring = "var un = 1; console.log(un);";
eval(jsstring); // logs "1"
jsstring = "var deux = 2; console.log(deux);";
new Function(jsstring)(); // logs "2"
jsstring = "var trois = 3; console.log(trois);";
(function () {
eval(jsstring);
}()); // logs "3"
console.log(typeof un); // "number"
console.log(typeof deux); // "undefined"
console.log(typeof trois); // "undefined"
またevalは実行されるスコープが現在のスコープであるため、ローカル変数はもちろんグローバル変数にもアクセスが可能です。しかしFunctionの場合はローカル変数にはアクセスできません。
(function () {
var local = 1;
eval("local = 3; console.log(local)"); // logs 3
console.log(local); // logs 3
}());
(function () {
var local = 1;
Function("console.log(typeof local);")(); // logs undefined
}());
おまけ:(あってる?)Functionがグローバルで実行されているように見えて、グローバル変数を定義できない(varで宣言してもグローバルにない)のは、実際にはグローバルスコープで実行されているわけではないからです。
Functionで関数スコープができて、それがグローバルに対してチェインしてるので、グローバルな変数宣言はできないけど、グローバルな値は参照できる。
なのでsandbox的になるためevalよりは安全ではある。
// => 動作的にはオブジェクト内で動く
new function () { // オブジェクト(this)が生成される
// 処理
}; // thisは破棄される
■Number Conversions with parseInt()
parseInt()を使って文字列から数値を得ようとする場合の注意。06といった0から始まるものをパースするトキに第二引数でちゃんと10進数であることを指定しいないと、8進数として扱ってしまいバグの原因となる。
var month = "06",
year = "09";
month = parseInt(month, 10);
year = parseInt(year, 10);
別の方法として下のような書き方もある。
+"08" // result is 8
Number("08") // 8
■Coding Conventions
コーディングルールについてIndentation
インデントは可読性のためにも重要。この本では4-spaceでインデントしてる(JSLintのデフォルト)
Curly Braces
{}のこと。forとかifで略すな
Opening Brace Location
どちらの書き方にするか。正解は上の書き方。
if (true) {
alert("It's TRUE!");
}
// もしくは
if (true)
{
alert("It's TRUE!");
}
なぜそうするのかというと、JavaScriptには自動的にセミコロンを入れるメカニズムが存在するからだ。下のように書くと想定外の値が返ってきてしまう。
function func() {
return
{
name: "Batman"
};// undefinedを返してしまう
}
そのため、一つ前のステートメントと同じ行に{をおいた方がよいことになる。White Space
+, -, *, =, <, >, <=, >=, ===, !==, &&, ||, などの前後にはスペースを挟むべき。■Naming Conventions
命名規則についてCapitalizing Constructors
newキーワードで呼び出すコンストラクタ関数は大文字から開始する。
function MyConstructor() {...}
function myFunction() {...}
Separating Words
JavaScriptではcamel caseが使われることが多い。ECMAScriptではlower camel caseが使われている。
アンダースコア区切りもみやすいんでないという話first_name
Other Naming Patterns
変化しない定数は大文字でアンダースコア区切り。
var PI = 3.14,
MAX_WIDTH = 800;
privateメンバは_から始める
var person = {
getName: function () {
return this._getFirst() + ' ' + this._getLast();
},
_getFirst: function () {
// ...
},
_getLast: function () {
// ...
}
};
これにはいくつかパターンがあって- 末尾につけるタイプgetElements_()
- _protectedと__privateにするタイプ
- Firefoxみたいに__proto__と前後に2個つけるタイプ
■Writing Comments
将来読み手がみたときに、関数名やプロパティ名とコメントを見ただけでそれはどのようなことをやっているかを理解できるように書く。コードの目的についての説明なら1行程度で書けるはず。
逆に複雑な正規表現などはコードよりコメントの方が多くなる場合もある。
■Writing API Docs
javadocのようなものはJavaScriptにもある。■Minify…In Production
Minificationはスペースやコメントを除去して転送量を減らしたりするプロセスのこと。Yahoo! YUICompressor や Google’s Closure Compiler などのツールがある。
■Summary
- グローバルは減らす
- 一つのfunctionに一つのvarにする、Single var パターン
- for,for-in,switch,eval,オブジェクト汚染
- コーディングルール
- TB-URL http://efcl.info/adiary/089/tb/
-
Functionコンストラクタと関数式(function expression)の違いなど
へびにっきJavaScript Patterns 2章ではevalの代替としてFunctionコンストラクタを使う方法が紹介されています。Functionコンストラクタは、普通にJavaScriptを書いている分にはなかなか使うことがないので、馴染みがない人も多いだ...
1: tkykmw 2011年01月06日(木) 午後5時28分
参考にさせていただいています。
ループの最適化についてですが、本文中に出てくるのはループ変数をデクリメントすることで0になる(falseになる)のを待つ方式なので、要素がfalseと判定されて止まってしまう問題はありませんね。欠点を挙げるとしたら列挙が逆順になってしまうことでしょうか。
Functionコンストラクタについては自分のブログにまとめてみたので、よろしければ参照ください(トラックバックをうってあります)。