aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/javascript/guide/functions/index.html
blob: 73a1efbdc4765feb93df898de828efc0506702e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
---
title: 函数
slug: Web/JavaScript/Guide/Functions
tags:
  - JavaScript
  - 函数
  - 初学者
  - 教程
translation_of: Web/JavaScript/Guide/Functions
---
<p>{{jsSidebar("JavaScript Guide")}} {{PreviousNext("Web/JavaScript/Guide/Loops_and_iteration", "Web/JavaScript/Guide/Expressions_and_Operators")}}</p>

<p>函数是 JavaScript 中的基本组件之一。 一个函数是 JavaScript 过程 — 一组执行任务或计算值的语句。要使用一个函数,你必须将其定义在你希望调用它的作用域内。</p>

<p>一个JavaScript 函数用<code>function</code>关键字定义,后面跟着函数名和圆括号。</p>

<p>查看 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions">JavaScript 函数详细参考文档</a> 了解更多。</p>

<h2 id="定义函数">定义函数</h2>

<h3 id="函数声明">函数声明</h3>

<p>一个<strong>函数定义</strong>(也称为<strong>函数声明</strong>,或<strong>函数语句</strong>)由一系列的<a href="/zh-CN/docs/JavaScript/Reference/Statements/function" title="function"><code>function</code></a>关键字组成,依次为:</p>

<ul>
 <li>函数的名称。</li>
 <li>函数参数列表,包围在括号中并由逗号分隔。</li>
 <li>定义函数的 JavaScript 语句,用大括号<code>{}</code>括起来。</li>
</ul>

<p>例如,以下的代码定义了一个简单的<code>square</code>函数:</p>

<pre class="brush: js">function square(number) {
  return number * number;
}
</pre>

<p>函数<code>square</code>使用了一个参数,叫作<code>number</code>。这个函数只有一个语句,它说明该函数将函数的参数(即<code>number</code>)自乘后返回。函数的<a href="/en-US/docs/JavaScript/Reference/Statements/return" title="return"><code>return</code></a>语句确定了函数的返回值:</p>

<pre class="brush: js">return number * number;
</pre>

<p>原始参数(比如一个具体的数字)被作为<strong></strong>传递给函数;值被传递给函数,如果被调用函数改变了这个参数的值,这样的改变不会影响到全局或调用函数。</p>

<p>如果你传递一个对象(即一个非原始值,例如{{jsxref("Array")}}或用户自定义的对象)作为参数,而函数改变了这个对象的属性,这样的改变对函数外部是可见的,如下面的例子所示:</p>

<pre class="brush: js">function myFunc(theObject) {
  theObject.make = "Toyota";
}

var mycar = {make: "Honda", model: "Accord", year: 1998};
var x, y;

x = mycar.make;     // x获取的值为 "Honda"

myFunc(mycar);
y = mycar.make;     // y获取的值为 "Toyota"
                    // (make属性被函数改变了)
</pre>

<h3 id="函数表达式">函数表达式</h3>

<p>虽然上面的函数声明在语法上是一个语句,但函数也可以由函数表达式创建。这样的函数可以是<strong>匿名</strong>的;它不必有一个名称。例如,函数<code>square</code>也可这样来定义:</p>

<pre class="brush: js"><code>const square = function(number) { return number * number; };
var x = square(4); // x gets the value 16</code></pre>

<p>然而,函数表达式也可以提供函数名,并且可以用于在函数内部代指其本身,或者在调试器堆栈跟踪中识别该函数:</p>

<pre class="brush: js">const factorial = function fac(n) {return n&lt;2 ? 1 : n*fac(n-1)};

console.log(factorial(3));
</pre>

<p>当将函数作为参数传递给另一个函数时,函数表达式很方便。下面的例子演示了一个叫<code>map</code>的函数如何被定义,而后使用一个表达式函数作为其第一个参数进行调用:</p>

<pre class="brush: js">function map(f,a) {
  let result = []; // 创建一个数组
  let i; // 声明一个值,用来循环
  for (i = 0; i != a.length; i++)
    result[i] = f(a[i]);
  return result;
}
</pre>

<p>下面的代码:</p>

