函数是对象,是引用类型(传址类型) 对象、类中的函数称为方法


声明定义

new Function(name, body) 不推荐

**_desc_** 对象创建函数 _**params**_ { string } name 函数名称 _**params**_ { string } body 函数体 _**return**_ { function }

  1. var fn = new Function("add", "return 1 + 1");
  2. var total = fn();
  3. console.log(total); // 2

function name(...args) {}

* 全局变量,具名函数默认存在 window 中
* 可能导致 函数提升(可以在函数声明前,调用该函数)

**_desc_** 具名函数 _**params**_ { string } name 函数名称 _**params**_ { any } args 函数参数 _**return**_ { function }

  1. var total = add(1, 2); // 此时产生了 函数提升
  2. function add(a, b) {
  3. return a + b;
  4. }
  5. console.log(total); // 3

const name = function(...args) {} 👍

* 使用 var 声明时,函数会挂载到最顶端作用域中,即 window
* 使用 let / const 声明时,函数不会挂载到 window 上

**_desc_** 匿名函数 _**params**_ { string } name 函数名称 _**params**_ { any } args 函数参数 _**return**_ { function }

  1. // var total = add(1, 2); // error:add is not a function
  2. const add = function(a, b) {
  3. return a + b;
  4. }
  5. const total = add(1, 2);
  6. console.log(total); // 3

参数类型

形式参数

函数声明时设置的参数,用于占位

* 行参数量 > 实参数量时,没有传入值的行参默认为 undefined
* 行参数量 < 实参数量时,多余的实参将被忽略且不会报错

  1. const fn = function(a, b) {}; // a 和 b 为形式参数

实际参数

调用函数时实际传入的值

  1. const fn = function(a, b) {}; // a 和 b 为形式参数
  2. fn(1, 2); // 1 和 2 为实际参数

默认参数

形式参数的默认值

* es5 通过 || 实现默认值
* es6 通过行参赋值实现默认值。默认参数一般放在最后方。只占位不覆盖默认值可传入 undefined

|| 与 ?? 的区别

* || 当左侧值转换成布尔类型为 false 时,返回右侧的值
* ?? 当左侧值为 null 或 undefined 时,返回右侧的值

  1. const fn1 = function(a, b) {
  2. a = a || 100; // es5 默认值
  3. return a + b;
  4. };
  5. const fn2 = function(a, b = 100) { // es6 默认值
  6. return a + b;
  7. };

不定参数

