JS 闭包

1.有关闭包定义

闭包是指有权访问另一个函数作用域中变量的函数,
创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量

说说你对闭包的理解

闭包 的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些
变量始终保持在内存中

闭包的另一个用处,是封装对象的私有属性和私有方法

好处:能够实现封装和缓存等;

坏处:就是消耗内存、不正当使用会造成内存溢出的问题

使用闭包的注意点

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用
闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露

解决方法是,在退出函数之前,将不使用的局部变量全部删除

闭包的定义其实很简单:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包

function A() {
  let a = 1
  window.B = function () {
      console.log(a)
  }
}
A()
B() // 1

闭包会产生一个很经典的问题:

多个子函数的[[scope]]都是同时指向父级,是完全共享的。因此当父级的
变量对象被修改时,所有子函数都受到影响。

解决:

变量可以通过 函数参数的形式 传入,避免使用默认的[[scope]]向上查找
使用setTimeout包裹,通过第三个参数传入
使用 块级作用域,让变量成为自己上下文的属性,避免共享

2.闭包简单例子
指的是有权访问另一个函数作用域中变量的函数,
创建闭包的常见方式,就是在一个函数内部创建另一个函数。

 function f1(){
    var n=999;
    function f2(){
      alert(n); // 999
    }
  }
function f1(){
    var n=999;
    function f2(){
      alert(n); 
    }
    return f2;
  }
  var result=f1();
  result(); // 999

3.闭包的用处:

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

4.使用必闭包的问题:

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题。

闭包的例子:

function outerFun()
{
 var a=0;
 function innerFun()
 {
  a++;
  alert(a);
 }
 return innerFun;  //注意这里
}
var obj=outerFun();
obj();  //结果为1
obj();  //结果为2
var obj2=outerFun();
obj2();  //结果为1
obj2();  //结果为2
function outerFun()
{
 //没有var
 a =0;
 alert(a);  
}
var a=4;
outerFun();
alert(a);
结果为 0,0 真是奇怪,为什么呢?

作用域链是描述一种路径的术语,沿着该路径可以确定变量的值 .当执行a=0时,因
为没有使用var关键字,因此赋值操作会沿着作用域链到var a=4;  并改变其值. 

5.闭包内的微观世界
参考学习:https://www.cnblogs.com/goloving/p/7062212.html
  如果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。

1.当定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。
当执行函数a的时候,a会进入相应的执行环境(excution context)。
2.在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。
3.然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过Javascript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。
4.下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
5.最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。

当在函数b中访问一个变量的时候,搜索顺序是:

先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,
依次查找,直到找到为止。
如果函数b存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型
对象,再继续查找。这就是Javascript中的变量查找机制。
如果整个作用域链上都无法找到,则返回undefined。

函数的定义与执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定

6.有关闭包经典案例
经典面试题,循环中使用闭包解决 var 定义函数的问题

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

首先因为 setTimeout 是个异步函数,所有会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。
解决办法两种,第一种使用闭包

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

第二种就是使用 setTimeout 的第三个参数

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

第三种就是使用 let 定义 i 了

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

封装对象的私有属性与私有方法

var book = (function(){
var page = 100;
return function(){
this.auther = 'dava';
this.price = 200;
this._page = function(){
alert(page);
}
}
})();
var a = new book();
a.auther//"dava"
a.price//   200
a.page//"wrong"
a._page()//   100

这里例子用了一个函数自动执行,一上来就执行了一个匿名函数,并且在匿名函数里面定义了一个局部变量page,然后又返回了一个匿名函数,并且被全局作用域下的book变量接收,此时使用new 调用book就会生成一个新对象a。其中auther属性和price属性可以直接通过对象访问,因为这些属性都是new的时候直接定义在返回的对象身上的,而page属性则没有,因此不能反回,但此时如果我想访问page属性,那就得依靠闭包了,返回的函数在外层的匿名函数里面,因此在返回的函数身上定义了一个方法叫_page,这个方法弹出了page属性,按照js作用域的关系,当前作用域找不到page,就会到上层作用域去寻找,这样就找到了。通过这种方式我们就把私有方法和公有方法区分开了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值