作用域和闭包

孤街浪徒 提交于 2020-01-25 21:39:09

1.作用域

作用域其实就代表了变量合法的使用范围。
作用域分为全局作用域、函数作用域和块级作用域。
在这里插入图片描述
Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

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

但是,在函数外部自然无法读取函数内的局部变量。

function f1(){
  var n=999;
}

alert(n); // error

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

function f1(){
  n=999;
}
f1();
alert(n); // 999

2.作用域链

一个变量在当前作用域没有被定义,但被使用了,会向上级作用域,一层层寻找,直至找到为止。
这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。
所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方!!!!!!

function f1(){
   var n=999;
   function f2(){
     alert(n); // n在这个作用域内就是自由变量,999
  }
}

3.闭包

闭包就是能够读取其他函数内部变量的函数。
在A函数中创建了一个B函数,那么B函数就是闭包。B函数可以访问到A函数中的局部变量。

闭包的作用:
  1. ,在外部访问到函数内部的变量
    js作用域中,函数内部可以调用函数外部的变量,反之,却不可以,可以使用闭包解决,在外部访问到函数内部的变量。
    function outer() {
        const a = 100;
         return function inner() {
              console.log(a)
         }
    }
    let fn = outer();
    const a = 200;
    fn();     //100
    
  2. 让函数内部的变量值始终保持在内存中。
    一般情况下, 函数执行完后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),但是闭包情况下,比如下面这个代码,函数f1执行完后,变量n不会被摧毁,因为f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。所以闭包,会占用更多的占用内存。
    function f1(){
      var n=999;
      return function f2(){
      	n++
        alert(n);
      }
    }
    var result=f1();
    result(); // 1000
    result(); // 1001
    //函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
    

3. 闭包的几种表现形式

根据闭包的定义,我们知道,无论通过何种手段,只要将内部函数传递到所在的词法作用域以外,它都会持有对原始作用域的引用,无论在何处执行这个函数都会使用闭包。接下来,本文将详细介绍闭包的7种形式。

函数作为返回值返回
function outer() {
	    const a = 100;
	     return function inner() {
	          console.log(a)
	     }
	}
let fn = outer();
const a = 200;
fn();     //100
将内部函数赋值给一个外部变量

这是将函数作为返回值的一种变形。
(将F作用域里的函数N赋值给全局作用域的inner,所以F执行之后,inner保持了对F作用域里的引用)

var inner;
function F(){
    var b = 'local';
    inner = functionN(){
        return b;
    };
};
F();
console.log(inner());//local
函数作为参数

闭包可以通过函数参数传递函数的形式来实现

const b = 100;
function fn(){
    console.log(b);
}
function print(fn) {
   const b = 200;
   fn();
}
print(fn); //100

IIFE

由前面的示例代码可知,外部函数都是在声明后立即被调用,因此可以使用IIFE来替代。但是,要注意的是,这里的Inner()只能使用函数声明语句的形式,而不能使用函数表达式。

function Inner(fn){
    console.log(fn());
}

(function(){
    var b = 'local';
     Inner(function(){
        return b;
    })
})();

闭包问题

在闭包问题上,最常见的一个错误就是循环赋值的错误。
如果在for循环中添加匿名函数

var btn = document.getElementsByTagName("button")
for(var i = 0;i< btn.length;i++) {
      btn[i].addEventListener('click',function(){
           alert(i)
      })    //这也是闭包
}                            

上面这段代码就形成了一个闭包:
当点击按钮后,匿名函数执行 alert i 语句的时候,由于匿名函数里面没有i这个变量,所以这个i他要从父级函数中寻找i,而父级函数中的i是全局变量,当找到这个i的时候,是for循环完毕的i,也就是3,所以弹出的是3.
解决这个问题有两种方法。
方法1:

//立即执行函数
//通过function垫了一层函数作用域
var btn = document.getElementsByTagName("button")
for(var i = 0;i< btn.length;i++) {
     (function(i){
          btn[i].addEventListener('click',function(){
                alert(i);   //3,3,3
          })
     })(i);
}

方法二:

//使用let,形成一个块级作用域
var btn = document.getElementsByTagName("button")
for(let i = 0;i< btn.length;i++) {
     btn[i].addEventListener('click',function(){
          alert(i);   //0,1,2
   	})
}
//let定义的i为块级作用域的变量,每次循环会形成块级作用域,
//点击按钮时,函数执行,向上级作用域寻找i的值,就找到了块级作用域中i的值

闭包的应用

闭包提供api访问被隐藏的数据

//隐藏数据
//做一个简单的cache工具
function createCache() {
    const data = {};//数据,被隐藏,不被外界访问
    return {
        set: function (key, val) {   //提供api访问被隐藏的数据
            data[key] = val
        },
        get: function (key) {
            return data[key]
        }
    }
}
const c = new createCache();
c.set('a',1000);
console.log(c.get('a'))
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!