Skip to content

函数

约 2163 字大约 7 分钟

原理前端JavaScript

2025-06-18

一个函数访问了此函数的父级及父级以上的作用域变量

简单来说,当一个嵌套的函数引用了其外部函数中的变量,并且该嵌套函数被返回或者以其他方式在外部使用时,就形成了闭包。闭包使得这些被引用的变量不会被垃圾回收机制清理掉,即使外部函数已经执行结束。

闭包可以在一个内层函数中访问到其外层函数的作用域,用于:

  • 创建私有变量
  • 延长变量的生命周期

优点:

  • 减少全局变量的定义,减少全局污染
  • 可以私有化内部变量,使其只能通过特定的方式(内层函数)来访问
  • 延长变量的生命周期 缺点:
  • 内存泄露
  • 性能影响

闭包的基本特性

  1. 保持状态:闭包可以“记住”并访问创建它的那个函数的作用域。
  2. 封装数据:通过闭包,你可以隐藏某些数据,防止外部直接访问。
  3. 延长变量生命周期:由于闭包持有对外部变量的引用,这些变量的生命期会延长至与闭包相同。

闭包的应用场景

  • 数据隐私:利用闭包可以实现私有变量和方法,防止外部直接访问或修改。
  • 回调函数:在事件处理、异步编程(如 AJAX 请求)等场景下经常需要用到闭包来保存状态。
  • 函数工厂:可以通过闭包创建具有特定配置的函数实例。
  • 模块模式:结合立即执行函数表达式 (IIFE),可以用来模拟类的概念,实现模块化开发。

尽管闭包非常有用,但如果不谨慎使用也可能带来问题:

  • 内存泄漏:由于闭包会持有对外部变量的引用,如果这些引用不再需要但仍被闭包持有,则可能导致内存无法释放。
  • 性能影响:过多地使用闭包可能会导致不必要的性能开销。 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)

IIFE

立即执行函数

let a=2
(function IIFE(){
	console.log(a)
})()

函数式编程(编程范式之一)

  • 面向过程编程:一步一步的实现功能,开发的基础阶段
  • 面向对象编程:现实世界中事物抽象成类和对象,采用封装、继承、多态来演示客观世界的联系
  • 函数式编程:现实世界中事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)

在函数式编程中把每一个函数看作是一个运算

优点

  1. 可预测与可测试:固定的输入得到固定的输出,易于理解函数的行为,编写单元测试非常简单,只需要断言输入和输出
  2. 并发与并行安全:不依赖或修改外部状态,天生具有线程或进程安全,避免了共享状态带来的复杂同步问题
  3. 代码理解与推导:函数的行为只取决于输入,易于隔离和局部化复杂性。

非纯函数示例

  • 修改引用类型参数
fuction foo(arr){
	arr.push(1)
	return arr
}

const arr=[1,2,3]
console.log(arr) //[1,2,3]
foo(arr)
console.log(arr) //[1,2,3,1]
  • io操作
function foo(a){
	console.log(a)
}
  • 结果不确定
fuction foo(){
	return Math.random() 
}

高阶函数

高阶函数表示的是缺失与延续 在运算缺失的时候需要函数作为参数,对运算需要延续的时候使用函数作为返回值

缺失 以map为例:在对数组中的元素进行运算时有一个运算规则的缺失,需要传入一个运算规则(函数)根据不同的规则对数组中的元素进行不同的运算 延续 以bind为例:bind接收对象和参数,并将绑定好的函数返回,整个过程中并不调用需要执行的参数,在需要调用的地方调用bind返回的函数

参数或返回值为函数的函数

常用的高阶函数

  • map
  • filter
  • reduce
  • bind
  • setTimeout
  • addEventListener

函数柯里化

是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数, 并且返回接受余下的参数的新函数的技术。

应用了闭包和高阶函数的概念

优点:

  1. 参数复用(利用了闭包和高阶函数的特性)
  2. 延迟运行(例如js中的bind方法,实现的机制就是Curying)

创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上 其实在大部分应用中,主要的性能瓶颈是在操作DOM节点上, 这js的性能损耗基本是可以忽略不计的,所以cury是可以直接放心的使用。

示例: 返回url的函数

function getUrl(protocol,hostname,pathname){
	reutrn `${protocol}://${hostname}/${pathname}`
}

