一、创建函数

JS 中的函数实际上是对象,每个函数都是 Function类型的实例

JS 中有多种创建函数的方法

1.1 函数声明

function sum(n1, n2) {
  return n1 + n2;
}

1.2 函数表达式

let sum = function(n1, n2) => {
  return n1 + n2;
};

1.3 构造函数

let sum = new Function("n1", "n2", "return n1+n2"); // 不推荐

构造函数创建函数,代码会被解释两次,不推荐,影响性能。

二、箭头函数

ES6 新增胖箭头函数,简洁且好用

let sum = (a, b) => {
  return a + b;
};

箭头函数非常适合用在嵌入函数的场景,例如数组的 mapfilter方法中。

虽然箭头函数语法简洁,但是它没有 argumentssupernew.target等参数,也没有 propotype属性。

三、函数名

ES6 中的函数都有一个只读的属性 name, 其中包含了函数的信息。

function foo() {}
let bar = function () {};
let baz = () => {};

console.log(foo.name); // foo
console.log(bar.name); // bar
console.log(baz.name); // baz
console.log((() => {}).name); //
console.log(new Function().name); // anonymous

四、函数参数

ES 的函数参数与大多数编程语言的参数都不同,它不关心你定义的函数数量,也不关心你传入的参数数量,也就是说,无论你定义或者不定义参数,传入或者不传入参数,解释器都不会报错。

4.1 函数未定义参数

function greet() {
  console.log("Hello!");
}

greet("Alice"); // 输出: Hello!

4.2 调用时不传入参数

function greet(name) {
  console.log("Hello, " + name + "!");
}

greet(); // 输出: Hello, undefined!

4.3 调用时传入多个参数

function greet(name) {
  console.log("Hello, " + name + "!");
}

greet("Alice", "Bob", "Charlie"); // 输出: Hello, Alice!

4.4 arguments 对象

JS 函数中的 arguments对象是一个类数组对象

你可以访问它的一些属性

  • arguments.length:返回传入参数的数量
function greet(name) {
  console.log(arguments); // ['Alice', 'Bob', 'Charlie', callee: ƒ, Symbol(Symbol.iterator): ƒ]
  console.log(arguments.length); // 3
}

greet("Alice", "Bob", "Charlie");

注意:arguments 参数只有在 function 关键字定义的函数中存在,在箭头函数中是不能使用的。

let sum = () => arguments;

sum(); // Uncaught ReferenceError: arguments is not defined

五、默认参数

ES6 支持显示定义参数默认值

function makeKing(name = "Bear") {
  return `King is ${name}`;
}

console.log(makeKing()); // King is Bear

六、参数扩展与收集

6.1 参数扩展

ES6 新增扩展操作符,使用它可以轻松的操作和组合集合数据。

我们定义一个 getSum函数

let values = [1, 2, 3, 4, 5];

function getSum() {
  let sum = 0;
  for (let i = 0; i < arguments.length; ++i) {
    sum += arguments[i];
  }
  return sum;
}

console.log(getSum(...values)); // 15

我们想再添加数据,可以使用扩展操作符

console.log(getSum(-1, ...values)); // 14

6.2 参数收集

扩展操作符可以把参数收集起来,在函数内部得到一个数组实例。但是有一定规则限制

function getSum(...values) {
  return values.reduce((x, y) => x + y, 0);
}

console.log(getSum(1, 2, 3)); // 6

七、函数声明和函数表达式

JavaScript 在加载函数声明和函数表达式时有区别!

JS 引擎在任何代码执行前,会先读取函数声明,并在执行上下文中生成函数定义(啥 🤔)。而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。

也就是说函数声明会有 函数声明提升,在执行代码前,JS 引擎会先执行一遍扫描,把发现的函数声明提升到源代码数的顶部。

  • 举个例子 🌰
console.log(sum(10, 10)); // 20
function sum(n1, n2) {
  return n1 + n2;
}
  • 举个函数表达式的例子

报错

console.log(sum(10, 10)); // Uncaught TypeError: sum is not a function
var sum = function sum(n1, n2) {
  return n1 + n2;
};

八、函数作为值

函数可以当作另一个函数的参数

举个例子 🌰

function callSomeFunction(someFunction, someArgument) {
  return someFunction(someArgument);
}
function add10(num) {
  return num + 10;
}

let result = callSomeFunction(add10, 10);
console.log(result); // 20

九、函数内部

ES5 中,函数内部存在两个特殊的对象:argumentsthis

ES6 新增一个 new.target属性

9.1 arguments.callee

arguments参数前面讨论过很多次了,它是一个类数组对象,我们要介绍的是它的一个属性 callee

callee属性指向 arguments对象所在函数的指针。

  • 举个例子

一个经典的递归求阶乘函数

function factorial(num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * factorial(num - 1);
  }
}
console.log(factorial(3));

我们可以使用 callee参数重写

function factorial(num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * arguments.callee(num - 1);
  }
}
console.log(factorial(3));

这个函数调用和上面的一样,你可能觉得这个没啥用。只是用 arguments.callee代替了 factorial。好像确实是 😅,但是红宝书给出了这样一个例子

let trueFac = factorial;

factorial = function () {
  return 0;
};

console.log(trueFac(5)); // 120
console.log(factorial(5)); // 0

依然觉得没啥用

9.2 this

this 是一个特殊的对象,它在标准函数和箭头函数中有不同的行为。

this 指代的对象根据上下文的不同会改变

  • 举个例子
window.color = "red";
let o = {
  color: "blue",
};

function sayColor() {
  console.log(this.color);
}

sayColor(); // red

o.sayColor = sayColor;
o.sayColor(); // blue

this 很复杂,后面会再写一篇总结。

9.3 caller

caller属性引用调用当前函数的函数

  • 举个例子
function outer() {
  inner();
}

function inner() {
  console.log(inner.caller);
}
outer();

显示 outer 的源代码

9.4 new.target

判断函数是不是使用 new关键字调用的

如果不是返回 undefined,如果是 new关键字调用,则引用被调用的构造函数。

十、函数属性和方法

10.1 prototype 属性

保存引用类型的所有实例方法

10.2 apply 方法

接收两个参数

  • this
  • 数组 或者 arguments

举个例子 🌰

function sum(n1, n2) {
  return n1 + n2;
}

function callSum1(n1, n2) {
  return sum.apply(this, arguments);
}

function callSum2(n1, n2) {
  return sum.apply(this, [n1, n2]);
}

console.log(callSum1(10, 10)); // 20
console.log(callSum2(10, 10)); // 20

在这个例子中,callSum1() 会调用 sum() 函数

10.3 call 方法

call方法与 apply方法的作用一样,但是传参不一样,第一个参数都是 this,但是第二个参数是逐个传递的。

function sum(n1, n2) {
  return n1 + n2;
}

function callSum1(n1, n2) {
  return sum.call(this, n1, n2);
}

console.log(callSum1(10, 10)); // 20

callapply方法作用一致,如何使用取决你觉得如何传参方便

10.4 bind 方法

bind 方法会创建一个新的函数实例,其 this 值会绑定到传给 bind() 的对象

window.color = "red";
var o = {
  color: "blue",
};

function sayColor() {
  console.log(this.color);
}

let objectSayColor = sayColor.bind(o);
objectSayColor(); // blue

十一、尾调用优化

看不懂,PASS😅

十二、闭包

过于重要,单独写一篇总结 ❤️

十三、立即调用的函数

立即调用函数表达式:

(function () {})();