Skip to content

Commit 1b0805c

Browse files
committed
edit function/desturcturing/array
1 parent 857944c commit 1b0805c

File tree

4 files changed

+154
-65
lines changed

4 files changed

+154
-65
lines changed

docs/array.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@
44

55
`Array.from`方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
66

7+
```javascript
8+
let arrayLike = {
9+
'0': 'a',
10+
'1': 'b',
11+
'2': 'c',
12+
length: 3
13+
};
14+
15+
// ES5的写法
16+
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
17+
18+
// ES6的写法
19+
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
20+
```
21+
22+
下面是更多的例子。
23+
724
```javascript
825
Array.from('hello')
926
// ['h', 'e', 'l', 'l', 'o']
@@ -46,13 +63,15 @@ function foo() {
4663

4764
扩展运算符背后调用的是遍历器接口(`Symbol.iterator`),如果一个对象没有部署这个接口,就无法转换。`Array.from`方法就不存在这个问题,比如下面的这个例子,扩展运算符就无法转换。
4865

49-
任何有`length`属性的对象,都可以通过`Array.from`方法转为数组。
66+
所谓类似数组的对象,本质特征只有一点,即必须有`length`属性。因此,任何有`length`属性的对象,都可以通过`Array.from`方法转为数组。
5067

5168
```javascript
52-
Array.from({ 0: "a", 1: "b", 2: "c", length: 3 });
53-
// [ "a", "b" , "c" ]
69+
Array.from({ length: 3 });
70+
// [ undefined, undefined, undefinded ]
5471
```
5572

73+
上面代码中,`Array.from`返回了一个具有三个成员的数组,每个位置的值都是`undefined`
74+
5675
对于还没有部署该方法的浏览器,可以用`Array.prototype.slice`方法替代。
5776

5877
```javascript

