(四)作用域闭包

产生闭包的条件

闭包的解释看这里
【要知道的】:
闭包是基于词法作用域书写代码时所产生的自然结果。因此闭包的创建和使用在我们的代码中随处可见。而我们只需要根据自己的意愿来识别并拥抱它就可以啦

词法作用域:它是定义在词法阶段的作用域。换句话说,它是由我们在写代码时,将变量和块作用域写在哪里的位置决定的

function foo(){
  var a = 2
  function bar(){
    console.log(a)
  }
  return bar // 将内部函数保存到外部
}
var baz = foo() 
baz() // 2 调用内部函数,产生了闭包
  1. 执行foo时:定义bar并将它保存到外部变量baz中
  2. 执行baz,相当于执行bar函数,因为bar函数中使用到了foo作用域中的变量a,因此它持有外层作用域的引用
  3. 当foo执行完之后,本应立即销毁他自己创建的作用域,但由于这个作用域同时也被baz这个外部变量所持有,因此不可以被销毁
    在这里插入图片描述
  function foo(){
    var a = 2
    function baz(){
      console.log(a)
    }
    bar(baz) // 将内部函数保存到外部,并调用
  }
  function bar(fn){
    fn(); // 呀呀,这就是闭包
  }
  foo()

在这里插入图片描述

var fn // 全局变量
function foo(){
    var a = 2
    function baz(){
      console.log(a)
    }
   fn = baz // 将内部函数保存到全局变量上
  }
  function bar(fn){
    fn(); // 呀呀,闭包
  }
  foo() // 主要是 fn = baz
  bar() //  主要是 fn()

因为baz函数中使用到了外层作用域即foo中的数据。一旦在外部执行bar(),baz便具有覆盖foo作用域的闭包

什么是闭包

当多个函数之间发生嵌套时,如果内层函数引用到了外层函数内的数据。此时调用外层函数,并将内层函数的引用保存到外部便产生了闭包。此时内层函数便具由涵盖外层函数作用域的闭包

发生在外层函数执行导致内层函数的定义,且内层函数中还必须得使用到外层函数的数据
当然,如果想要生成的闭包有意义,还需要在外层函数执行的时候将内层函数的引用保存到外部。否则就算产生了闭包,他也是保存在外层函数的作用域里,一旦外层函数执行完,它的作用域没人引用就会成为垃圾被销毁,此时刚形成的闭包也跟着被销毁了

循环和闭包

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

每秒一次,每次一个,输出6
在这里插入图片描述

  for(var i = 1; i <= 5; i++) {
    (function(){ // 立即执行函数,每次循环都会创建一个单独作用域
	    setTimeout(function timer(){
	      console.log(i)
	    },1000)
    })()
  }

通过声明并立即执行一个函数来创建一个新的作用域。但是、但是这个新创建的作用域其实并没有任何卵用,因为里面没有任何数据,而最后打印的i,最终找的还是全局作用域里面的i

  for(var i = 1; i <= 5; i++) {
    (function(){ // 立即执行函数,每次循环都会创建一个单独作用域
        var j = i // 每次循环都会将i的值保存到自己的作用中的j里面
	    setTimeout(function timer(){
	      console.log(j) // 此时打印的j就是,自己作用域里面的j
	    },1000)
    })()
  }

实际上每次循环都形成了一个新的闭包。而timer函数里面使用的变量j就是新闭包中的变量

  for(var i = 1; i <= 5; i++) {
    (function(j){ // 立即执行函数,每次循环都会创建一个单独作用域
	    setTimeout(function timer(){
	      console.log(j) // 此时打印的j就是,自己作用域里面的j
	    },1000)
    })(i)
  }

块作用域和闭包

for (var i = 0; i <= 5; i++) {
  let j = i // 闭包的块作用域
  setTimeout(function timer(){
    console.log(j)
  }, 1000)
}

let可以劫持每一次循环时的块作用域,因此一个块就转换成一个可以被关闭的作用域。而timer覆盖了每次循环迭代生成的块作用域形成的闭包。也是生成了五个闭包

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

即for循环头部的let声明还会有一个特殊的行为,也就是变量在循环的过程中不止被声明一次每次迭代都会被声明,随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量

模块

模块可是利用了闭包的强大威力的典型例子

function foo(){
  var something = "cool"
  var another = [1,2,3]
  function doSomething(){
    console.log(something)
  }
  function doAnother(){
    console.log(another.join("!"))
  }
}

在这里插入图片描述

因此模块模式需要具备的两个必要条件

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

一个具有函数属性的对象并不是真正的模块

 var obj = {
	  function doSomething(){
	    console.log(something)
	  }
	  function doAnother(){
	    console.log(another.join("!"))
	  }
 }

一个从函数调用所返回的,只有数据属性而没有闭包函数的对象也不是一个真正的模块

function foo(){
  var something = "cool"
  var another = [1,2,3]
  function doSomething(){
    console.log(something)
  }
  function doAnother(){
    console.log(another.join("!"))
  }
  return {
    something: something
    another: another
  }
}  

上一个示例代码中有一个叫做CoolModule()的独立的模块创建器,他可以被调用任意多次,且每次调用所返回的都会创建一个新的模块实例。当只需要一个实例时,我们可以对这个模式进行简单的改建来实现单例模式

var foo = (function CoolModule(){
    var something = "cool"
    var another = [1,2,3]
    function doSomething(){
      console.log(something)
    }
    function doAnother(){
      console.log(another.join("!"))
    }
    return {
      doSomething: doSomething,
      doAnother: doAnother
    }
})()
foo.doSomething() // cool
foo.doAnother() // 1!2!3

单例:通过立即执行函数,立即调用并将返回值直接赋值给单例的模块实例标识符foo
【模块的另一个强大的用法:给将要作为公共API返回的对象命名】

var foo = (function(id){
  function change () {
    // 修改公共的API
    publicAPI.identify = identify2
  }
  function identify1 () {
    console.log(id)
  }
  function identify2 () {
    console.log(id.toUpperCase())
  }
  var publicAPI = {
    change: change
    identify: identify1
  }
  return publicAPI
})("foo module")
foo.identify() // foo module
foo.change() // 更改模块实例中的方法
foo.identify() // FOO MODULE

通过在模块实例的内部保留对公共API对象的内部引用,从而可以从内部对模块实例进行修改,包括添加或删除方法和属性,以及修改他们的值

现代的模块机制

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] = imp.apply(impl, deps)
  }
  function get(name) {
   return modules[name]
  }
  return {
    define: define
    get: get
  }
})()

使用

MyModules.define("bar", [], function(){
  function hello(who) {
    return "let me introduce:" + who
  }
  return {
    hello: hello
  }
})
MyModules.define("foo", ["bar"], function(bar){
  var hungry = "hippo"
  function awesome(who) {
    console.log(bar.hello(hungry).toUpperCase())
  }
  return {
    awesome: awesome
  }
})
var bar = MyModules.get("bar")
var foo = MyModules.get("foo")
console.log( bar.hello("hippo")) // let me introduce: hippo
foo.awesome() // LET ME INTRODUCE: HIPPO

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值