zl程序教程

您现在的位置是:首页 >  前端

当前栏目

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

JavaScriptJS编程开发 指南 中文版 现代 风格
2023-06-13 09:15:27 时间

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

一、空白

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

二、美化语法

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

复制代码代码如下:

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

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

if(condition)doSomething();

while(condition)iterating++;

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

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

if(condition){
 //语句
}

while(condition){
 //语句
}

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

//更好的做法:

vari,
 length=100;

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

//或者...

vari=0,
 length=100;

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

varprop;

for(propinobject){
 //语句
}

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


B.赋值,声明,函数(命名函数,函数表达式,构建函数)
复制代码代码如下:

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

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

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

//不好
varfoo="";
varbar="";
varqux;

//好
varfoo="",
 bar="",
 quux;

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

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

//不好
functionfoo(){

 //在变量前有语句

 varbar="",
   qux;
}

//好
functionfoo(){
 varbar="",
   qux;

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

}

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

//2.B.2.2
//命名函数声明
functionsquare(number){
 returnnumber*number;
}

//使用方法
square(10);

//非常不自然的连带传参(continuationpassing)风格
functionsquare(number,callback){
 callback(number*number);
}

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

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

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

 returnnumber*factorial(number-1);
};

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

 this.options=options;
}

//使用方法
varfooBar=newFooBar({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"inobj)){

}


D.一致性(统一)总是笑到最后的(ConsistencyAlwaysWins)

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

复制代码代码如下:
//2.D.1.1

if(condition){
 //语句
}

while(condition){
 //语句
}

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

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


E.引号

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

F.行末和空行

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

三、类型检测(来源于jQueryCoreStyleGuidelines)

A.直接类型(实际类型,ActualTypes)

String:

复制代码代码如下:
typeofvariable==="string"
Number:
复制代码代码如下:
typeofvariable==="number"
Boolean:
复制代码代码如下:
typeofvariable==="boolean"
Object:
复制代码代码如下:
typeofvariable==="object"
Array:
复制代码代码如下:
Array.isArray(arrayLikeObject)
(如果可能的话)
Node:
复制代码代码如下:
elem.nodeType===1
null:
复制代码代码如下:
variable===null
nullorundefined:
复制代码代码如下:
variable==null
undefined:

全局变量:

复制代码代码如下:
typeofvariable==="undefined"
局部变量:
复制代码代码如下:
variable===undefined
属性:
复制代码代码如下:
object.prop===undefined
object.hasOwnProperty(prop)
"prop"inobject
B.转换类型(强制类型,CoercedTypes)

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

给定的HTML:

复制代码代码如下:
<inputtype="text"id="foo-input"value="1">

//3.B.1.1

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

//typeoffoo;
//"number"
...

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

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

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

if(foo===1){

 importantTask();

}

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

//3.B.1.2

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

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

//typeoffoo;
//"number"

if(foo===1){

 importantTask();

}

//`importantTask()`将被调用


对于强制类型转换这里有几个例子:
复制代码代码如下:
//3.B.2.1

varnumber=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

varnumber=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

vararray=["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

varnum=2.5;

parseInt(num,10);

//等价于...

~~num;

num>>0;

num>>>0;

//结果都是2

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

varneg=-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,void0

五、实用风格

复制代码代码如下:
//5.1.1
//一个实用的模块

(function(global){
 varModule=(function(){

   vardata="secret";

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

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

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

})(this);

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

(function(global){

 functionCtor(foo){

   this.foo=foo;

   returnthis;
 }

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

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

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

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

})(this);

六、命名

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

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

复制代码代码如下:
//6.A.1.1
//糟糕命名的示例代码

functionq(s){
 returndocument.querySelectorAll(s);
}
vari,a=[],els=q("#foo");
for(i=0;i<els.length;i++){a.push(els[i]);}


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

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

复制代码代码如下:
//6.A.2.1
//改善过命名的示例代码

functionquery(selector){
 returndocument.querySelectorAll(selector);
}

varidx=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
//来自GoogleClosureLibraryStyleGuide

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


B.面对this

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

复制代码代码如下:
//6.B.1
functionDevice(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()
functionDevice(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
functionDevice(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
functionDevice(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

functionDevice(opts){
 varself=this;

 this.value=null;

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

   self.value=data;

 });

 setInterval(function(){

   self.emit("event");

 },opts.freq||100);
}

C.使用thisArg

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

复制代码代码如下:
//6.C.1

varobj;

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,现代方法跟踪(methodtracing)将会把带有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来做委派:

varcases,delegator;

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

delegator=function(){
 varargs,key,delegate;

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

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

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

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

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

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

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

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

varcaseKey,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
//不好:
functionreturnLate(foo){
 varret;

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

//好:

functionreturnEarly(foo){

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

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

最基本的原则是:

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

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

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

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

九、注释

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

十、单用一门语言

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

 

附录

前置逗号(CommaFirst)

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