ArthurWangCN / notepad

reading notepad
0 stars 2 forks source link

闭包的理解 #1

Open ArthurWangCN opened 2 years ago

ArthurWangCN commented 2 years ago

不复杂,本质就是上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放。

ArthurWangCN commented 2 years ago
ArthurWangCN commented 2 years ago

MDN闭包: 一个函数和对其周围状态(词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

ArthurWangCN commented 2 years ago

看个示例:

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

在一些编程语言中,一个函数中的局部变量仅存在于此函数的执行期间。一旦 makeFunc() 执行完毕,你可能会认为 name 变量将不能再被访问。然而,因为代码仍按预期运行,所以在 JavaScript 中情况显然与此不同。

原因在于,JavaScript 中的函数会形成了闭包。 闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。在本例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用。displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。因此,当 myFunc 被调用时,变量 name 仍然可用。

ArthurWangCN commented 2 years ago

闭包的用途:

1. 实现面向对象编程

闭包允许将函数与其所操作的某些数据(环境)关联起来,这显然类似于面向对象编程。

传统的对象语言都提供类的模板机制,这样不同的对象(类的实例)拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,我们可以模拟出这样的机制:

function Person(){   
    var name = "default";      

    return {   
       getName : function(){   
           return name;   
       },   
       setName : function(newName){   
           name = newName;   
       }   
    }   
}; 

var john = Person();
john.setName("john");
console.log(john.getName());

var jack = Person();
jack.setName("jack");
console.log(jack.getName());

2. 用闭包模拟私有方法

编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。

而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。这个方式也称为模块模式(module pattern)。

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
};

var counter1 = makeCounter();
console.log(counter1.privateCounter);   // undefined
console.log(counter1.value());  // 0
counter1.increment();
console.log(counter1.value());  // 1

以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。

3. 缓存耗时的操作结果

设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间。

那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。

闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。

var CachedSearchBox = (function () {
    var cache = {},
        count = [];
    return {
        attachSearchBox: function (id) {
            if (id in cache) {  // 如果结果在缓存中
                return cache[id]; // 直接返回缓存中的对象
            }
            var result = new uikit.webctrl.SearchBox(id);  // 新建
            cache[id] = result;  // 更新缓存
            if (count.length > 100) { // 保证缓存的大小<=100
                delete cache[count.shift()];
            }
            return result;
        },
    };
})();

CachedSearchBox.attachSearchBox("input1");
ArthurWangCN commented 2 years ago

使用闭包的注意点:

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

总结

解决了什么问题:

  1. 避免污染全局环境
  2. 提供对局部变量的间接访问
  3. 维持变量,使其不被垃圾回收

缺点:

闭包使用不当可能造成内存泄漏