js——重点之闭包

  不同人站在不同的角度对闭包有不同的解释,分别有以下三种比较权威的解释:

    函数与其词法环境的引用共同构成了闭包。也就是说,闭包可以让你从内部函数访问外部函数作用域,在JavaScript中函数每次创建时生成闭包。——MDN

    函数可以记住并访问所有的词法作用域时,就产生了闭包。即使函数是在当前作用域外执行。——《你不知道的JavaScript(上卷)》

    有权访问另一个函数作用域中的变量的函数就是闭包。——《JavaScript高级程序设计(第三版本)》

  在我看来,浏览器加载页面会把代码放到栈内存中执行(ECStack),函数进栈执行会产生一个私有的上下文(EC),这个上下文能保护里面的私有变量(AO)不受外界干扰,并且如果当前上下文中的某些内容被上下文以外的内容所占用,当前上下文是不会出栈释放的,这样可以保存里面的变量和变量值,所以闭包可以看成是一种保存和保护内部私有变量的机制。

  下面可以通过几道题目深入的理解什么是闭包?闭包的作用?

let x = 1;
function A(y){
   let x = 2;
   function B(z){
       console.log(x+y+z);
   }
   return B;
}
let C = A(2);
C(3);

  

  在上面的代码执行中我们要注意以下几点:

  1.VO和GO的区别和关联性

    区别:GO是全局对象,它是一个堆内存,里面存放的是浏览器提供的内置属性和方法,浏览器会让window指向GO,所以在浏览器端window代表的就是全局对象。每一个执行环境都是有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中, 这个变量对象称之为VO(Variable Object)。它们是不同的。

    关联:基于var/function在全局上下文中声明的全局变量根据映射机制也会给GO赋值一份,但就let/const等es6方式在全局上下文中创建的全局变量和GO没有任何关系。

  2.函数执行

    每一个函数执行都会形成一个私有的上下文,私有上下文形成后,会有一个存放私有变量对象的AO,它会进入ECStack执行。每一个执行上下文形成之后,代码执行之前,要进行变量提升 。var/function存在变量提升,let/const不存在变量提升。接下来要初始化作用域链、初始化this、初始化arguments、形参赋值、变量提升,最后才是代码执行。

  3.不销毁的上下文

    正常情况下,代码执行完成后,私有上下文会出栈释放,节约栈内存空间,但是如果当前私有上下文中的东西被上下文以外的事物所引用,则上下文不会在出栈释放,形成了不销毁的上下文。

let x = 5;
function fn(x) {
    return function(y) {
        console.log(y + (++x));
    }
}
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x);

 

   真实项目中为了保证js的性能(堆栈内存的性能优化)应该减少闭包的使用。但是又因为闭包它可以保护私有变量不受外界的干扰,形成不销毁的栈内存,把一些值保存下来,方便后面代码调取使用又不可避免的使用闭包。

  示例:选项卡

  html:

var oTab = document.getElementById('tabBox'),
    navBox = document.getElementById('navBox');
tabList = navBox.getElementsByTagName('li'),
    divList = oTab.getElementsByTagName('div');

function changeTab(curIndex) {
    for (var i = 0; i < tabList.length; i++) {
        tabList[i].className = divList[i].className = '';

        tabList[curIndex].className = 'active';
        divList[curIndex].className = 'active';
    }
}

  css;

* {padding: 0; margin: 0;}
ul {list-style: none;}
#tabBox {box-sizing: border-box; 500px;margin: 100px auto;}
#navBox {display: flex;position: relative;top: 1px;}
#navBox li {box-sizing: border-box;padding: 0 10px;margin-right: 10px;line-height: 35px;border: 1px solid #999;}
#navBox li.active {border-bottom-color: #fff;}
#tabBox div {display: none;box-sizing: border-box;padding: 10px;height: 150px;border: 1px solid #999;}
#tabBox div.active {display: block;}

  js:

var oTab = document.getElementById('tabBox'),
    navBox = document.getElementById('navBox'),
    tabList = navBox.getElementsByTagName('li'),
    divList = oTab.getElementsByTagName('div');

function changeTab(curIndex) {
    for (var i = 0; i < tabList.length; i++) {
        tabList[i].className = divList[i].className = '';
        tabList[curIndex].className = 'active';
        divList[curIndex].className = 'active';
    }
}
/* 
for (var i = 0; i < tabList.length; i++) {
    tabList[i].onclick = function () {
        changeTab(i);
        //解释:
        //=> 事件绑定是“异步编程”,当触发点击行为,绑定的方法执行时,外层循环已经结束:方法执行产生私有作用域,用到变量i,这个i不是私有的变量,
     //按“作用域链”的查找机制,找到的是全局下的i(此时全局的i已经完成,成为循环最后的结果) } } */ // 解决方法: /* //1.自定义属性 for (var i = 0; i
< tabList.length; i++) { tabList[i].myIndex = i; tabList[i].onclick = function () { changeTab(this.myIndex); //this:给当前元素的某个事件绑定方法,当事件触发,方法执行的时候,方法中的this是当前操作的元素对象 } } */
//2.闭包 利用闭包机制,把后续需要的索引存储到自己的私有作用域中“闭包有保存作用” /* for (var i
= 0; i < tabList.length; i++) { tabList[i].onclick = (function (n) { //让自执行函数执行,把执行的返回值return赋值给onclick var i = n; return function () { changeTab(i); //上级作用域:自执行函数形成的作用域 } })(i); } */
//3.es6 它和闭包机制类似,es6中let创建变量时会形成块级作用域,当前案例中,每一轮循环都是会形成一个自己的块级作用域,把后续需要用到的索引i存储到自己的作用域中 /* for (let i
= 0; i < tabList.length; i++) { tabList[i].onclick = function () { changeTab(i); } } */

  

 

  

原文地址:https://www.cnblogs.com/davina123/p/12012036.html