【笔记】JS的函数

前言

JS的函数学习笔记

定义函数

通过函数声明定义函数

无返回值

1
2
3
function 函数名(形参列表) {
...
}

有返回值

1
2
3
4
function 函数名(形参列表) {
...
return 返回值;
}

通过函数表达式定义函数

  • 通过函数表达式定义的函数,在调用时,使用变量名当作函数名进行调用
1
2
3
var 变量名 = function (形参列表) {
...
}

箭头函数(ES6)

  • 箭头函数是函数表达式定义函数的语法糖
  • 箭头函数不绑定this,箭头函数体中调用this会向上层作用于查找
  • 箭头函数不绑定arguments,箭头函数体中调用arguments会向上层作用于查找
1
2
3
var 变量名 = (形参列表) => {
...
}

通过构造函数创建函数

1
var 变量名 = new Function("形参1", "形参2", "return 返回值");

调用函数

  • 函数的实参可以是任意的数据类型,调用函数时解析器不会检查实参的类型,所以要注意,是否有可能会接收到非法的参数,如果有可能则对参数进行类型的检查
  • 调用函数时解析器不会检查实参的数量,多余实参不会被赋值,如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined

无返回值

1
函数名(实参列表);

有返回值

1
var result = 函数名(实参列表);

构造函数

  • 相当于Java中定义类,用于创建对象
1
2
3
4
var 变量名 = function (属性1, 属性2) {
this.属性1 = 属性1;
this.属性2 = 属性2;
}

通过类语法定义构造函数

传送门

1
2
3
4
5
6
class 函数名 {
constructor(属性1, 属性2) {
this.属性1 = 属性1;
this.属性2 = 属性2;
}
}

函数声明定义函数与函数表达式定义函数的区别

  • 函数声明定义函数时,可以将调用函数的代码写在函数定义之前
  • 函数表达式定义函数时,必须将调用函数的代码写在函数定义之后
1
2
3
function 函数名() {}

函数名();
1
2
3
函数名();

function 函数名() {}
1
2
3
var 变量名 = function () {}

变量名();

头等函数

  • 头等函数(first-class function)又称为第一级函数,是指在JS中函数被当作头等公民
  • JS支持使用头等公民的方式编程,这种编程方式被称为函数式编程

头等函数的特点

函数可以赋值给变量

1
2
3
var 变量名 = function () {}

变量名();

函数可以在变量之间来回传递

1
2
3
4
5
var 变量名 = function () {}

var 变量名2 = 变量名;

变量名2();

函数可以作为函数的参数进行传递

1
2
3
4
5
var 函数名 = function () {}

function 函数名2(参数) {}

函数名2(函数名);

函数可以作为函数的返回值

1
2
3
4
5
6
7
function 函数名(参数) {
return function () {}
}

var 变量名 = 函数名();

变量名();

函数可以作为对象的属性值

1
2
3
4
5
var 对象名 = {
属性名: function () {}
}

对象名.属性名();

匿名函数

  • 如果一个函数没有定义函数名,则这个函数就是匿名函数
1
function () {}

立即执行函数(Immediate Invocation Function Expression, IIFE)

  • 立即执行函数实质上是匿名函数自调用

使用括号包裹匿名函数

1
2
3
(function (形参列表) {
...
})(实参列表);
1
2
3
(function (形参列表) {
...
}(实参列表));

使用运算符作为前缀

1
2
3
+function (形参列表) {
...
}(实参列表);
1
2
3
-function (形参列表) {
...
}(实参列表);
1
2
3
!function (形参列表) {
...
}(实参列表);

回调函数

  • 将函数作为参数传递给其他函数,如果这个函数在其他函数中被执行,这个函数就被称为回调函数
  • 传递回调函数时通常传递匿名函数
1
2
3
4
5
6
7
function 函数名(函数形参) {
函数形参();
}

函数名(function () {
/* 回调函数 */
});

闭包

  • 闭包(Closure):在支持头等函数的语言中,实现词法绑定的一种技术
  • 闭包在实现上是一个结构体,它存储了一个函数和一个关联环境
  • 闭包跟函数最大的区别在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行
1
2
3
4
5
6
7
function 函数名() {
return function () {
/* 闭包 */
}
}

var 变量名 = 函数名();

小闭包

  • 匿名函数自调用是小闭包
1
2
3
(function (形参) {
/* 小闭包 */
})(实参);

高阶函数

  • 如果一个函数满足以下两个条件的任意条件,则该函数就是一个高阶函数
    • 如果一个函数的实参传递的是一个函数(这个实参就是回调函数)
    • 如果一个函数的返回值是一个函数(这个返回值就是闭包)

递归函数

  • 如果一个函数自己调用自己,则这个函数就是递归函数
1
2
3
function 函数名() {
return 函数名();
}
  • 如果没有定义跳出条件,则会产生无限调用,报错:Maximum call stack size exceeded

函数的参数

剩余参数(ES6)

  • 剩余参数(Rest Parameter):将不定数量的实参放到一个数组作为形参
1
2
3
function 函数名(...形参) {}

函数名(实参1, 实参2);
  • 如果函数有除了剩余参数以外的形参,剩余参数必须放在末尾
1
2
3
function 函数名(形参1, ...形参2) {}

函数名(实参1, 实参2, 实参3);