docs/destructuring.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,14 @@ function add([x, y]){
429429
add([1, 2]) // 3
430430
```
431431

432-
上面代码中,函数add的参数实际上不是一个数组,而是通过解构得到的变量`x``y`
432+
上面代码中,函数`add`的参数实际上不是一个数组,而是通过解构得到的变量`x``y`
433+
434+
下面是另一个例子。
435+
436+
```javascript
437+
[[1, 2], [3, 4]].map(([a, b]) => a + b)
438+
// [ 3, 7 ]
439+
```
433440

434441
函数参数的解构也可以使用默认值。
435442

@@ -446,7 +453,7 @@ move(); // [0, 0]
446453

447454
上面代码中,函数`move`的参数是一个对象,通过对这个对象进行解构,得到变量`x``y`的值。如果解构失败,`x``y`等于默认值。
448455

449-
注意,指定函数参数的默认值时,不能采用下面的写法
456+
注意,下面的写法会得到不一样的结果
450457

451458
```javascript
452459
function move({x, y} = { x: 0, y: 0 }) {
@@ -459,7 +466,14 @@ move({}); // [undefined, undefined]
459466
move(); // [0, 0]
460467
```
461468

462-
上面代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。
469+
上面代码是为函数`move`的参数指定默认值,而不是为变量`x``y`指定默认值,所以会得到与前一种写法不同的结果。
470+
471+
`undefined`就会触发函数参数的默认值。
472+
473+
```javascript
474+
[1, undefined, 3].map((x = 'yes') => x)
475+
// [ 1, 'yes', 3 ]
476+
```
463477

464478
## 圆括号问题
465479

docs/function.md

Lines changed: 115 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## 函数参数的默认值
44

5+
### 基本用法
6+
57
在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
68

79
```javascript
@@ -52,10 +54,37 @@ function Point(x = 0, y = 0) {
5254
}
5355

5456
var p = new Point();
55-
// p = { x:0, y:0 }
57+
p // { x: 0, y: 0 }
58+
```
59+
60+
除了简洁,ES6的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本彻底拿掉这个参数,也不会导致以前的代码无法运行。
61+
62+
参数默认值可以与解构赋值,联合起来使用。
63+
64+
```javascript
65+
function foo({x, y = 5}) {
66+
console.log(x, y);
67+
}
68+
69+
foo({}) // undefined, 5
70+
foo({x: 1}) // 1, 5
71+
foo({x: 1, y: 2}) // 1, 2
72+
```
73+
74+
上面代码中,`foo`函数的参数是一个对象,变量`x``y`用于解构赋值,`y`有默认值5。
75+
76+
另外,参数变量是默认声明的,所以不能用let或const再次声明。
77+
78+
```javascript
79+
function foo(x = 5) {
80+
let x = 1; // error
81+
const x = 2; // error
82+
}
5683
```
5784

58-
除了简洁,ES6的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本彻底拿到这个参数,也不会导致以前的代码无法运行。
85+
上面代码中,参数变量`x`是默认声明的,在函数体中,不能用let或const再次声明,否则会报错。
86+
87+
### 双重默认值
5988

6089
默认值的写法非常灵活,下面是一个为对象属性设置默认值的例子。
6190

@@ -86,7 +115,7 @@ fetch('http://example.com')
86115

87116
上面代码中,调用函数`fetch`时,第二个参数默认为一个空对象,而只要有第二个参数,`method`参数就默认为`GET`
88117

89-
请问下面两种写法有什么差别
118+
再请问下面两种写法有什么差别
90119

91120
```javascript
92121
// 写法一
@@ -123,7 +152,9 @@ m1({z: 3}) // [0, 0]
123152
m2({z: 3}) // [undefined, undefined]
124153
```
125154

126-
通常情况下,定义了默认值的参数,都是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。但是,非尾部的参数,也是可以设置默认值的。
155+
### 参数默认值的位置
156+
157+
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
127158

128159
```javascript
129160
// 例一
@@ -149,7 +180,7 @@ f(1, undefined, 2) // [1, 5, 2]
149180

150181
上面代码中,有默认值的参数都不是尾参数。这时,无法只省略该参数,而不省略它后面的参数,除非显式输入`undefined`
151182

152-
如果传入`undefined`,将触发该参数等于默认值,null则没有这个效果
183+
如果传入`undefined`,将触发该参数等于默认值,`null`则没有这个效果
153184

154185
```javascript
155186
function foo(x = 5, y = 6){
@@ -162,6 +193,8 @@ foo(undefined, null)
162193

163194
上面代码中,`x`参数对应`undefined`,结果触发了默认值,`y`参数等于`null`,就没有触发默认值。
164195

196+
### 函数的length属性
197+
165198
指定了默认值以后,函数的`length`属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,`length`属性将失真。
166199

167200
```javascript
@@ -172,63 +205,106 @@ foo(undefined, null)
172205

173206
上面代码中,`length`属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。
174207

175-
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
208+
### 作用域
209+
210+
一个需要注意的地方是,如果参数默认值是一个变量,则该变量所处的作用域,与其他变量的作用域规则是一样的,即先是当前函数的作用域,然后才是全局作用域。
176211

177212
```javascript
178-
function throwIfMissing() {
179-
throw new Error('Missing parameter');
213+
var x = 1;
214+
215+
function f(x, y = x) {
216+
console.log(y);
180217
}
181218

182-
function foo(mustBeProvided = throwIfMissing()) {
183-
return mustBeProvided;
219+
f(2) // 2
220+
```
221+
222+
上面代码中,参数`y`的默认值等于`x`。调用时,由于函数作用域内部的变量`x`已经生成,所以`y`等于参数`x`,而不是全局变量`x`
223+
224+
如果调用时,函数作用域内部的变量`x`没有生成,结果就会不一样。
225+
226+
```javascript
227+
let x = 1;
228+
229+
function f(y = x) {
230+
let x = 2;
231+
console.log(y);
184232
}
185233

186-
foo()
187-
// Error: Missing parameter
234+
f() // 1
188235
```
189236

190-
上面代码的`foo`函数,如果调用的时候没有参数,就会调用默认值`throwIfMissing`函数,从而抛出一个错误
237+
上面代码中,函数调用时,`y`的默认值变量`x`尚未在函数内部生成,所以`x`指向全局变量,结果又不一样
191238

192-
从上面代码还可以看到,参数`mustBeProvided`的默认值等于`throwIfMissing`函数的运行结果(即函数名之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与python语言不一样
239+
如果此时,全局变量`x`不存在,就会报错
193240

194-
另一个需要注意的地方是,参数默认值所处的作用域,不是全局作用域,而是函数作用域。
241+
```javascript
242+
function f(y = x) {
243+
let x = 2;
244+
console.log(y);
245+
}
246+
247+
f() // ReferenceError: x is not defined
248+
```
249+
250+
如果函数`A`的参数默认值是函数`B`,由于函数的作用域是其声明时所在的作用域,那么函数`B`的作用域不是函数`A`,而是全局作用域。请看下面的例子。
195251

196252
```javascript
197-
var x = 1;
253+
let foo = 'outer';
198254

199-
function foo(x, y = x) {
200-
console.log(y);
255+
function bar(func = x => foo) {
256+
let foo = 'inner';
257+
console.log(func()); // outer
201258
}
202259

203-
foo(2) // 2
260+
bar();
204261
```
205262

206-
上面代码中,参数y的默认值等于x,由于处在函数作用域,所以y等于参数x,而不是全局变量x
263+
上面代码中,函数`bar`的参数`func`,默认是一个匿名函数,返回值为变量`foo`。这个匿名函数的作用域就不是`bar`。这个匿名函数声明时,是处在外层作用域,所以内部的`foo`指向函数体外的声明,输出`outer`。它实际上等同于下面的代码
207264

208-
参数变量是默认声明的,所以不能用let或const再次声明。
265+
```javascript
266+
let foo = 'outer';
267+
let f = x => foo;
268+
269+
function bar(func = f) {
270+
let foo = 'inner';
271+
console.log(func()); // outer
272+
}
273+
274+
bar();
275+
```
276+
277+
如果写成下面这样,就会报错。
209278

210279
```javascript
211-
function foo(x = 5) {
212-
let x = 1; // error
213-
const x = 2; // error
280+
function bar(func = () => foo) {
281+
let foo = 'inner';
282+
console.log(func());
214283
}
284+
285+
bar() // ReferenceError: foo is not defined
215286
```
216287

217-
上面代码中,参数变量x是默认声明的,在函数体中,不能用let或const再次声明,否则会报错。
288+
### 应用
218289

219-
参数默认值可以与解构赋值,联合起来使用
290+
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误
220291

221292
```javascript
222-
function foo({x, y = 5}) {
223-
console.log(x, y);
293+
function throwIfMissing() {
294+
throw new Error('Missing parameter');
224295
}
225296

226-
foo({}) // undefined, 5
227-
foo({x: 1}) // 1, 5
228-
foo({x: 1, y: 2}) // 1, 2
297+
function foo(mustBeProvided = throwIfMissing()) {
298+
return mustBeProvided;
299+
}
300+
301+
foo()
302+
// Error: Missing parameter
229303
```
230304

231-
上面代码中,foo函数的参数是一个对象,变量x和y用于解构赋值,y有默认值5。
305+
上面代码的`foo`函数,如果调用的时候没有参数,就会调用默认值`throwIfMissing`函数,从而抛出一个错误。
306+
307+
从上面代码还可以看到,参数`mustBeProvided`的默认值等于`throwIfMissing`函数的运行结果(即函数名之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与python语言不一样。
232308

233309
## rest参数
234310

@@ -544,6 +620,13 @@ var go = function*(){
544620

545621
上面代码中,变量`go`是一个Generator函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。
546622

623+
如果对没有`iterator`接口的对象,使用扩展运算符,将会报错。
624+
625+
```javascript
626+
var obj = {a: 1, b: 2};
627+
let arr = [...obj]; // TypeError: Cannot spread non-iterable object
628+
```
629+
547630
## name属性
548631

549632
函数的`name`属性,返回该函数的函数名。

docs/let.md

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -127,33 +127,6 @@ function bar(x = 2, y = x) {
127127
bar(); // [2, 2]
128128
```
129129

130-
需要注意的是,函数的作用域是其声明时所在的作用域。如果函数`A`的参数是函数`B`,那么函数`B`的作用域不是函数`A`
131-
132-
```javascript
133-
let foo = 'outer';
134-
135-
function bar(func = x => foo) {
136-
let foo = 'inner';
137-
console.log(func()); // outer
138-
}
139-
140-
bar();
141-
```
142-
143-
上面代码中,函数`bar`的参数`func`,默认是一个匿名函数,返回值为变量`foo`。这个匿名函数的作用域就不是`bar`。这个匿名函数声明时,是处在外层作用域,所以内部的`foo`指向函数体外的声明,输出`outer`。它实际上等同于下面的代码。
144-
145-
```javascript
146-
let foo = 'outer';
147-
let f = x => foo;
148-
149-
function bar(func = f) {
150-
let foo = 'inner';
151-
console.log(func()); // outer
152-
}
153-
154-
bar();
155-
```
156-
157130
ES6规定暂时性死区和不存在变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在ES5是很常见的,现在有了这种规定,避免此类错误就很容易了。
158131

159132
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

0 commit comments

Comments
 (0)