变量
Javascript是一门解释型的动态语言。
所谓解释型,区别于 C/C++/Java
等编译型语言,并不会在运行将源代码编译为类文件,而是在运行时动态解析,边解释边运行。
所谓动态,即是在 编写代码时时,不用强制声明变量的类型,而是在执行时,动态确定变量的类型。动态语言的特性,确定了Javascript这门语言 随性 的气质,也是Javascript的灵动飘逸所在。
理解Javascript的精髓,首先应该理解Javascript中的变量。
声明与定义
var a = 1;
这是一句最简单的变量定义。而实际上基本等价于做了两个操作:声明变量a,然后将数值1复制给变量a。
var a;
a = 1;
var a;
称为 变量的声明。如果只是对变量声明,而未赋初始值,则默认的值为 undefined
。
a = 1;
为对变量的赋值。将数值1 赋值给 变量a。
在JavaScript中,允许省略var,直接对未声明的变量赋值。也就是说,var a = 1;
与 a = 1;
,这两条语句的在执行时的效果看上去是相同的。
但是,需要特别注意的是,这两种方法并不完全相同。
如果对变量未声明而直接赋值的话(如 a = 1;
),此时的变量将定义为 全局变量(即便语句在函数内部)!此时往往会不知不觉的造成 全局变量污染。所以一定要使用关键字 var
对变量先进行声明。
还有一种写法也会造成类似的问题,如:
function foo() {
var a = b = 0;
// ...
}
这段代码,声明了一个 局部变量a
与一个 全局变量b
。
原因是这里的计算顺序是从右至左的。首先计算表达式 b=0
,这里的b是未声明的。然后通过var创建了 局部变量a
,并赋值为0。等价于:
var a = (b = 0);
原则1:变量要先声明再赋值,不要忘记使用var。
动态类型
Javascript中变量的一大特点,即为动态类型。变量的类型没有限制,可以赋予各种类型的值。
var a = 1;
a = "hello";
在首次赋值时,变量 a
为数值型,而赋值为 hello
后,则变为字符串型。
变量提升(hoisting)
变量提升的概念并不难理解,但却很容易被忽略。先看这么一段代码:
var myvar = "my value";
(function() {
console.log(myvar);
var myvar = 'local value';
})();
在 console.log(myvar)
时,结果是什么呢?答案是 undefined
。不知道是否和你想的一样。
在上面的例子中,结果为 undefined
原因,正是因为 变量提升这个概念。
在Javascript中,会自动将变量的声明进行提升。这意味着 var表达式
和 function 声明
都将会被提升到当前作用域的顶部。在上文的例子中,实际的执行顺序,我们可以理解为这样:
var myvar = "my value";
(function() {
var myvar;
console.log(myvar);
myvar = 'local value';
})();
所以便不难理解,为什么会输出 undefined
,因为变量的声明已经被提升了。
为了避免这种情况可能带来的问题,我们应该将变量的声明显示的放在作用域最上方。这样做的好处有:
- 在同一个位置可以查找到函数所需的所有变量
- 避免当在变量声明之前使用这个变量时产生的逻辑错误
- 提醒你不要忘记声明变量,顺便减少潜在的全局变量
- 代码量更少(输入更少且更易做代码优化)
原则2:在作用域开始时,先声明好所有可能需要使用的变量。
区块(block)
JavaScript支持一对花括号创建的代码段,花括号所包裹的部分,就称为 区块
。
与 C
等语言中,区块使用独立的作用域不同的是,在Javascript中,区块可以说是 然而并没有什么卵用。区块中的变量与区块外的变量,属于同一个作用域。
{
var a = 1;
}
console.log(a); // 1
原则3:只在
function
、if
、switch
、while
、for
、do
、try
等有意义的代码块中使用花括号。
条件语句
JavaScript提供 if结构
和 switch结构
,完成条件判断。
比较运算符
Javascript中,一般有两种运算符。严格运算符 ===
与非严格运算符 ==
。
使用 ===
时,等式两边不会进行隐式转换,而是直接比较,只有等式两边数值与类型完全一样时,才返回 true
。
使用 ==
时,在比较前会将两个被比较的值转换为相同类型,然后再进行比较。
var a = "1";
var b = 1;
a == b // true
a === b // false
表达式的值
下面这些值将被计算为 false
:
- false
- undefined
- null
- 0
- NaN
- 空字符串 (“”)
当传递给条件语句时,所有其他值,包括所有 对象 会被计算为 true
。
switch结构
下例为switch的标准代码风格。参考自JavaScript模式一书。
var inspect_me = 0,
var result = '';
switch (inspect_me) {
case 0:
result = "zero";
break;
case 1:
result = "one";
break;
default:
result = "unknown";
}
- 每个case和switch对齐(这里不考虑花括号相关的缩进规则)
- 每个case中的代码整齐缩进
- 每个case都以break作为结束
- 避免连续执行多个case语句块(当省略break时会发生),如果你坚持认为连续执行多case语句块是最好的方法,请务必补充文档说明,对于其他人来说,这种情况看起来是错误的
- 以default结束整个switch,以确保即便是在找不到匹配项时也会有正常的结果
循环
Javascript中的循环与其他编程语言没有太大区别。主要有 while
、do...while
、for
循环三种。重点说一下 for循环
的两种形式。
基本for循环
在 基本for循环
中,可以对数组或类似数组的对象作遍历。
for (var i = 0; i < myArray.length; i++) {
// do something with myarray[i]
}
但是这样的使用方式,在某些情况下可能并不是最佳实践。因为每次进入循环时,都会读取 myArray.length
属性。当 myArray
为一个 HTMLCollection 对象时,如 document.images
、 document.forms
时,每次循环都会先调用dom的查询操作,非常耗费资源。
最佳实践是这种形式:
var i = 0,
var max,
for (i = 0, max = myArray.length; i < max; i++) {
// do something with myarray[i]
}
使用变量 max
记录 myArray
的长度。同时将 i
与 max
都使用 var
单独进行声明。
for-in循环
for-in
循环用于对非数组对象作遍历。通过for-in进行循环也被称作 枚举。
对对象使用 for-in
循环,可以遍历对象的所有属性,也包括从 原型链上继承 的的属性。如果不想包含从原型链上继承的的属性,需要使用 hasOwnProperty
方法。
Object.prototype.bar = 1;
var foo = {moo: 2};
/* 不使用hasOwnProperty过滤 */
for(var i in foo) {
console.log(i); // 输出两个属性:bar 和 moo
}
/* 使用hasOwnProperty过滤 */
for(var i in foo) {
if (foo.hasOwnProperty(i)) {
console.log(i);// 只输出属性moo
}
}