<pre class="brush: js line-numbers  language-js">function map(f, a) {
  let result = []; // 创建一个数组
  let i; // 声明一个值,用来循环
  for (i = 0; i != a.length; i++)
    result[i] = f(a[i]);
  return result;
}
const f = function(x) {
   return x * x * x;
}
let numbers = [0,1, 2, 5,10];
let cube = map(f,numbers);
console.log(cube);</pre>

<p>返回 [0, 1, 8, 125, 1000]。</p>

<p>在 JavaScript 中,可以根据条件来定义一个函数。比如下面的代码,当<code>num</code> 等于 0 的时候才会定义 <code>myFunc</code> :</p>

<pre class="brush: js">var myFunc;
if (num == 0){
  myFunc = function(theObject) {
    theObject.make = "Toyota"
  }
}</pre>

<p>除了上述的定义函数方法外,你也可以在运行时用 {{jsxref("Function")}} 构造器由一个字符串来创建一个函数 ,很像 {{jsxref("Global_Objects/eval", "eval()")}} 函数。</p>

<p>当一个函数是一个对象的属性时,称之为<strong>方法</strong>。了解更多关于对象和方法的知识 <a href="/zh-CN/docs/Web/JavaScript/Guide/Working_with_Objects" title="en-US/docs/JavaScript/Guide/Working with Objects">使用对象</a></p>

<h2 id="调用函数">调用函数</h2>

<p>定义一个函数并不会自动的执行它。定义了函数仅仅是赋予函数以名称并明确函数被调用时该做些什么。<strong>调用</strong>函数才会以给定的参数真正执行这些动作。例如,一旦你定义了函数<code>square</code>,你可以如下这样调用它:</p>

<pre class="brush: js">square(5);
</pre>

<p>上述语句通过提供参数 5 来调用函数。函数执行完它的语句会返回值25。</p>

<p>函数一定要处于调用它们的域中,但是函数的声明可以被提升(出现在调用语句之后),如下例:</p>

<pre class="brush: js">console.log(square(5));
/* ... */
function square(n) { return n*n }
</pre>

<p>函数域是指函数声明时的所在的地方,或者函数在顶层被声明时指整个程序。</p>

<div class="note">
<p><strong>提示:</strong>注意只有使用如上的语法形式(即 <code>function funcName(){}</code>)才可以。而下面的代码是无效的。就是说,函数提升仅适用于函数声明,而不适用于函数表达式。</p>
</div>

<pre class="brush: js example-bad line-numbers  language-js">console.log(square); // square is hoisted with an initial value undefined.
console.log(square(5)); // Uncaught TypeError: square is not a function
const square = function (n) {
  return n * n;
}</pre>

<p>函数的参数并不局限于字符串或数字。你也可以将整个对象传递给函数。函数 <code>show_props</code>(其定义参见 <a href="https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Working_with_Objects#Objects_and_Properties" title="https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Working_with_Objects#Objects_and_Properties">用对象编程</a>)就是一个将对象作为参数的例子。</p>

<p>函数可以被递归,就是说函数可以调用其本身。例如,下面这个函数就是用递归计算阶乘:</p>

<pre class="brush: js">function factorial(n){
  if ((n == 0) || (n == 1))
    return 1;
  else
    return (n * factorial(n - 1));
}
</pre>

<p>你可以计算1-5的阶乘如下:</p>

<pre class="brush: js">var a, b, c, d, e;

a = factorial(1); // 1赋值给a
b = factorial(2); // 2赋值给b
c = factorial(3); // 6赋值给c
d = factorial(4); // 24赋值给d
e = factorial(5); // 120赋值给e
</pre>

<p>还有其它的方式来调用函数。常见的一些情形是某些地方需要动态调用函数,或者函数的实参数量是变化的,或者调用函数的上下文需要指定为在运行时确定的特定对象。显然,函数本身就是对象,因此这些对象也有方法(参考{{jsxref("Function")}} )。作为此中情形之一,{{jsxref("Function.apply", "apply()")}}方法可以实现这些目的。</p>

<h2 class="deki-transform" id="函数作用域">函数作用域</h2>

<p>在函数内定义的变量不能在函数之外的任何地方访问,因为变量仅仅在该函数的域的内部有定义。相对应的,一个函数可以访问定义在其范围内的任何变量和函数。换言之,定义在全局域中的函数可以访问所有定义在全局域中的变量。在另一个函数中定义的函数也可以访问在其父函数中定义的所有变量和父函数有权访问的任何其他变量。</p>

