目录
闭包(Closure)是 JavaScript 中的一个重要概念,它允许函数外部访问并操作其内部作用域中的变量,即使这个函数在其声明的作用域之外被调用。
递归是一种编程技巧,它允许一个函数直接或间接地调用自身。(即函数内部的自调用)
js作用域:静态 是定义函数的作用域,不是执行的作用域
一、闭包
闭包(Closure)是 JavaScript 中的一个重要概念,它允许函数外部访问并操作其内部作用域中的变量,即使这个函数在其声明的作用域之外被调用。
闭包的优点和缺点
优点:
1. 数据封装:闭包可以用来创建私有变量,只能通过特定的公开方法进行访问和修改。这有助于保护数据不被外部直接修改,提高了数据的安全性。
2. 实现回调函数和高阶函数:闭包常常被用来作为回调函数,因为它们可以记住自己的词法环境,包括 this
和外部变量。这使得闭包非常适合用于异步编程和事件处理程序。
3. 实现装饰器/函数修饰器:闭包可以用来修改或增强函数的行为。例如,你可以使用闭包来记录函数的调用情况、测量函数的执行时间等。
4. 实现函数工厂:闭包可以用来创建一系列相关或相互依赖的函数。这使得代码更加模块化和可重用。
缺点:
1. 内存泄漏:由于闭包可以使得变量常驻在内存中,如果不当使用,可能导致内存泄漏。例如,如果你不再需要一个闭包引用的变量,但没有正确地解除引用,那么这个变量将不会被垃圾回收器回收。
2. 性能开销:闭包可能会导致额外的性能开销,因为它们需要维护额外的作用域链。在大量使用闭包的情况下,这可能会对性能产生影响。
3. 代码可读性降低:过度使用闭包可能导致代码结构变得复杂,难以理解和维护。因此,在使用闭包时,需要注意保持代码的简洁和清晰。
// 闭包应用-计算打车价格 // 打车起步价12元(3公里内),之后每多一公里增加5块钱 //用户输入公里数就可以计算打车价格 // 如果有拥堵情况,总价格多收取10块钱拥堵费 function price(miles, a) { let allPrice = 0 if (a) { allPrice += 10 } if (miles <= 3) { allPrice += 12 } else { allPrice += 12 + (miles - 3) * 5 } return allPrice } let zc = price(4, false) console.log(zc);
二、递归
递归是一种编程技巧,它允许一个函数直接或间接地调用自身。(即函数内部的自调用)
递归函数通常包括两个部分:基本情况(base case)和递归情况(recursive case)。
基本情况
基本情况是指函数可以直接解决的最简单问题,不需要进一步拆分。这是递归终止的条件。
递归情况
递归情况是指函数将问题拆分为更小的子问题,并对这些子问题进行递归调用。通过递归调用,问题最终会被简化到基本情况,从而得到解决。
// 递归的一个典型例子是计算阶乘。
// 阶乘表示一个正整数n的连乘积,用n!表示。
// 例如,5! = 5 × 4 × 3 × 2 × 1 = 120。
function factorial(n) {
if (n === 0 || n === 1) { // 基本情况
return 1;
} else { // 递归情况
return n * factorial(n - 1);
}
}
递归的优缺点
优点
1. 简洁性:
递归往往能够以更简洁的方式表达问题的解决方案,使代码更加清晰易懂。
2. 自然性:
对于某些问题,如树形结构的遍历、分治算法等,递归是一种非常自然的解决方案。
3. 易于理解:
递归函数的逻辑往往与问题的描述非常接近,有助于理解和解决问题。
缺点
1. 性能开销:
递归函数可能会导致较大的性能开销,因为每次递归调用都需要在内存中创建新的栈帧来保存函数的局部变量和状态信息。这可能导致栈溢出或性能下降。
2. 空间复杂度:
递归函数的空间复杂度通常较高,因为它需要额外的栈空间来存储递归调用的信息。
3. 递归深度限制:
大多数编程语言对递归深度有限制,过深的递归可能导致栈溢出错误。
4. 难以调试:
由于递归函数的执行过程涉及多次函数调用,当出现问题时,定位和调试可能变得更加困难。
计算机的存储方式
栈(Stack)和堆(Heap)是计算机内存中的两种不同区域,它们用于存储不同类型的数据,并且具有不同的访问和管理方式。
在JavaScript中,基本数据类型(如数字、布尔值、字符串等)通常存储在栈上
而引用数据类型(如对象、数组、函数等)则存储在堆上。
栈上的数据在函数调用结束后会被自动清理,而堆上的数据则需要程序员手动管理,确保不再使用时释放内存。
三、斐波那契数列的递归演示
斐波那契数列(Fibonacci sequence)是一个著名的数列,也被称为“黄金分割数列”,这个数列从0和1开始,之后的每个数字都是前两个数字的和。
//斐波那契数列 :兔子数列 1,1,2,3,5,8,13..... 计算第20位的值
function fei(a) {
if (a == 1 || a == 2) return 1;
return fei(a - 1) + fei(a - 2)
}
console.log(fei(20));
//fei(20)=6765
//使用递归对斐波那契数列进行计算,需注意我们的递归函数时是从后往前
//进入函数判断是否满足条件,后进入递归程序
//在递归到满足条件之后进入判断,达成退出条件后开始返回结果
四、递归函数的一些规律性使用
有64个格子,第一个格子放1粒麦子,第二个格子放2粒麦子,第三个格子放4粒麦子,第n个格子放2的n - 1次方粒麦子,问:100个格子一共放多少粒麦子?
function fei(n) {
if (n == 1) {
return 1;
}
return Math.pow(2, n - 1) + Math.pow(2, n - 2)
}
console.log(fei(64));
五、柯里化(Curry)
柯里化作为一种函数的变式,需要在见到代码时可以理解,修改,延续即可,对于应用等方面不做要求,是一种基础后进阶的知识
柯里化(Curry):把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
柯里化的主要优点包括:
1. 参数复用:通过柯里化,可以将一些常用的参数组合成新的函数,从而减少重复代码。
2. 延迟计算:柯里化允许将复杂的计算拆分成多个步骤,只在需要时进行计算,提高程序的执行效率。
3. 函数组合:柯里化后的函数更容易与其他函数组合使用,实现更复杂的功能。
柯里化的简洁代码展示
function fn(a) { return function (b) { return function (c) { return a + b + c } } } console.log(fn(1)(2)(3)); // 当有一个顾客购买商品就会使用0.1的折扣去调用一次discoun方法, // 那么当有很多顾客的时候,就会每次都变化discount的第一个参数, // 而第二个参数就一直重复一样为0.1, // 这样就会导致代码的冗余, // 所以我们可以使用柯里化来优化代码, // 使代码更加简洁, // 代码如下: function discount(price, discount) { return price * discount } // 柯里化 function fn(discount) { return function (price) { return discount(price, discount) } } console.log(fn(0.1)(100));