函数闭包

128 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

函数有很多概念,其中有一个很重要而且必学掌握的概念,那就是闭包,今天我们一起来学习吧。

定义

下面文字来自《Javascript权威指南 第六版》

函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。

函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包。

闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。

function sum(a){
    return function (b){
        return a + b;
    }
}

const sum1 = sum(10);
const result = sum1(20); // 30

a就是另外一个函数的变量。

因为闭包会保留它们包含函数的作用域,所以比其他函数更占用内存。过度使用闭包可能导致内存过度占用,因此建议仅在十分必要时使用。V8等优化的JavaScript引擎会努力回收被闭包困住的内存,不过我们还是建议在使用闭包时要谨慎。

var utils = (function (global) {

  function getQueryString(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
    var r = window.location.search.substr(1).match(reg);
    if (r != null) return unescape(r[2]);
    return null;
  }
  
  function getType(obj){
      return typeof obj;
  }

  return {
    get now() {
      return Date.now()
    },
    getQueryString,
    getType
  }
})(window)

上面utils不被释放,其内部的函数getQueryString,getType,now都不会被释放的。

想释放怎么办?

utils = null;

this

在闭包中使用this会让代码变复杂。如果内部函数没有使用箭头函数定义,则this对象会在运行时绑定到执行函数的上下文。

var name = "Tom";
var createPerson = function (name){
    
    return {
        name,
        getName(){
            return this.name;
        },
        getName2: ()=> {
            return this.name
        }
    }
}

var person = createPerson("Henry");
person.getName(); // Henry
person.getName2(); // Tom

科里化和偏函数

科里化和偏函数是闭包的一种典型应用。

function currying(fn){
    var allArgs = [];

    return function next(){
        var args = [].slice.call(arguments);

        if(args.length > 0){
            allArgs = allArgs.concat(args);
            return next;
        }else{
            return fn.apply(null, allArgs);
        }
    } 
}

是不是一种浓浓的闭包信息呢?

闭包创建私有变量

利用闭包可以创建私有变量,当然现在的class已经有#私有变量符号,不过真心的丑,还不然TypeScript的私有变量好看好用。


function Person() {
  var name, age;
  function getName() {
    return name;
  }
  function setName(val) {
    name = val;
  }
  function getAge() {
    return age
  }
  function setAge(val) {
    age = val;
  }
  return {
    getName,
    setName,
    getAge,
    setAge
  }
}

var person = new Person();
person.setAge(10);
person.setName("Tom");

console.log(person.getName(), person.getAge());
// Tom  10

经典案例

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

// 5 5 5 5 5

在没有let, const块作用域的时候,怎么解决这个问题呢? 其中一种答案就是IIFE,立即执行函数表达式, 下篇继续。

小结

今天你收获了吗?