<pre class="brush: js">// 下面的变量定义在全局作用域(global scope)中
var num1 = 20,
    num2 = 3,
    name = "Chamahk";

// 本函数定义在全局作用域
function multiply() {
  return num1 * num2;
}

multiply(); // 返回 60

// 嵌套函数的例子
function getScore() {
  var num1 = 2,
      num2 = 3;

  function add() {
    return name + " scored " + (num1 + num2);
  }

  return add();
}

getScore(); // 返回 "Chamahk scored 5"
</pre>

<h2 id="作用域和函数堆栈">作用域和函数堆栈</h2>

<h3 id="递归">递归</h3>

<p>一个函数可以指向并调用自身。有三种方法可以达到这个目的:</p>

<ol>
 <li>函数名</li>
 <li><code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/callee">arguments.callee</a></code></li>
 <li>作用域下的一个指向该函数的变量名</li>
</ol>

<p>例如,思考一下如下的函数定义:</p>

<pre class="brush: js">var foo = function bar() {
   // statements go here
};
</pre>

<p>在这个函数体内,以下的语句是等价的:</p>

<ol>
 <li><code>bar()</code></li>
 <li><code>arguments.callee()</code> <strong>(译者注:ES5禁止在严格模式下使用此属性)</strong></li>
 <li><code>foo()</code></li>
</ol>

<p>调用自身的函数我们称之为<em>递归函数</em>。在某种意义上说,递归近似于循环。两者都重复执行相同的代码,并且两者都需要一个终止条件(避免无限循环或者无限递归)。例如以下的循环:</p>

<pre class="brush: js">var x = 0;
while (x &lt; 10) { // "x &lt; 10" 是循环条件
   // do stuff
   x++;
}
</pre>

<p>可以被转化成一个递归函数和对其的调用:</p>

<pre class="brush: js">function loop(x) {
  if (x &gt;= 10) // "x &gt;= 10" 是退出条件(等同于 "!(x &lt; 10)")
    return;
  // 做些什么
  loop(x + 1); // 递归调用
}
loop(0);
</pre>

<p>不过,有些算法并不能简单的用迭代来实现。例如,获取树结构中所有的节点时,使用递归实现要容易得多:</p>

<pre class="brush: js">function walkTree(node) {
  if (node == null) //
    return;
  // do something with node
  for (var i = 0; i &lt; node.childNodes.length; i++) {
    walkTree(node.childNodes[i]);
  }
}
</pre>

<p><code>loop</code>函数相比,这里每个递归调用都产生了更多的递归。</p>

<p>将递归算法转换为非递归算法是可能的,不过逻辑上通常会更加复杂,而且需要使用堆栈。事实上,递归函数就使用了堆栈:函数堆栈。</p>

<p>这种类似堆栈的行为可以在下例中看到:</p>

<pre class="brush: js">function foo(i) {
  if (i &lt; 0)
    return;
  console.log('begin:' + i);
  foo(i - 1);
  console.log('end:' + i);
}
foo(3);

// 输出:

// begin:3
// begin:2
// begin:1
// begin:0
// end:0
// end:1
// end:2
// end:3</pre>

<h3 id="嵌套函数和闭包">嵌套函数和闭包</h3>

<p>你可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的表达式(通常是函数)。</p>

<p>既然嵌套函数是一个闭包,就意味着一个嵌套函数可以”继承“容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。</p>

<p>可以总结如下:</p>

<ul>
 <li>内部函数只可以在外部函数中访问。</li>
 <li>内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。</li>
</ul>

<p>下面的例子展示了嵌套函数:</p>

<pre class="brush: js">function addSquares(a, b) {
  function square(x) {
    return x * x;
  }
  return square(a) + square(b);
}
a = addSquares(2, 3); // returns 13
b = addSquares(3, 4); // returns 25
c = addSquares(4, 5); // returns 41</pre>

<p>由于内部函数形成了闭包,因此你可以调用外部函数并为外部函数和内部函数指定参数:</p>

<pre class="brush: js">function outside(x) {
  function inside(y) {
    return x + y;
  }
  return inside;
}
fn_inside = outside(3); // 可以这样想:给一个函数,使它的值加3
result = fn_inside(5); // returns 8

result1 = outside(3)(5); // returns 8</pre>

<h3 id="保存变量">保存变量</h3>

