mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2025-05-24 18:32:22 +00:00
docs: fix function/参数默认值有单独作用域
This commit is contained in:
parent
1fa35d5c7b
commit
eb2a62215a
@ -27,7 +27,7 @@ if (typeof y === 'undefined') {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
|
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function log(x, y = 'World') {
|
function log(x, y = 'World') {
|
||||||
@ -39,7 +39,7 @@ log('Hello', 'China') // Hello China
|
|||||||
log('Hello', '') // Hello
|
log('Hello', '') // Hello
|
||||||
```
|
```
|
||||||
|
|
||||||
可以看到,ES6的写法比ES5简洁许多,而且非常自然。下面是另一个例子。
|
可以看到,ES6 的写法比 ES5 简洁许多,而且非常自然。下面是另一个例子。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function Point(x = 0, y = 0) {
|
function Point(x = 0, y = 0) {
|
||||||
@ -51,7 +51,7 @@ var p = new Point();
|
|||||||
p // { x: 0, y: 0 }
|
p // { x: 0, y: 0 }
|
||||||
```
|
```
|
||||||
|
|
||||||
除了简洁,ES6的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。
|
除了简洁,ES6 的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。
|
||||||
|
|
||||||
参数变量是默认声明的,所以不能用`let`或`const`再次声明。
|
参数变量是默认声明的,所以不能用`let`或`const`再次声明。
|
||||||
|
|
||||||
@ -64,6 +64,31 @@ function foo(x = 5) {
|
|||||||
|
|
||||||
上面代码中,参数变量`x`是默认声明的,在函数体中,不能用`let`或`const`再次声明,否则会报错。
|
上面代码中,参数变量`x`是默认声明的,在函数体中,不能用`let`或`const`再次声明,否则会报错。
|
||||||
|
|
||||||
|
使用参数默认值时,函数不能有同名参数。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function foo(x, x, y = 1) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
// SyntaxError: Duplicate parameter name not allowed in this context
|
||||||
|
```
|
||||||
|
|
||||||
|
另外,一个容易忽略的地方是,如果参数默认值是变量,那么参数就不是传值的,而是每次都重新计算默认值表达式的值。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
let x = 99;
|
||||||
|
function foo(p = x + 1) {
|
||||||
|
console.log(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
foo() // 100
|
||||||
|
|
||||||
|
x = 100;
|
||||||
|
foo() // 101
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码中,参数`p`的默认值是`x + 1`。这时,每次调用函数`foo`,都会重新计算`x + 1`,而不是默认`p`等于 100。
|
||||||
|
|
||||||
### 与解构赋值默认值结合使用
|
### 与解构赋值默认值结合使用
|
||||||
|
|
||||||
参数默认值可以与解构赋值的默认值,结合起来使用。
|
参数默认值可以与解构赋值的默认值,结合起来使用。
|
||||||
@ -188,7 +213,7 @@ foo(undefined, null)
|
|||||||
|
|
||||||
上面代码中,`x`参数对应`undefined`,结果触发了默认值,`y`参数等于`null`,就没有触发默认值。
|
上面代码中,`x`参数对应`undefined`,结果触发了默认值,`y`参数等于`null`,就没有触发默认值。
|
||||||
|
|
||||||
### 函数的length属性
|
### 函数的 length 属性
|
||||||
|
|
||||||
指定了默认值以后,函数的`length`属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,`length`属性将失真。
|
指定了默认值以后,函数的`length`属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,`length`属性将失真。
|
||||||
|
|
||||||
@ -215,7 +240,7 @@ foo(undefined, null)
|
|||||||
|
|
||||||
### 作用域
|
### 作用域
|
||||||
|
|
||||||
一个需要注意的地方是,如果参数默认值是一个变量,则该变量所处的作用域,与其他变量的作用域规则是一样的,即先是当前函数的作用域,然后才是全局作用域。
|
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var x = 1;
|
var x = 1;
|
||||||
@ -227,9 +252,9 @@ function f(x, y = x) {
|
|||||||
f(2) // 2
|
f(2) // 2
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,参数`y`的默认值等于`x`。调用时,由于函数作用域内部的变量`x`已经生成,所以`y`等于参数`x`,而不是全局变量`x`。
|
上面代码中,参数`y`的默认值等于变量`x`。调用函数`f`时,参数形成一个单独的作用域。在这个作用域里面,默认值变量`x`指向第一个参数`x`,而不是全局变量`x`,所以输出是`2`。
|
||||||
|
|
||||||
如果调用时,函数作用域内部的变量`x`没有生成,结果就会不一样。
|
再看下面的例子。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let x = 1;
|
let x = 1;
|
||||||
@ -242,7 +267,7 @@ function f(y = x) {
|
|||||||
f() // 1
|
f() // 1
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,函数调用时,`y`的默认值变量`x`尚未在函数内部生成,所以`x`指向全局变量。
|
上面代码中,函数`f`调用时,参数`y = x`形成一个单独的作用域。这个作用域里面,变量`x`本身没有定义,所以指向外层的全局变量`x`。函数调用时,函数体内部的局部变量`x`影响不到默认值变量`x`。
|
||||||
|
|
||||||
如果此时,全局变量`x`不存在,就会报错。
|
如果此时,全局变量`x`不存在,就会报错。
|
||||||
|
|
||||||
@ -267,9 +292,9 @@ function foo(x = x) {
|
|||||||
foo() // ReferenceError: x is not defined
|
foo() // ReferenceError: x is not defined
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,函数`foo`的参数`x`的默认值也是`x`。这时,默认值`x`的作用域是函数作用域,而不是全局作用域。由于在函数作用域中,存在变量`x`,但是默认值在`x`赋值之前先执行了,所以这时属于暂时性死区(参见《let和const命令》一章),任何对`x`的操作都会报错。
|
上面代码中,参数`x = x`形成一个单独作用域。实际执行的是`let x = x`,由于暂时性死区的原因,这行代码会报错”x 未定义“。
|
||||||
|
|
||||||
如果参数的默认值是一个函数,该函数的作用域是其声明时所在的作用域。请看下面的例子。
|
如果参数的默认值是一个函数,该函数的作用域也遵守这个规则。请看下面的例子。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let foo = 'outer';
|
let foo = 'outer';
|
||||||
@ -282,7 +307,7 @@ function bar(func = x => foo) {
|
|||||||
bar();
|
bar();
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,函数`bar`的参数`func`的默认值是一个匿名函数,返回值为变量`foo`。这个匿名函数声明时,`bar`函数的作用域还没有形成,所以匿名函数里面的`foo`指向外层作用域的`foo`,输出`outer`。
|
上面代码中,函数`bar`的参数`func`的默认值是一个匿名函数,返回值为变量`foo`。函数参数形成的单独作用域里面,并没有定义变量`foo`,所以`foo`指向外层的全局变量`foo`,因此输出`outer`。
|
||||||
|
|
||||||
如果写成下面这样,就会报错。
|
如果写成下面这样,就会报错。
|
||||||
|
|
||||||
@ -295,7 +320,7 @@ function bar(func = () => foo) {
|
|||||||
bar() // ReferenceError: foo is not defined
|
bar() // ReferenceError: foo is not defined
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,匿名函数里面的`foo`指向函数外层,但是函数外层并没有声明`foo`,所以就报错了。
|
上面代码中,匿名函数里面的`foo`指向函数外层,但是函数外层并没有声明变量`foo`,所以就报错了。
|
||||||
|
|
||||||
下面是一个更复杂的例子。
|
下面是一个更复杂的例子。
|
||||||
|
|
||||||
@ -308,11 +333,12 @@ function foo(x, y = function() { x = 2; }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foo() // 3
|
foo() // 3
|
||||||
|
x // 1
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码中,函数`foo`的参数`y`的默认值是一个匿名函数。函数`foo`调用时,它的参数`x`的值为`undefined`,所以`y`函数内部的`x`一开始是`undefined`,后来被重新赋值`2`。但是,函数`foo`内部重新声明了一个`x`,值为`3`,这两个`x`是不一样的,互相不产生影响,因此最后输出`3`。
|
上面代码中,函数`foo`的参数形成一个单独作用域。这个作用域里面,首先声明了变量`x`,然后声明了变量`y`,`y`的默认值是一个匿名函数。这个匿名函数内部的变量`x`,指向同一个作用域的第一个参数`x`。函数`foo`内部又声明了一个内部变量`x`,该变量与第一个参数`x`由于不是同一个作用域,所以不是同一个变量,因此执行`y`后,内部变量`x`和外部全局变量`x`的值都没变。
|
||||||
|
|
||||||
如果将`var x = 3`的`var`去除,两个`x`就是一样的,最后输出的就是`2`。
|
如果将`var x = 3`的`var`去除,函数`foo`的内部变量`x`就指向第一个参数`x`,与匿名函数内部的`x`是一致的,所以最后输出的就是`2`,而外层的全局变量`x`依然不受影响。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var x = 1;
|
var x = 1;
|
||||||
@ -323,6 +349,7 @@ function foo(x, y = function() { x = 2; }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foo() // 2
|
foo() // 2
|
||||||
|
x // 1
|
||||||
```
|
```
|
||||||
|
|
||||||
### 应用
|
### 应用
|
||||||
@ -344,7 +371,7 @@ foo()
|
|||||||
|
|
||||||
上面代码的`foo`函数,如果调用的时候没有参数,就会调用默认值`throwIfMissing`函数,从而抛出一个错误。
|
上面代码的`foo`函数,如果调用的时候没有参数,就会调用默认值`throwIfMissing`函数,从而抛出一个错误。
|
||||||
|
|
||||||
从上面代码还可以看到,参数`mustBeProvided`的默认值等于`throwIfMissing`函数的运行结果(即函数名之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与python语言不一样。
|
从上面代码还可以看到,参数`mustBeProvided`的默认值等于`throwIfMissing`函数的运行结果(即函数名之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与 Python 语言不一样。
|
||||||
|
|
||||||
另外,可以将参数默认值设为`undefined`,表明这个参数是可以省略的。
|
另外,可以将参数默认值设为`undefined`,表明这个参数是可以省略的。
|
||||||
|
|
||||||
@ -354,7 +381,7 @@ function foo(optional = undefined) { ··· }
|
|||||||
|
|
||||||
## rest参数
|
## rest参数
|
||||||
|
|
||||||
ES6引入rest参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
|
ES6 引入 rest 参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用`arguments`对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function add(...values) {
|
function add(...values) {
|
||||||
@ -370,9 +397,9 @@ function add(...values) {
|
|||||||
add(2, 5, 3) // 10
|
add(2, 5, 3) // 10
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码的add函数是一个求和函数,利用rest参数,可以向该函数传入任意数目的参数。
|
上面代码的`add`函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。
|
||||||
|
|
||||||
下面是一个rest参数代替arguments变量的例子。
|
下面是一个 rest 参数代替`arguments`变量的例子。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// arguments变量的写法
|
// arguments变量的写法
|
||||||
@ -384,9 +411,9 @@ function sortNumbers() {
|
|||||||
const sortNumbers = (...numbers) => numbers.sort();
|
const sortNumbers = (...numbers) => numbers.sort();
|
||||||
```
|
```
|
||||||
|
|
||||||
上面代码的两种写法,比较后可以发现,rest参数的写法更自然也更简洁。
|
上面代码的两种写法,比较后可以发现,rest 参数的写法更自然也更简洁。
|
||||||
|
|
||||||
rest参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。下面是一个利用rest参数改写数组push方法的例子。
|
rest 参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。下面是一个利用 rest 参数改写数组`push`方法的例子。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function push(array, ...items) {
|
function push(array, ...items) {
|
||||||
@ -400,7 +427,7 @@ var a = [];
|
|||||||
push(a, 1, 2, 3)
|
push(a, 1, 2, 3)
|
||||||
```
|
```
|
||||||
|
|
||||||
注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
|
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 报错
|
// 报错
|
||||||
@ -409,7 +436,7 @@ function f(a, ...b, c) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
函数的length属性,不包括rest参数。
|
函数的`length`属性,不包括 rest 参数。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
(function(a) {}).length // 1
|
(function(a) {}).length // 1
|
||||||
@ -421,7 +448,7 @@ function f(a, ...b, c) {
|
|||||||
|
|
||||||
### 含义
|
### 含义
|
||||||
|
|
||||||
扩展运算符(spread)是三个点(`...`)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
|
扩展运算符(spread)是三个点(`...`)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
console.log(...[1, 2, 3])
|
console.log(...[1, 2, 3])
|
||||||
|
29
docs/let.md
29
docs/let.md
@ -57,6 +57,20 @@ a[6](); // 6
|
|||||||
|
|
||||||
上面代码中,变量`i`是`let`声明的,当前的`i`只在本轮循环有效,所以每一次循环的`i`其实都是一个新的变量,所以最后输出的是`6`。你可能会问,如果每一轮循环的变量`i`都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量`i`时,就在上一轮循环的基础上进行计算。
|
上面代码中,变量`i`是`let`声明的,当前的`i`只在本轮循环有效,所以每一次循环的`i`其实都是一个新的变量,所以最后输出的是`6`。你可能会问,如果每一轮循环的变量`i`都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量`i`时,就在上一轮循环的基础上进行计算。
|
||||||
|
|
||||||
|
另外,`for`循环还有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
let i = 'abc';
|
||||||
|
console.log(i);
|
||||||
|
}
|
||||||
|
// abc
|
||||||
|
// abc
|
||||||
|
// abc
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码输出了3次`abc`,这表明函数内部的变量`i`和外部的变量`i`是分离的。
|
||||||
|
|
||||||
### 不存在变量提升
|
### 不存在变量提升
|
||||||
|
|
||||||
`let`不像`var`那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。
|
`let`不像`var`那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。
|
||||||
@ -144,7 +158,20 @@ function bar(x = 2, y = x) {
|
|||||||
bar(); // [2, 2]
|
bar(); // [2, 2]
|
||||||
```
|
```
|
||||||
|
|
||||||
ES6规定暂时性死区和`let`、`const`语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在ES5是很常见的,现在有了这种规定,避免此类错误就很容易了。
|
另外,下面的代码也会报错,与`var`的行为不同。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 不报错
|
||||||
|
var x = x;
|
||||||
|
|
||||||
|
// 报错
|
||||||
|
let x = x;
|
||||||
|
// ReferenceError: x is not defined
|
||||||
|
```
|
||||||
|
|
||||||
|
上面代码报错,也是因为暂时性死区。使用`let`声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量`x`的声明语句还没有执行完成前,就去取`x`的值,导致报错”x 未定义“。
|
||||||
|
|
||||||
|
ES6规定暂时性死区和`let`、`const`语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
|
||||||
|
|
||||||
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
|
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user