现代 JavaScript 开发编程风格Idiomatic.js指南中文版

你为项目所择风格都应为最高准则。作为一个描述放置于你的项目中,并链接到这个文档作为代码风格一致性、可读性和可维护性的保证。

一、空白

1.永远都不要混用空格和Tab。
2.开始一个项目,在写代码之前,选择软缩进(空格)或者 Tab(作为缩进方式),并将其作为最高准则。
a).为了可读, 我总是推荐在你的编辑中设计2个字母宽度的缩进 — 这等同于两个空格或者两个空格替代一个 Tab。
3.如果你的编辑器支持,请总是打开 “显示不可见字符” 这个设置。好处是:
a).保证一致性
b).去掉行末的空格
c).去掉空行的空格
d).提交和对比更具可读性

二、美化语法

A. 小括号, 花括号, 换行


代码如下:

// if/else/for/while/try 通常都有小括号、花括号和多行
// 这有助于可读

// 2.A.1.1
// 难辨语法(cramped syntax)的例子

if(condition) doSomething();

while(condition) iterating++;

for(var i=0;i<100;i++) someIterativeFn();

// 2.A.1.1
// 使用空格来提升可读性

if ( condition ) {
  // 语句
}

while ( condition ) {
  // 语句
}

for ( var i = 0; i < 100; i++ ) {
  // 语句
}

// 更好的做法:

var i,
  length = 100;

for ( i = 0; i < length; i++ ) {
  // 语句
}

// 或者...

var i = 0,
  length = 100;

for ( ; i < length; i++ ) {
  // 语句
}

var prop;

for ( prop in object ) {
  // 语句
}

if ( true ) {
  // 语句
} else {
  // 语句
}

B. 赋值, 声明, 函数 ( 命名函数, 函数表达式, 构建函数 )


代码如下:

// 2.B.1.1
// 变量
var foo = "bar",
  num = 1,
  undef;

// 字面量标识:
var array = [],
  object = {};

// 2.B.1.2
// 在一个作用域(函数)内只使用一个 `var` 有助于提升可读性
// 并且让你的声明列表变得有条不紊 (还帮你省了几次键盘敲击)

// 不好
var foo = "";
var bar = "";
var qux;

// 好
var foo = "",
  bar = "",
  quux;

// 或者..
var // 对这些变量的注释
foo = "",
bar = "",
quux;

// 2.B.1.3
// `var` 语句必须总是在各自作用域(函数)顶部
// 同样适应于来自 ECMAScript 6 的常量

// 不好
function foo() {

// 在变量前有语句

var bar = "",
    qux;
}

// 好
function foo() {
  var bar = "",
    qux;

// 所有语句都在变量之后
}
// 2.B.2.1
// 命名函数声明
function foo( arg1, argN ) {

}

// 使用方法
foo( arg1, argN );

// 2.B.2.2
// 命名函数声明
function square( number ) {
  return number * number;
}

// 使用方法
square( 10 );

// 非常不自然的连带传参(continuation passing)风格
function square( number, callback ) {
  callback( number * number );
}

square( 10, function( square ) {
  // 回调内容
});

// 2.B.2.3
// 函数表达式
var square = function( number ) {
  // 返回有价值的、相关的内容
  return number * number;
};

// 带标识符的函数表达式
// 这种首选形式有附加的功能让其可以调用自身
// 并且在堆栈中有标识符
var factorial = function factorial( number ) {
  if ( number < 2 ) {
    return 1;
  }

return number * factorial( number-1 );
};

// 2.B.2.4
// 构造函数声明
function FooBar( options ) {

this.options = options;
}

// 使用方法
var fooBar = new FooBar({ a: "alpha" });

fooBar.options;
// { a: "alpha" }

C. 异常, 细节


代码如下:

// 2.C.1.1
// 带回调的函数
foo(function() {
  // 注意:在第一函数调用的小括号和 `function` 处并没有空格
});

// 函数接受 `array` 作为参数,没有空格
foo([ "alpha", "beta" ]);

// 2.C.1.2
// 函数接受 `object` 作为参数,没有空格
foo({
  a: "alpha",
  b: "beta"
});

// 函数接受 `string` 字面量作为参数,没有空格
foo("bar");