console.log(getUrl('https','baidu.com','passport')) // https://baidu.com/passport
console.log(getUrl('https','tencent.com','404')) // https://tencent.com/404

可以看到有一些参数是可以复用的,本例中的为协议字段,可以将函数进一步封装,根据不同协议生成不同协议的url函数

部分柯里化

function getUrl(protocol){
	return (hostname,pathname){
		reutrn `${protocol}://${hostname}/${pathname}`
	}
}

const url=getUrl('https')
console.log(url('baidu.com','passport')) // https://baidu.com/passport
console.log(url('tencent.com','404')) // https://tencent.com/404

完全柯里化

function getUrl(protocol){
	return (hostname){
		return(pathname){
			reutrn `${protocol}://${hostname}/${pathname}`
		}
	}
}

const httpsUrl=getUrl('https')
const baiduUrl=httpsUrl('baidu.com')
console.log(badiduUrl('passport')) // https://baidu.com/passport
console.log(badiduUrl('404')) // https://baidu.com/404

实现一个能够将任何函数都转换成柯里化的函数的函数

function curry(fn,...args){
	if(arg.length >= fn.length){
		return fn(...args)
	}
	return(...rest)=>{
		return curry(fn,...args,...rest)
	}
}

call、apply、bind

方法/特征callapplybind
方法参数多个单个数组多个
方法功能函数调用改变this函数调用改变this函数调用改变this
返回结果直接执行直接执行返回待执行函数
底层实现通过eval通过eval间接调用apply

call

call方法允许调用一个函数,并且显式地指定this值以及以参数列表的形式传入其他参数。其语法如下:

  • thisArg: 在函数体内使用的this值。
  • arg1, arg2, ...: 传递给被调用函数的参数。
function.call(thisArg, arg1, arg2, ...)

例如:

const obj = { num: 42 };
function add(a, b) {
    return this.num + a + b;
}
console.log(add.call(obj, 1, 2)); // 输出 45

apply

applycall非常相似,主要区别在于传递参数的方式:apply接受一个数组或类数组对象作为参数列表。其语法如下:

  • thisArg: 在函数体内使用的this值。
  • [argsArray]: 包含传递给函数的参数的数组或类数组对象。
function.apply(thisArg, [argsArray])

例如:

const obj = { num: 42 };
function add(a, b) {
    return this.num + a + b;
}
console.log(add.apply(obj, [1, 2])); // 输出 45

bind

bind方法不会立即调用函数,而是返回一个新的函数,这个新函数在被调用时会将指定的this值绑定到它内部。此外,还可以预先设置一些默认参数。其语法如下:

  • thisArg: 在新函数内使用的this值。
  • arg1, arg2, ...: 可选参数,这些参数会被预设为新函数的默认参数。
const newFunction = function.bind(thisArg, arg1, arg2, ...)

例如:

const obj = { num: 42 };
function add(a, b) {
    return this.num + a + b;
}
const boundAdd = add.bind(obj, 1);
console.log(boundAdd(2)); // 输出 45

箭头函数和function的区别

箭头函数与传统函数function有以下几个主要区别:

this绑定

  • 箭头函数没有自己的this,继承自外层作用域的this
  • 传统函数的this根据调用方式动态绑定。 构造函数
  • 箭头函数不能作为构造函数使用,不能使用new关键字。
  • 传统函数可以作为构造函数,创建对象实例。 arguments对象
  • 箭头函数没有自己的arguments对象,可以使用剩余参数...args代替。
  • 传统函数有arguments对象,包含所有传入的参数。 原型对象
  • 箭头函数没有prototype属性。
  • 传统函数有prototype属性,用于创建对象实例的原型。 call、apply、bind
  • 箭头函数的this无法通过callapplybind方法改变。
  • 传统函数的this可以通过这些方法修改。

贡献者

PinkDopeyBug

公告

本博客内容原本使用obsidian编写,由于没有仔细配置,以至图片引用使用obsidian风格。

且图片存储路径频繁变更导致部分文章图片无法正常显示。

为您带来不便请谅解。

ps:贡献者一直都只有wynnsimon一人,显示Pink的贡献者是因为我没好好配置git。后面因为懒就没一个个修改。如果被提及的人不希望被显示可以联系我我会立即删除。