参考:http://javascript.ruanyifeng.com/
1、字符串
1)由于 HTML 语言的属性值使用双引号,所以很多项目约定 JavaScript 语言的字符串只使用单引号,本教程遵守这个约定。当然,只使用双引号也完全可以。重要的是坚持使用一种风格,不要一会使用单引号表示字符串,一会又使用双引号表示。
2)反斜杠()在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符。
如果字符串的正常内容之中,需要包含反斜杠,则反斜杠前面需要再加一个反斜杠,用来对自身转义。【"Prev \ Next"=》
"Prev Next"
】
3)字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。
var str = 'hello'; console.log(str[0],str[5]); // h undefined str[5] = '!'; console.log(str); // hello
4)length
属性返回字符串的长度,该属性也是无法改变的。
var str = 'hello'; console.log(str.length); // 5 str.length = 3; console.log(str.length); // 5
2、对象
var obj = { p:1, q:2 }; var props = []; for(var p in obj) { props.push(p); } console.log(props); // ['p','q'] console.log(Object.keys(obj)); // ['p','q']
3、数组
1)数组的length
属性,返回数组的成员数量。length
属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到length
设置的值。
var arr = [ 'a', 'b', 'c' ]; arr.length // 3 arr.length = 2; arr // ["a", "b"]
清空数组的一个有效方法,就是将length
属性设为0。
var arr = [ 'a', 'b', 'c' ]; arr.length = 0; // 等价于arr = [] arr // []
2)数组的遍历可以考虑使用for
循环或while
循环;数组的forEach
方法,也可以用来遍历数组。
var colors = ['red', 'green', 'blue']; colors.forEach(function (color) { console.log(color); }); // red // green // blue
4、函数
1)函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值。这意味着,在函数体内修改参数值,不会影响到函数外部。
var p = 2; function f(p) { p = 3; } f(p); p // 2
如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
var obj = { p: 1 }; function f(o) { o.p = 2; } f(obj); obj.p // 2
注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
var obj = [1, 2, 3]; function f(o) { o = [2, 3, 4]; } f(obj); obj // [1, 2, 3]
/*在函数f
内部,参数对象obj
被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(o
)的值实际是参数obj
的地址,重新对o
赋值导致o
指向另一个地址,
保存在原地址上的值当然不受影响。*/
2)arguments 对象
由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments
对象的由来。arguments
对象包含了函数运行时的所有参数,arguments[0]
就是第一个参数,arguments[1]
就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。
function f(a) { console.log(arguments) } f(1,2,3) // [1, 2, 3]
通过arguments
对象的length
属性,可以判断函数调用时到底带几个参数。
3)闭包
由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
4)立即调用的函数表达式
有时,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。为了避免解析上的歧义,JavaScript 引擎规定,如果function
关键字出现在行首,一律解释成语句。因此,JavaScript引擎看到行首是function
关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。解决方法就是不要让function
出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。
(function(){ /* code */ }()); // 或者 (function(){ /* code */ })();
两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义语句,所以就避免了错误。这就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称 IIFE。注意,上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个 IIFE,就会报错。
通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
// 写法一 var tmp = newData; processData(tmp); storeData(tmp); // 写法二 (function () { var tmp = newData; processData(tmp); storeData(tmp); }()); // 写法二比写法一更好,因为完全避免了污染全局变量
5、运算符
1)任何值(包括NaN
本身)与NaN
比较,返回的都是false
。
NaN == NaN // false
2)(严格)相等运算符
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址。
{} === {} // false [] === [] // false (function () {} === function () {}) // false
如果两个变量引用同一个对象,则它们相等。
var v1 = {}; var v2 = v1; v1 === v2 // true
3)布尔运算符
3.1)取反运算符(!)
如果对一个值连续做两次取反运算,等于将其转为对应的布尔值,与Boolean
函数的作用相同。这是一种常用的类型转换的写法。
!!x // 等同于Boolean(x)
3.2)且运算符(&&)
't' && '' // "" 't' && 'f' // "f" 't' && (1 + 2) // 3 '' && 'f' // "" var x = 1; (1 - 1) && ( x += 1) // 0 x // 1
上面代码的最后一个例子,由于且运算符的第一个运算子的布尔值为false
,则直接返回它的值0
,而不再对第二个运算子求值,所以变量x
的值没变。
这种跳过第二个运算子的机制,被称为“短路”。有些程序员喜欢用它取代if
结构
if (i) { doSomething(); } // 等价于i && doSomething();
上面代码的两种写法是等价的,但是后一种不容易看出目的,也不容易出错,建议谨慎使用。
3.3)或运算符(||)
't' || '' // "t" 't' || 'f' // "t" '' || 'f' // "f" '' || '' // ""
var x = 1; true || (x = 2) // true x // 1
短路规则对这个运算符也适用。上面代码中,且运算符的第一个运算子为true
,所以直接返回true
,不再运行第二个运算子。所以,x
的值没有改变。这种只通过第一个表达式的值,控制是否运行第二个表达式的机制,就称为“短路”。
或运算符常用于为一个变量设置默认值。
function saveText(text) { text = text || ''; } // 如果函数调用时,没有提供参数,则该参数默认设置为空字符串
3.4)圆括号的作用
圆括号(()
)可以用来提高运算的优先级,因为它的优先级是最高的。
4 + 5 * 6 // 34 (4 + 5) * 6 // 54 // 由于使用了圆括号,加法会先于乘法执行
运算符的优先级别十分繁杂,且都是硬性规定,因此建议总是使用圆括号,保证运算顺序清晰可读,这对代码的维护和除错至关重要。
6、数据类型转换
1)强制转换
1.1)Number
函数将字符串转为数值,要比parseInt
函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN
。
parseInt('42 cats') // 42 Number('42 cats') // NaN
1.2)String
函数可以将任意类型的值转化成字符串
String(123) // "123" String('abc') // "abc" String(true) // "true" String(undefined) // "undefined" String(null) // "null"
String
方法的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。
String({a: 1}) // "[object Object]" String([1, 2, 3]) // "1,2,3"
1.3)Boolean
函数可以将任意类型的值转为布尔值。
所有对象的布尔值都是true
,这是因为 JavaScript 语言设计的时候,出于性能的考虑,如果对象需要计算才能得到布尔值,对于obj1 && obj2
这样的场景,可能会需要较多的计算。为了保证性能,就统一规定,对象的布尔值为true
。
Boolean({}) // true Boolean([]) // true
2)自动转换
由于自动转换具有不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用Boolean
、Number
和String
函数进行显式转换。
7、错误处理机制
一旦发生错误,程序就中止执行了。JavaScript 提供了try...catch
结构,允许对错误进行处理,选择是否往下执行。如果你不确定某些代码是否会报错,就可以把它们放在try...catch
代码块之中,便于进一步对错误进行处理。catch代码块捕获错误之后,程序不会中断,会按照正常流程继续执行下去
// a //Uncaught ReferenceError: a is not defined // 上一行报错,程序中止执行了 try { a } catch(e) { console.log(e); // ReferenceError: a is not defined } console.log('try catch'); // try catch
8、编程风格
1)全局变量
JavaScript 最大的语法缺点,可能就是全局变量对于任何一个代码块,都是可读可写。这对代码的模块化和重复使用,非常不利。
因此,建议避免使用全局变量。如果不得不使用,可以考虑用大写字母表示变量名,这样更容易看出这是全局变量,比如UPPER_CASE
。
2)变量声明
JavaScript 会自动将变量声明”提升“到代码块的头部。
if (!x) { var x = {}; } // 等同于 var x; if (!x) { x = {}; }
这意味着,变量x
是if
代码块之前就存在了。为了避免可能出现的问题,最好把变量声明都放在代码块的头部。
for (var i = 0; i < 10; i++) { // ... } // 写成如下,容易看出存在一个全局的循环变量i var i; for (i = 0; i < 10; i++) { // ... }
另外,所有函数都应该在使用之前定义。函数内部的变量声明,都应该放在函数的头部。
9、包装对象
原始类型的值,可以自动当作对象调用,即调用各种对象的方法和参数。这时,JavaScript 引擎会自动将原始类型的值转为包装对象实例,在使用后立刻销毁实例。比如,字符串可以调用length
属性,返回字符串的长度。
'abc'.length // 3 /* abc是一个字符串,本身不是对象,不能调用length属性。JavaScript 引擎自动将其转为包装对象,在这个对象上调用length属性。
调用结束后,这个临时对象就会被销毁。这就叫原始类型与实例对象的自动转换。*/
自动转换生成的包装对象是只读的,无法修改。所以,字符串无法添加新属性。
var s = 'Hello World'; s.x = 123; s.x // undefined /* 调用结束后,包装对象实例会自动销毁。这意味着,下一次调用字符串的属性时,实际是调用一个新生成的对象,
而不是上一次调用时生成的那个对象,所以取不到赋值在上一个对象的属性。*/
10、JSON
对象
JSON
对象是 JavaScript 的原生对象,用来处理 JSON 格式数据。它有两个静态方法:JSON.stringify()
和JSON.parse()
。
JSON.stringify
方法用于将一个值转为 JSON 字符串。该字符串符合 JSON 格式,并且可以被JSON.parse
方法还原。如果对象的属性是undefined
、函数或 XML 对象,该属性会被JSON.stringify
过滤。如果数组的成员是undefined
、函数或 XML 对象,则这些值被转成null
。
var obj = { a: undefined, b: function () {} }; JSON.stringify(obj) // "{}" var arr = [undefined, function () {}]; JSON.stringify(arr) // "[null,null]"
JSON.parse
方法用于将 JSON 字符串转换成对应的值。如果传入的字符串不是有效的 JSON 格式,JSON.parse
方法将报错。