// 分组用的小括号内部,没有空格
if ( !("foo" in obj) ) {

}

D. 一致性(统一)总是笑到最后的(Consistency Always Wins)

在 2.A-2.C 节,留白作为一个推荐方式被提出,基于单纯的、更高的目的:统一。值得注意的是格式化偏好,像“内部留白”必须是可选的,但在整个项目的源码中必须只存在着一种。


代码如下:

// 2.D.1.1

if (condition) {
  // 语句
}

while (condition) {
  // 语句
}

for (var i = 0; i < 100; i++) {
  // 语句
}

if (true) {
  // 语句
} else {
  // 语句
}

E. 引号

无论你选择单引号还是双引号都无所谓,在 JavaScript 中它们在解析上没有区别。而绝对需要强制的是一致性。 永远不要在同一个项目中混用两种引号,选择一种,并保持一致。

F. 行末和空行

留白会破坏区别并使用变更不可读。考虑包括一个预提交的 hook 自动删除行末和空行中的空格。

三、类型检测 (来源于 jQuery Core Style Guidelines)

A. 直接类型(实际类型,Actual Types)

String:


代码如下:

typeof variable === "string"

Number:


代码如下:

typeof variable === "number"

Boolean:


代码如下:

typeof variable === "boolean"

Object:


代码如下:

typeof variable === "object"

Array:


代码如下:

Array.isArray( arrayLikeObject )
(如果可能的话)

Node:


代码如下:

elem.nodeType === 1

null:


代码如下:

variable === null

null or undefined:


代码如下:

variable == null

undefined:

全局变量:


代码如下:

typeof variable === "undefined"

局部变量:


代码如下:

variable === undefined

属性:


代码如下:

object.prop === undefined
object.hasOwnProperty( prop )
"prop" in object

B. 转换类型(强制类型,Coerced Types)

考虑下面这个的含义...

给定的 HTML:


代码如下:

<input type="text" id="foo-input" value="1">

// 3.B.1.1

// `foo` 已经被赋予值 `0`,类型为 `number`
var foo = 0;

// typeof foo;
// "number"
...

// 在后续的代码中,你需要更新 `foo`,赋予在 input 元素中得到的新值

foo = document.getElementById("foo-input").value;

// 如果你现在测试 `typeof foo`, 结果将是 `string`
// 这意味着你在 if 语句检测 `foo` 有类似于此的逻辑:

if ( foo === 1 ) {

importantTask();

}

// `importantTask()` 将永远不会被执行,即使 `foo` 有一个值 "1"

// 3.B.1.2

// 你可以巧妙地使用 + / - 一元运算符强制转换类型以解决问题:

foo = +document.getElementById("foo-input").value;
//    ^ + 一元运算符将它右边的运算对象转换为 `number`

// typeof foo;
// "number"

if ( foo === 1 ) {

importantTask();

}

// `importantTask()` 将被调用

对于强制类型转换这里有几个例子:


代码如下:

// 3.B.2.1

var number = 1,
  string = "1",
  bool = false;

number;
// 1

number + "";
// "1"

string;
// "1"

+string;
// 1

+string++;
// 1

string;
// 2

bool;
// false

+bool;
// 0

bool + "";
// "false"
// 3.B.2.2

var number = 1,
  string = "1",
  bool = true;

string === number;
// false

string === number + "";
// true

+string === number;
// true

bool === number;
// false

+bool === number;
// true

bool === string;
// false

bool === !!string;
// true
// 3.B.2.3

var array = [ "a", "b", "c" ];

!!~array.indexOf("a");
// true

!!~array.indexOf("b");
// true

!!~array.indexOf("c");
// true

!!~array.indexOf("d");
// false

// 值得注意的是上述都是 "不必要的聪明"
// 采用明确的方案来比较返回的值
// 如 indexOf:

if ( array.indexOf( "a" ) >= 0 ) {
  // ...
}
// 3.B.2.3

var num = 2.5;

parseInt( num, 10 );

// 等价于...

~~num;

num >> 0;

num >>> 0;

// 结果都是 2

// 时刻牢记心底, 负值将被区别对待...

var neg = -2.5;

parseInt( neg, 10 );

