JavaScript-闭包

x33g5p2x  于2022-04-17 转载在 Java  
字(4.8k)|赞(0)|评价(0)|浏览(548)

闭包介绍

闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
要理解闭包,首先必须理解Javascript特殊的变量作用域。

1.全局变量和局部变量。

2.js中当前作用域能访问其上层作用域的变量和函数

JS中当遇到对变量名或者函数名的使用时,会首先在当前作用域查找变量或者函数,如果没有找到,就会到其上层作用域中寻找,并以此类推。

  1.  function f1(){
  2.     var n=999;
  3.   }
  4.   alert(n); // error

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现,那就是在函数的内部,再定义一个函数。

  1.  function f1(){
  2.     var n=999;
  3.     function f2(){
  4.       alert(n); // 999
  5.     }
  6.   }

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

  1.  function f1(){
  2.     var n=999;
  3.     function f2(){
  4.       alert(n);
  5.     }
  6.     return f2;
  7.   }
  8.   var result=f1();
  9.   result(); // 999

以上就是闭包的核心概念

闭包概念

闭包特点:

  1. 函数嵌套函数
  2. 内部函数可以访问外部函数的变量
  3. 参数和变量不会被回收

各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。

由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。怎么来理解这句话呢?请看下面的代码。

  1. function f1() {
  2. var n = 999;
  3. //在函数内部没有使用修饰符修饰的变量,自动升级为全局变量
  4. nAdd = function () {
  5. n += 1
  6. }
  7. function f2() {
  8. alert(n);
  9. }
  10. return f2;
  11. }
  12. var result = f1();
  13. result(); // 999
  14. nAdd();
  15. result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var let 等关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除,也就是变量=null

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

闭包的优缺点

闭包的优点
可以可在全局重复使用变量,并且不会造成变量污染

全局变量可以重复使用,但是容易造成变量污染。局部变量仅在局部作用域内有效,不可以重复使用,不会造成变量污染。闭包结合了全局变量和局部变量的优点。
可以用来定义私有属性和私有方法。

闭包的缺点
比普通函数更占用内存,会导致网页性能变差,在IE下容易造成内存泄露。
什么是内存泄漏
首先,需要了解浏览器自身的内存回收机制。
每个浏览器会有自己的一套回收机制,当分配出去的内存不使用的时候便会回收;内存泄露的根本原因就是你的代码中分配了一些‘顽固的’内存,浏览器无法进行回收,如果这些’顽固的’内存还在一直不停地分配就会导致后面所用内存不足,造成泄露。

闭包造成内存泄漏
因为闭包就是能够访问外部函数变量的一个函数,而函数是必须保存在内存中的对象,所以位于函数执行上下文中的所有变量也需要保存在内存中,这样就不会被回收,如果一旦循环引用或创建闭包,就会占据大量内存,可能会引起内存泄漏

闭包的使用场景案例

函数防抖

  1. /**
  2. * 就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
  3. * 通俗一点:在一段固定的时间内,只能触发一次函数,在多次触发事件时,只执行最后一次。
  4. * @function debounce 函数防抖
  5. * @param {Function} fn 需要防抖的函数
  6. * @param {Number} interval 间隔时间
  7. * @return {Function} 经过防抖处理的函数
  8. * */
  9. function debounce(fn, interval = 300) {
  10. let timer = null; // 定时器
  11. return function () {
  12. // 清除上一次的定时器
  13. clearTimeout(timer);
  14. // 拿到当前的函数作用域
  15. let _this = this;
  16. // 拿到当前函数的参数数组
  17. let args = Array.prototype.slice.call(arguments, 0);
  18. // 开启倒计时定时器
  19. timer = setTimeout(function () {
  20. // 通过apply传递当前函数this,以及参数
  21. fn.apply(_this, args);
  22. // 默认300ms执行
  23. }, interval)
  24. }
  25. }

使用

  1. <button onclick="testf()">函数防抖点击</button>
  2. function test() {
  3. console.log("========");
  4. }
  5. let testf = debounce(test)

函数限流

  1. /**
  2. * 就是限制一个函数在一定时间内只能执行一次。
  3. * @function throttle 函数节流
  4. * @param {Function} fn 需要节流的函数
  5. * @param {Number} interval 间隔时间
  6. * @return {Function} 经过节流处理的函数
  7. * */
  8. function throttle(fn, interval = 500) {
  9. let timer = null; // 定时器
  10. let firstTime = true; // 判断是否是第一次执行
  11. // 利用闭包
  12. return function () {
  13. // 拿到函数的参数数组
  14. let args = Array.prototype.slice.call(arguments, 0);
  15. // 拿到当前的函数作用域
  16. let _this = this;
  17. // 如果是第一次执行的话,需要立即执行该函数
  18. if (firstTime) {
  19. // 通过apply,绑定当前函数的作用域以及传递参数
  20. fn.apply(_this, args);
  21. // 修改标识为null,释放内存
  22. firstTime = null;
  23. }
  24. // 如果当前有正在等待执行的函数则直接返回
  25. if (timer) return;
  26. // 开启一个倒计时定时器
  27. timer = setTimeout(function () {
  28. // 通过apply,绑定当前函数的作用域以及传递参数
  29. fn.apply(_this, args);
  30. // 清除之前的定时器
  31. timer = null;
  32. // 默认500ms执行一次
  33. }, interval)
  34. }
  35. }

使用

  1. <button onclick="testT()">函数限流</button>
  2. function test() {
  3. console.log("========");
  4. }
  5. let testT = throttle(test)

使用闭包方式完成递归

arguments 的主要用途是保存函数参数,这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数,这有利于匿名函数的递归或者保证函数的封装性。。

  1. function show(n) {
  2. var arr = [];
  3. return (function () {
  4. arr.unshift(n);
  5. n--;
  6. if (n != 0) {
  7. // 在次调用当前的匿名函数
  8. arguments.callee();
  9. }
  10. return arr;
  11. })()
  12. }
  13. show(5)//[1,2,3,4,5]

实现变量共享

学过java的同学相信下面代码看着并不陌生,简单来说就是将方法内的变量能提供给外部访问和修改

  1. var person=function (){
  2. //变量作用域为函数内部,外部无法访问
  3. var name = 'ruirui.xu';
  4. return {
  5. getName:function(){
  6. return name;
  7. }
  8. setName :function(){
  9. name = newName;
  10. }
  11. }
  12. }()
  13. //在person之外的地方无法访问其内部变量,而通过提供闭包的形式访问。
  14. alertperson.name //直接访问,结果undefined
  15. alert(person.getName()) //ruirui.xu
  16. person.setName('xuruirui')
  17. alert('person.getName())//xuruirui

实现闭包继承

  1. function Person(){
  2. var name = "default";
  3. return {
  4. getName : function(){
  5. return name;
  6. },
  7. setName : function(newName){
  8. name = newName;
  9. }
  10. }
  11. };
  12. var p = new Person();
  13. p.setName("Tom");
  14. alert(p.getName());
  15. //创建一个新的方法
  16. var Jack = function () {
  17. alert("Jack")
  18. };
  19. //继承自Person
  20. Jack.prototype = new Person();
  21. //添加私有方法
  22. Jack.prototype.Say = function(){
  23. alert("Hello,my name is Jack");
  24. };
  25. var j = new Jack();
  26. j.setName("Jack");
  27. j.Say();
  28. alert(j.getName());

点赞 -收藏-关注-便于以后复习和收到最新内容有其他问题在评论区讨论-或者私信我-收到会在第一时间回复如有侵权,请私信联系我感谢,配合,希望我的努力对你有帮助^_^

相关文章

最新文章

更多