# 函式的擴展

## 函式引數的預設值

### 基本用法

``````function log(x, y) {
y = y || 'World';
console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World
``````

``````if (typeof y === 'undefined') {
y = 'World';
}
``````

ES6 允許為函式的引數設定預設值，即直接寫在引數定義的後面。

``````function log(x, y = 'World') {
console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
``````

``````function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}

var p = new Point();
p // { x: 0, y: 0 }
``````

``````function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}
``````

``````function foo(x, x, y = 1) {
// ...
}
// SyntaxError: Duplicate parameter name not allowed in this context
``````

``````let x = 99;
function foo(p = x + 1) {
console.log(p);
}

foo() // 100

x = 100;
foo() // 101
``````

### 與解構賦值預設值結合使用

``````function foo({x, y = 5}) {
console.log(x, y);
}

foo({}) // undefined, 5
foo({x: 1}) // 1, 5
foo({x: 1, y: 2}) // 1, 2
foo() // TypeError: Cannot read property 'x' of undefined
``````

``````function fetch(url, { body = '', method = 'GET', headers = {} }) {
console.log(method);
}

fetch('http://example.com', {})
// "GET"

fetch('http://example.com')
// 報錯
``````

``````function fetch(url, { method = 'GET' } = {}) {
console.log(method);
}

fetch('http://example.com')
// "GET"
``````

``````// 寫法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}

// 寫法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
``````

``````// 函式沒有引數的情況
m1() // [0, 0]
m2() // [0, 0]

// x和y都有值的情況
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]

// x有值，y無值的情況
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]

// x和y都無值的情況
m1({}) // [0, 0];
m2({}) // [undefined, undefined]

m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
``````

### 引數預設值的位置

``````// 例一
function f(x = 1, y) {
return [x, y];
}

f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 報錯
f(undefined, 1) // [1, 1]

// 例二
function f(x, y = 5, z) {
return [x, y, z];
}

f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 報錯
f(1, undefined, 2) // [1, 5, 2]
``````

``````function foo(x = 5, y = 6) {
console.log(x, y);
}

foo(undefined, null)
// 5 null
``````

### 函式的 length 屬性

``````(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
``````

``````(function(...args) {}).length // 0
``````

``````(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
``````

### 作用域

``````var x = 1;

function f(x, y = x) {
console.log(y);
}

f(2) // 2
``````

``````let x = 1;

function f(y = x) {
let x = 2;
console.log(y);
}

f() // 1
``````

``````function f(y = x) {
let x = 2;
console.log(y);
}

f() // ReferenceError: x is not defined
``````

``````var x = 1;

function foo(x = x) {
// ...
}

foo() // ReferenceError: x is not defined
``````

``````let foo = 'outer';

function bar(func = x => foo) {
let foo = 'inner';
console.log(func()); // outer
}

bar();
``````

``````function bar(func = () => foo) {
let foo = 'inner';
console.log(func());
}

bar() // ReferenceError: foo is not defined
``````

``````var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}

foo() // 3
x // 1
``````

``````var x = 1;
function foo(x, y = function() { x = 2; }) {
x = 3;
y();
console.log(x);
}

foo() // 2
x // 1
``````

### 應用

``````function throwIfMissing() {
throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}

foo()
// Error: Missing parameter
``````

``````function foo(optional = undefined) { ··· }
``````

## rest引數

ES6 引入 rest 引數（形式為“...變數名”），用於獲取函式的多餘引數，這樣就不需要使用`arguments`物件了。rest 引數搭配的變數是一個數組，該變數將多餘的引數放入陣列中。

``````function add(...values) {
let sum = 0;

for (var val of values) {
sum += val;
}

return sum;
}

``````

``````// arguments變數的寫法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}

// rest引數的寫法
const sortNumbers = (...numbers) => numbers.sort();
``````

rest 引數中的變數代表一個數組，所以陣列特有的方法都可以用於這個變數。下面是一個利用 rest 引數改寫陣列`push`方法的例子。

``````function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}

var a = [];
push(a, 1, 2, 3)
``````

``````// 報錯
function f(a, ...b, c) {
// ...
}
``````

