JS语言理解05 闭包

定义

函数可以访问它被创建时的上下文环境,称为闭包

内部函数比它的外部函数具有更长的生命周期

function closure(name) {
  var status = 1;
  return {
    getName: function() {
      return name;
    },
    getStatus: function() {
      return status++;
    }
  }
}
var a = closure('w3ctech');
alert(a.getName()); // w3ctech
alert(a.getStatus()); // 1
alert(a.getStatus()); // 2

函数作用域:

  • 变量和参数在函数外不可见
  • 变量可以在函数内任何位置定义,并在函数内任何地方可见
  • 嵌套函数可以访问外部函数的参数和变量

闭包应用场景:

  • 实现私有成员
  • 保护命名空间
  • 避免污染全局变量
  • 变量需要长期驻留在内存

对闭包的理解

闭包return的是一个funciton

函数A中return了一个函数B,所以外界C在调用A时,相当于调用了B,C是不可以直接访问A中的变量的,但是通过B,C就访问了A中的变量

闭包的目的有两个:

  • 从外部得到函数内部的局部变量
  • 让这些局部变量的值始终存在内存当中
function f1(){
    var n=999;
    function f2(){
        alert(n);//获得f1的内部变量
    }
    return f2;
}
f1()();//f1()返回值是f2,需要对f2再次引用

闭包的定义:闭包(closure)是能读取他函数内部变量的函数(即定义在函数内部的函数),上面的例子中f2就是一个闭包,f2在f1内部需要作为返回值被输出。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

f1()()的引用相当于一个全局变量,所以f2式中存在于全局变量中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

使用闭包要注意:变量式中存在于内存中,内存消耗大。

思考题

var name = "The Window";
var object = {
  name: "My Object",
  getNameFunc: function() {
    return function() {
      return this.name;
    };
  }
};
alert(object.getNameFunc()()); //结果 The Window

由于this始终表示对调用者的引用,object.getNameFunc()的返回值是object对象内部的匿名函数,这个匿名函数的调用者是window,所以this的指向就是window,最后一句相当于打印的就是window.name,结果是'The Window'

那么为什么返回的匿名函数没有取得其包含作用域的this对象呢?

每个函数在被调用时都会自动取得两个特殊变量,thisarguments内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数的这两个变量。不过把外部作用域的this对象保存在一个闭包能搞访问的变量里,就可以让闭包访问该对象了。 – 《JavaScript高级程序设计》 P182

var name = "The Window";
var object = {
  name: "My Object",
  getNameFunc: function() {
    var that = this;
    return function() {
      return that.name;
    };
  }
};
alert(object.getNameFunc()()); //结果My Object

that是对象内部的this的值,所以结果是My Object

for循环赋值的例子

引入

下面的这个例子,本意是点击不同的li,打印出对应的索引,但是结果是无论点击哪个li,结果都是3

<body>
<p title="选择你最喜欢的水果?">你最喜欢的水果是?</p>
<ul>
    <li id="li1" title="苹果">苹果</li>
    <li title="橘子">橘子</li>
    <li title="菠萝">菠萝</li>
</ul>
<script>
  var li = document.getElementsByTagName('li');
  
  for(var i= 0; i<li.length; i++){
    li[i].onclick = function(){
      alert(i)
    }
  }
</script>

JavaScript的事件处理机制

一般而言,操作分为:发出调用和得到结果两步。发出调用,立即得到结果是为同步。发出调用,但无法立即得到结果,需要额外的操作才能得到预期的结果是为异步。同步就是调用之后一直等待,直到返回结果。异步则是调用之后,不能直接拿到结果,通过一系列的手段才最终拿到结果(调用之后,拿到结果中间的时间可以介入其他任务)

JavaScript语言的一大特点就是单线程,对于用户操作事件都是异步调用的,比如onclick事件,当onlick事件被定义后,它会调用浏览器对应的API,监听对应对象的click行为,当行为产生后,浏览器就会将这个事件对应的回调函数放到任务队列中。

当引擎解析完所有语句与dom之后(也就是栈中的代码执行完毕之后),主线程就会去读取任务队列,依次执行那些事件对应的回调函数。

执行栈中的代码(同步任务),总是在读取"任务队列"(异步任务)之前执行。

原因分析

会产生这种原因有两个:

  1. 作用域,JS中是没有块作用域的,只有全局作用域和函数作用域
  2. JS的事件处理机制

上面的for循环的执行过程大致如下:

  1. for循环每执行一次,调用dom中的onclick事件,将事件加入到任务队列中,总共加入了3个click事件
  2. 包括for在内的所有语句执行完毕,此时i=3(当i=2时for内部语句执行最后一次,i++后i=3)
  3. event loop开始起作用,轮询任务队列,分别执行
  4. 此时函数中的i都指向全局环境中的i,所以都为3

解决方法

利用闭包创建局部的作用域,将每个i都传入到函数内部并保存起来,具体有两种方法

方法1:创建一个自执行的匿名函数,将变化的i传入函数内部并保存起来(自执行是关键,这样就不需要放到队列中去等待进行,而是在循环的过程中执行)

  /***利用自执行匿名函数创造一个闭包域空间,将i的值储存在函数内部
  for(var i= 0; i<li.length; i++){
    (function(j){
      li[j].onclick = function(){
        alert(j)
      }
    })(i)
  }
  ****/

方法二:也是利用闭包,办法一是在新增的匿名闭包空间内完成事件的绑定,而此例是将事件绑定在新增的匿名函数返回的函数上

for(var i= 0; i<li.length; i++){
    li[i].onclick = (function(j){
      return function(){
        alert(j)
      }
    })(i)
  }

方法三:利用ES6的let声明块级变量

  for(let i= 0; i<li.length; i++){
    li[i].onclick = function(){
      alert(i)
    }
  }

原因是:变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。

用闭包隐藏数据

闭包经常用于创建含有隐藏数据的函数(但并不总是这样)。

var db = (function() {
// 创建一个隐藏的object, 这个object持有一些数据
// 从外部是不能访问这个object的
var data = {};
// 创建一个函数, 这个函数提供一些访问data的数据的方法
return function(key, val) {
    if (val === undefined) { return data[key] } // get
    else { return data[key] = val } // set
    }
// 我们可以调用这个匿名方法
// 返回这个内部函数,它是一个闭包
})();

db('x'); // 返回 undefined
db('x', 1); // 设置data['x']为1
db('x'); // 返回 1
// 我们不可能访问data这个object本身
// 但是我们可以设置它的成员

参考

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值