数量不确定的行参 使用 …args 表示不定参数,args 是传入的所有参数组成的数组

  1. const fn = function(...args) {
  2. console.log(args);
  3. }
  4. fn(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

arguments

函数内置的属性,效果等同于不定参数 是一个伪数组

  1. const fn = function() {
  2. console.log(arguments);
  3. };
  4. fn(1, 2, 3, 4, 5); // Arguments(5) [1, 2, 3, 4, 5]

函数类型

箭头函数

箭头函数是函数声明的简写方式 箭头函数是匿名函数

* 普通函数和箭头函数内部的 this 不同
* 普通函数内部拥有属于自己的 this。this 指向调用者,即谁调用指向谁。默认为 window,严格模式下为 undefined
* 构造函数是普通函数中的例外,其内部 this 指向实例对象
* 箭头函数内部没有属于自己的 this。 其内部的 this 实际上是从外部获取的,内部的 this 始终与上一层的 this 保持一致。箭头函数会一直向上寻找 this,直到找到最近的一个 this
* 普通函数内置有 arguments 属性。箭头函数没该内置属性
* 普通函数具有原型对象。箭头函数没有原型对象
* 普通函数可以作为构造函数,使用 new 关键字构造实例。箭头函数不能作为构造函数,使用 new 关键字时会抛出异常。
* 由于箭头函数没有原型对象,原型链上没有 constructor 构造方法,所以无法作为构造函数使用

  1. /**
  2. * this 指向说明
  3. */
  4. const fn1 = function() {
  5. console.log(this);
  6. };
  7. const arrowFn1 = () => {
  8. console.log(this);
  9. };
  10. fn1(); // window,严格模式为 undefined
  11. function Human(name) {
  12. this.name = name;
  13. this.eat = function() {
  14. console.log(this); // 该函数是普通函数,内部 this 指向调用该方法的 this,即 Human 实例
  15. },
  16. this.run = function() {
  17. fn1(); // 等同于 window.fn1()
  18. },
  19. this.sing = () => {
  20. arrowFn1(); // 该函数是箭头函数,内部 this 与上一层函数内部的 this 保持一致。上一层也是箭头函数,则继续向上找,直到最顶层函数。顶层函数是 Human,为构造函数,构造函数 this 指向其实例,故该箭头函数的 this 也指向实例
  21. },
  22. }
  23. var human = new Human("张三");
  24. human.eat(); // { name: "张三" }
  25. human.run(); // window,严格模式为 undefined
  26. human.sing(); // { name: "张三" }
  27. /**
  28. * 普通函数内置有 arguments 属性。箭头函数没该内置属性
  29. */
  30. const fn2 = function() {
  31. console.log(arguments); // [1, 2, 3]
  32. };
  33. const arrowFn2 = () => {
  34. console.log(arguments); // error: arguments is not defined
  35. };
  36. fn2(1, 2, 3);
  37. arrowFn2(1, 2, 3);
  38. /**
  39. * 普通函数具有原型对象。箭头函数没有原型对象
  40. */
  41. const fn3 = function() {};
  42. const arrowFn3 = () => {};
  43. console.log(fn3.prototype); // { constructor: f() }
  44. console.log(arrowFn3.prototype); // undefined
  45. /**
  46. * 普通函数可以作为构造函数,使用 new 操作符构造实例。箭头函数不能作为构造函数,使用 new 操作符时会抛出异常
  47. */
  48. const Fn4 = function() {};
  49. const ArrowFn4 = () => {};
  50. const fn4 = new Fn4();
  51. console.log(fn4); // object
  52. const arrowFn4 = new ArrowFn4(); // error: ArrowFn4 is not constructor

递归函数

递归指函数内部调用自身

* 主要用于数量不确定的循环操作
* 必须明确退出循环的条件,避免发生死循环

  1. var width = 5;
  2. function triangle(width) {
  3. if (width <= 0) return;
  4. console.log("*".repeat(width));
  5. triangle(--width);
  6. }
  7. triangle(5);
  8. *****
  9. ****
  10. ***
  11. **
  12. *

回调函数

A callback is a function that is passed as an argument to another function and is executed after its parent function has completed. 回调是一个函数,是另一个函数的参数,是在该函数完成后执行执行的

* 回调与同步、异步没有直接关系。但使用场景主要是异步回调

  1. const getRunnerTime = function(callback) {
  2. var count = 0;
  3. var time = Date.now();
  4. var interval = setInterval(() => {
  5. count = Math.random();
  6. if (count > 0.5) {
  7. clearInterval(interval);
  8. callback(Date.now() - time);
  9. }
  10. }, 500);
  11. }
  12. getRunnerTime(function(time) {
  13. console.log(time);
  14. });

立即执行函数

立刻执行,无需调用

**(function(...args) {})(...args)**

_**params**_ { any } args 传入的参数 _**return**_ { void }

  1. (function(a) {
  2. console.log(a); // 100
  3. })(100)

构造函数

使用 new 关键字调用的函数,称为构造函数 构造函数首字母一般大写

* 构造函数中的 this 指向其实例对象
* 构造函数的返回值始终是 this,人为修改返回值无效
* 构造函数不能使用箭头函数,箭头函数内部无法创建自己的 this
* 可以判断构造函数内部的 this 是否在其原型链上,实现不使用 new 关键字调用构造函数

  1. /**
  2. * 不使用 new 关键字实现调用构造函数
  3. */
  4. function Male(name) {
  5. this.name = name;
  6. }
  7. function Female(name) {
  8. if (!(this instanceof Female)) {
  9. return new Female(name);
  10. }
  11. this.name = name;
  12. }
  13. var male1 = new Male("zhangsan");
  14. var male2 = Male("lisi");
  15. var female1 = new Female("wangwu");
  16. var female2 = Female("maliu");
  17. console.log(male1); // { name: "zhangsan" }
  18. console.log(male2); // undefined
  19. console.log(window.name); // "lisi"
  20. console.log(female1); // { name: "wangwu" }
  21. console.log(female2); // { name: "maliu" }

标签函数 了解,很少见

函数解析模版字符串

* 调用标签函数时,不需要使用 (),直接方法名 + 模版字符串即可
* 标签函数本质是将模版字符串中的 ${} 符号作为分割符,将模版字符串分割为常量和变量数组后,再传入函数

**function name(constant, ...variable) {}**

_**params**_ { string } name 函数名称 _**params**_ { array } constant 模版字符串中常量组成的数组 _**params**_ { array } variable 模版字符串中变量组成的数组 _**return**_ { any }

  1. const fn = function(constant, ...variable) {
  2. console.log(constant);
  3. console.log(variable);
  4. };
  5. fn `Hello${1}World${2}`; // ["Hello", "World"], [1, 2]

this

this 指函数的上下文环境 this 是动态可变的 this 在函数声明时是无法确定的,只有在函数执行时才能确定函数的指向

* 普通函数内部拥有属于自己的 this。this 指向调用者,即谁调用指向谁。默认为 window,严格模式下为 undefined
* 构造函数是普通函数中的例外,其内部 this 指向实例对象
* 箭头函数内部没有属于自己的 this。 其内部的 this 实际上是从外部获取的,内部的 this 始终与上一层的 this 保持一致。
PS:代码示例见 函数类型 - 箭头函数**

动态设置 this

即对象方法借用

fn.apply(ctx, [...args])

* 立即执行
* 使用数组传参
* 本质:对象 ctx 借用 fn 方法

_**params**_ { function } fn 被调用的方法 _**params**_ { object } ctx 借用方法的对象 _**params**_ { any } args 参数 _**return**_ { void }

  1. function Human() {
  2. this.run = function(a, b) {
  3. console.log(`Human run~${a + b}`);
  4. }
  5. }
  6. function fn () {};
  7. var human = new Human();
  8. human.run.apply(fn, [1, 2]); // "Human run~3"

fn.call(ctx, ...args)

* 立即执行
* 使用不定参数传参
* 与 apply 作用完全相同,唯一不同是传参格式不同

_**params**_ { function } fn 被调用的方法 _**params**_ { object } ctx 借用方法的对象 _**params**_ { any } args 参数 _**return**_ { void }

  1. function Human() {
  2. this.run = function(a, b) {
  3. console.log(`Human run~${a + b}`);
  4. }
  5. }
  6. function fn () {};
  7. var human = new Human();
  8. human.run.call(fn, 1, 2); // "Human run~3"

fn.bind(ctx, ...args)

* 不会立即执行
* bind 的本质是复制函数行为,并返回新的函数

_**params**_ { function } fn 被调用的方法 _**params**_ { object } ctx 借用方法的对象 _**params**_ { any } args 参数 _**return**_ { function }

  1. function Human() {
  2. this.run = function(a, b) {
  3. console.log(`Human run~${a + b}`);
  4. }
  5. }
  6. function fn () {};
  7. var human = new Human();
  8. var bindFn = human.run.bind(fn);
  9. console.log(bindFn === fn); // false, bindFn 是复制出的具有 human.run() 方法体的新函数,并非原 fn 函数
  10. bindFn(1, 2); // "Human run~3"

**bind 传参**
* bind 绑定时已传递全部参数,调用时再传参,此时传递的参数无效
* bind 绑定时不传递参数,调用时再传参,此时传递的参数有效
* bind 绑定时传递部分参数,调用时再传参,此时传递的参数会在绑定时传递的参数后面进行补位,多出的参数自动被删除

  1. function sum(a, b, c) {
  2. console.log(a + b + c);
  3. }
  4. function baseFn() {}
  5. var fn1 = sum.bind(baseFn, 1, 2, 3);
  6. var fn2 = sum.bind(baseFn);
  7. var fn3 = sum.bind(baseFn, 1);
  8. fn1(4, 5); // 6。绑定时已传递全部参数,此时参数无效
  9. fn2(1, 2, 3); // 6。绑定时未传递参数,此时参数有效
  10. fn3(4, 5, 6); // 10。绑定时传递部分参数,此时实参4和5有效,6已超出参数个数,自动删除

函数命名冲突

在同一个作用域中,相同名称的函数名会产出冲突(具名函数/var 会覆盖已有函数,let/const 会抛出异常) 解决思路:使相同名称的函数处于不同的局部作用域中

立即执行函数

  1. (function(window) {
  2. function add() {
  3. console.log("fn1.add");
  4. }
  5. window.fn1 = {
  6. add
  7. }
  8. })(window);
  9. (function(window) {
  10. function add() {
  11. console.log("fn2.add");
  12. }
  13. window.fn2 = {
  14. add
  15. }
  16. })(window);
  17. fn1.add(); // "fn1.add"
  18. fn2.add(); // "fn2.add"


块状作用域

  1. {
  2. function add() {
  3. console.log("fn1.add");
  4. }
  5. window.fn1 = {
  6. add
  7. }
  8. }
  9. {
  10. function add() {
  11. console.log("fn2.add");
  12. }
  13. window.fn2 = {
  14. add
  15. }
  16. }
  17. fn1.add(); // "fn1.add"
  18. fn2.add(); // "fn2.add"

Proxy 代理函数 ❓

* 在调用函数前添加一层拦截,可以对调用操作进行统一的处理。类似拦截器的作用

new Proxy(target, hander)

_**params**_ { object } target 使用 Proxy 代理的目标对象 _**params**_ { object } hander 定义代理行为的对象 _**return**_ { object } 代理对象实例

  1. function add(a, b) {
  2. console.log(a + b);
  3. }
  4. const proxy = new Proxy(add, {
  5. apply(fn, ctx, args) {
  6. for (let key in args) {
  7. args[key] = args[key] * 2;
  8. }
  9. fn.apply(ctx, args);
  10. }
  11. })
  12. add(1, 2); // 3
  13. proxy.apply(window, [1, 2]); // 6