# 函数MDN

在 JavaScript中,函数是头等(first-class)对象,因为它们可以像任何其他对象一样具有属性和方法。它们与其他对象的区别在于函数可以被调用。简而言之,它们是Function对象。

# 定义函数

# 函数声明

一个函数定义(也称为函数声明,或函数语句)由一系列的function关键字组成,依次为:

  • 函数的名称。
  • 函数参数列表,包围在括号中并由逗号分隔。
  • 定义函数的 JavaScript 语句,用大括号{}括起来。
// 一个名为square的函数
function square(number) {
  return number * number;
}
1
2
3
4

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

如果你传递一个对象(即一个非原始值,例如Array或用户自定义的对象)作为参数,而函数改变了这个对象的属性,这样的改变对函数外部是可见的,如下面的例子所示:(可通过深克隆解决)

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属性被函数改变了)
1
2
3
4
5
6
7
8
9
10
11
12

# 调用函数

定义一个函数并不会自动的执行它。定义了函数仅仅是赋予函数以名称并明确函数被调用时该做些什么

函数声明之后被提升了:

console.log(square(5)) // 25
function square(number) {
  return number * number;
}
1
2
3
4

函数表达式不会被提升

console.log(square(5)); // Uncaught ReferenceError: Cannot access 'square' before initialization
const square = function (number) { 
  return number * number; 
}
1
2
3
4

# 函数作用域

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

// 下面的变量定义在全局作用域(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"
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

# 作用域和函数堆栈

# 递归

函数调用自身

WARNING

使用递归必须要有终止条件,否则会造成死循环

// 获取该dom数结构的所有节点
<body>
  <div class="box">
    <div class="box-child">
    </div>
  </div>
  <div class="box">
    <div class="box-child">
    </div>
  </div>
</body>

const domTree = document.querySelector('body')
const nodeList = []

function getNodeList(node){
  for (var i = 0; i < node.childNodes.length; i++) {   // 子节点长度为零时终止
    nodeList.push(node.childNodes[i])
    getNodeList(node.childNodes[i]);
  }
}
getNodeList(domTree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 闭包

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

  • 内部函数只可以在外部函数中访问。
  • 内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。
  • 参数和变量不会被垃圾回收机制回收。
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
1
2
3
4
5
6
7
8
9

# arguments 对象

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

function sum(){
  let count = 0
  for(let i = 0; i < arguments.length;i++){
   count += arguments[i]
  }
  return count
}
sum(1,2,3,4) // 10
1
2
3
4
5
6
7
8

# 函数参数

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

# 默认参数

在JavaScript中,函数参数的默认值是undefined。然而,在某些情况下设置不同的默认值是有用的

// 当参数啊a,b默认为1
function multiply(a = 1, b = 1) {
  return a*b;
}

1
2
3
4
5

# 剩余参数

剩余参数语法允许将不确定数量的参数表示为数组。

function multiply(multiplier, ...theArgs) {
  return theArgs.map(x => multiplier * x);
}
multiply(2, 1, 2, 3); // [2, 4, 6]
1
2
3
4

# 箭头函数

箭头函数表达式(也称胖箭头函数)相比函数表达式具有较短的语法并以词法的方式绑定 this。箭头函数总是匿名的。

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 => s.length );

console.log(a3); // logs [ 8, 6, 7, 9 ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# this 的词法

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

  setInterval(function growUp() {
    // 在非严格模式下,growUp()函数将`this`定义为“全局对象”,
    // 这与Person()定义的`this`不同,
    // 所以下面的语句不会起到预期的效果。
    // 这里的this指向的是window 可以通过在外部绑定一个this或者使用箭头函数让this指向上下文
    this.age++;
  }, 1000);
}

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

  setInterval(() =>{
    // 在非严格模式下,growUp()函数将`this`定义为“全局对象”,
    // 这与Person()定义的`this`不同,
    // 所以下面的语句不会起到预期的效果。
    // 这里的this指向的是window 可以通过在外部绑定一个this或者使用箭头函数让this指向上下文
    this.age++;
  }, 1000);
}

function Person() {
  // 构造函数Person()将`this`定义为自身
  this.age = 0;
  const that = this
  setInterval(function() {
    // 在非严格模式下,growUp()函数将`this`定义为“全局对象”,
    // 这与Person()定义的`this`不同,
    // 所以下面的语句不会起到预期的效果。
    // 这里的this指向的是window 可以通过在外部绑定一个this或者使用箭头函数让this指向上下文
    that.age++;
  }, 1000);
}

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

# 预定义函数

# eval()

eval()方法会对一串字符串形式的JavaScript代码字符求值。

const str = 'alert(hello eval)'

eval(str) // hello eval
1
2
3

# isFinite()

你可以用这个方法来判定一个数字是否是有限数字。isFinite 方法检测它参数的数值。如果参数是 NaN,正无穷大或者负无穷大,会返回false,其他返回 true。

isFinite(Infinity);  // false
isFinite(NaN);       // false
isFinite(-Infinity); // false

isFinite(0);         // true
isFinite(2e64);      // true, 在更强壮的Number.isFinite(null)中将会得到false


isFinite("0");       // true, 在更强壮的Number.isFinite('0')中将会得到false
1
2
3
4
5
6
7
8
9

# isNaN()

如果给定值为 NaN则返回值为true;否则为false。

isNaN(NaN);  // true

NaN === NaN;  // false
1
2
3

# parseFloat()

给定值被解析成浮点数。如果给定值不能被转换成数值,则会返回 NaN。

// 都返回3.14
parseFloat(3.14);
parseFloat('3.14');
parseFloat('  3.14  ');
parseFloat('314e-2');
parseFloat('0.0314E+2');
parseFloat('3.14some non-digit characters');
parseFloat({ toString: function() { return "3.14" } });
1
2
3
4
5
6
7
8

# parseInt()

string 要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。 radix 可选 从 2 到 36,代表该进位系统的数字。例如说指定 10 就等于指定十进位。请注意,通常预设值不是 10 进位!

parseInt('123', 5)  // 将'123'看作5进制数,返回十进制数38 => 1*5^2 + 2*5^1 + 3*5^0 = 38
parseInt('123')  // 将'123'看作5进制数,返回十进制数38 => 1*5^2 + 2*5^1 + 3*5^0 = 38
1
2

# decodeURI()

一个完整的编码过的 URI

# decodeURIComponent()

一个解码后的统一资源标识符(URI)字符串,处理前的URI经过了给定格式的编码。

# encodeURI()

encodeURI 会替换所有的字符,但不包括以下字符,即使它们具有适当的UTF-8转义序列:

类型 包含
保留字符 ; , / ? : @ & = + $
非转义的字符 单元格
数字符号 #

# encodeURIComponent()

原字符串作为URI组成部分被编码后形成的字符串。