<p>注意到上例中 <code>inside</code> 被返回时 <code>x</code> 是怎么被保留下来的。一个闭包必须保存它可见作用域中所有参数和变量。因为每一次调用传入的参数都可能不同,每一次对外部函数的调用实际上重新创建了一遍这个闭包。只有当返回的 <code>inside</code> 没有再被引用时,内存才会被释放。</p>

<p>这与在其他对象中存储引用没什么不同,但是通常不太明显,因为并不能直接设置引用,也不能检查它们。</p>

<h3 id="多层嵌套函数">多层嵌套函数</h3>

<p>函数可以被多层嵌套。例如,函数A可以包含函数B,函数B可以再包含函数C。B和C都形成了闭包,所以B可以访问A,C可以访问B和A。因此,闭包可以包含多个作用域;他们递归式的包含了所有包含它的函数作用域。这个称之为作用<em>域链</em>。(稍后会详细解释)</p>

<p>思考一下下面的例子:</p>

<pre class="brush: js">function A(x) {
  function B(y) {
    function C(z) {
      console.log(x + y + z);
    }
    C(3);
  }
  B(2);
}
A(1); // logs 6 (1 + 2 + 3)</pre>

<p>在这个例子里面,C可以访问B的y和A的x。这是因为:</p>

<ol>
 <li>B形成了一个包含A的闭包,B可以访问A的参数和变量</li>
 <li>C形成了一个包含B的闭包</li>
 <li>B包含A,所以C也包含A,C可以访问B和A的参数和变量。换言之,C用这个顺序链接了B和A的作用域</li>
</ol>

<p>反过来却不是这样。A不能访问C,因为A看不到B中的参数和变量,C是B中的一个变量,所以C是B私有的。</p>

<h3 id="命名冲突">命名冲突</h3>

<p>当同一个闭包作用域下两个参数或者变量同名时,就会产生命名冲突。更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。这就是作用域链。链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域。</p>

<p>看以下的例子:</p>

<pre class="brush: js">function outside() {
  var x = 5;
  function inside(x) {
    return x * 2;
  }
  return inside;
}

outside()(10); // returns 20 instead of 10</pre>

<p>命名冲突发生在<code>return x</code>上,<code>inside</code>的参数<code>x</code><code>outside</code>变量<code>x</code>发生了冲突。这里的作用链域是{<code>inside</code><code>outside</code>, 全局对象}。因此<code>inside</code><code>x</code>具有最高优先权,返回了20(<code>inside</code><code>x</code>)而不是10(<code>outside</code><code>x</code>)。</p>

<h2 id="闭包">闭包</h2>

<p>闭包是 JavaScript 中最强大的特性之一。JavaScript 允许函数嵌套,并且内部函数可以访问定义在外部函数中的所有变量和函数,以及外部函数能访问的所有变量和函数。</p>

<p>但是,外部函数却不能够访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一定的安全性。</p>

<p>此外,由于内部函数可以访问外部函数的作用域,因此当内部函数生存周期大于外部函数时,外部函数中定义的变量和函数的生存周期将比内部函数执行时间长。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。</p>

<pre class="brush: js">var pet = function(name) {          //外部函数定义了一个变量"name"
  var getName = function() {
    //内部函数可以访问 外部函数定义的"name"
    return name;
  }
  //返回这个内部函数,从而将其暴露在外部函数作用域
  return getName;
};
myPet = pet("Vivie");

myPet();                            // 返回结果 "Vivie"
</pre>

<p>实际上可能会比上面的代码复杂的多。在下面这种情形中,返回了一个包含可以操作外部函数的内部变量方法的对象。</p>

