闭包及闭包的应用


title: 闭包及闭包的应用 date: 2019-04-25 17:00:00 tags: -JavaScript
-closure categories: JavaScript password: tgfe abstract: Welcome to my blog, enter password to read. message: Welcome to my blog, enter password to read.

前端开发人员对闭包这个知识点,必定都不陌生,咱们都知道闭包的概念是指有权访问另外一个函数做用域中的变量的函数。那么,闭包在js中的实际应用都有哪些呢,今天就一块儿来了解一下吧。javascript

执行环境及做用域链

要理解闭包,首先要对js的执行环境和做用域有了解。html

概念太长不想看系列:前端

执行环境定义了变量或函数有权访问的其余数据,决定了它们各自的行为。每一个执行环境都有与之关联的变量对象集合[[scope]],环境中定义的全部变量和函数都保存在这个对象中,这个集合被称为该环境的做用域链。vue

js中,每一个函数都有本身的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。在函数执行以后,栈将其环境弹出,环境随之销毁,控制权返回给以前的执行环境。java

当代码在一个函数的环境中执行时,会建立一个变量对象的做用域链。做用域链的前端,始终都是当前执行的代码所在的变量对象。若是这个环境是函数,则将其活动对象(active object)做为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。做用域链中的下一个对象来自包含(外部)环境,而再下一个变量则来自下一个变量环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是做用域链中的最后一个对象。 当运行期上下文被销毁,活动对象也随之销毁。react

标识符解析是沿着做用域链一级一级搜索标识符的过程。搜索过程始终从做用域链的前端开始,而后逐级的向后回溯,直至找到标识符为止。web

总结:面试

  • 每一个函数有一个执行环境,一个执行环境关联一个变量对象,变量对象的集合叫作做用域链。
  • 做用域链的前端是当前的执行代码所在的变量对象,下一个对象是外部函数,一直延续到全局变量。
  • 标识符解析是沿着做用域链从前端开始逐级回溯的过程。
  • 代码执行完毕后,所在的环境会被销毁,web中全局执行环境是window对象,全局环境会在应用程序退出时被销毁。

闭包的定义

闭包是指有权访问另外一个函数做用域中的变量的函数。编程

建立闭包的常见方式,就是在一个函数内部建立另外一个函数。设计模式

在javascript语言中,闭包就是函数和该函数做用域的组合。从这个概念上来说,在js中,全部函数都是闭包(函数都是对象而且函数都有和他们相关联的做用域链scope chain)。

大多数函数被调用时(invoked),使用的做用域和他们被定义时(defined)使用的做用域是同一个做用域,这种状况下,闭包神马的,可有可无。可是,当他们被invoked的时候,使用的做用域不一样于他们定义时使用的做用域的时候,闭包就会变的很是有趣,而且开始有了不少的使用场景,这就是你之因此要掌握闭包的缘由。 用途:

  • 读取函数内部的变量。
  • 让这些变量的值始终保持在内存中。不会在f1调用后被自动清除。
  • 方便调用上下文的局部变量。利于代码封装。

面试题助消化

var scope = "window scope"; 
function checkScope() {
    var scope = "local scope";
    function f() {
        return scope;
    }
    return f();
}
checkScope();   //=> "local scope"
复制代码
  • checkScope被invoke时,return f(),运行内部嵌套函数f,f沿着做用域链从内向外寻找变量scope,找到“local scope”,中止寻找,所以,函数返回 “local scope”;
var scope = "window scope"; 
function checkScope() {
    var scope = "local scope";
    function f() {
        return scope;
    }
    return f;
}
checkScope()();   //=> "local scope"
复制代码
  • checkScope被invoke时,将内部嵌套的函数f返回,所以checkScope()()这句执行时,其实运行的是f(),f函数返回scope变量,在这种状况下,f会从哪一个做用域里去寻找变量scope呢?
  • 词法做用域的基础规则:函数被执行时(executed)使用的做用域链(scope chain)是被定义时的scope chain,而不是执行时的scope chain。 嵌套函数f(), 被定义时,所在的做用域链中,变量scope是被绑定的值是“local scope”,而不是"window scope",所以,以上代码的结果是啥?没错,是"local scope"。 这就是闭包的神奇特性:闭包能够捕获到局部变量和参数的外部函数绑定,即使外部函数的调用已经结束。
var scope = "window scope"; 
function checkScope() {
    var scope = "local scope";
    function f() {
        return this.scope;
    }
    return f;
}
checkScope()();   //=> "window scope"
复制代码
  • 闭包的this指向的是它定义的地方的this,非严格模式下,函数内部的this指向全局对象(严格模式下,this为undefined),函数 checkScope 的this指向的是window对象,因此返回了window scope

闭包的应用场景

为节点循环绑定click事件

当前页面有5个button,要求是点击每一个button的时候弹出对应的编号

//html代码

<!DOCTYPE html>
<html>
<head>
     <meta charset="UTF-8">
</head>
<body>
    <button>Button0</button>
    <button>Button1</button>
    <button>Button2</button>
    <button>Button3</button>
    <button>Button4</button>
</body>
</html>
复制代码

//js