// 等价于...

~~neg;

neg >> 0;

// 结果都是 -2
// 但是...

neg >>> 0;

// 结果即是 4294967294

四、对比运算

代码如下:

// 4.1.1
// 当只是判断一个 array 是否有长度,相对于使用这个:
if ( array.length > 0 ) ...

// ...判断真伪, 请使用这种:
if ( array.length ) ...

// 4.1.2
// 当只是判断一个 array 是否为空,相对于使用这个:
if ( array.length === 0 ) ...

// ...判断真伪, 请使用这种:
if ( !array.length ) ...

// 4.1.3
// 当只是判断一个 string 是否为空,相对于使用这个:
if ( string !== "" ) ...

// ...判断真伪, 请使用这种:
if ( string ) ...

// 4.1.4
// 当只是判断一个 string 是为空,相对于使用这个:
if ( string === "" ) ...

// ...判断真伪, 请使用这种:
if ( !string ) ...

// 4.1.5
// 当只是判断一个引用是为真,相对于使用这个:
if ( foo === true ) ...

// ...判断只需像你所想,享受内置功能的好处:
if ( foo ) ...

// 4.1.6
// 当只是判断一个引用是为假,相对于使用这个:
if ( foo === false ) ...

// ...使用叹号将其转换为真
if ( !foo ) ...

// ...需要注意的是:这个将会匹配 0, "", null, undefined, NaN
// 如果你 _必须_ 是布尔类型的 false,请这样用:
if ( foo === false ) ...

// 4.1.7
// 如果想计算一个引用可能是 null 或者 undefined,但并不是 false, "" 或者 0,
// 相对于使用这个:
if ( foo === null || foo === undefined ) ...

// ...享受 == 类型强制转换的好处,像这样:
if ( foo == null ) ...

// 谨记,使用 == 将会令 `null` 匹配 `null` 和 `undefined`
// 但不是 `false`,"" 或者 0
null == undefined

总是判断最好、最精确的值,上述是指南而非教条。


代码如下:

// 4.2.1
// 类型转换和对比运算说明

// 首次 `===`,`==` 次之 (除非需要松散类型的对比)

// `===` 总不做类型转换,这意味着:

"1" === 1;
// false

// `==` 会转换类型,这意味着:

"1" == 1;
// true

// 4.2.2
// 布尔, 真 & 伪

// 布尔:
true, false

// 真:
"foo", 1

// 伪:
"", 0, null, undefined, NaN, void 0

五、实用风格


代码如下:

// 5.1.1
// 一个实用的模块

(function( global ) {
  var Module = (function() {

var data = "secret";

return {
      // 这是一个布尔值
      bool: true,
      // 一个字符串
      string: "a string",
      // 一个数组
      array: [ 1, 2, 3, 4 ],
      // 一个对象
      object: {
        lang: "en-Us"
      },
      getData: function() {
        // 得到 `data` 的值
        return data;
      },
      setData: function( value ) {
        // 返回赋值过的 `data` 的值
        return ( data = value );
      }
    };
  })();

// 其他一些将会出现在这里

// 把你的模块变成全局对象
  global.Module = Module;

})( this );

// 5.2.1
// 一个实用的构建函数

(function( global ) {

function Ctor( foo ) {

this.foo = foo;

return this;
  }

Ctor.prototype.getFoo = function() {
    return this.foo;
  };

Ctor.prototype.setFoo = function( val ) {
    return ( this.foo = val );
  };

// 不使用 `new` 来调用构建函数,你可能会这样做:
  var ctor = function( foo ) {
    return new Ctor( foo );
  };

// 把我们的构建函数变成全局对象
  global.ctor = ctor;

})( this );

六、命名

A. 你并不是一个人肉 编译器/压缩器,所以尝试去变身为其一。

下面的代码是一个极糟命名的典范:


代码如下:

// 6.A.1.1
// 糟糕命名的示例代码

function q(s) {
  return document.querySelectorAll(s);
}
var i,a=[],els=q("#foo");
for(i=0;i<els.length;i++){a.push(els[i]);}

毫无疑问,你写过这样的代码 —— 希望从今天它不再出现。

这里有一份相同逻辑的代码,但拥有更健壮、贴切的命名(和一个可读的结构):