<pre class="brush: js">var createPet = function(name) {
  var sex;

  return {
    setName: function(newName) {
      name = newName;
    },

    getName: function() {
      return name;
    },

    getSex: function() {
      return sex;
    },

    setSex: function(newSex) {
      if(typeof newSex == "string"
        &amp;&amp; (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
        sex = newSex;
      }
    }
  }
}

var pet = createPet("Vivie");
pet.getName();                  // Vivie

pet.setName("Oliver");
pet.setSex("male");
pet.getSex();                   // male
pet.getName();                  // Oliver
</pre>

<p>在上面的代码中,外部函数的<code>name</code>变量对内嵌函数来说是可取得的,而除了通过内嵌函数本身,没有其它任何方法可以取得内嵌的变量。内嵌函数的内嵌变量就像内嵌函数的保险柜。它们会为内嵌函数保留“稳定”——而又安全——的数据参与运行。而这些内嵌函数甚至不会被分配给一个变量,或者不必一定要有名字。</p>

<pre class="brush: js">var getCode = (function(){
  var secureCode = "0]Eal(eh&amp;2";    // A code we do not want outsiders to be able to modify...

  return function () {
    return secureCode;
  };
})();

getCode();    // Returns the secret code
</pre>

<div class="blockIndicator note">
<p>尽管有上述优点,使用闭包时仍然要小心避免一些陷阱。如果一个闭包的函数定义了一个和外部函数的某个变量名称相同的变量,那么这个闭包将无法引用外部函数的这个变量。</p>

<pre class="brush: js">var createPet = function(name) {  // Outer function defines a variable called "name"
  return {
    setName: function(name) {    // Enclosed function also defines a variable called "name"
      name = name;               // ??? How do we access the "name" defined by the outer function ???
    }
  }
}
</pre>
</div>

<h2 id="使用_arguments_对象">使用 arguments 对象</h2>

<p>函数的实际参数会被保存在一个类似数组的arguments对象中。在函数内,你可以按如下方式找出传入的参数:</p>

<pre class="brush: js">arguments[i]
</pre>

<p>其中<code>i</code>是参数的序数编号(译注:数组索引),以0开始。所以第一个传来的参数会是<code>arguments[0]</code>。参数的数量由<code>arguments.length</code>表示。</p>

<p>使用arguments对象,你可以处理比声明的更多的参数来调用函数。这在你事先不知道会需要将多少参数传递给函数时十分有用。你可以用<code>arguments.length</code>来获得实际传递给函数的参数的数量,然后用<code>arguments</code>对象来取得每个参数。</p>

<p>例如,设想有一个用来连接字符串的函数。唯一事先确定的参数是在连接后的字符串中用来分隔各个连接部分的字符(译注:比如例子里的分号“;”)。该函数定义如下:</p>

<pre class="brush: js line-numbers  language-js">function myConcat(separator) {
   var result = ''; // 把值初始化成一个字符串,这样就可以用来保存字符串了!!
   var i;
   // iterate through arguments
   for (i = 1; i &lt; arguments.length; i++) {
      result += arguments[i] + separator;
   }
   return result;
}</pre>

<p>你可以给这个函数传递任意数量的参数,它会将各个参数连接成一个字符串“列表”:</p>

<pre class="brush: js">// returns "red, orange, blue, "
myConcat(", ", "red", "orange", "blue");

// returns "elephant; giraffe; lion; cheetah; "
myConcat("; ", "elephant", "giraffe", "lion", "cheetah");

// returns "sage. basil. oregano. pepper. parsley. "
myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");
</pre>

<div class="note"><strong>提示:</strong><code>arguments</code>变量只是 <em></em><strong>类数组对象</strong>“,并不是一个数组。称其为类数组对象是说它有一个索引编号和<code>length</code>属性。尽管如此,它并不拥有全部的Array对象的操作方法。</div>

<p>更多信息请阅读JavaScript参考里的{{jsxref("Function")}}一文。</p>

<h2 id="函数参数">函数参数</h2>

<p>从ECMAScript 6开始,有两个新的类型的参数:默认参数,剩余参数。</p>

<h3 id="默认参数">默认参数</h3>

<p>在JavaScript中,函数参数的默认值是<code>undefined</code>。然而,在某些情况下设置不同的默认值是有用的。这时默认参数可以提供帮助。</p>

<p>在过去,用于设定默认参数的一般策略是在函数的主体中测试参数值是否为<code>undefined</code>,如果是则赋予这个参数一个默认值。如果在下面的例子中,调用函数时没有实参传递给<code>b</code>,那么它的值就是<code>undefined</code>,于是计算<code>a*b</code>得到、函数返回的是 <code>NaN</code>。但是,在下面的例子中,这个已经被第二行获取处理:</p>

<pre class="brush: js">function multiply(a, b) {
  b = (typeof b !== 'undefined') ?  b : 1;

  return a*b;
}

multiply(5); // 5
</pre>

<p>使用默认参数,在函数体的检查就不再需要了。现在,你可以在函数头简单地把1设定为<code>b</code>的默认值:</p>

<pre class="brush: js">function multiply(a, b = 1) {
  return a*b;
}

multiply(5); // 5</pre>

<p>了解更多<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters">默认参数</a>的信息。</p>

<h3 id="剩余参数">剩余参数</h3>

<p><a href="/zh-CN/docs/Web/JavaScript/Reference/Functions/rest_parameters">剩余参数</a>语法允许将不确定数量的参数表示为数组。在下面的例子中,使用剩余参数收集从第二个到最后参数。然后,我们将这个数组的每一个数与第一个参数相乘。这个例子是使用了一个箭头函数,这将在下一节介绍。</p>

<pre class="brush: js">function multiply(multiplier, ...theArgs) {
  return theArgs.map(x =&gt; multiplier * x);
}

var arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]</pre>

