# 函数

## 定义函数

### 函数声明

• 函数名称。
• 函数参数列表，包围在括号中并由逗号分隔。
• 定义函数的 JavaScript 语句，用大括号括起来，`{ /* … */ }`

js
``````function square(number) {
return number * number;
}
``````

js
``````function myFunc(theObject) {
theObject.make = "Toyota";
}

const mycar = {
make: "Honda",
model: "Accord",
year: 1998,
};

console.log(mycar.make); // "Honda"
myFunc(mycar);
console.log(mycar.make); // "Toyota"
``````

js
``````function myFunc(theArr) {
theArr[0] = 30;
}

const arr = [45];

console.log(arr[0]); // 45
myFunc(arr);
console.log(arr[0]); // 30
``````

### 函数表达式

js
``````const square = function (number) {
return number * number;
};

console.log(square(4)); // 16
``````

js
``````const factorial = function fac(n) {
return n < 2 ? 1 : n * fac(n - 1);
};

console.log(factorial(3)); // 6
``````

js
``````function map(f, a) {
const result = new Array(a.length);
for (let i = 0; i < a.length; i++) {
result[i] = f(a[i]);
}
return result;
}
``````

js
``````function map(f, a) {
const result = new Array(a.length);
for (let i = 0; i < a.length; i++) {
result[i] = f(a[i]);
}
return result;
}

const cube = function (x) {
return x * x * x;
};

const numbers = [0, 1, 2, 5, 10];
console.log(map(cube, numbers)); // [0, 1, 8, 125, 1000]
``````

js
``````let myFunc;
if (num === 0) {
myFunc = function (theObject) {
theObject.make = "Toyota";
};
}
``````

## 调用函数

js
``````square(5);
``````