for(var i = 0;i<btnList.length;i++){
	//错误的代码 onclick是异步触发的,
	btnList[i].onclick = function(){
		console.log(i)
	}
 
	//正确的代码
	//采用“当即执行函数Immediately-Invoked Function Expression (IIFE)”的方式建立做用域
	(function(i){
		btnList[i].onclick = function(){
			console.log(i)
		}
	})(i);
}
复制代码

todo 当即执行函数不传i

延续局部变量的寿命

img对象常常用于数据上报,以下:

var report = function(src) {
    var img = new Image();
    img.src = src;
}
report('http://xxx.com/getUserInfo');
复制代码

这段代码在运行时,发如今一些低版本浏览器上存在bug,会丢失部分数据上报,缘由是img是report函数中的局部变量,当report函数调用结束后,img对象随即被销毁,而此时可能还没来得及发出http请求,因此这次请求就会丢失。

所以,咱们使用闭包把img对象封闭起来,就能够解决数据丢失的问题:

var report = (function() {
    var imgs = [];
    return function(src) {
        var img = new Image();
        imgs.push(img);
        img.src = src;
    }
})()


 (function(){
        //i在外部就不认识啦
        for(var i=0;i<count;i++){}
  })();
  console.log(i);//报错,没法访问
复制代码

对结果进行缓存

todo 传参复杂数据 react vue 的实现 tgou https

var fn=function(){
    var sum=0;
    for(var i=0;i<arguments.length;i++){
        sum+=arguments[i];
    }
    return sum;
}
console.log(fn(1,2));//3
 
//优化版本
var fn=(function(){
    var cache={}//将结果缓存到该对象中
    return function(){
        var str=JSON.stringify(arguments);
        if(cache[str]){//判断缓存中是否存在传递过来的参数,存在直接返回结果,无需计算
            return cache[str];
        }else{//进行计算并返回结果
            var sum=0;
            for(var i=0;i<arguments.length;i++){
                sum+=arguments[i];
            }
            return cache[str]=sum;
        }
    }
})()
复制代码

设计模式之 构造器模式

在经典面向对象的编程语言中,Constructor是一种在内存已分配给该对象的状况下,用于初始化新建立对象的方法。 在JavaScript中,几乎全部的东西都是对象,咱们一般最感兴趣的是object构造器。

Object构造器用于构建特定类型的对象--准备好对象以备使用,同时接收构造器可使用的参数,以在第一次建立对象时,设置成员属性和方法的值。

// 构造器模式
function Car(model, year, miles){
	this.model = model;
	this.year = year;
	this.miles = miles;

	Car.prototype.toString = function(){
		return this.model + 'has done ' + this.miles + ' miles';
	}
}
 
var civic = new Car('honda civic', 2019, 2000);
console.log(civic.toString()); 
复制代码
  • avaScript不支持类的概念,但支持与对象一块儿用的特殊constructor(构造器)函数,经过在构造器前面加new关键字,告诉JavaScript像使用构造器同样实例化一个新对象,而且对象成员由该函数定义。
  • toString这样的函数在这里在每次建立新的实例的时候都被从新定义,这不是最理想的,toString应该在全部的Car构造的实例之间共享。因此把toString放在Car的prototype(原型对象)上,Car构建的全部实例,都会访问同一个原型对象并获取到toString方法。 这里toString就是一个闭包,function能够访问实例中的model,miles和year变量。

todo prototype位置

Module(模块)模式

在JavaScript中,Module模式用于进一步模拟类的概念。经过这种方式,可以使一个单独的对象拥有公有/私有方法和变量,从而屏蔽来自全局做用域的特殊部分。 产生的结果是:函数名与在页面上其余脚本定义的函数冲突的可能性下降。

var myNamespace = (function(){
    var obj = {};
    // 私有计数器变量
    var myPrivateVar = 0;
    
    // 记录全部参数的私有函数
    var myPrivateMethods  = function(bar){
        console.log('my privateVar in private = '+ bar);
    }
    
    //公有变量
    var myPublicVar = 'foo';
    // 调用私有变量和函数的公有函数
    function myPublicMethods(bar){
        // 增长私有计数器值
        myPrivateVar = bar;
        console.log('my privateVar in public = '+ myPrivateVar);
        // 调用私有函数 并传入参数
        myPrivateMethods(bar);
    }
    
    obj.myPublicVar = myPublicVar;
    obj.myPublicMethods = myPublicMethods;
    
    return obj;
    
})();

// myNamespace.myPrivateVar;
// 用户能够调用公有函数,访问和变动私有函数及私有变量
myNamespace.myPublicMethods('user input');
复制代码

Module模式使用闭包封装“私有”状态和组织。它提供了一种包装混合共有/私有方法和变量的方式,防止其泄漏至全局做用域,并与别的开发人员的接口发生冲突。经过该模式,只需返回一个共有API,而其余的一切都维持在私有闭包里。 在Module模式中,共有部分(闭包)能够接触私有部分,然而外界没法接触类的私有部分,模块中的 myPrivateVar 和 myPrivateMethods 是私有的,所以应用程序的其余部分没法直接读取它。它只与模块的闭包一块儿存在。

相关文章
相关标签/搜索