zoukankan      html  css  js  c++  java
  • 经典题目谈闭包

    侃侃闭包

    春暖花开,又到了程序猿换领地的季节了,各大论坛出现很多的面试题和各种押题,然后我和小伙伴仔细研究,果然大部分不会。其中有一个讲zepto源码的,提到了js三座大山:闭包原型异步。我曾经入门的时候在这3个山里面饶了很久很久,而且多次以为绕出去的时候才发现我只是过了一个小山头,苦不堪言。当然,我觉得现在自己已经绕出来了,那么就先讲一讲闭包这座山的路。

    经典回顾

    先放一段经典的面试题

    for(var i = 0; i < 10; i ++) {
    	bt[i].onclick = function(){
    		console.log(i);
    	}
    }
    

    初次接触闭包就是这个问题,但是这样看,好像和闭包没有什么关系,而且确实没有什么关系。这个每次结果都是9已经毫无疑问了,但是为什么是9?我们慢慢拆解。

    var i;
    for(i = 0; i < 10; i ++) {
    	bt[i].onclick = function(){
    		console.log(i);
    	}
    }	
    

    i作为变量被提升了,我们再把循环展开

    bt[0].onclick = function(){
    	console.log(i);
    }
    bt[1].onclick = function(){
    	console.log(i);
    }
    bt[2].onclick = function(){
    	console.log(i);
    }
    ....
    
    
    

    这时有人肯定和我当年有一样的疑问,为什么function里的是i,不应该对应的是当时的i的值吗?

    先举个例子

    var i = 10;
    function test(){
    	var i = 1;
    	console.log(i);
    }
    

    如果i在js加载就赋值了,那么是不是就成了console.log(10),test()执行后打印的应该是10而不是1啊。很明显,解析js的时候,test这个函数内部并没有赋值,只是给它了一个变量,具体什么值等调用了才会拿到。其实拆到这的时候我也很懵,什么鬼,为什么会是这样。

    执行上下文

    在ES6之前是没有块级作用域这个概念的,只有全局作用域、函数作用域和eval作用域。

    在浏览器加载js脚本的时候,解析器在解析js代码时,会先进入全局作用域,如果碰到函数调用,会创造函数上下文,并将这个上下文压入到执行栈中,如果这个函数内部还有另一个函数调用,那么就会把另一个函数压入到执行栈的顶部,依次类推,最后调用的函数总是被放到执行栈的最顶部,然后执行最上层的函数。

    这个过程涉及到js很重要的几个点:js是单线程同步执行函数被调用的时候才会创建执行上下文

    既然知道了函数调用的时候会创建执行上下文,那么就要探究下怎么执行上下文内部怎么执行的:

    • 初始化作用域链
    • 创建变量对象:创建 参数对象, 检查参数的上下文, 初始化其名称和值并创建一个引用拷贝
    • 扫描上下文中的函数声明:对于每个被发现的函数, 在 变量对象 中创建一个和函数名同名的属性,这是函数在内存中的引用
    • 扫描上下文中的变量声明:1. 对于每个被发现的变量声明,在变量对象中创建一个同名属性并初始化值为 undefined。2. 如果变量名在 变量对象 中已经存在, 什么都不做,继续扫描。
    • 确定上下文中的 "this"
    • 激活 / 代码执行阶段:执行 / 在上下文中解释函数代码,并在代码逐行执行时给变量赋值。

    上面这些步骤可以分为2个阶段:初始化阶段(创建阶段)和执行阶段。

    我们看个例子

    var a = 100;
    function fn(n) {
    	var a = 10;
    	const b = function(){};
    	function c(o) {}
    }
    

    当在调用fn(11)时,创建阶段其实是这样的:

    function fn(n) {
    	a:100  //父级上下文中的变量(作用域链)
    	arguments:{
    		0:11,
    		length:1
    	}
    	c:function c(o)
    	a: undefined
    	b: undefined
    	this:{//全局是window}
    }
    

    很明显,在创建阶段只是做了属性名的定义,并没有给函数内变量赋值,全局变量和参数除外。创建阶段完成后,便开始进入执行阶段。代码执行阶段看起来是这样得:

    function fn(n) {
    	a:100  //父级上下文中的变量(作用域链)
    	arguments:{
    		0:11,
    		length:1
    	}
    	c:function c(o)
    	a: 10
    	b: function()
    	this:{//全局是window}
    }
    

    这就是整个执行环境。

    闭包

    说了这么多废(pu)话(dian),终于到正题了。红皮书是这样说的:闭包是有权访问另一个函数作用域中的变量的函数。如果熟读红皮书的话应该很好理解这句话,主要在作用域链那。当然通俗点就是内部可以访问外部,外部不能访问内部。

    现在返回去看看那道经典的题目,应该很好解决了。根据执行上下文的过程,我们在外层加一个函数,并且执行它,因为初始化过程中参数是可以直接被赋值的。

    for(var i = 0; i < 10; i ++) {
    	(function(num){
    		bt[i].onclick = function(){
    			console.log(num);
    		}
    	})(i)
    	
    }
    

    这样就ok了。

    总结

    闭包并不可怕,只要搞清js的一些执行机制,很多也就迎刃而解了。当时我并不理解为什么要加一个理解执行函数,后来看到很多关于作用域链和执行上下文的文章,反过来想想,也就没什么难的了。当然,闭包一个很重要的作用就是设置私有变量,其实这个题目也算一个。

    这个讲的很简单,如果仔细说闭包,估计要说2小时。这个只是来理解这一个题目而已。

    他的原创之处并不优秀,他的优秀之处并非原创!!!

  • 相关阅读:
    python的字符串连接操作符+
    python-在定义函数时,不定长参数中,默认值参数不能放在必选参数前面
    python中的sort方法使用详解
    详解Python中的join()函数的用法
    python中map()函数
    python的匿名函数lambda解释及用法
    python 代码的缩进位置决定执行部分
    python代码位置引发的错误
    python中如何使输出不换行
    git stash
  • 原文地址:https://www.cnblogs.com/open-wang/p/8749076.html
Copyright © 2011-2022 走看看