js
``````function factorial(n) {
if (n === 0 || n === 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
``````

js
``````console.log(factorial(1)); // 1
console.log(factorial(2)); // 2
console.log(factorial(3)); // 6
console.log(factorial(4)); // 24
console.log(factorial(5)); // 120
``````

### 函数提升

js
``````console.log(square(5)); // 25

function square(n) {
return n * n;
}
``````

js
``````// 所有函数声明实际上都位于作用域的顶部
function square(n) {
return n * n;
}

console.log(square(5)); // 25
``````

js
``````console.log(square(5)); // ReferenceError: Cannot access 'square' before initialization
const square = function (n) {
return n * n;
};
``````

## 函数作用域

js
``````// 下面的变量定义在全局作用域中
const num1 = 20;
const num2 = 3;
const name = "Chamakh";

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

console.log(multiply()); // 60

// 嵌套函数示例
function getScore() {
const num1 = 2;
const num2 = 3;

return `\${name} 的得分为 \${num1 + num2}`;
}

}

console.log(getScore()); // "Chamakh 的得分为 5"
``````

## 作用域和函数栈

### 递归

1. 函数名
2. `arguments.callee`
3. 作用域内一个指向该函数的变量名

js
``````const foo = function bar() {
// 这里编写语句
};
``````

1. `bar()`
2. `arguments.callee()`
3. `foo()`

js
``````let x = 0;
// “x < 10”是循环条件
while (x < 10) {
// 做些什么
x++;
}
``````

js
``````function loop(x) {
// “x >= 10”是退出条件（等同于“!(x < 10)”）
if (x >= 10) {
return;
}
// 做些什么
loop(x + 1); // 递归调用
}
loop(0);
``````

js
``````function walkTree(node) {
if (node === null) {
return;
}
// 对节点做些什么
for (let i = 0; i < node.childNodes.length; i++) {
walkTree(node.childNodes[i]);
}
}
``````

`loop` 函数相比，这里每个递归调用都产生了更多的递归调用。

js
``````function foo(i) {
if (i < 0) {
return;
}
console.log(`开始：\${i}`);
foo(i - 1);
console.log(`结束：\${i}`);
}
foo(3);

// 打印：
// 开始：3
// 开始：2
// 开始：1
// 开始：0
// 结束：0
// 结束：1
// 结束：2
// 结束：3
``````

### 嵌套函数和闭包

• 内部函数只可以在外部函数中访问。
• 内部函数形成了一个闭包：它可以访问外部函数的参数和变量，但是外部函数却不能使用它的参数和变量。

js
``````function addSquares(a, b) {
function square(x) {
return x * x;
}
return square(a) + square(b);
}

console.log(addSquares(2, 3)); // 13
console.log(addSquares(3, 4)); // 25
console.log(addSquares(4, 5)); // 41
``````

js
``````function outside(x) {
function inside(y) {
return x + y;
}
return inside;
}

const fnInside = outside(3); // 可以这样想：给我一个可以将提供的值加上 3 的函数
console.log(fnInside(5)); // 8
console.log(outside(3)(5)); // 8
``````

### 多层嵌套函数

• 函数（`A`）可以包含函数（`B`），后者可以再包含函数（`C`）。
• 这里的函数 `B``C` 都形成了闭包，所以 `B` 可以访问 `A``C` 可以访问 `B`
• 此外，因为 `C` 可以访问 `B`（而 `B` 可以访问 `A`），所以 `C` 也可以访问 `A`

js
``````function A(x) {
function B(y) {
function C(z) {
console.log(x + y + z);
}
C(3);
}
B(2);
}
A(1); // 打印 6（即 1 + 2 + 3）
``````

1. `B` 形成了一个包含 `A` 的闭包（即，`B` 可以访问 `A` 的参数和变量）
2. `C` 形成了一个包含 `B` 的闭包。
3. `C` 的闭包包含 `B`，且 `B` 的闭包包含 `A`，所以 `C` 的闭包也包含 `A`。这意味着 `C` 同时可以访问 `B` `A` 的参数和变量。换言之，`C` 用这个顺序链接`B``A` 的作用域。

### 命名冲突

js
``````function outside() {
const x = 5;
function inside(x) {
return x * 2;
}
return inside;
}

console.log(outside()(10)); // 20（而不是 10）
``````

## 闭包

js
``````// 外部函数定义了一个名为“name”的变量
const pet = function (name) {
const getName = function () {
// 内部函数可以访问外部函数的“name”变量
return name;
};
return getName; // 返回内部函数，从而将其暴露给外部作用域
};
const myPet = pet("Vivie");

console.log(myPet()); // "Vivie"
``````

js
``````const createPet = function (name) {
let sex;

const pet = {
// 在这个上下文中：setName(newName) 等价于 setName: function (newName)
setName(newName) {
name = newName;
},

getName() {
return name;
},

getSex() {
return sex;
},

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

return pet;
};

const pet = createPet("Vivie");
console.log(pet.getName()); // Vivie

pet.setName("Oliver");
pet.setSex("male");
console.log(pet.getSex()); // male
console.log(pet.getName()); // Oliver
``````

js
``````const getCode = (function () {
const apiCode = "0]Eal(eh&2"; // 我们不希望外部能够修改的代码......

return function () {
return apiCode;
};
})();

console.log(getCode()); // "0]Eal(eh&2"
``````

js
``````const createPet = function (name) {
// 外部函数定义了一个名为“name”的变量。
return {
setName(name) {
// 闭包函数还定义了一个名为“name”的变量。
name = name; // 我们如何访问外部函数定义的“name”？
},
};
};
``````

## 使用 arguments 对象

js
``````arguments[i];
``````

js
``````function myConcat(separator) {
let result = ""; // 初始化列表
// 迭代 arguments
for (let i = 1; i < arguments.length; i++) {
result += arguments[i] + separator;
}
return result;
}
``````

js
``````console.log(myConcat("、", "红", "橙", "蓝"));
// "红、橙、蓝、"

console.log(myConcat("；", "大象", "长颈鹿", "狮子", "猎豹"));
// "大象；长颈鹿；狮子；猎豹；"

console.log(myConcat("。", "智者", "罗勒", "牛至", "胡椒", "香菜"));
// "智者。罗勒。牛至。胡椒。香菜。"
``````

## 函数参数

### 默认参数

js
``````function multiply(a, b) {
b = typeof b !== "undefined" ? b : 1;
return a * b;
}

console.log(multiply(5)); // 5
``````

js
``````function multiply(a, b = 1) {
return a * b;
}

console.log(multiply(5)); // 5
``````

### 剩余参数

js
``````function multiply(multiplier, ...theArgs) {
return theArgs.map((x) => multiplier * x);
}

const arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]
``````

## 箭头函数

### 更简洁的函数

js
``````const a = ["Hydrogen", "Helium", "Lithium", "Beryllium"];

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

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

const a3 = a.map((s) => s.length);

console.log(a3); // [8, 6, 7, 9]
``````

### 无单独的 this

js
``````function Person() {
// 构造函数 Person() 将 `this` 定义为自身。
this.age = 0;

setInterval(function growUp() {
// 在非严格模式下，growUp() 函数将 `this` 定义为“全局对象”，
// 这与 Person() 定义的 `this` 不同。
this.age++;
}, 1000);
}

const p = new Person();
``````

js
``````function Person() {
// 有的人习惯用 `that` 而不是 `self`。
// 请选择一种方式，并保持前后代码的一致性
const self = this;
self.age = 0;

setInterval(function growUp() {
// 回调引用 `self` 变量，其值为预期的对象。
self.age++;
}, 1000);
}
``````

js
``````function Person() {
this.age = 0;

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

const p = new Person();
``````

## 预定义函数

JavaScript 语言有几个顶级的内置函数：

`eval()`

`eval()` 方法执行方法计算以字符串表示的 JavaScript 代码。

`isFinite()`

`isFinite()` 全局函数判断传入的值是否是有限的数值。如果需要的话，其参数首先被转换为一个数值。

`isNaN()`

`isNaN()` 函数判断一个值是否是 `NaN`。注意：`isNaN` 函数内部的强制转换规则十分有趣。你也可以使用 `Number.isNaN()` 来判断该值是否为 NaN。

`parseFloat()`

`parseFloat()` 函数解析字符串参数，并返回一个浮点数。

`parseInt()`

`parseInt()` 函数解析字符串参数，并返回指定的基数（基础数学中的数制）的整数。

`decodeURI()`

`decodeURI()` 函数对先前经过 `encodeURI` 函数或者其他类似方法编码过的统一资源标志符（URI）进行解码。

`decodeURIComponent()`

`decodeURIComponent()` 方法对先前经过 `encodeURIComponent` 函数或者其他类似方法编码的统一资源标志符（URI）进行解码。

`encodeURI()`

`encodeURI()` 方法通过以表示字符的 UTF-8 编码的一个、两个、三个或四个转义序列替换统一资源标识符（URI）的某些字符来进行编码（对于由两个“代理（surrogate）”字符组成的字符，只会编码为四个转义序列）。

`encodeURIComponent()`

`encodeURIComponent()` 方法通过以表示字符的 UTF-8 编码的一个、两个、三个或四个转义序列替换统一资源标识符（URI）的某些字符来进行编码（对于由两个“代理”字符组成的字符，只会编码为四个转义序列）。

`escape()` 已弃用

`escape()` 方法生成一个新的字符串，其中的某些字符已被替换为十六进制转义序列。其已被弃用，请使用 `encodeURI()``encodeURIComponent()` 代替。

`unescape()` 已弃用

`unescape()` 方法计算生成一个新的字符串，其中的十六进制转义序列将被其表示的字符替换。上述的转义序列就像 `escape` 介绍的一样。其已被弃用，请使用 `decodeURI()``decodeURIComponent()` 替代。