代码如下:

// 6.A.2.1
// 改善过命名的示例代码

function query( selector ) {
  return document.querySelectorAll( selector );
}

var idx = 0,
  elements = [],
  matches = query("#foo"),
  length = matches.length;

for ( ; idx < length; idx++ ) {
  elements.push( matches[ idx ] );
}

一些额外的命名提示:


代码如下:

// 6.A.3.1
// 命名字符串

`dog` 是一个 string

// 6.A.3.2
// 命名 arrays

`['dogs']` 是一个包含 `dog 字符串的 array

// 6.A.3.3
// 命名函数、对象、实例,等

camlCase; function 和 var 声明

// 6.A.3.4
// 命名构建器、原型,等

PascalCase; 构建函数

// 6.A.3.5
// 命名正则表达式

rDesc = //;

// 6.A.3.6
// 来自 Google Closure Library Style Guide

functionNamesLikeThis;
variableNamesLikeThis;
ConstructorNamesLikeThis;
EnumNamesLikeThis;
methodNamesLikeThis;
SYMBOLIC_CONSTANTS_LIKE_THIS;

B. 面对 this

除使用众所周知的 call 和 apply 外,总是优先选择 .bind( this ) 或者一个功能上等价于它的。创建 BoundFunction 声明供后续调用,当没有更好的选择时才使用别名。


代码如下:

// 6.B.1
function Device( opts ) {

this.value = null;

// 新建一个异步的 stream,这个将被持续调用
  stream.read( opts.path, function( data ) {

// 使用 stream 返回 data 最新的值,更新实例的值
    this.value = data;

}.bind(this) );

// 控制事件触发的频率
  setInterval(function() {

// 发出一个被控制的事件
    this.emit("event");

}.bind(this), opts.freq || 100 );
}

// 假设我们已继承了事件发送器(EventEmitter) ;)

当不能运行时,等价于 .bind 的功能在多数现代 JavaScript 库中都有提供。


代码如下:

// 6.B.2

// 示例:lodash/underscore,_.bind()
function Device( opts ) {

this.value = null;

stream.read( opts.path, _.bind(function( data ) {

this.value = data;

}, this) );

setInterval(_.bind(function() {

this.emit("event");

}, this), opts.freq || 100 );
}

// 示例:jQuery.proxy
function Device( opts ) {

this.value = null;

stream.read( opts.path, jQuery.proxy(function( data ) {

this.value = data;

}, this) );

setInterval( jQuery.proxy(function() {

this.emit("event");

}, this), opts.freq || 100 );
}

// 示例:dojo.hitch
function Device( opts ) {

this.value = null;

stream.read( opts.path, dojo.hitch( this, function( data ) {

this.value = data;

}) );

setInterval( dojo.hitch( this, function() {

this.emit("event");

}), opts.freq || 100 );
}

提供一个候选,创建一个 this 的别名,以 self 作为标识符。这很有可能出 bug,应尽可能避免。


代码如下:

// 6.B.3

function Device( opts ) {
  var self = this;

this.value = null;

stream.read( opts.path, function( data ) {

self.value = data;

});

setInterval(function() {

self.emit("event");

}, opts.freq || 100 );
}

C. 使用 thisArg

好几个 ES 5.1 中的原型的方法都内置了一个特殊的 thisArg 标记,尽可能多地使用它


代码如下:

// 6.C.1

var obj;

obj = { f: "foo", b: "bar", q: "qux" };

Object.keys( obj ).forEach(function( key ) {

// |this| 现在是 `obj`

console.log( this[ key ] );

}, obj ); // <-- 最后的参数是 `thisArg`

// 打印出来...

// "foo"
// "bar"
// "qux"

thisArg 在 Array.prototype.every、 Array.prototype.forEach、 Array.prototype.some、 Array.prototype.map、 Array.prototype.filter 中都可以使用。

七、Misc

这个部分将要说明的想法和理念都并非教条。相反更鼓励对现存实践保持好奇,以尝试提供完成一般 JavaScript 编程任务的更好方案。

A. 避免使用 switch,现代方法跟踪(method tracing)将会把带有 switch 表达式的函数列为黑名单。

似乎在最新版本的 Firefox 和 Chrome 都对 switch 语句有重大改进。http://jsperf.com/switch-vs-object-literal-vs-module

值得注意的是,改进可以这里看到: https://github.com/rwldrn/idiomatic.js/issues/13


代码如下:

// 7.A.1.1
// switch 语句示例

switch( foo ) {
  case "alpha":
    alpha();
    break;
  case "beta":
    beta();
    break;
  default:
    // 默认分支
    break;
}

// 7.A.1.2
// 一个可支持组合、重用的方法是使用一个对象来存储 “cases”,
// 使用一个 function 来做委派:

var cases, delegator;

// 返回值仅作说明用
cases = {
  alpha: function() {
    // 语句
    // 一个返回值
    return [ "Alpha", arguments.length ];
  },
  beta: function() {
    // 语句
    // 一个返回值
    return [ "Beta", arguments.length ];
  },
  _default: function() {
    // 语句
    // 一个返回值
    return [ "Default", arguments.length ];
  }
};

delegator = function() {
  var args, key, delegate;

// 把 `argument` 转换成数组
  args = [].slice.call( arguments );

// 从 `argument` 中抽出最前一个值
  key = args.shift();

// 调用默认分支
  delegate = cases._default;

// 从对象中对方法进行委派操作
  if ( cases.hasOwnProperty( key ) ) {
    delegate = cases[ key ];
  }

// arg 的作用域可以设置成特定值,
  // 这种情况下,|null| 就可以了
  return delegate.apply( null, args );
};

// 7.A.1.3
// 使用 7.A.1.2 中的 API:

delegator( "alpha", 1, 2, 3, 4, 5 );
// [ "Alpha", 5 ]

// 当然 `case` key 的值可以轻松地换成任意值

var caseKey, someUserInput;

// 有没有可能是某种形式的输入?
someUserInput = 9;

if ( someUserInput > 10 ) {
  caseKey = "alpha";
} else {
  caseKey = "beta";
}

// 或者...

caseKey = someUserInput > 10 ? "alpha" : "beta";

// 然后...

delegator( caseKey, someUserInput );
// [ "Beta", 1 ]

// 当然还可以这样搞...

delegator();
// [ "Default", 0 ]

B. 提前返回值提升代码的可读性并且没有太多性能上的差别


代码如下:

// 7.B.1.1
// 不好:
function returnLate( foo ) {
  var ret;

if ( foo ) {
    ret = "foo";
  } else {
    ret = "quux";
  }
  return ret;
}

// 好:

function returnEarly( foo ) {

if ( foo ) {
    return "foo";
  }
  return "quux";
}

八、原生 & 宿主对象(注:其实一直觉得 Host Objects 真不应该翻译过来,这是就按一般书的写法翻出来吧)

最基本的原则是:

不要干任何蠢事,事情总会变好的。

为了加强这个观念,请观看这个演示:

“一切都被允许: 原生扩展” by Andrew Dupont (JSConf2011, Portland, Oregon)

http://blip.tv/jsconf/jsconf2011-andrew-dupont-everything-is-permitted-extending-built-ins-5211542

九、注释

单行注释放于代码上方为首选
多行也可以
行末注释应被避免!
JSDoc 的方式也不错,但需要比较多的时间

十、单用一门语言

无论是什么语言程序维护者(或团队)规定使用何种语言,程序都应只用同一种语言书写。

附录

前置逗号(Comma First)

所有使用这个文档作为基本风格指南的项目都不允许前置逗号的代码格式,除非明确指定或者作者要求。

时间: 2014-05-25

Javascript模块化编程(三)require.js的用法及功能介绍

这个系列的第一部分和第二部分,介绍了Javascript模块原型和理论概念,今天介绍如何将它们用于实战. 我采用的是一个非常流行的库require.js.  一.为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代码,相信很多人都见过. 复制代码 代码如下: <script src="1.js"></script> &l

JavaScript 模块化编程(笔记)

一直对JS都是一知半解,最近遇到这方面问题,所以在网上学习了一下,现在还没有完全明白,先贴出笔记; 第一章 JavaScript模块化编程 (一):模块的写法 一 原始写法 // 模块就是实现特定功能的一组方法;只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块;     function m1(){         // ...     }     function m2(){         // ...     } // 上面的函数m1()和m2(),组成一个模块;使用时

JavaScript编码风格指南(中文版)

前言: 程序语言的编码风格对于一个长期维护的软件非常重要,特别是在团队协作中.如果一个团队使用统一规范的编码分风格,可以提高团队的协作水平和工作效率.编程风格指南的核心是基本的格式化规则,这些规则决定了如何编写高水准的代码.本指南来自于<编写可维护的JavaScript>这本书,基于"Java语言编码规范"和Crockford的JavaScript编程规范,还有Nicbolas的一些个人经验和喜好.写作本文旨在加深自己印象,也为了更多人的了解到JS编码风格,提高自己的编码质

JavaScript 开发工具webstrom使用指南

看到网上一篇介绍webstrom的文章,觉得功能确实强大,也知道为什么阿里巴巴的前端传到github上的文件为啥都有一个 .idea 文件,(传说淘宝内部推荐写js用webstrom) 我们可以理解 IDE 就是集成了很多你想要的功能,或者你不想要的功能.换句话说就是装了很多插件的 editor ,所以到目前为止,我还觉得没必要给它装什么插件. 那么接下来开始介绍webstrom的特色功能: WebStorm 是 JetBrains 推出的一款商业的 JavaScript 开发工具 任何一个编辑

javascript 开发之百度地图使用到的js函数整理

 javascript 开发之百度地图使用到的js函数整理 接项目用到的地图,客户要求用百度地图,没办法只好用百度地图,这里总结一下,写的一些函数,注释比较详细! //创建和初始化地图函数: function initMap(){ createMap();//创建地图 setMapEvent();//设置地图事件 addMapControl();//向地图添加控件 addMarker();//向地图中添加marker } //创建地图函数: function createMap(){ var m

浅谈Javascript编程风格

Douglas Crockford是Javascript权威,Json格式就是他的发明. 去年11月他有一个演讲,谈到了好的Javascript编程风格是什么. 我非常推荐这个演讲,它不仅有助于学习Javascript,而且能让你心情舒畅,因为Crockford讲得很幽默,时不时让听众会心一笑. 下面,我根据这个演讲和Crockford编写的代码规范,总结一下"Javascript编程风格". 所谓"编程风格"(programming style),指的是编写代码的

使用VS开发 Node.js指南

NTVS(Node.jsToolsforVisualStudio)是一款 可以运行在VS2012.VS2013上的一个IDE工具. 使用这个插件对于我们传统.net的开发人员学习node.js无疑是一大福音! NTVS也是开源的,它支持编辑,智能感知,分析,npm,本地与远程调试,以及发布到Azure网站和云服务. NTVS下载地址 装之前不用多说 先把node.js的一套东西给装齐了 1·安装NTVS下载地址(直接下一步-下一步) 2.打开VS2013 3.欣赏一下目录 server.js(有

JavaScript的代码编写格式规范指南

对于熟悉 C/C++ 或 Java 语言的工程师来说,JavaScript 显得灵活,简单易懂,对代码的格式的要求也相对松散.很容易学习,并运用到自己的代码中.也正因为这样,JavaScript 的编码规范也往往被轻视,开发过程中修修补补,最终也就演变成为后续维护人员的恶梦.软件存在的长期价值直接与编码的质量成比例.编码规范能帮助我们降低编程中不必要的麻烦.而 JavaScript 代码是直接发送给客户浏览器的,直接与客户见面,编码的质量更应该受到关注. 本文浅谈 JavaScript 编程中关

Javascript函数式编程简单介绍

几十年来,函数式编程一直是计算机科学狂热者的至爱,由于数学的纯洁性和谜一般的本质, 它被埋藏在计算机实验室,只有数据学家和有希望获得博士学位的人士使用.但是现在,它正经历一场复兴, 这要感谢一些现代语言比如Python,Julia,Ruby,Clojure以及--但不是最后一个--Javascript. 你是说Javascript?这个WEB脚本语言?没错! Javascript已经被证明是一项长期以来都没有消失的重要的技术.这主要是由于它扩展的一些框架和库而使其具有重生的能力, 比如backb