首页 > 编程语言 >什么是 JavaScript 闭包?

什么是 JavaScript 闭包?

时间:2023-12-19 14:00:24浏览次数:37  
标签:闭包 function 函数 作用域 什么 JavaScript count 变量

什么是 JavaScript 闭包?

在 JavaScript 中,闭包是指一个函数能够访问在它外部定义的变量。这些变量通常被称为“自由变量”,因为它们不是该函数的局部变量,也不是该函数的参数。闭包可以在函数内部创建,也可以在函数外部创建。

JavaScript 中的每个函数都是一个闭包,因为它们都能够访问自由变量。当一个函数被调用时,它会创建一个新的执行环境,其中包含该函数的局部变量和参数。这个执行环境还包括一个指向该函数定义所在的作用域的引用。这个引用被称为函数的“作用域链”,它是由所有包含该函数定义的作用域对象组成的链表。【推荐学习:javascript视频教程

当函数内部需要访问一个自由变量时,它会先在自己的局部变量中查找是否存在该变量。如果不存在,它会继续沿着作用域链向上查找,直到找到该变量为止。这就是闭包的核心机制。

简单来说,闭包就是一个函数,其中包含了对外部变量的引用,这些变量在函数外部定义,但在函数内部仍然可以被访问和操作。闭包的本质是将函数和其引用的外部变量封装在一起,形成了一个不受外部干扰的环境,使得函数可以访问和修改外部变量,并且这些修改也会反映到函数外部的变量中。

理解闭包的工作原理对于编写高质量的 JavaScript 代码至关重要,因为它可以让我们更好地管理变量和函数的作用域,以及实现更加复杂的功能。

闭包的用途

封装变量和函数

闭包可以用来封装变量,使其不受外部干扰。这是因为闭包可以在函数内部定义一个变量,并在函数外部创建一个访问该变量的函数。这个访问函数可以访问该变量,但是外部无法直接访问该变量,从而保证了变量的安全性。

例如,我们可以使用闭包来实现一个计数器:

function createCounter() {
let count = 0;
return function() {
count++;
return count;
}
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

在这个例子中,我们使用闭包来封装了计数器变量 count,使其不受外部干扰。每次调用 counter 函数时,它都会返回计数器的下一个值。

 

缓存数据

使用闭包可以缓存函数的计算结果,避免多次计算同样的值,从而提高代码的性能。这种方式适用于那些计算量较大、但结果不经常变化的函数,例如斐波那契数列等。

下面看一个代码示例:

function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) {
return cache[key];
} else {
const result = fn(...args);
cache[key] = result;
return result;
}
}
}

function fibonacci(n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}

const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(10)); // 输出 55
console.log(memoizedFib(10)); // 输出 55,直接从缓存中读取

在这个示例中,我们定义了一个 memoize 函数,它接受一个函数作为参数,并返回了一个闭包函数。闭包函数内部维护了一个缓存对象 cache,用于保存函数的计算结果。每次调用闭包函数时,它会根据传入的参数生成一个唯一的键值,并从缓存中尝试读取计算结果。如果缓存中已经存在该键值,直接返回缓存结果,否则调用传入的函数计算结果,并将结果保存到缓存中。这种方式可以避免多次计算同样的值,从而提高代码的性能。

 

实现模块化

使用闭包可以实现模块化的编程方式,这种方式可以将代码分割成多个模块,使得每个模块只关注自己的功能,从而提高代码的可维护性和可读性。同时,闭包也可以实现公共和私有变量的封装,避免了全局变量的污染。

例如,我们可以使用闭包来实现一个简单的模块:

 

const module = (function() {
const privateVar = 'I am private';
const publicVar = 'I am public';
function privateFn() {
console.log('I am a private function');
}
function publicFn() {
console.log('I am a public function');
}
return {
publicVar,
publicFn
};
})();

