一. 尽量少用全局变量
Javascript 使用的是函数作用域,在函数内声明的变量,只有在函数内有效,不能在函数外使用。全局变量则是在函数外部声明,在函数内无需声明就可以使用。
每一个javascript环境都有一个全局对象,在函数外部使用this进行访问。创建的全局变量都归全局对象所有。
在浏览器中,使用window表示全局变量本身
1 | myName = "haha" |
全局变量的产生
javascript的两个特性总让我们在不知不觉中就创建了全局变量
可直接使用变量。甚至无需声明
javascript有个隐含全局变量。即任何变量,如果未经声明,就为全局对象所有
例1:
1
2
3
4function sum() {
result = x + y;
return result;
}结果:例子中result未经声明, 归全局对象所有,在一般情况下可以正常使用, 但是在相同的全局命名空间使用了另外的result 变量, 就会有问题。
带有var声明的链式赋值有可能导致隐含全局变量
面的例子中你估计想要的结果?
例2:
1
2
3function foo() {
var a = b = 0;
}结果:a是局部变量,b是全局变量
原因:从右到左的操作符优先级。首先是优先级较高的表达式b=0,此时b未经声明(归全局对象所有)。表达式的返回值是0, 被赋给了var声明的局部变量a。
全局变量的问题
全局变量存在于同一个全局命名空间内,很有可能发生命名冲突
变量释放时的副作用
隐含全局变量与明确定义的全局变量有细微的不同。
使用var创建的全局变量(这类变量在函数外部创建),不可以使用delete操作符撤销变量
不使用var创建的隐含全局变量(即使它是在函数内部创建),也可以使用delete操作符撤销变量
例3:
1
2
3
4
5
6
7
8
9
10
11
12var a = 1;
b = 2;
(function foo() {
c = 3;
}());
// 企图删除
delete a; //false
delete b; //true
delete c; //true
// 测试删除情况
console.log(typeof a, typeof b, typeof c)
// "number", "undefined", "undefined"
这说明隐含全局变量不是真正的变量。而是只是全局对象的属性,属性是可以通过delete操作符删除的,但是变量不可以
如何最小化全局变量的数量
- 命名空间模式
- 即时函数
- 使用var声明变量
最重要的方式还是使用var声明。上面的例子改造如下:
例1:
1 | function sum() { |
例2:
1 | function foo() { |
二. 变量声明提升(凌散变量)的问题
javascript允许在任何地方声明变量,无论在哪里声明,效果都等同于在函数顶部进行声明。这就是变量声明提升。注意:提升的只是声明部分,赋值部分不提升。
1 | a = "shu" |
结果:‘undefined’,’ liqi’
原因:在函数getName作用域内,a被看做函数作用域内的变量。函数中所有的变量声明都会被提升的函数的最顶成,但是赋值部分位置不变。所以导致log出:’undefined’, liqi。
1 | name = "shu" |
结论
为了避免的这样的混乱, 我们最好在开始就声明要用的变量。
三. for循环
for循环常用在数组或者类数组对象(伪数组)上面。类对象数组例如:arguments,HTML DOM对象:
1 | document.getElementsByName() |
通常的for循环使用:
1 | for(var i = 0; i < arr.length; i++) { |
这种写法在于每次循环都需要访问数组的长度。这样会使代码变慢。特别当arr不是数组,是HTML DOM
对象的时候。更耗时。
第一种改进的方案:
1 | for(var i = 0, len = arr.length; i < len; i++) { |
这种方式下, 对长度的值只提取一次。但是可以应用的整个循环。
第二种改进的方案:
逐步减到0,因为同0比较比同数组的长度比较(同非0数组)比较更有效率
1 | for(var i = arr.length; i--;) { |
两种方式的比较:
1 | var arr = [1,2,3,4] |
但是也存在一些缺点,就是处理的顺序倒过来了。
四. for-in循环
for-in是用来循环非数组对象的。当遍历对象属性时遇到原型链的属性时,使用hasOwnProperty()方法来过滤是非常重要的。
1 | var obj = { |
为了避免在枚举的时候出现clone()。 需要调用hasOwnproperty()方法来过滤原型链属性。
1 | for (var key in obj) { |
结果: name : shu, age : 12
五.避免使用隐式类型转换
#####1.字符串连接符与算术运算符(+ - * / %)隐式转换规则
1 | console.log( 1 + 'true') |
结果会是什么样的?
转换规则:
1.字符串连接符(“+”两边有一边是字符串):会把其他数据类型调用String()方法然后拼接。
2.运算操作符(除了不是字符串连接符的”+”就都是是运算操作符):会把其他数据类型调用Number()方法转成数字然后做运算。
所以例子的结果是:
1 | // "+“ 是字符串连接符:String(1) + 'true' = '1true' |
2.关系运算符( > < >= <= == != === !==):会把其他数据类型转换成number之后再比较关系
1 | console.log('2' > 10) |
结果会是什么样的呢?
转换规则:
1.关系运算符两边有一边是字符串,会将其使用Number()转成数字,然后比较关系。
2.关系运算符两边都是字符串,两边按照字符串对应的unicode编码(可以使用charCodeAt()查看)转成数字,然后比较。
3.当有多个字符串进行比较,依次从左到右比较。
4.如果数据类型是null,undefined 不是严格比较两者都是相等的。
5.NaN 类型与任何数据类型比较都是NaN
所以例子的结果是:
1 | // false Number(2) > 10 = 2 > 10 = false |
结论
JavaScript在语句比较时会执行隐式类型转换(除了严格比较)。 有时候自己理不清。最好避免使用。使用就最好使用严格比较如:===, !==。
六. 避免使用eval()
eval函数的作用: 在当前作用域中执行一段JavaScript代码字符串。
1 | var one = 1; |
不推荐使用使用eval()的原因
1. eval()可以访问和修改它外部作用域的变量
1 | function myFunOne() { |
2.eval() 只在被直接调用并且调用函数就是 eval ()本身时,才在当前作用域中执行, 否则就是在全局作用域执行
1 | var one = 1; |
这段代码等价于在全局作用域中调用 eval。
3.安全问题
eval 也存在安全问题,因为它会执行任意传给它的代码字符串, 在代码字符串未知或者是来自一个不信任的源时就会有安全问题。
结论
绝对不要使用 eval,任何使用它的代码都会在它的工作方式,性能和安全性方面受到质疑。 如果一些情况必 须使用到 eval 才能正常工作,首先它的设计会受到质疑,这不应该是首选的解决方案。
eval() 的替代方式
new Fuction()构造函数和eval()比较类似。如果一定需要使用eval(),那么可以考虑使用new Fuction()来代替eval()。这样的好处是:new Function()中的代码将在局部函数空间中运行, 因此代码中的任何采用var定义的变量不会自定成为全局变量。
1 | console.log(typeof one); // "undefine" |
注意:
setInterval(),setTimeout,function()等构造函数来传递参数。在特殊情况下,会导致类似eval()的隐患。
1 | function myFun() { |
七. 不要增加内置的原型
增加内置构造函数(Object(),Array(),Fuction()等)的原型,但是这可能会严重影响可维护性。因为这种做法使得代码更加不可预测。其他开发者在使用你的代码的时候可能期望的内置的Javascript方法,而不是期望有一些你自己添加的方法。
并且,给原型添加属性在没有使用hasOwnproperpty()时可能会在循环中出现。这会导致一些混乱。
八. switch模式:
可以使用一下switch模式来提高代码的的可读性和健壮性
1 | var type = 0; |
注意:每一个case 需要有一个明确的break语句