<h2 id="箭头函数">箭头函数</h2>

<p><a href="/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions">箭头函数表达式</a>(也称胖箭头函数)相比函数表达式具有较短的语法并以词法的方式绑定 <code>this</code>。箭头函数总是匿名的。另见 hacks.mozilla.org 的博文:“<a href="https://hacks.mozilla.org/2015/06/es6-in-depth-arrow-functions/">深度了解ES6:箭头函数</a>”。</p>

<p>有两个因素会影响引入箭头函数:更简洁的函数和 <code>this</code></p>

<h3 id="更简洁的函数">更简洁的函数</h3>

<p>在一些函数模式中,更简洁的函数很受欢迎。对比一下:</p>

<pre class="brush: js line-numbers  language-js">var a = [
  "Hydrogen",
  "Helium",
  "Lithium",
  "Beryllium"
];

var a2 = a.map(function(s){ return s.length });

console.log(a2); // logs [ 8, 6, 7, 9 ]

var a3 = a.map( s =&gt; s.length );

console.log(a3); // logs [ 8, 6, 7, 9 ]</pre>

<h3 id="this_的词法"><code>this</code> 的词法</h3>

<p>在箭头函数出现之前,每一个新函数都重新定义了自己的 <a href="/zh-CN/docs/Web/JavaScript/Reference/Operators/this">this</a> 值(在构造函数中是一个新的对象;在严格模式下是未定义的;在作为“对象方法”调用的函数中指向这个对象;等等)。以面向对象的编程风格,这样着实有点恼人。</p>

<pre class="brush: js line-numbers  language-js">function Person() {
  // 构造函数Person()将`this`定义为自身
  this.age = 0;

  setInterval(function growUp() {
    // 在非严格模式下,growUp()函数将`this`定义为“全局对象”,
    // 这与Person()定义的`this`不同,
    // 所以下面的语句不会起到预期的效果。
    this.age++;
  }, 1000);
}

var p = new Person();</pre>

<p>在ECMAScript 3/5里,通过把<code>this</code>的值赋值给一个变量可以修复这个问题。</p>

<pre class="brush: js line-numbers  language-js">function Person() {
  var self = this; // 有的人习惯用`that`而不是`self`,
                   // 无论你选择哪一种方式,请保持前后代码的一致性
  self.age = 0;

  setInterval(function growUp() {
    // 以下语句可以实现预期的功能
    self.age++;
  }, 1000);
}</pre>

<p>另外,创建一个<a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind">约束函数</a>可以使得 <code>this</code>值被正确传递给 <code>growUp()</code> 函数。</p>

<p>箭头函数捕捉闭包上下文的<code>this</code>值,所以下面的代码工作正常。</p>

<pre class="brush: js line-numbers  language-js">function Person(){
  this.age = 0;

  setInterval(() =&gt; {
    this.age++; // 这里的`this`正确地指向person对象
  }, 1000);
}

var p = new Person();</pre>

<h2 id="预定义函数">预定义函数</h2>

<p>JavaScript语言有好些个顶级的内建函数:</p>