console.log(module.publicVar); // 输出 'I am public'
module.publicFn(); // 输出 'I am a public function'
console.log(module.privateVar); // 输出 undefined
module.privateFn(); // 报错,无法访问私有函数

在这个示例中,我们定义了一个立即执行函数,内部返回了一个对象。对象中包含了公共变量和函数,以及私有变量和函数。通过这种方式,我们可以将代码分割成多个模块,每个模块只关注自己的功能,从而提高代码的可维护性和可读性。同时,私有变量和函数只在函数内部可见,外部无法访问和修改它们,从而避免了全局变量的污染。

 

事件处理

以下是一个使用闭包进行事件处理的例子:

 

function createCounter() {
let count = 0;

function increment() {
count++;
console.log(`Clicked ${count} times`);
}

function decrement() {
count--;
console.log(`Clicked ${count} times`);
}

function getCount() {
return count;
}

return {
increment,
decrement,
getCount
};
}

const counter = createCounter();

document.querySelector('#increment').addEventListener('click', counter.increment);
document.querySelector('#decrement').addEventListener('click', counter.decrement);

 

在这个示例中,我们定义了一个名为createCounter的函数,该函数返回一个对象,该对象包含三个方法:increment,decrement和getCount。increment方法将计数器加1,decrement方法将计数器减1,getCount方法返回当前计数器的值。

我们使用createCounter函数创建了一个计数器对象counter,并将increment方法和decrement方法分别注册为加1和减1按钮的点击事件处理函数。由于increment和decrement方法内部引用了createCounter函数内部的局部变量count,因此它们形成了闭包,可以访问和修改count变量。

这个示例中,我们将计数器对象的逻辑封装在一个函数内部,并返回一个包含方法的对象,这样可以避免全局变量的使用,提高代码的可维护性和可重用性。

函数柯里化

以下是一个使用闭包实现的函数柯里化例子:

function add(x) {
return function(y) {
return x + y;
}
}

const add5 = add(5); // x = 5
console.log(add5(3)); // 输出 8
console.log(add5(7)); // 输出 12

 

 

在这个例子中,我们定义了一个名为add的函数,该函数接受一个参数x并返回一个内部函数,内部函数接受一个参数y,并返回x + y的结果。

我们使用add函数创建了一个新的函数add5,该函数的x值为5。我们可以多次调用add5函数,每次传入不同的y值进行求和运算。由于add函数返回了一个内部函数,并且内部函数引用了add函数内部的参数x,因此内部函数形成了一个闭包,可以访问和保留x值的状态。

这个例子中,我们实现了一个简单的函数柯里化,将接收多个参数的函数转化为接收一个参数的函数。函数柯里化可以帮助我们更方便地进行函数复合和函数重用。

异步编程

以下是一个使用闭包实现的异步编程的例子:

function fetchData(url) {
return function(callback) {
fetch(url)
.then(response => response.json())
.then(data => {
callback(null, data);
})
.catch(error => {
callback(error, null);
});
}
}

const getData = fetchData('https://jsonplaceholder.typicode.com/todos/1');
getData(function(error, data) {
if (error) {
console.error(error);
} else {
console.log(data);
}
});

 

 

在这个例子中,我们定义了一个名为fetchData的函数,该函数接受一个URL参数,并返回一个内部函数。内部函数执行异步操作,请求URL并将响应解析为JSON格式的数据,然后调用传入的回调函数并将解析后的数据或错误作为参数传递。

我们使用fetchData函数创建了一个getData函数,该函数请求JSONPlaceholder API的一个TODO项,并将响应解析为JSON格式的数据,然后将数据或错误传递给回调函数。由于fetchData函数返回了一个内部函数,并且内部函数引用了fetchData函数内部的URL参数和回调函数,因此内部函数形成了闭包,可以访问和保留URL参数和回调函数的状态。

