mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2024-04-21 12:32:22 +00:00
docs: fix function/参数默认值有单独作用域
This commit is contained in:
+50
-23
@@ -27,7 +27,7 @@ if (typeof y === 'undefined') {
|
||||
}
|
||||
```
|
||||
|
||||
ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
|
||||
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
|
||||
|
||||
```javascript
|
||||
function log(x, y = 'World') {
|
||||
@@ -39,7 +39,7 @@ log('Hello', 'China') // Hello China
|
||||
log('Hello', '') // Hello
|
||||
```
|
||||
|
||||
可以看到,ES6的写法比ES5简洁许多,而且非常自然。下面是另一个例子。
|
||||
可以看到,ES6 的写法比 ES5 简洁许多,而且非常自然。下面是另一个例子。
|
||||
|
||||
```javascript
|
||||
function Point(x = 0, y = 0) {
|
||||
@@ -51,7 +51,7 @@ var p = new Point();
|
||||
p // { x: 0, y: 0 }
|
||||
```
|
||||
|
||||
除了简洁,ES6的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。
|
||||
除了简洁,ES6 的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。
|
||||
|
||||
参数变量是默认声明的,所以不能用`let`或`const`再次声明。
|
||||
|
||||
@@ -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。
|
||||
|
||||
### 与解构赋值默认值结合使用
|
||||
|
||||
参数默认值可以与解构赋值的默认值,结合起来使用。
|
||||
@@ -188,7 +213,7 @@ foo(undefined, null)
|
||||
|
||||
上面代码中,`x`参数对应`undefined`,结果触发了默认值,`y`参数等于`null`,就没有触发默认值。
|
||||
|
||||
### 函数的length属性
|
||||
### 函数的 length 属性
|
||||
|
||||
指定了默认值以后,函数的`length`属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,`length`属性将失真。
|
||||
|
||||
@@ -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变量的写法
|
||||
@@ -384,9 +411,9 @@ function sortNumbers() {
|
||||
const sortNumbers = (...numbers) => numbers.sort();
|
||||
```
|
||||
|
||||
上面代码的两种写法,比较后可以发现,rest参数的写法更自然也更简洁。
|
||||
上面代码的两种写法,比较后可以发现,rest 参数的写法更自然也更简洁。
|
||||
|
||||
rest参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。下面是一个利用rest参数改写数组push方法的例子。
|
||||
rest 参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。下面是一个利用 rest 参数改写数组`push`方法的例子。
|
||||
|
||||
```javascript
|
||||
function push(array, ...items) {
|
||||
@@ -400,7 +427,7 @@ var a = [];
|
||||
push(a, 1, 2, 3)
|
||||
```
|
||||
|
||||
注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
|
||||
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
|
||||
|
||||
```javascript
|
||||
// 报错
|
||||
@@ -409,7 +436,7 @@ function f(a, ...b, c) {
|
||||
}
|
||||
```
|
||||
|
||||
函数的length属性,不包括rest参数。
|
||||
函数的`length`属性,不包括 rest 参数。
|
||||
|
||||
```javascript
|
||||
(function(a) {}).length // 1
|
||||
@@ -421,7 +448,7 @@ function f(a, ...b, c) {
|
||||
|
||||
### 含义
|
||||
|
||||
扩展运算符(spread)是三个点(`...`)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
|
||||
扩展运算符(spread)是三个点(`...`)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
|
||||
|
||||
```javascript
|
||||
console.log(...[1, 2, 3])
|
||||
|
||||
Reference in New Issue
Block a user