``````(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1
``````

## 展開運算子

### 含義

``````console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
``````

``````function push(array, ...items) {
array.push(...items);
}

return x + y;
}

var numbers = [4, 38];
``````

``````function f(v, w, x, y, z) { }
var args = [0, 1];
f(-1, ...args, 2, ...);
``````

### 替代陣列的apply方法

``````// ES5的寫法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的寫法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f(...args);
``````

``````// ES5的寫法
Math.max.apply(null, [14, 3, 77])

// ES6的寫法
Math.max(...[14, 3, 77])

// 等同於
Math.max(14, 3, 77);
``````

``````// ES5的寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6的寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);
``````

``````// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
// ES6
new Date(...[2015, 1, 1]);
``````

### 展開運算子的應用

（1）合併陣列

``````// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more]

var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];

// ES5的合併陣列
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6的合併陣列
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
``````

（2）與解構賦值結合

``````// ES5
a = list, rest = list.slice(1)
// ES6
[a, ...rest] = list
``````

``````const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest  // []:

const [first, ...rest] = ["foo"];
first  // "foo"
rest   // []
``````

``````const [...butLast, last] = [1, 2, 3, 4, 5];
// 報錯

const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 報錯
``````

（3）函式的返回值

JavaScript的函式只能返回一個值，如果需要返回多個值，只能返回陣列或物件。展開運算子提供瞭解決這個問題的一種變通方法。

``````var dateFields = readDateFields(database);
var d = new Date(...dateFields);
``````

（4）字串

``````[...'hello']
// [ "h", "e", "l", "l", "o" ]
``````

``````'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3
``````

``````function length(str) {
return [...str].length;
}

length('x\uD83D\uDE80y') // 3
``````

``````let str = 'x\uD83D\uDE80y';

str.split('').reverse().join('')
// 'y\uDE80\uD83Dx'

[...str].reverse().join('')
// 'y\uD83D\uDE80x'
``````

（5）實現了Iterator介面的物件

``````var nodeList = document.querySelectorAll('div');
var array = [...nodeList];
``````

``````let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};

// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];
``````

（6）Map和Set結構，Generator函式

``````let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]
``````

Generator函式執行後，返回一個遍歷器物件，因此也可以使用展開運算子。

``````var go = function*(){
yield 1;
yield 2;
yield 3;
};

[...go()] // [1, 2, 3]
``````

``````var obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object
``````

## 嚴格模式

``````function doSomething(a, b) {
'use strict';
// code
}
``````

《ECMAScript 2016標準》做了一點修改，規定只要函式引數使用了預設值、解構賦值、或者展開運算子，那麼函式內部就不能顯式設定為嚴格模式，否則會報錯。

``````// 報錯
function doSomething(a, b = a) {
'use strict';
// code
}

// 報錯
const doSomething = function ({a, b}) {
'use strict';
// code
};

// 報錯
const doSomething = (...a) => {
'use strict';
// code
};

const obj = {
// 報錯
doSomething({a, b}) {
'use strict';
// code
}
};
``````

``````// 報錯
function doSomething(value = 070) {
'use strict';
return value;
}
``````

``````'use strict';

function doSomething(a, b = a) {
// code
}
``````

``````const doSomething = (function () {
'use strict';
return function(value = 42) {
return value;
};
}());
``````

## name 屬性

``````function foo() {}
foo.name // "foo"
``````

``````var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"
``````

``````const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"
``````

`Function`建構函式返回的函式實例，`name`屬性的值為`anonymous`

``````(new Function).name // "anonymous"
``````

`bind`返回的函式，`name`屬性值會加上`bound`字首。

``````function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "
``````

## 箭頭函式

### 基本用法

ES6允許使用“箭頭”（`=>`）定義函式。

``````var f = v => v;
``````

``````var f = function(v) {
return v;
};
``````

``````var f = () => 5;
// 等同於
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同於
var sum = function(num1, num2) {
return num1 + num2;
};
``````

``````var sum = (num1, num2) => { return num1 + num2; }
``````

``````var getTempItem = id => ({ id: id, name: "Temp" });
``````

``````const full = ({ first, last }) => first + ' ' + last;

// 等同於
function full(person) {
return person.first + ' ' + person.last;
}
``````

