本质与解析

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

function outter() {
  var a = 1
  function inner() {
    console.log(a)
  }
  return inner
}
var accept = outter()
accept() // 1

在此例中,将内部函数inner作为返回值,当outter函数执行后,赋值给accept,并且调用accept;实际上调用了inner函数,它在自己定义的词法作用域以外的地方执行。
通常在outter函数执行完成之后,由于js的垃圾回收机制,其内部的作用域会被销毁回收,但是由于闭包的存在,accept持有对inner的引用,而inner声明的位置为outter的内部作用域,因此该作用能够一直存活

循环与闭包

面试常见的闭包考题就是循环闭包。例如:

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

正常情况下期待每隔一秒输出1~8,但是实际每秒输出一个9,其实不难理解,从执行栈来看,延迟函数会在整个for循环执行完成之后才会执行,即使setTimeout设置的延迟时间是0,同时其被封闭在全局的作用域中的,因此每次得到的都是对全局i变量的引用,实际上只有一个i。
可以通过立即执行函数将作用域封闭起来,同时通过自己定义一个变量j在每次迭代中存储i的值来解决这个问题,代码如下:

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

还有更加简单的解决方案,可以通过let声明来劫持块作用域,并且在这个块作用域中声明一个变量,代码如下:

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

for循环头部的let声明会有一个特殊行为,这个行为指出变量在循环的过程中不止被声明一次,每次迭代都会声明,随后每次迭代都会使用上一个迭代结束时的值来初始化这个变量,这就意味着每次循环都会声明一个新的i,且会将上一个迭代结束时的值赋给i

模块模式与闭包

模块模式实例:

function module() { //封闭函数
  var arr = [1,2,3]
  function one() {
    console.log(arr.join(''))
  }
  function another() {
    arr.push(4)
    console.log(arr.join(''))
  }
  return {
    one: one, //内部函数
    another: another
  }
}
var accept = module()
accept.one() //123 创建一个新的模块实例
accept.another() //1234 创建一个新的模块实例

解析:module是一个函数,需要通过调用它来创建一个模块实例。其返回一个{key:value}形式的对象,这个对象中包含内部函数的引用,将其暴露提供调用,且module内部数据变量是私有隐藏状态,这个对象返回值本质上是这个模块的公共API
分析得出模块模式需要两个必备条件:

  1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)
  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态

模块机制

var MyModules = (function Manager() {
  var modules = {};
  function define(name, deps, impl) {
    for (var i=0; i<deps.length; i++) {
      deps [i] = modules[deps[i]]
    }
    modules [name] = impl.apply(impl, deps)
  }
  function get(name) {
    return modules[name]
  }

  return { //公共API函数
    define : define,
    get : get
  }
})()

MyModules.define("one", [], function () {
  function hello(who) {
    return "hello" + who
  }
  return {
    hello : hello
  }
})

MyModules.define("another",["one"], function (one) {
  var name = 'world'

  function output() {
    console.log(one.hello(name))
  }

  return {
    output : output
  }
})
var another = MyModules.get('another')
another.output()

'one'和'another'两个模块都是通过公共API函数来定义,并且'another'接受'one'的实例作为参数注入,这就是模块之间可以存在依赖关系

以上内容是个人的一点总结,如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞收藏


william
2.7k 声望826 粉丝

love and share


« 上一篇
js作用域
下一篇 »
this绑定规则