1
0
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:
ruanyf 2017-01-20 20:39:18 +08:00
parent 1fa35d5c7b
commit eb2a62215a
2 changed files with 78 additions and 24 deletions

View File

@ -64,6 +64,31 @@ function foo(x = 5) {
上面代码中,参数变量`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。
### 与解构赋值默认值结合使用
参数默认值可以与解构赋值的默认值,结合起来使用。
@ -215,7 +240,7 @@ foo(undefined, null)
### 作用域
个需要注意的地方是,如果参数默认值是一个变量,则该变量所处的作用域,与其他变量的作用域规则是一样的,即先是当前函数的作用域,然后才是全局作用域
旦设置了参数的默认值函数进行声明初始化时参数会形成一个单独的作用域context。等到初始化结束这个作用域就会消失。这种语法行为在不设置参数默认值时是不会出现的
```javascript
var x = 1;
@ -227,9 +252,9 @@ function f(x, y = x) {
f(2) // 2
```
上面代码中,参数`y`的默认值等于`x`。调用时,由于函数作用域内部的变量`x`已经生成,所以`y`等于参数`x`,而不是全局变量`x`。
上面代码中,参数`y`的默认值等于变量`x`。调用函数`f`时,参数形成一个单独的作用域。在这个作用域里面,默认值变量`x`指向第一个参数`x`,而不是全局变量`x`,所以输出是`2`。
如果调用时,函数作用域内部的变量`x`没有生成,结果就会不一样
再看下面的例子
```javascript
let x = 1;
@ -242,7 +267,7 @@ function f(y = x) {
f() // 1
```
上面代码中,函数调用时,`y`的默认值变量`x`尚未在函数内部生成,所以`x`指向全局变量
上面代码中,函数`f`调用时,参数`y = x`形成一个单独的作用域。这个作用域里面,变量`x`本身没有定义,所以指向外层的全局变量`x`。函数调用时,函数体内部的局部变量`x`影响不到默认值变量`x`
如果此时,全局变量`x`不存在,就会报错。
@ -267,9 +292,9 @@ function foo(x = x) {
foo() // ReferenceError: x is not defined
```
上面代码中,函数`foo`的参数`x`的默认值也是`x`。这时,默认值`x`的作用域是函数作用域,而不是全局作用域。由于在函数作用域中,存在变量`x`,但是默认值在`x`赋值之前先执行了所以这时属于暂时性死区参见《let和const命令》一章任何对`x`的操作都会报错
上面代码中,参数`x = x`形成一个单独作用域。实际执行的是`let x = x`由于暂时性死区的原因这行代码会报错”x 未定义“
如果参数的默认值是一个函数,该函数的作用域是其声明时所在的作用域。请看下面的例子。
如果参数的默认值是一个函数,该函数的作用域也遵守这个规则。请看下面的例子。
```javascript
let foo = 'outer';
@ -282,7 +307,7 @@ function bar(func = x => foo) {
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
```
上面代码中,匿名函数里面的`foo`指向函数外层,但是函数外层并没有声明`foo`,所以就报错了。
上面代码中,匿名函数里面的`foo`指向函数外层,但是函数外层并没有声明变量`foo`,所以就报错了。
下面是一个更复杂的例子。
@ -308,11 +333,12 @@ function foo(x, y = function() { x = 2; }) {
}
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
var x = 1;
@ -323,6 +349,7 @@ function foo(x, y = function() { x = 2; }) {
}
foo() // 2
x // 1
```
### 应用
@ -344,7 +371,7 @@ foo()
上面代码的`foo`函数,如果调用的时候没有参数,就会调用默认值`throwIfMissing`函数,从而抛出一个错误。
从上面代码还可以看到,参数`mustBeProvided`的默认值等于`throwIfMissing`函数的运行结果(即函数名之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与python语言不一样。
从上面代码还可以看到,参数`mustBeProvided`的默认值等于`throwIfMissing`函数的运行结果(即函数名之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与 Python 语言不一样。
另外,可以将参数默认值设为`undefined`,表明这个参数是可以省略的。
@ -354,7 +381,7 @@ function foo(optional = undefined) { ··· }
## rest参数
ES6引入rest参数形式为“...变量名”用于获取函数的多余参数这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组该变量将多余的参数放入数组中。
ES6 引入 rest 参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用`arguments`对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
```javascript
function add(...values) {
@ -370,9 +397,9 @@ function add(...values) {
add(2, 5, 3) // 10
```
上面代码的add函数是一个求和函数利用rest参数可以向该函数传入任意数目的参数。
上面代码的`add`函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。
下面是一个rest参数代替arguments变量的例子。
下面是一个 rest 参数代替`arguments`变量的例子。
```javascript
// arguments变量的写法
@ -386,7 +413,7 @@ const sortNumbers = (...numbers) => numbers.sort();
上面代码的两种写法比较后可以发现rest 参数的写法更自然也更简洁。
rest参数中的变量代表一个数组所以数组特有的方法都可以用于这个变量。下面是一个利用rest参数改写数组push方法的例子。
rest 参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。下面是一个利用 rest 参数改写数组`push`方法的例子。
```javascript
function push(array, ...items) {
@ -409,7 +436,7 @@ function f(a, ...b, c) {
}
```
函数的length属性不包括rest参数。
函数的`length`属性,不包括 rest 参数。
```javascript
(function(a) {}).length // 1

View File

@ -57,6 +57,20 @@ a[6](); // 6
上面代码中,变量`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`那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。
@ -144,6 +158,19 @@ function bar(x = 2, y = x) {
bar(); // [2, 2]
```
另外,下面的代码也会报错,与`var`的行为不同。
```javascript
// 不报错
var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined
```
上面代码报错,也是因为暂时性死区。使用`let`声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量`x`的声明语句还没有执行完成前,就去取`x`的值导致报错”x 未定义“。
ES6规定暂时性死区和`let``const`语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。