``````const isEven = n => n % 2 == 0;
const square = n => n * n;
``````

``````// 正常函式寫法
[1,2,3].map(function (x) {
return x * x;
});

// 箭頭函式寫法
[1,2,3].map(x => x * x);
``````

``````// 正常函式寫法
var result = values.sort(function (a, b) {
return a - b;
});

// 箭頭函式寫法
var result = values.sort((a, b) => a - b);
``````

``````const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

// [1,[2,3,4,5]]
``````

### 使用注意點

（1）函式體內的`this`物件，就是定義時所在的物件，而不是使用時所在的物件。

（2）不可以當作建構函式，也就是說，不可以使用`new`命令，否則會丟擲一個錯誤。

（3）不可以使用`arguments`物件，該物件在函式體內不存在。如果要用，可以用Rest引數代替。

（4）不可以使用`yield`命令，因此箭頭函式不能用作Generator函式。

``````function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42
``````

``````function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭頭函式
setInterval(() => this.s1++, 1000);
// 普通函式
setInterval(function () {
this.s2++;
}, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
``````

``````var handler = {
id: '123456',

init: function() {
event => this.doSomething(event.type), false);
},

doSomething: function(type) {
console.log('Handling ' + type  + ' for ' + this.id);
}
};
``````

`this`指向的固定化，並不是因為箭頭函式內部有繫結`this`的機制，實際原因是箭頭函式根本沒有自己的`this`，導致內部的`this`就是外層程式碼塊的`this`。正是因為它沒有`this`，所以也就不能用作建構函式。

``````// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}

// ES5
function foo() {
var _this = this;

setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
``````

``````function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
``````

``````function foo() {
setTimeout(() => {
console.log('args:', arguments);
}, 100);
}

foo(2, 4, 6, 8)
// args: [2, 4, 6, 8]
``````

``````(function() {
return [
(() => this.x).bind({ x: 'inner' })()
];
}).call({ x: 'outer' });
// ['outer']
``````

### 巢狀的箭頭函式

``````function insert(value) {
return {into: function (array) {
return {after: function (afterValue) {
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}};
}};
}

insert(2).into([1, 3]).after(1); //[1, 2, 3]
``````

``````let insert = (value) => ({into: (array) => ({after: (afterValue) => {
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}})});

insert(2).into([1, 3]).after(1); //[1, 2, 3]
``````

``````const pipeline = (...funcs) =>
val => funcs.reduce((a, b) => b(a), val);

const plus1 = a => a + 1;
const mult2 = a => a * 2;

// 12
``````

``````const plus1 = a => a + 1;
const mult2 = a => a * 2;

mult2(plus1(5))
// 12
``````

``````// λ演算的寫法
fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))

// ES6的寫法
var fix = f => (x => f(v => x(x)(v)))
(x => f(v => x(x)(v)));
``````

## 繫結 this

``````foo::bar;
// 等同於
bar.bind(foo);

foo::bar(...arguments);
// 等同於
bar.apply(foo, arguments);

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return obj::hasOwnProperty(key);
}
``````

``````var method = obj::obj.foo;
// 等同於
var method = ::obj.foo;

let log = ::console.log;
// 等同於
var log = console.log.bind(console);
``````

``````// 例一
import { map, takeWhile, forEach } from "iterlib";

getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));

// 例二
let { find, html } = jake;

document.querySelectorAll("div.myClass")
::find("p")
::html("hahaha");
``````

## 尾呼叫優化

### 什麼是尾呼叫？

``````function f(x){
return g(x);
}
``````

``````// 情況一
function f(x){
let y = g(x);
return y;
}

// 情況二
function f(x){
return g(x) + 1;
}

// 情況三
function f(x){
g(x);
}
``````

``````function f(x){
g(x);
return undefined;
}
``````

``````function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}
``````

### 尾呼叫優化

``````function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();

// 等同於
function f() {
return g(3);
}
f();

// 等同於
g(3);
``````

``````function addOne(a){
var one = 1;
function inner(b){
return b + one;
}
return inner(a);
}
``````

### 尾遞迴

``````function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}

factorial(5) // 120
``````

``````function factorial(n, total) {
return factorial(n - 1, n * total);
}

