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
+50 -23
View File
@@ -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])