这个例子中,我们使用了异步编程模型,通过将回调函数作为参数传递,实现了在异步请求完成后执行相关的操作。使用闭包可以方便地管理异步请求和相关的状态,提高代码的可读性和可维护性。

闭包的缺陷

JS 闭包具有许多优点,但也有一些缺点,包括:

内存泄漏问题

由于闭包会将外部函数的局部变量引用保存在内存中,因此如果闭包一直存在,外部函数的局部变量也会一直存在,从而导致内存泄漏。

在 JavaScript 中,闭包是指一个函数能够访问并操作其父级作用域中的变量,即便该函数已经执行完毕,这些变量仍然存在。由于闭包会引用父级作用域中的变量,因此,这些变量不会在函数执行完毕时被垃圾回收机制回收,从而占用了内存资源,这就是闭包引起内存泄漏的原因。

以下是一个闭包引起内存泄漏的示例:

 

function myFunc() {
var count = 0;
setInterval(function() {
console.log(++count);
}, 1000);
}

myFunc();

 

 

在这个示例中,myFunc 函数中定义了一个变量 count,然后创建了一个计时器,在每秒钟打印 count 的值。由于计时器函数是一个闭包,它会保留对 myFunc 中的 count 变量的引用,这意味着即使 myFunc 函数执行完毕,计时器函数仍然可以访问 count 变量,从而阻止 count 变量被垃圾回收机制回收。如果我们不停地调用 myFunc 函数,将会创建多个计时器函数,每个函数都会占用一定的内存资源,最终会导致内存泄漏。

性能问题

由于闭包会在每次函数调用时创建新的作用域链,因此会增加函数的内存消耗和运行时间。在循环中创建闭包时,尤其需要注意性能问题。

在JavaScript中,每当创建一个函数时,都会为该函数创建一个新的作用域链。函数作用域链是一个指向其父级作用域的指针列表,其中包含了该函数能够访问的变量和函数。

闭包是指在函数内部定义的函数,它可以访问外部函数的变量和参数,并且可以在外部函数调用后继续使用这些变量和参数。在创建闭包时,它会保存对外部函数作用域链的引用,以便在需要时可以访问它。

由于闭包保存了对外部函数作用域链的引用,因此在每次函数调用时会创建一个新的作用域链。这是因为每次调用函数都会创建一个新的函数作用域链,即使该函数是由同一闭包创建的。这意味着每个闭包都有自己的作用域链,而且每次调用该闭包都会创建一个新的作用域链。

这也是为什么在使用闭包时需要小心的原因之一。由于每次调用闭包都会创建一个新的作用域链,因此可能会导致内存消耗和性能问题。在某些情况下,可能需要手动释放闭包的资源以避免内存泄漏问题。

安全问题

由于闭包可以访问外部函数的局部变量,如果不小心将私密数据存储在局部变量中,可能会被闭包访问和修改,从而导致安全问题。

可读性问题

由于闭包会延长变量的生命周期并隐式传递数据,因此可能会使代码变得难以理解和调试,尤其是在嵌套多层函数时。

总结

因此,尽管闭包是一种强大的编程技术,但在使用时需要注意以上缺点,并选择合适的应用场景和编程风格,以确保代码的可维护性和性能表现。

更多编程相关知识,请访问:编程教学!!

以上就是一文详解JavaScript中的闭包的详细内容,更多请关注php中文网其它相关文章!

 

 

   

引用:https://www.php.cn/faq/526848.html

标签:闭包,function,函数,作用域,什么,JavaScript,count,变量
From: https://www.cnblogs.com/xhu218/p/17913567.html