factorial(5, 1) // 120
``````

``````function Fibonacci (n) {
if ( n <= 1 ) {return 1};

return Fibonacci(n - 1) + Fibonacci(n - 2);
}

Fibonacci(10); // 89
// Fibonacci(100)
// Fibonacci(500)
// 堆疊溢位了
``````

``````function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};

return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity
``````

### 遞迴函式的改寫

``````function tailFactorial(n, total) {
return tailFactorial(n - 1, n * total);
}

function factorial(n) {
return tailFactorial(n, 1);
}

factorial(5) // 120
``````

``````function currying(fn, n) {
return function (m) {
return fn.call(this, m, n);
};
}

function tailFactorial(n, total) {
return tailFactorial(n - 1, n * total);
}

const factorial = currying(tailFactorial, 1);

factorial(5) // 120
``````

``````function factorial(n, total = 1) {
return factorial(n - 1, n * total);
}

factorial(5) // 120
``````

### 嚴格模式

ES6的尾呼叫優化只在嚴格模式下開啟，正常模式是無效的。

• `func.arguments`：返回呼叫時函式的引數。
• `func.caller`：返回呼叫當前函式的那個函式。

``````function restricted() {
"use strict";
restricted.caller;    // 報錯
restricted.arguments; // 報錯
}
restricted();
``````

### 尾遞迴優化的實現

``````function sum(x, y) {
if (y > 0) {
return sum(x + 1, y - 1);
} else {
return x;
}
}

sum(1, 100000)
// Uncaught RangeError: Maximum call stack size exceeded(…)
``````

``````function trampoline(f) {
while (f && f instanceof Function) {
f = f();
}
return f;
}
``````

``````function sum(x, y) {
if (y > 0) {
return sum.bind(null, x + 1, y - 1);
} else {
return x;
}
}
``````

``````trampoline(sum(1, 100000))
// 100001
``````

``````function tco(f) {
var value;
var active = false;
var accumulated = [];

return function accumulator() {
accumulated.push(arguments);
if (!active) {
active = true;
while (accumulated.length) {
value = f.apply(this, accumulated.shift());
}
active = false;
return value;
}
};
}

var sum = tco(function(x, y) {
if (y > 0) {
return sum(x + 1, y - 1)
}
else {
return x
}
});

sum(1, 100000)
// 100001
``````

## 函式引數的尾逗號

ES2017 允許函式的最後一個引數有尾逗號（trailing comma）。

``````function clownsEverywhere(
param1,
param2
) { /* ... */ }

clownsEverywhere(
'foo',
'bar'
);
``````

``````function clownsEverywhere(
param1,
param2,
) { /* ... */ }

clownsEverywhere(
'foo',
'bar',
);
``````
{"ft_lang_label":"__label__zh","ft_lang_prob":0.6965064,"math_prob":0.99873847,"size":24221,"snap":"2022-27-2022-33","text_gpt3_token_len":17583,"char_repetition_ratio":0.13110624,"word_repetition_ratio":0.11994517,"special_character_ratio":0.33706287,"punctuation_ratio":0.23201525,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9974883,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2022-08-18T13:28:57Z\",\"WARC-Record-ID\":\"<urn:uuid:c200993e-cbcb-45b6-9eee-b9e87066f0d2>\",\"Content-Length\":\"162911\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:9642fcba-3ec4-4b96-a272-58533948d774>\",\"WARC-Concurrent-To\":\"<urn:uuid:5b93055c-78e3-459d-af2a-1e2b616757e5>\",\"WARC-IP-Address\":\"104.18.3.206\",\"WARC-Target-URI\":\"https://yucj.gitbooks.io/ecmascript-6/content/docs/function.html\",\"WARC-Payload-Digest\":\"sha1:BRBUO55PYKLE2G5JS6KX6MYAHK3EER6S\",\"WARC-Block-Digest\":\"sha1:YEGO3TLV5EYY7CEQWQXR4D2YNOO3PEDI\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2022/CC-MAIN-2022-33/CC-MAIN-2022-33_segments_1659882573197.34_warc_CC-MAIN-20220818124424-20220818154424-00094.warc.gz\"}"}