<dl>
 <dt>{{jsxref("Global_Objects/eval", "eval()")}}</dt>
 <dd>
 <p><code><strong>eval()</strong></code>方法会对一串字符串形式的JavaScript代码字符求值。</p>
 </dd>
 <dt>{{jsxref("Global_Objects/uneval", "uneval()")}} {{non-standard_inline}}</dt>
 <dd>
 <p><code><strong>uneval()</strong></code>方法创建的一个{{jsxref("Object")}}的源代码的字符串表示。</p>
 </dd>
 <dt>{{jsxref("Global_Objects/isFinite", "isFinite()")}}</dt>
 <dd>
 <p><code><strong>isFinite()</strong></code>函数判断传入的值是否是有限的数值。 如果需要的话,其参数首先被转换为一个数值。</p>
 </dd>
 <dt>{{jsxref("Global_Objects/isNaN", "isNaN()")}}</dt>
 <dd>
 <p><code><strong>isNaN()</strong></code>函数判断一个值是否是{{jsxref("Global_Objects/NaN", "NaN")}}。注意:<code>isNaN</code>函数内部的<code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/isNaN#Description">强制转换规则</a></code>十分有趣; 另一个可供选择的是ECMAScript 6 中定义{{jsxref("Number.isNaN()")}} , 或者使用 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Operators/typeof">typeof</a></code>来判断数值类型。</p>
 </dd>
 <dt>{{jsxref("Global_Objects/parseFloat", "parseFloat()")}}</dt>
 <dd>
 <p><code><strong>parseFloat()</strong></code> 函数解析字符串参数,并返回一个浮点数。</p>
 </dd>
 <dt>{{jsxref("Global_Objects/parseInt", "parseInt()")}}</dt>
 <dd>
 <p><code><strong>parseInt()</strong></code> 函数解析字符串参数,并返回指定的基数(基础数学中的数制)的整数。</p>
 </dd>
 <dt>{{jsxref("Global_Objects/decodeURI", "decodeURI()")}}</dt>
 <dd>
 <p><code><strong>decodeURI()</strong></code> 函数对先前经过{{jsxref("Global_Objects/encodeURI", "encodeURI")}}函数或者其他类似方法编码过的字符串进行解码。</p>
 </dd>
 <dt>{{jsxref("Global_Objects/decodeURIComponent", "decodeURIComponent()")}}</dt>
 <dd>
 <p><code><strong>decodeURIComponent()</strong></code>方法对先前经过{{jsxref("Global_Objects/encodeURIComponent", "encodeURIComponent")}}函数或者其他类似方法编码过的字符串进行解码。</p>
 </dd>
 <dt>{{jsxref("Global_Objects/encodeURI", "encodeURI()")}}</dt>
 <dd>
 <p><code><strong>encodeURI()</strong></code>方法通过用以一个,两个,三个或四个转义序列表示字符的UTF-8编码替换统一资源标识符(URI)的某些字符来进行编码(每个字符对应四个转义序列,这四个序列组了两个”替代“字符)。</p>
 </dd>
 <dt>{{jsxref("Global_Objects/encodeURIComponent", "encodeURIComponent()")}}</dt>
 <dd>
 <p><code><strong>encodeURIComponent()</strong></code> 方法通过用以一个,两个,三个或四个转义序列表示字符的UTF-8编码替换统一资源标识符(URI)的每个字符来进行编码(每个字符对应四个转义序列,这四个序列组了两个”替代“字符)。</p>
 </dd>
 <dt>{{jsxref("Global_Objects/escape", "escape()")}} {{deprecated_inline}}</dt>
 <dd>
 <p>已废弃的 <code><strong>escape()</strong></code> 方法计算生成一个新的字符串,其中的某些字符已被替换为十六进制转义序列。使用 {{jsxref("Global_Objects/encodeURI", "encodeURI")}}或者{{jsxref("Global_Objects/encodeURIComponent", "encodeURIComponent")}}替代本方法。</p>
 </dd>
 <dt>{{jsxref("Global_Objects/unescape", "unescape()")}} {{deprecated_inline}}</dt>
 <dd>
 <p>已废弃的 <code><strong>unescape()</strong></code> 方法计算生成一个新的字符串,其中的十六进制转义序列将被其表示的字符替换。上述的转义序列就像{{jsxref("Global_Objects/escape", "escape")}}里介绍的一样。因为 <code>unescape</code> 已经废弃,建议使用{{jsxref("Global_Objects/decodeURI", "decodeURI()")}}或者{{jsxref("Global_Objects/decodeURIComponent", "decodeURIComponent")}} 替代本方法。</p>
 </dd>
</dl>

<div>{{PreviousNext("Web/JavaScript/Guide/Loops_and_iteration", "Web/JavaScript/Guide/Expressions_and_Operators")}}</div>