一、Block Binding
在大多数基于 C 的编程语言中,变量通常会在声明时创建。而对于 JavaScript 语言,变量创建的时间点则取决于具体的声明方式。
JavaScript 中经典的使用 var
声明变量的方式容易引起困惑,因此 ECMAScript 6 中引入了块级别的变量绑定(block-level binding)。
var 关键字
var
关键字对于变量的声明,会默认该声明位于函数顶部(位于函数外部时为全局作用域),而不去管声明语句实际出现的位置。称为 hoisting 。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function getValue(condition) {
if (condition) {
var value = "blue"
console.log("condition is true and value is " + value)
} else {
console.log("condition is false and value is " + value)
}
console.log("outside if, value is " + value)
}
getValue(true)
// condition is true and value is blue
// outside if, value is blue
getValue(false)
// condition is false and value is undefined
// outside if, value is undefined
习惯上会认为,上述代码中只有 condition
为 True 时变量 value
才会被创建;实际上 value
变量存在于函数的各个部分,只是在 condition
为 False 时未被初始化(undefined
)。
上面的代码会被 JavaScript 引擎视作如下形式:1
2
3
4
5
6
7
8
9
10function getValue(condition) {
var value;
if (condition) {
value = "blue";
console.log("condition is true and value is " + value)
} else {
console.log("condition is false and value is " + value)
}
console.log("outside if, value is " + value)
}
块级声明和 let 语句
由块级声明创建的变量无法被该代码块以外的部分访问。
块作用域(Block scopes)一般创建于以下位置:
- 函数内部
- 代码块内部(被大括号
{}
包裹的部分)
块作用域符合大部分基于 C 的编程语言的工作方式。
let
关键字会将变量的作用域限制在当前代码块内部。1
2
3
4
5
6
7
8
9
10
11
12
13function getValue(condition) {
if (condition) {
let value = "blue"
console.log("condition is true and value is " + value)
} else {
console.log("condition is false and value is " + value)
}
console.log("outside if, value is " + value)
}
getValue(true)
// condition is true and value is blue
// ReferenceError: value is not defined
No Redeclaration
如果同一作用域内已有相同名称的变量被声明,则 let 语句会报错。1
2
3var count = 30
let count = 40
// SyntaxError: Identifier 'count' has already been declared
但是在不同作用域中,类似的情况则不会报错:1
2
3
4var count = 30
if (true) {
let count = 40
}
const
关键字用于声明常量,常量的值一旦确定后即不可再变更,因此在声明的同时必须赋值以完成初始化。1
2
3const maxItems = 30
const name;
// SyntaxError: Missing initializer in const declaration
需要注意的是,常量的“不可变”仅针对变量与值的绑定关系,而并不限制值本身的改动。即使用 const
声明某个对象,则对象本身的改动不被禁止。1
2
3
4
5
6
7
8
9
10const person = {
name: "Nicholas"
}
person.name = "Greg"
person.name
// 'Greg'
person = {
name: "Grep"
}
// TypeError: Assignment to constant variable.
循环中的块级绑定
var:1
2
3
4
5for (var i = 0; i < 10; i++) {
// do nothing
}
console.log(i)
// 10
let:1
2
3
4
5for (let i = 0; i < 10; i++) {
// do nothing
}
console.log(i)
// ReferenceError: i is not defined
var 声明语句的特性(loop 变量可以从 loop 外部访问)使得在循环中创建函数的行为会产生问题。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i)
})
}
funcs.forEach(function(func) {
func()
})
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
// 10
解决的办法是使用如下代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value)
}
}(i)))
}
funcs.forEach(function(func) {
func()
})
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
有了块级声明以后,上述需求可以被简单地实现(只需要将第一段代码中的 var
关键字改为 let
即可):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var funcs = []
for (let i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i)
})
}
funcs.forEach(function(func) {
func()
})
// 0
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
二、函数
参数带默认值的函数
在 ECMAScript 5 及以前版本的 JavaScript 中,通常使用如下模式创建带默认参数的函数:1
2
3
4
5function makeRequest(url, timeout, callback) {
timeout = timeout || 2000
callback = callback || function() {}
// the rest code
}
但上述 ||
(或)操作符的使用存在一定问题,如传递给 timeout
参数的值为 0 时,timeout || 2000
表达式的值为 2000 而不是 0,导致程序出现意想不到的结果。改进如下:1
2
3
4
5function makeRequest(url, timeout, callback) {
timeout = (typeof timeout !== "undefined") ? timeout : 2000
callback = (typeof callback !== "undefined") ? callback : function() {}
// the rest code
}
在 ECMAScript 6 中,为函数的参数提供默认值的方式则非常简单直观:1
2
3function makeRequest(url, timeout = 2000, callback = function() {}) {
// the rest code
}
表达式作为参数默认值:1
2
3
4
5
6
7
8
9
10function getValue() {
return 5
}
function add(first, second = getValue()) {
return first + second
}
console.log(add(1, 1)) // 2
console.log(add(1)) // 6
甚至可以使用如下代码:1
2
3
4
5
6
7
8
9
10function getValue(value) {
return value + 5
}
function add(first, second = getValue(first)) {
return first + second
}
console.log(add(1, 1)) // 2
console.log(add(1)) // 7
匿名参数
ECMAScript 5 中的匿名参数(通过 arguments
对象获取所有参数,包含定义函数时未显式指定的参数):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function pick(object) {
let result = Object.create(null)
for (let i = 1, len = arguments.length; i < len; i++) {
result[arguments[i]] = object[arguments[i]]
}
return result
}
let book = {
title: "Understanding ECMAScript 6",
author: "Nicholas C. Zakas",
year: 2016
}
let bookData = pick(book, "author", "year")
console.log(bookData.author) // "Nicholas C. Zakas"
console.log(bookData.year) // 2016
注意 for
循环是从 i=1
即第二个参数开始的。
Rest Parameters
上述 pick
函数可以利用 ECMAScript 6 支持的 Rest Parameters
特性重写为如下形式:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function pick(object, ...keys) {
let result = Object.create(null)
for (let i = 0, len = keys.length; i < len; i++) {
result[keys[i]] = object[keys[i]]
}
return result
}
let book = {
title: "Understanding ECMAScript 6",
author: "Nicholas C. Zakas",
year: 2016
}
let bookData = pick(book, "author", "year")
console.log(bookData.author) // "Nicholas C. Zakas"
console.log(bookData.year) // 2016
函数构造器
1 | var add = new Function("first", "second", "return first + second") |
ECMAScript 6 使得函数构造器可以支持默认参数和 rest parameters 等特性:1
2
3
4
5
6
7
8var add = new Function("first", "second = first", "return first + second")
console.log(add(1, 1)) // 2
console.log(add(1)) // 2
var pickFirst = new Function("...args", "return args[0]")
console.log(pickFirst(1, 2)) // 1
函数的两种调用方式
1 | function Person(name) { |
JavaScript 有两个针对函数的内部方法:[[Call]]
和 [[Construct]]
。
当函数不通过 new
关键字调用时,[[Call]]
方法执行,接着运行函数体中的代码;
当函数通过 new
关键字调用时,[[Construct]]
方法执行,创建一个新的对象实例并绑定给 this
,之后继续执行函数体中的代码。
ECMAScript 6 中可以使用 new.target
判断当前函数是否由 new
调用:1
2
3
4
5
6
7
8
9
10function Person(name) {
if (typeof new.target !== "undefined") {
this.name = name
} else {
throw new Error("You must use new with Person.")
}
}
var person = new Person("Nicholas")
var notAPerson = Person("Michael") // error
Arrow Function
Arrow Function 是指使用 =>
符号定义的函数。与传统的 JavaScript 函数相比,Arrow Function 主要有以下几个不同点:
- 没有
this, super, arguments, new.target
的绑定。Arrow Function 中this, super, arguments, new.target
的值由距离最近的非 Arrow Function 定义 - 不能被 new 调用。Arrow Function 没有构造方法因此不能作为构造器使用
- 没有 prototype。Arrow Function 的
prototype
属性不存在(函数本身不能被 new 调用,prototype 没有必要) - 函数中的 this 的值不能被修改
基本语法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39let reflect = value => value
// equivalent to:
let reflect = function(value) {
return value
}
let sum = (num1, num2) => num1 + num2
// equivalent to:
let sum = function(num1, num2) {
return num1 + num2
}
let getName = () => "Nicholas"
// equivalent to:
let getName = function() {
return "Nicholas"
}
let doNothing = () => {}
// equivalent to:
let doNothing = function() {}
let getTempItem = id => ({ id: id, name: "Temp" })
// equivalent to:
let getTempItem = function(id) {
return {
id: id,
name: "Temp"
}
}