这一篇就会讨论如何写成高质量的JavaScript代码,如避免使用全局变量,仅使用一个var声明变量,提前获取length在循环的时候,编码约定等;
还有一些其他的习惯和技巧,写JavaScript API 文档等。
myglobal = "hello"; // antipattern
console.log(myglobal); // "hello"
console.log(window.myglobal); // "hello"
console.log(window["myglobal"]); // "hello"
console.log(this.myglobal); // "hello"
function sum(x, y) {
// antipattern: implied global
result = x + y;
return result;
}
上面这段代码中,result未经声明就直接使用,这段代码功能是完全可以的,但当函数调用结束之后,就多了一个result全局变量,这可能会造成其他问题;
经验告诉我们声明一个变量一定要带上var,像下面这样;
function sum(x, y) {
var result = x + y;
return result;
}
但要注意的是,即使你使用var声明变量了,但也有可能无意中造就一个全局变量,就想下面一样(实际中可能真会这么干),a是局部的,但b是全局的;
// antipattern, do not use
function foo() {
var a = b = 0;
// ...
}
表达式b=0赋值,在这种情况下b是未声明(定义)的,所以b会成为一个全局变量;
返回结果是0然后再赋值给a(使用var声明的)这个局部变量;
上面的代码就等同于:var a = (b = 0);
如何你已经声明了变量,使用链式赋值就没有问题了,就不会无意中产生新的全局变量;
function foo() {
var a, b;
// ...
a = b = 0; // both local
}
另一个原因避免全局变量是为了移植性,如果你想你的代码运行在不一样的环境(host)中,使用全局变量是非常危险的,因为它可能无意中
重写global对象的属性,而在你原来的环境中不存在。你认为变量的名称(name)是安全的,但其实不然。
// 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"
var global = (function() {
return this;
} ());
function func() {
var a = 1,
b = 2,
sum = a + b,
myobject = {},
i, j;
// function body...
}
可以使用一个var声明多个变量,中间用逗号隔开;这也是个好机会去初始化变量,可以防止一些逻辑错误(所有声明但未初始化的变量,初始值都是undefined),增加代码可读性;
function updateElement() {
var el = document.getElementById("result"),
style = el.style;
// do something with el and style...
}
// antipattern
myname = "global"; // global variable
function func() {
alert(myname); // "undefined"
var myname = "local";
alert(myname); // "local"
}
func()
在这个例子中,你可能期望第一个alert()提示“global”并且第二个alert()提示“local”;这是个合情合理的判断,因为在第一次alert()时,myname并没有被声明,
myname = "global"; // global variable
function func() {
var myname; // same as -> var myname = undefined;
alert(myname); // "undefined"
myname = "local";
alert(myname); // "local"
}
func();
// sub-optimal loop
for (var i = 0; i < myarray.length; i++) {
// do something with myarray[i]
}
这种模式有个问题就是在每次循环都要访问一次数组的长度,这会减慢你的代码执行的速度,当myarray不是一个数组而是一个HTMLCollection对象时;
for (var i = 0, max = myarray.length; i < max; i++) {
// do something with myarray[i]
}
这种方法,只需要访问一次length的值,在整个for循序过程中都可以使用;
function looper() {
var i = 0,
max, myarray = [];
// ...
for (i = 0, max = myarray.length; i < max; i++) {
// do something with myarray[i]
}
}
这样的好处就是严格遵守使用一个var声明变量的原则,缺点就是复制和粘贴整个for循序变的复杂,好比重构的时候,当你从一个函数复制一个for循序到另外一个函数,
var i, myarray = [];
for (i = myarray.length; i--;) {
// do something with myarray[i]
}
var myarray = [],
i = myarray.length;
while (i--) {
// do something with myarray[i]
}
// the object
var man = {
hands: 2,
legs: 2,
heads: 1
};
// somewhere else in the code
// a method was added to all objects
if (typeof Object.prototype.clone === "undefined") {
Object.prototype.clone = function() {};
}
在这个例子中,我们用对象字面量(object literal)声明的简单对象man;在其它什么地方或者在man声明后面,Object对象的原型增加了一个有用方法clone();
// 1.
// for-in loop
for (var i in man) {
if (man.hasOwnProperty(i)) { // filter
console.log(i, ":", man[i]);
}
}
/*
result in the console
hands : 2
legs : 2
heads : 1
*/
// 2.
// antipattern:
// for-in loop without checking hasOwnProperty()
for (var i in man) {
console.log(i, ":", man[i]);
}
/*
result in the console
hands : 2
legs : 2
heads : 1
clone: function()
*/
18
另外一种调用hasOwnProperty()方法是从Object.prototype对象身上调用;for (var i in man) {
if (Object.prototype.hasOwnProperty.call(man, i)) { // filter
console.log(i, ":", man[i]);
}
}
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
if (hasOwn.call(man, i)) { // filter
console.log(i, ":", man[i]);
}
}
if (typeof Object.protoype.myMethod !== "function") {
Object.protoype.myMethod = function() {
// implementation...
};
}
var inspect_me = 0,
result = '';
switch (inspect_me) {
case 0:
result = "zero";
break;
case 1:
result = "one";
break;
default:
result = "unknown";
}
var zero = 0;
if (zero === false) {
// not executing because zero is 0, not false
}
// antipattern
if (zero == false) {
// this block is executed...
}
也有人认为当使用 == 是足够的的时候使用 === 是多余的,好比你在使用typeof的时候,明确知道它会返回一个字符串,所以没有理由去使用严格的相等(===);
// antipattern
var property = "name";
alert(eval("obj." + property));
// preferred
var property = "name";
alert(obj[property]);
使用eval()也会有安全性的影响,因为你有可能执行被篡改的代码(比如从网上获取到的),有一个常见的不好的模式就是处理Ajax请求的JSON反馈(response);
// antipatterns
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);
// preferred
setTimeout(myFunc, 1000);
setTimeout(function() {
myFunc(1, 2, 3);
},
1000)
使用new Function()构造函数和eval()类似,要格外小心;这是个强大的构造方法但是又经常被滥用;如果你就绝对必须要使用eval(),你可以考虑用new Function() 替代;
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构造方法另一个不同之处就是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
} ());
eval()就是执行传入的代码,和写在该处的效果一模一样;而Function则是声明了一个新的函数(在全局作用域中定义的),并且该处调用它;var month = "06",
year = "09";
month = parseInt(month, 10);
year = parseInt(year, 10);
在这个例子中,如果你忽略了基数,好比parseInt(year),那么返回的是0,因为“09”被假定为8进制,但“09”不是一个合法的8进制数;+"08" // result is 8
Number("08") // 8
这些通常都比parseInt()快,就像parseInt()名字一样,parse(解析)而不是简单的转换(convert),但是如果你想把“08 hello”传递给parseInt()将会返回一个整数,但是其它的都会返回NaN。
function outer(a, b) { //我的的的确确缩进了,难看不是我的错
var c = 1,
d = 2,
inner;
if (a > b) {
inner = function() {
return {
r: c - d
};
};
} else {
inner = function() {
return {
r: c + d
};
};
}
return inner;
}
// bad practice
for (var i = 0; i < 10; i += 1)
alert(i);
但是,稍后如果你在循环体中又加入一行会怎样呢?// bad practice
for (var i = 0; i < 10; i += 1)
alert(i);
alert(i + " is " + (i % 2 ? "odd" : "even"));
第二个alert就会在for循环体之外,尽管这个缩进可能会欺骗你;
// better
for (var i = 0; i < 10; i += 1) {
alert(i);
}
if也是类似的:// bad
if (true)
alert(1);
else
alert(2);
// better
if (true) {
alert(1);
} else {
alert(2);
}
if (true) {
alert("It's TRUE!");
}
if (true)
{
alert("It's TRUE!");
}
在这个特定的例子中,括号在什么地方都没有影响,但有些情况下大括号的位置不同会导致不同的结果;
// warning: unexpected return value
function func() {
return {
name: "Batman"
};
}
如果你想这个函数返回一个对象——有个一个name属性,你会非常吃惊!因为分号插入机制,这个函数会返回undefined,上面的代码等同于:// warning: unexpected return value
function func() {
return undefined;
// unreachable code follows...
{
name: "Batman"
};
}
在函数结束的时候,要使用大括号的话,一定要将大括号放在同一行;function func() {
return {
name: "Batman"
};
}
for (var i = 0;i < 10;i += 1) {...}
for (var i = 0,max = 10; i < max; i += 1) {...}
var a = [1, 2, 3];
var o = {a:1, b: 2};
myFunc(a, c)
function myFunc() {}
var myFunc = function() {};
// generous and consistent spacing
// makes the code easier to read
// allowing it to "breathe"
var d = 0,
a = b + 1;
if (a && b && c) {
d = a % c;
a += d;
}
// antipattern
// missing or inconsistent spaces
// make the code confusing
var d = 0,
a = b + 1;
if (a && b && c) {
d = a % c;
a += d;
}
常见的命名方式有骆驼命名法:
大写骆驼命名法,变量或函数名每个单词首字母大写,一般适用于构造函数与其它普通方法以示区别;
var PI = 3.14,
MAX_WIDTH = 800;
还有用命名规范去表示私有成员,虽然你使用JavaScript实现真正的私有成员,但是程序猿发现使用一个前缀去标识私有属性和方法更加简单;var person = {
getName: function() {
return this._getFirst() + ' ' + this._getLast();
},
_getFirst: function() {
// ...
},
_getLast: function() {
// ...
}
}
在这个例子中,getName()是一个公共方法,_getFirst()和_getLast()有意作为私有方法;
/**
* Reverse a string
*
* @param {String} input String to reverse
* @return {String} The reversed string
*/
var reverse = function(input) {
// ...
return output;
};
/**
* Constructs Person objects
* @class Person
* @constructor
* @namespace MYAPP
* @param {String} first First name
* @param {String} last Last name
*/
MYAPP.Person = function(first, last) {
/**
* Name of the person
* @property first_name
* @type String
*/
this.first_name = first;
/**
* Last (family) name of the person
* @property last_name
* @type String
*/
this.last_name = last;
};
/**
* Returns the name of the person object
*
* @method getName
* @return {String} The name of the person
*/
MYAPP.Person.prototype.getName = function() {
return this.first_name + ' ' + this.last_name;
};