编写可维护的JavaScript
边看书边做笔记,这个仓库主要是参照《编写可维护的JavaScript》这本书,来总结自己的学习笔记。
只是自己的总结,和书本不一样,如果觉得不错,你可以购买这本书自己研究。书本还涉及到JSLint,JSHint
中的区别。实例代码从哪里取材,各种组织或公司的代码风格,并进行比较以及总结。对于某些写法的观点有时有些模棱两可,我自己的总结没有这些东西,更偏向与自己怎么写。
最后,我写这篇东西也不容易,如果感觉不错,请点Star
,谢谢大家。
基本的格式化
注释
语句和表达式
变量、函数和运算符
if (wl && wl.length) {
for (i = 0, 1 = wl.length; i < 1; ++i) {
p = wl[i];
type = Y.Lang.type(r[p]);
if (s.hasOwnProperty(p)) { if (merge && type == 'object') {
Y.mix(r(p], s{p]);
} else if (ov || !(p in r)) {
r[p] = s[p];
}
}
}
}
if (wl && wl.length) {
for (i = 0, 1 = wl.length; i < 1; ++i) {
p = wl[i];
type = Y.Lang.type(r[p]);
if (s.hasOwnProperty(p)) {
if (merge && type == 'object') {
Y.mix(r[p], s[p]);
} else if (ov || !(p in r)) {
r[p] = s[p];
}
}
}
}
有两种主张
以上两种方法都可以,但是不要两种方法混用。
// 原始代码
function getData(){
return
{
title: "Maintainable JavaScript",
author: "Nicholas C. Zakas"
}
}
// 分析器会将它理解成
function getData(){
return;
{
title: "Maintainable JavaScript",
author: "Nicholas C. Zakas"
};
}
{
放到 return
之后,但是分析器的判断比较复杂,不能保证我们所有我们应该更加倾向于使用它们,而不是省略它们。
上面是书的作者的看法,我确实看到一种风格是结尾一律不加分号,这种写法要求在语句是以[
,(
,`开头的时候,在前面加上分号。
;[1,2,3].foreach(function(){
// 处理内容
})
;shenxf${nickname}
.length
// 很多代码都习惯于在圆括号之前加一个分号,这是由于之前引入的代码可能有漏了分号的情况,
// 为了以防万一,都会有类似于下面的代码。
;(function(){
// 处理内容。
})()
- 我比较喜欢不省略分号,如果想采用省略分号的规范,切记在`[`,`(`,`之前加上分号。
[返回顶部](#编程风格)
### 行的长度
- 如果一行的内容太长,编辑窗口就会出现滚动条。这样不利于我们查看代码,也比较变扭。
我们应该规定我们一行的长度不要超过80个字符。超过80个字符应该折行。这是因为很多编辑器是
在80个字符以后出现滚动条
[返回顶部](#编程风格)
### 换行
- 当一行的长度太长我们就会进行折行。通常要在运算符后面换行,这是因为分析器不会再运算符后面自动加分号。
不容易出错。
```javascript
// 好的做法:在运算符后面换行。 第二行追加二个缩进
callAFunction(document, element, window, "some string value", true, 123,
navigator);
// 不好的做法第二行只有一个缩进
callAFunction(document, element, window, "some string value", true, 123,
navigator);
// 不好的做法运算符之前换行了
callAFunction(document, element, window, "some string value", true, 123
, navigator);
if (isLeapYear && isFebruary && day && day ==29 && itsYourBirthdady &&
noPlans) {
waitAnotherFourYears();
}
var result = something + anotherThing + yetAnotherThing + someThingElse +
anotherSomethingElse;
if (wl && wl.length) {
for (i = 0, 1 = wl.length; i < 1; ++i) {
p = wl[i];
type = Y.Lang.type(r[p]);
if (s.hasOwnProperty(p)) {
if (merge && type == 'object') {
Y.mix(r[p], s[p]);
} else if (ov || !(p in r)) {
r[p] = s[p];
}
}
}
}
if (wl && wl.length) {
for (i = 0, 1 = wl.length; i < 1; ++i) {
p = wl[i];
type = Y.Lang.type(r[p]);
if (s.hasOwnProperty(p)) {
if (merge && type == 'object') {
Y.mix(r[p], s[p]);
} else if (ov || !(p in r)) {
r[p] = s[p];
}
}
}
}
// 好的写法
var count = 10;
var myName = "shenxf";
var found = true;
// 不好的写法: 变量看起来像函数
var getCount = 10;
var isFound = true;
// 好的写法
function getName() {
return myName;
}
// 不好的写法
function theName() {
return myName;
}
要经量避免写无意义的命名。
对于方法的命名,第一个单词应该是动词。下面是一些常见的约定
动词 | 含义 |
---|---|
can | 函数返回一个布尔值 |
has | 函数返回一个布尔值 |
is | 函数返回一个布尔值 |
get | 函数返回一个非布尔值 |
set | 函数用来保存一个值 |
if (isEnabled()) {
setName("shenxf");
}
if (getName() === "shenxf") {
doSomething();
}
var MAX_COUNT = 10;
var URL = "http://shenxf.top";
if (count < MAX_COUNT) {
doSomething();
}
// 好的做法
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
alert(this.name);
}
var me = new Person("shenxf");
var me = Person("shenxf"); // 这里缺少了new
var you = getPerson("xx");
// 下面都是合法的javascript代码
var name = "shenxf says, \"Hi.\"";
var name = 'shenxf says, "Hi."';
我个人推荐使用单引号,本人之前使用的很多编码规范插件都是以单引号来声明变量的。我已经习惯了,所以我推荐使用单引号。不管使用哪一种,自己的代码风格保持一致我觉得就可以了。
当字符串换行的时候,应该使用运算符来连接。
// 不好的写法
var longString = "Here's the story, of a man \
named Brady.";
// 好的写法
var longString = "Here's the story, of a man" +
"named Brady.";
// 整数
var count = 10;
// 小数
var price = 10.0;
var price = 10.00;
// 不推荐小数的写法:没有小数部分
var pricee = 10.;
// 不推荐小数的写法:没有整数部分
var price = .1;
// 不推荐的写法:八进制写法已经被弃用
var num = 010;
// 十六进制写法
var num = 0xA2;
// 科学计数法
var num = 1e23;
10
,其实是8
,所以不推荐。null比较特殊经常和undefined混淆。
应该使用null的场景
不应该使用null的场景
例子
// 好的用法
var person = null;
// 好的用法
function getPerson() {
if (condition) {
return new Person("shenxf");
} else {
return null;
}
}
// 好的用法
var person = getPerson();
if (person !== null) {
doSomething();
}
// 不好的写法:用来和未初始化的变量进行比较
var person;
if (person != null) {
doSomething();
}
// 不好的写法:检测是否传入了参数
function doSomething(arg1, arg2, arg3, arg4) {
if (arg4 != null) {
doSomethingElse();
}
}
null
最好的方法是把它理解成一个占位符null == undefined
的结果是true
,然而,这2个值的用法各不相同。undefined
,表示这个变量等待被赋值。
// 不好的写法
var person;
console.log(person === undefined); //true
undefined
.undefined
有很多让人费解的地方,比如typeof
// foo未被声明
var person;
console.log(typeof person); // "undefined"
console.log(typeof foo); // "undefined"
undefined
,可以使得typeof
出来的结果只有一种可能出现undefined
。那就是
// 好的做法
var person = null;
console.log(person === null); // true
typeof null
值返回的是Object
,这样可以避开undefined
,这样就区分开了。
// 不好的写法
var book = new Object();
book.title = "shenxfsbook";
book.author = "shenxf";
// 好的写法
var book = {
title: "shenxfsbook",
author: "shenxf"
};
// 不好的写法
var colors = new Array("red", "green", "blue");
var numbers = new Array(1, 2, 3, 4);
// 好的写法
var colors = ["red", "green", "blue"];
var numbers = [1, 2, 3, 4];
// 这是一句单行注释
单行注释 有3中使用方法
单行注释不应该用于多行,除非是注释的大段代码。
// 好的写法
if (condition) {
// 如果代码执行到这里,则表示通过了左右的安全性检查
allowed();
}
// 不好的写法:注释之前没有空行
if (condition) {
// 如果代码执行到这里,则表示通过了左右的安全性检查
allowed();
}
// 不好的写法:错误的缩进
if (condition) {
// 如果代码执行到这里,则表示通过了左右的安全性检查
allowed();
}
// 好的写法
var result = something + somethingElse; // somethingElse不应当取值为null
// 不好的写法:代码和注释之间没有间隔
var result = something + somethingElse;// somethingElse不应当取值为null
// 好的写法
// if (condition) {
// dosomething();
// thenDoSomethingElse();
// }
// 不好的写法:这里应当用多行注释
// 接下来的这段代码非常难,那么,让我详细的解释下
// 这段代码首先判断条件是否为真
// 。。。。。。。
if (condition) {
// 如果代码执行到这里,则表示通过了左右的安全性检查
allowed();
}
/* 我的注释 */
/* 另一段注释
这个注释包含2行 */
/*
又是一段注释
这段注释同样包含2行
*/
/*
,最后一行是*/
,当中每一行都以*
开头,且空开一个空格
/*
* 另一段注释
* 这个注释包含2行
*/
// 好的写法
if (condition) {
/*
* 如果代码执行到这里
* 说明通过了所有安全性检查
*/
allowed();
}
// 不好的写法:注释之前无空行
if (condition) {
/*
* 如果代码执行到这里
* 说明通过了所有安全性检查
*/
allowed();
}
// 不好的写法:星号之后没有空格
if (condition) {
/*
*如果代码执行到这里
*说明通过了所有安全性检查
*/
allowed();
}
// 不好的写法:错误的缩进
if (condition) {
/*
* 如果代码执行到这里
* 说明通过了所有安全性检查
*/
allowed();
}
// 不好的写法:代码尾部不要用多行注释
var result = something + somethingElse; /*somethingElse不应当取值为null*/
// 不好的写法:注释并没有提供有价值的信息
// 初始化count
var count = 10;
// 好的写法
// 改变这个值可能会使它变成青蛙
var count = 10;
// 好的写法
if (mode) {
/*
* 当 mode 为2时
* 用来执行原型合并的操作。。。。
* 。。。。
*/
if (mode === 2) {
Y.mix(receiver.prototype, supplier.prototype, overwrite,
whitelis, 0, merge);
}
/*
* 根据模式的类型。。。
* 。。。。
*/
from = mode === 1 || mode === 3 ? supplier.prototype : supperlier;
to = mode === 1 || mode === 4 ? receiver.prototype : receiver;
/*
* .......
* ......
*/
if (!from || !to) {
from = supperlier;
to = receiver;
}
} else {
from = supplier;
to = receiver;
}
while (element && (element = element[asix])) { // 赋值操作
if( (all || element[TAG_NAME]) &&
(!fn || fn(element)) ) {
return element;
}
}
var ret = false;
if ( !needle || !element || !needle[NODE_TYPE] || !element[NODE_TYPE]) {
ret = false;
} else if (element[CONTAINS]) {
// 如果needle不是ELEMENT_NODE时,IE和Safari下会有错误
if (Y.UA.opera || needle[NODE_TYPE] === 1) {
ret = element[CONTAINS](needle);
} else {
ret = Y_DOM._bruteContains(element, needle);
}
} else if (element[COMPARE_DOCUMENT_POSITION]) { // gecko
if (element === needle || !!(element[COMPARE_DOCUMENT_POSITION](needle) & 16)) {
ret = true
}
}
/**
开始,接下来是描述信息,其中@
符号来表示一个或多个属性。
/**
返回一个对象,这个对象包含被提供对象的所有属性。
后一个对象的属性会覆盖前一个对象的属性。
传入一个单独对象。。。。
@method merge
@param {Object} 被合并的一个或多个对象
@param {Object} 一个新的合并后的对象
**/
Y.merge = function() {
var args = arguments,
i = 0,
len = args.length,
result = {};
for (; i<len; ++i) {
Y.mix(result, args[i], true);
}
return result;
};
有2中风格
第一个风格是,将左括号放置在第一句代码的末尾
if (condition) {
doSomething();
} else {
doSomethingElse();
}
第二种风格是将左括号放置于块语句首行的下一行。
if (condition)
{
doSomething();
}
else
{
doSomethingElse();
}
有三种主要的风格
第一种风格,在语句名,圆括号和左花括号之间没有空格间隔。
if(condition){
doSomething();
}
第二种风格,在左圆括号之前和右圆括号之后各添加一个空格
if (condition) {
doSomething();
}
第三种风格,在左圆括号后和右圆括号前各添加一个空格
if ( condition ) {
doSomething();
}
本人和书的作者一样比较推荐第二种风格。
switch
语句的行为和在其他语言中是不一样的:switch语句中可以使用任意类型值,任何表达式都可合法地用于case从句。但在其他语言中则必须使用原始值和常量。很多人用java的风格
switch(condition) {
case "first":
// 代码
break;
case "second":
// 代码
break;
case "third":
// 代码
break;
default:
// 代码
}
独特之处
case
语句相对于switch
关键字都缩进一个层级。case
语句前后各有一个空行。另一种风格
switch(condition) {
case "first":
// 代码
break;
case "second":
// 代码
break;
case "third":
// 代码
break;
default:
// 代码
}
不同之处是case
关键字与switch
保持左对齐,
我和作者都喜欢java的风格。
case
后面不加break
,就会连续执行下面的条件,这个成为很多系统bug的原罪。但是还是有许多人接受这种连续执行的编程方法。但是逻辑一定要写的清晰。
switch(condition) {
// 明显的依次执行
case "first":
case "second":
// 代码
break;
case "third":
// 代码
/* fall through */
default:
// 代码
}
third
里面由于添加了注释,说明这是有意为之。这也是合理的。比较有争论的议题是,是否需要default
,很多人不论何时都不省略default
,尽管它什么也不做。
switch(condition) {
case "first":
// 代码
break;
case "second":
// 代码
break;
default:
// default中没有逻辑
}
作者比较倾向于,没有默认行为,并且写了注释的情况下可以省略
switch(condition) {
case "first":
// 代码
break;
case "second":
// 代码
break;
// 没有 default
}
这样即表明了没有默认行为,又省下了字节。
我比较喜欢第一种,你可以只写default:
不写注释。这样也清楚的知道没有默认行为,以后要加默认行为的时候可以少写一行代码。其实2种方法都可以,就看你喜欢哪一种。
var book = {
title: "shenxf javascrpt",
author: "shenxf"
};
var message = " The book is ";
with (book) {
message += title;
message += " by " + author;
}
title
和author
出现在哪个位置,也难分辨出message
到底是局部变量还是book
的一个属性。从程序上来讲,JavaScript引擎和压缩工具无法对这段代码进行优化,应为它们无法猜出代码的真正含义。
var values = [ 1, 2, 3, 4, 5, 6, 7],
i, len;
for (i=0, len=values.length; i < len; i++) {
process(values[i]);
}
break
和continue
语句,相信大家肯定知道怎么用。continue
,用continue
还不如用条件语句,逻辑更清晰。
var values = [ 1, 2, 3, 4, 5, 6, 7],
i, len;
for (i=0, len=values.length; i < len; i++) {
if (i !== 2) {
process(values[i]);
}
}
hasOwnProperty()
方法来为for-in循环过滤出实例属性。
var prop;
for (prop in object) {
if (object.hasOwnProperty(prop)) {
console.log("Property name is " + prop);
console.log("Property value is " + object[prop]);
}
}
hasOwnProperty
,但是如果你就是想查找原型链,请加上注释。
var prop;
for (prop in object) { // 包含对原型链的遍历
console.log("Property name is " + prop);
console.log("Property value is " + object[prop]);
}
// 不好的用法
var values = [ 1, 2, 3, 4, 5 ],
i;
for (i in values) {
process(item[i]);
}
function doSomething() {
var result = 10 + value;
var value = 10;
return result;
}
上面代码的理解
function doSomething() {
var result;
var value
result = 10 + value;
value = 10;
return result;
}
这个函数得出的结果是NaN值。
作者倾向的风格,把所有定义放在作用域开始部分,用逗号分隔,可以节省写var
的字节。
function doSomethingWithItems(items) {
var value = 10,
result = value + 10,
i,
len;
for (i=0, len=items.length; i < len; i++) {
doSomething(items[i]);
}
}
函数声明也会被javaScript引擎提前。
// 不好的写法
doSomething();
function doSomething() {
alert('Hello world!');
}
javaScript会把代码解释为
function doSomething() {
alert('Hello world!');
}
doSomething();
我们应该先声明函数再使用
function doSomethingWithItems(items) {
var i,len,
value = 10,
result = value + 10;
function doSomething(item) {
// 代码逻辑
}
for (i=0, len=items.length; i < len; i++) {
doSomething(items[i]);
}
}
另外,函数不应该出现在语句块之内。
// 不好的写法
if (condition) {
function doSomething() {
alert('hi');
}
} else {
function doSomething() {
alert('Yo');
}
}
// 好的写法
doSomething(item);
// 不好的写法:看起来像一个语句块
doSomething (item);
// 用来做对比的块语句
while (item) {
// 代码逻辑
}
// 一般的写法
var doSomething = function() {
// 函数体
};
// 不好的写法 : 被误解为函数的赋值,其实是被赋值为一个对象
var value = function() {
// 函数体
return {
message: "Hi"
}
}();
// 好的写法
var value = (function() {
// 函数体
return {
message: "Hi"
}
}());
use strict
// 不好的写法 - 全局严格模式
"use strict"
function doSomething() {
// 代码
}
// 好的写法
function doSomething() {
"use strict"
// 代码
}
// 好的写法
(function() {
"use strict"
function doSomething() {
// 代码
}
function doSomethingElse() {
// 代码
}
})();
// 比较数字5和字符串5
console.log(5 == "5"); // true
// 比较数字25和十六进制的字符串25
console.log(25 == "0x19"); // true
发生强制转换的时候字符串会被转换为数字,类似使用Number()
转换函数。它能正确转换十六进制的数字。所以第二个表达式是相等的。
布尔值和数字比较
// 数字 1 和 true
console.log(1 == true); // true
// 数字 0 和 false
console.log(0 == false); // true
// 数字 2 和 true
console.log(2 == true); // false
如果其中一个是对象另一个不是,则会先调用valueOf()
方法得到原始类型再进行比较,如果没有valueOf()
,则调用toString()
。
var object = {
toString: function() {
return "0x19";
}
};
console.log(object == 25); // true
根据ECMAScript标准规范的描述,null
和undefined
是相等的。
console.log(null == undefined); // true
由于以上强制类型转换的原因,推荐不要使用==
和!=
,应当使用===
和!==
,它们不会类型转换只要类型不一样就返回false
// 比较数字5和字符串5
console.log(5 == "5"); // true
console.log(5 === "5"); // false
// 比较数字25和十六进制的字符串25
console.log(25 == "0x19"); // true
console.log(25 === "0x19"); // false
// 数字 1 和 true
console.log(1 == true); // true
console.log(1 === true); // false
// 数字 0 和 false
console.log(0 == false); // true
console.log(0 === false); // false
// 数字 2 和 true
console.log(2 == true); // false
console.log(2 === true); // false
var object = {
toString: function() {
return "0x19";
}
};
// 一个对象和25
console.log(object == 25); // true
console.log(object === 25); // false
// null和undefined
console.log(null == undefined); // true
console.log(null === undefined); // false
也可以直接把pdf转成png文件,然后用ocr抓取文字。
抓取英文 tesseract xxx.png output_1 -l eng
抓取中文 tesseract xxx.png output_1 -l chi_sim