展开运算符(ES6)

  • 将一个数组中的多个实参打散,依次传递给函数
1
2
3
4
5
6
function fn(形参1, 形参2) {
...
}

var 实参列表 = [实参1, 实参2];
fn(...实参列表);

参数默认值(ES6)

1
function 函数名(形参 = 默认值) {}

函数对象

  • JS中函数也是对象

属性

函数名

1
函数名.name;

形参总数

  • 对于剩余参数,length不会计算
  • 对于传递了默认值的形参,length不会计算
1
函数名.length;

this指向

this绑定的分类

  • 默认绑定

    • 函数没有绑定到对象上,则默认绑定到window对象上
  • 隐式绑定

    • 函数由哪个对象调用的,就隐式绑定到哪个对象
  • 显式绑定

    • 通过bind()call()apply()函数改变this指向
  • new绑定

    • 通过new关键字创建对象时,第三个内置操作会进行this绑定

this绑定的优先级

  • 优先级从高到底
    1. new绑定
    2. 显示绑定通过bind()绑定
    3. 显示绑定通过apply()call()绑定
    4. 隐式绑定
    5. 默认绑定

bind函数

改变函数的this指向

  • bind()不会立即执行函数,只会改变函数的this指向
  • 返回的是原函数的拷贝
1
2
3
4
function 函数名() {}
let 对象名 = {};

let 新函数 = 函数名.bind(对象名);
  • 使用bind()函数传递的实参将作为新函数参数的默认值
1
2
3
4
function 函数名(形参) {}
let 对象名 = {};

let 新函数 = 函数名.bind(对象名, 实参);

call函数

调用函数

  • call()只能调用无需传递参数的函数
1
2
3
4
5
function 函数名() {
...
}

函数名.call();

改变函数的this指向

  • call()会立即执行函数,并改变函数的this指向
1
2
3
4
function 函数名() {}
let 对象名 = {};

函数名.call(对象名);
1
2
3
4
function 函数名(形参) {}
let 对象名 = {};

函数名.call(对象名, 实参);

apply函数

  • apply()可以调用需要传递参数的函数,向调用的函数传递参数时只能是数组类型数据

调用函数

1
2
3
function 函数名() {}

函数名.apply(null);
1
2
3
function 函数名(形参) {}

函数名.apply(null, [实参]);

改变函数的this指向

  • apply()会立即执行函数,并改变函数的this指向
  • 如果将nullundefined作为第一个参数,则函数的this指向为window对象
1
2
3
4
function 函数名() {}
let 对象名 = {};

函数名.apply(对象名);
1
2
3
4
function 函数名(形参) {}
let 对象名 = {};

函数名.apply(对象名, [实参]);

特殊的this指向

  • 将对象2的函数赋值给对象1并直接调用,则函数的this指向为window对象
1
2
3
4
5
6
var 对象名1 = {};
var 对象名2 = {
函数名: function () {}
};

(对象名1.函数名 = 对象名2.函数名)();

传递this的指向

通过上层变量传递(ES5)

1
2
3
4
5
6
function 函数名() {
var _this = this;
function 函数名() {
console.log(_this);
}
}

通过箭头函数传递(ES6)

  • 由于箭头函数不会绑定this指向,所以可以用作传递this的指向
1
2
3
4
5
function 函数名() {
var 函数名 = () => {
console.log(this);
}
}

arguments对象

  • 在函数中,可以通过可迭代对象arguments,获取实参列表中的任意参数,不论形参定义的参数个数如何,都能正确拿到所有实参
1
arguments;

通过索引访问运算符获取指定实参

1
arguments[索引];

遍历实参列表

1
2
3
4
5
function 函数名() {
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
1
2
3
4
5
function 函数名() {
for (var arg of arguments) {
console.log(arg);
}
}

arguments转数组

可迭代对象转数组(ES6)

1
var arr = Array.from(arguments);

展开运算符(ES6)

1
var arr = [...arguments];

切片

  • 从开头截取到末尾
1
var arr = [].slice.apply(arguments);
1
var arr = Array.prototype.slice.apply(arguments);

纯函数(Pure)

  • 所有支持函数式编程的语言中均支持

  • 在程序设计中,若一个函数符合以下要求,则它可能被认为是纯函数:(维基百科

    • 此函数在相同的输入值时,需产生相同的输出。函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
    • 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。

柯里化函数

  • 所有支持函数式编程的语言中均支持

  • 在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的,尽管它是Moses Schönfinkel和戈特洛布·弗雷格发明的。(维基百科

  • 将普通函数转化成柯里化函数的过程被称为柯里化

1
2
3
4
5
6
7
8
9
function fn(x) {
return function (y) {
return function (z) {
return x + y + z;
};
}
}

fn(1)(2);

简写

1
2
3
var fn = x => y => z => x + y + z;

fn(1)(2)(3);

with扩展代码快的作用域链

  • 将指定对象的属性和方法,添加到当前作用域链中
1
2
3
4
5
6
7
8
9
var obj = {
key: value,
fn: function () {},
}

with(obj) {
console.log(key);
fn();
}

eval将字符串作为函数执行

  • 最后一个语句的返回值会作为eval()函数的返回值
1
var result = eval('function fn() {return 1}; fn();');

完成

参考文献

哔哩哔哩——Python小清风
哔哩哔哩——黑马前端