相关文章

  • 微课设计思路是什么?微课如何设计?
      微课设计方案在微课拍摄和制作中非常关键,其重要性好比是电影拍摄中的编剧和常规课堂教学中的备课。那么微课的设计思路是什么呢?  一、精心进行教学设计  确定选题  微课作为一种媒体,内容的设计要适合使用多媒体特性,对于不适合使用多媒体表达的内容,制作的结果也许是徒劳......
  • 经济“灭霸”拼多多们,会让我们失去什么?
    东方甄选董宇辉热得发烫。其实相比董宇辉事件,近期互联网圈有两个更重要的“大瓜”,既是当下互联网时代的写照,也可能是未来的风向标。冬天里的两把火第一个事件是,网购+社交应用拼多多(PDD.US)Q3财务数据超级靓眼,带来了冬天里的一把火:Q3实现营业收入688.4亿元,同比大涨93.89%;实现美国通用......
  • JavaScript 文件优化指南
    本文将探讨实用的JavaScript文件优化技术、如何处理与JavaScript文件相关的性能问题以及帮助优化过程的工具。你将获得提升web应用程序速度的相关知识,从而为你的用户提供无缝体验。JavaScript文件是web应用程序的重要组成部分,但网站速度和用户体验对网站的成功至关重要。......
  • JavaScript 执行上下文
    一旦整个JavaScript程序运行,就会创建执行上下文。全局执行上下文已创建。它有两个组件,变量环境和变量。执行线程,它分两个阶段创建。第一阶段,是创建阶段。在创建阶段,我们为全局空间内的所有变量和函数分配内存。我们分配了一个未定义的变量。对于函数,我们实际上存储整个函数。这......
  • 数据可视化对个人用户有什么帮助?
    数据可视化是一种强大的工具,不仅可以为企业和专业人士提供见解,也对个人用户带来了许多实际的帮助。下面我就以一个数据可视化从业者的视角,来谈谈数据可视化对个人用户的益处:首先对于个人用户来说,数据可视化可以让平时接触的数据更易于理解。它能够将数字转化为图形或图表,为用户......
  • 【Optimization in Operations Research 运筹学】牛顿法、高斯牛顿法、拟牛顿法与BFGS
    牛顿法\(F(x+\Deltax)=F(x)+F'(x)\Deltax+\frac{1}{2}F''(x)\Deltax^2\)泰勒展开之后保留二次项然后对展开式再进行求导令导数等于0直接得到前进的步长和方向即\(Hx=b\)这里的\(x\)就是牛顿法求解的前进步长和方向。如何理解呢?加\(\Deltax\)之后得到的解析式再对\(x......
  • 金融行业CRM和普通CRM有什么区别?金融CRM功能解析
    市场形式波诡云谲,金融行业也面临着资源体系分散、竞争力后继不足、未知风险无法规避等问题。金融企业该如何解决这些问题,或许可以了解一下CRM管理系统,和其提供的金融行业CRM解决方案。金融行业是银行业、保险业、信托业、证券业和租赁业的总称,有客户数量大、资金管理复杂、员工工......
  • 制造行业什么样的CRM系统好用?制造业CRM选型指南
      当前,推动制造业数字化转型已成时代发展趋势。为了适应这一趋势,制造业使用CRM管理系统是非常重要的。那么,制造业CRM应该怎么选?1、全方位客户管理订单价值大,交货周期长,客户开发难。。。这一直是制造业的痛点。前二点是由于行业特性,第三点是制造业客户一般来自不同规模和行业......
  • JavaScript 中 let、var 和 const 的区别及使用建议
    前言JavaScript中的let、var和const是三种不同的变量声明方式。虽然它们都可以用来声明变量,但它们之间有很大的区别。在本篇文章中,我们将深入探讨这三种变量声明方式的区别以及它们在实际开发中的应用。正文内容一、let的用法let是ES6中新增的变量声明方式,它的作用域......
  • 为什么大多数语言都不支持LINQ机制?
    C# 3.0可以说是C#历史上最大的一次改动,从语言到库的层面可以说是做了革命性的变化,单单是上下文关键字,就一次性新增了from、where、select、group、by、join、equals、on、let、order、into、asscending、descending一大堆,加上原有的in啥的。这特么就是重新设计了一个新的语言。......