一、语法
1、基础语法
-
区分大小写
- 无论是变量、函数名还是操作符,都区分大小写。换句话说,变量
test
和变量Test
是两个不同的变量。
- 无论是变量、函数名还是操作符,都区分大小写。换句话说,变量
-
标识符(变量) 命名
- 由 字母、下划线(_)或美元符号($) 组成,首字母必须非数字。
- 标识符中的字母可以是扩展
ASCII
(Extended ASCII)中的字母,也可以是Unicode
的字母字符,如 À 和 Æ(但不推荐使用)。 - 推荐驼峰命名,如:
userInfo
。因为这种形式跟 ECMAScript 内置函数和对象的命名方式一致。
注意:关键字、保留字、
true
、false
和null
不能作为标识符。 -
注释
- 单行注释:
// ...
- 多行注释:
/* ... */
使用热键!
在大多数的编辑器中,一行代码可以使用
Ctrl+/
热键进行单行注释,诸如Ctrl+Shift+/
的热键可以进行多行注释(选择代码,然后按下热键)。对于 Mac 电脑,应使用
Cmd
而不是Ctrl
,使用Option
而不是Shift
。 - 单行注释:
-
语句
- 以 分号
;
分隔。通常,换行符也被视为分隔符,所以在换行情况下也可不使用分号。- 加分号有助于:
- 防止省略造成的问题,比如可以避免输入内容不完整。
- 便于开发者通过删除空行来压缩代码。
- 有助于在某些情况下提升性能,因为解析器会尝试在合适的位置补上分号以纠正语法错误。
- 加分号有助于:
- 多行语句可以用代码块
{}
包起来。代码块后不需要加分号,但是加了也不会报错,会被自动忽略。- 可以让内容更清晰,在需要修改代码时也可以减少出错的可能性。
- 以 分号
一个不加分号导致的错误的例子:
// 1
[1, 2].forEach(alert) // 先显示 1,然后显示 2
// 2
alert("There will be an error")
[1, 2].forEach(alert)
// 只有第一个 alert 语句的内容被显示了出来,随后我们收到了一个错误!
// 因为 JavaScript 并不会在方括号 [...] 前添加一个隐式的分号
// alert("There will be an error")[1, 2].forEach(alert)
// 3
alert("All fine now");
[1, 2].forEach(alert)
// 先显示 There will be an error,再是 1,然后是 2
复制代码
2、严格模式 'use strict'
长久以来,JavaScript 不断向前发展且并未带来任何兼容性问题。新的特性被加入,旧的功能也没有改变。
这么做有利于兼容旧代码,但缺点是 JavaScript 创造者的任何错误或不完善的决定也将永远被保留在 JavaScript 语言中。
这种情况一直持续到 2009 年 ECMAScript 5 (ES5) 的出现。ES5 规范增加了新的语言特性并且修改了一些已经存在的特性。为了保证旧的功能能够使用,大部分的修改是默认不生效的。你需要一个特殊的预处理指令 —— 'use strict'
来明确地激活这些特性。ECMAScript 3 的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。
注意:'use strict'
必须在脚本的顶部或函数体的开头才能生效:
// 1.
alert('some code');
// 下面的 "use strict" 会被忽略,必须在最顶部。
'use strict';
// 严格模式没有被激活
// 2.
'use strict';
alert('some code');
// 严格模式激活
复制代码
可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可:
function doSomething() {
'use strict';
// 函数体
}
复制代码
所有现代浏览器都支持严格模式。
没有办法取消
use strict
没有类似于 “no use strict” 这样的指令可以使程序返回默认模式。一旦进入了严格模式,就没有回头路了。
语言的一些现代特征(比如
class
和module
)会自动启用use strict
。
2.1 浏览器控制台启动 use strict
当你使用 开发者控制台 运行代码时,请注意它默认是不启动 use strict
的。
有时,当 use strict
会对代码产生一些影响时,你会得到错误的结果。
那么,怎么在控制台中启用 use strict
呢?
首先,你可以尝试搭配使用 Shift+Enter
按键去输入多行代码,然后将 use strict
放在代码最顶部,就像这样:
'use strict'; <Shift+Enter 换行>
// ...你的代码
<按下 Enter 以运行>
复制代码
它在大部分浏览器中都有效,像 Firefox 和 Chrome。
如果依然不行,例如你使用的是旧版本的浏览器,那么有一种很丑但可靠的启用 use strict
的方法。将你的代码放在这样的包装器中:
(function() {
'use strict';
// ...你的代码...
})()
复制代码
二、变量声明
1、let
、var
和 const
-
var
-
可以重复声明;
-
作用域:全局作用域、函数作用域;
-
使用
var
声明变量时,变量会被自动添加到最接近的上下文; -
在函数内定义变量时省略
var
操作符,会自动被添加到全局上下文,可在函数外访问(不推荐,严格模式下抛出 ReferenceError 错误); -
非函数内定义的变量会被添加到全局上下文。浏览器的全局上下文是 window 对象,可通过 window 对象访问变量。
-
-
会进行变量提升(即声明前置:声明前,调用不报错)。
var
声明会被拿到函数或全局作用域的顶部
,位于作用域中所有代码之前。这个现象叫作“提升
”(hoisting)。提升让同一作用域中的代码不必考虑变量是否声明就可直接使用(输出undefined
而不是 Reference Error)。 -
-
let
-
同一作用域(块)下 不能重复声明;
- 对声明冗余报错不会因混用
let
和var
而受影响。
- 对声明冗余报错不会因混用
-
作用域:全局作用域、块级作用域(由最近的一对包含花括号 {} 界定);
块作用域是函数作用域的子集,因此适用于
var
的作用域限制同样也适用于let
。-
不会提升(先声明,再使用)。
let
声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出 ReferenceError。
严格来讲,
let
在 JavaScript 运行时中也会被提升,但由于“暂时性死区”的缘故,实际上不能在声明之前使用let
变量。因此,从写 JavaScript 代码的角度说,let
的提升跟var
是不一样的。
-
-
const
-
常量只能在声明时赋值,并且一旦赋值不能修改;
-
const
对象外层不可改变,但内层可以; -
要内层也不能改变,可以使用
Object.freeze(obj)
冻结对象。这样再给属性赋值时虽然不会报错,但会静默失败。
-
-
不能重复声明;
-
作用域:块级作用域;
-
不会提升。
-
在严格模式下,不能定义名为
eval
和arguments
的变量,否则会导致语法错误。
1.1 var
// 1. var 可以重复声明
var a = 1;
var a = 12
console.log(a) // 12
// 2. 作用域
// 变量未经声明就被初始化,添加到全局上下文
function add(num1, num2) {
sum = num1 + num2; // 变量未经声明就被初始化,会自动被添加到全局上下文
return sum;
}
let result = add(10, 20); // 30
console.log(sum); // 30
// sum被添加到全局上下文,在函数退出之后依然存在,可以在函数外被访问到
// 3. window 对象的属性
var name = 'Matt';
console.log(window.name); // 'Matt' 其实是被提升到全局上下文,浏览器的全局上下文是 window 对象
// 4. 变量提升
console.log(name); // undefined
var name = 'Jake';
复制代码
1.2 let
// 1. let 不可重复声明
let a = 1
let a = 2 // 报错
// 1.1 对声明冗余报错不会因混用 let 和 var 而受影响
let a = 1
var a = 2 // 报错
// 2. 块作用域
// 这不是对象字面量,而是一个独立的块
// JavaScript 解释器会根据其中内容识别出它来
{
let a;
}
console.log(a); // ReferenceError: a 没有定义
// 3. let 不会提升
console.log(e) // ReferenceError: e is not defined。
let e = 1
复制代码
1.3 const
// 1.1 常量声明时必须赋值
const b; // SyntaxError: Missing initializer in const declaration。
// 1.2 常量一旦赋值不能修改
const d = 20;
d = 30; // TypeError: Assignment to constant variable.
// 1.3 const 对象冻结
// const 声明仅固定了对象的值,而不是值(该对象)里面的内容。
// 仅当我们尝试将 `obj=...` 作为一个整体进行赋值时,const 会抛出错误。
const obj = {name: '张三', age: 20}
// obj = { name: '李四' } // TypeError: Assignment to constant variable.
// const 对象外层不可改变,但内层可以
obj.name = '李四' // 不报错
console.log(obj) // {name: '李四', age: 20}
// 要内层也不能改变,可以使用 Object.freeze(obj) 冻结对象
Object.freeze(obj);
obj.name = '王五' // 不报错,但也不会改变
console.log(obj) // {name: '李四', age: 20}
// 2. 不能重复声明
const a = 1
const a = 2 // 报错
// 3. 不会提升
console.log(g) // ReferenceError: 无法在初始化之前访问'g'
const g = 20
复制代码
2、let
与 var
的差异
-
var
可以重复声明,let
不可重复声明。 -
let
声明的范围是块作用域,var
声明的范围是函数作用域。 -
var
会进行变量提升,let
不会被提升(暂时性死区)。 -
在全局作用域中声明
var
会挂在 window 对象上,let
则不会。 -
let
不能依赖条件声明模式。因为let
是块级作用域,条件块中let
声明的作用域仅限于该块。
/* 1. 重复声明 */
// 1.1 var 可以重复声明
var a = 1;
var a = 2
console.log(a) // 2
// 1.2 let 不可重复声明
let a = 1
let a = 2 // 报错
/* 2. 作用域 */
if(true) {
let b = 2; // 块级作用域
// var b = 4; // 报错,let 同一作用域下不能重复声明。对声明冗余报错不会因混用 let 和 var 而受影响。
var c = 3; // 函数作用域
console.log(b); // 2
}
// console.log(b); // ReferenceError: b is not defined
console.log(c); // 3
/* 3. 变量提升,声明前置 */
// 3.1 var 会提升
console.log(d) // undefined
var d = 1
// 等价于
var d;
console.log(d);
d = 1;
// 3.2 let 不会提升
console.log(e) // ReferenceError: e is not defined。
let e = 1
/* 4. window 对象的属性 */
var name = 'Matt';
console.log(window.name); // 'Matt' 其实是被提升到全局上下文,浏览器的全局上下文是 window 对象
let age = 26;
console.log(window.age); // undefined
/* 5. 条件声明 */
<script>
let name = 'Nicholas';
let age = 36;
</script>
<script>
// 假设脚本不确定页面中是否已经声明了同名变量,那它可以假设还没有声明过
if (typeof name === 'undefined') {
let name;
}
name = 'Matt'; // name 被限制在 if {} 块的作用域内,因此这个赋值形同全局赋值
try {
console.log(age);
}
catch(error) {
let age;
}
age = 26; // age 被限制在 catch {}块的作用域内,因此这个赋值形同全局赋值
</script>
复制代码
3、声明风格及最佳实践
-
不使用
var
- 有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。
-
const
优先,let
次之-
由于
const
声明暗示变量的值是单一类型且不可修改,浏览器运行时编译器可以将其所有实例都替换成实际的值,而不会通过查询表进行变量查找,保持变量不变; -
让静态代码分析工具提前发现不合法的赋值操作。能让开发者迅速发现因意外赋值导致的非预期行为。
-
-
let 手册地址:developer.mozilla.org/zh-CN/docs/…
-
const 手册地址:developer.mozilla.org/zh-CN/docs/…
4、面试题: var
、let
、const
的区别
var
可重复声明;声明时挂在window
对象上;函数作用域;会变量提升;let
不允许重复声明;不会通过window
访问;块级作用域;不会变量提升;声明前使用会造成“暂时性死区”;const
不允许重复声明;一旦声明就不允许更改(注意下对象,内层可以被更改); 不会通过window
访问;块级作用域;不会变量提升;
三、关键字和保留字
ECMA-262 描述了一组保留的关键字,这些关键字有特殊用途,比如表示控制语句的开始和结束,或者执行特定的操作。按照规定,保留的关键字不能用作标识符或属性名。
ECMA-262 第 6 版规定的所有关键字如下:
break | do | in | typeof |
case | else | instanceof | var |
catch | export | new | void |
class | extends | return | while |
const | finally | super | with |
continue | for | switch | yield |
debugger | function | this | |
default | if | throw | |
delete | import | try |
规范中也描述了一组未来的保留字,同样不能用作标识符或属性名。虽然保留字在语言中没有特定用途,但它们是保留给将来做关键字用的。
以下是 ECMA-262 第 6 版为将来保留的所有词汇:
始终保留:
标题 | |
---|---|
enum |
严格模式下保留:
implements | package | public |
interface | protected | static |
let | private |
模块代码中保留:
标题 | |
---|---|
await |
不要使用关键字和保留字作为标识符和属性名,以确保兼容过去和未来的 ECMAScript 版本。