你不知道的js异步、作用域、闭包
例题如下:
-
for (var i = 0; i < 3; i++) {setTimeout(function() {console.log(i);}, 0);console.log(i);}
结果是:0 1 2 3 3 3
很多公司面试都爱出这道题,此题考察的知识点还是蛮多的。
为了防止初学者栽在此问题上,此文稍微分析一下。
都考察了那些知识点呢?
异步、作用域、闭包,你没听错,是闭包。
我们来简化此题:
setTimeout(function() {
console.log(1);
}, 0);
console.log(2);
先打印2,后打印1。
因为是setTimeout是异步的。
正确的理解setTimeout的方式(注册事件):
有两个参数,第一个参数是函数,第二参数是时间值。
调用setTimeout时,把函数参数,放到事件队列中。等主程序运行完,再调用。
没啥不好理解的。就像我们给按钮绑定事件一样:
btn.onclick = function() {
alert(1);
};
这么写完,会弹出1吗。不会!!只是绑定事件而已!
必须等我们去触发事件,比如去点击这个按钮,才会弹出1。
setTimeout也是这样的!只是绑定事件,等主程序运行完毕后,再去调用。
setTimeout的时间值是怎么回事呢?
比如:
setTimeout(fn, 2000)
我们可以理解为2000之后,再放入事件队列中,如果此时队列为空,那么就直接调用fn。如果前面还有其他的事件,那就等待。
因此setTimeout是一个约会从来都不准时的童鞋。
继续看:
setTimeout(function() {
console.log(i);
}, 0);
var i = 1;
程序会不会报错?
不会!而且还会准确得打印1。
为什么?
因为真正去执行console.log(i)这句代码时,var i = 1已经执行完毕了!
所以我们进行dom操作。可以先绑定事件,然后再去写其他逻辑。
window.onload = function() {
fn();
}
var fn = function() {
alert('hello')
};
这么写,完全是可以的。因为异步!
es5中是没有块级作用域的
for (var i = 0; i < 3; i++) {}
console.log(i);
也就说i可以在for循环体外访问到。所以是没有块级作用域。
但此问题在es6里终结了,因为es6,发明了let。
这回我们再来看看原题。
原题使用了for循环。循环的本质是干嘛的?
是为了方便我们程序员,少写重复代码。
让我们倒退50年,原题等价于:
-
var i = 0;setTimeout(function() {console.log(i);}, 0);console.log(i);i++;setTimeout(function() {console.log(i);}, 0);console.log(i);i++;setTimeout(function() {console.log(i);}, 0);console.log(i);i++;
因为setTimeout是注册事件。根据前面的讨论,可以都放在后面。
原题又等价于如下的写法:
-
var i = 0;console.log(i);i++;console.log(i);i++;console.log(i);i++;setTimeout(function() {console.log(i);}, 0);setTimeout(function() {console.log(i);}, 0);setTimeout(function() {console.log(i);}, 0);
这回你明白了为啥结果是0 1 2 3 3 3了吧。
那个,老姚你说它是闭包,又是怎么回事?
为了很好的说明白这个事情,我们把它放到一个函数中:
var fn = function() {
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 0);
console.log(i);
}
};
fn();
上面的函数跟我们常见另一个例子(div绑定事件)有什么区别:
var fn = function() {
var divs = document.querySelectorAll('div');
for (var i = 0; i < 3; i++) {
divs[i].onclick = function() {
alert(i);
};
}
};
fn();
点击每个div都会弹出3。道理是一样的。因为alert(i)中的i是fn作用域中的,因而这是闭包。
《javascript忍者秘籍》书里把一个函数能调用全局变量,也称闭包。
因为作者认为全局环境也可以想象成一个大的顶级函数。
怎么保证能弹出0,1, 2呢。
解决之道:以毒攻毒!
再创建个闭包!!
var fn = function() {
var divs = document.querySelectorAll('div');
for (var i = 0; i < 3; i++) {
divs[i].onclick = (function(i) {
return function() {
alert(i);
};
})(i);
}
};
fn();
或者如下的写法:
var fn = function() {
var divs = document.querySelectorAll('div');
for (var i = 0; i < 3; i++) {
(function(i) {
divs[i].onclick = function() {
alert(i);
};
})(i);
}
};
fn();
因此原题如果也想setTimeout也弹出0,1,2的话,改成如下:
-
for (var i = 0; i < 3; i++) {setTimeout((function(i) {return function() {console.log(i);};})(i), 0);console.log(i);}
本文完。
原文链接:http://www.qdfuns.com/notes/17398/e8a1ce8f863e8b5abb530069b388a158/page/.html
你不知道的js异步、作用域、闭包的更多相关文章
- 你不知道的JS之作用域和闭包(五)作用域闭包
原文:你不知道的js系列 一个简单粗暴的定义 闭包就是即使一个函数在它所在的词法作用域外部被执行,这个函数依然可以访问这个作用域. 比如: function foo() { var a = 2; fu ...
- 你不知道的JS之作用域和闭包 附录
原文:你不知道的js系列 A 动态作用域 动态作用域 是和 JavaScript中的词法作用域 对立的概念. 动态作用域和 JavaScript 中的另外一个机制 (this)很相似. 词法作用域是 ...
- 你不知道的JS之作用域和闭包(三)函数 vs. 块级作用域
原文:你不知道的js系列 在第(二)节中提到的,标识符在作用域中声明,这些作用域就像是一个容器,一个嵌套一个,这个嵌套关系是在代码编写时定义的. 那么到底是什么产生了一个新的作用域,只有函数能做到 ...
- 你不知道的JS之作用域和闭包(四)(声明)提升
原文:你不知道的js系列 先有鸡还是先有蛋? 如下代码: a = 2; var a; console.log( a ); 很多开发者可能会认为结果会输出 undefined,因为 var a 在 a ...
- 你不知道的JS之作用域和闭包(二)词法作用域
原文:你不知道的js系列 词法作用域(Lexical Scope) Lex time 一个标准的编译器的第一个阶段就是分词(token化) 词法作用域就是在词法分析时定义的作用域.换句话说,词法作用域 ...
- 你不知道的JS之作用域和闭包(一)什么是作用域?
原文:你不知道的js系列 什么是作用域(Scope)? 作用域 是这样一组规则——它定义了如何存放变量,以及程序如何找到之前定义的变量. 编译器原理 JavaScript 通常被归类为动态语言或者解释 ...
- 说说循环与闭包——《你不知道的JS》读书笔记(一)
什么是闭包 <你不知道的JS>里有对闭包的定义:"当函数可以记住并访问所在的词法作用域,即使函数是在当前作用域之外执行,这就产生了闭包." 讲闭包是啥的太多了...就一 ...
- 读《你不知道的JavaScript(上卷)》后感-作用域闭包(二)
github原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们, ...
- JS 之作用域链和闭包
1.JS无块级作用域 <script> function Main(){ if (1==1){ var name = "alex"; } console.log(nam ...
随机推荐
- ZOJ 3204 Connect them(最小生成树+最小字典序)
Connect them Time Limit: 1 Second Memory Limit: 32768 KB You have n computers numbered from 1 t ...
- react: typescript-webpack项目基本配置
1.webpack.config.js basic const webpack = require('webpack'); const autoprefixer = require('autopref ...
- Puzzle Game HihoCoder - 1634
题目链接:https://cn.vjudge.net/problem/HihoCoder-1634 题目意思:可以让矩阵里的某一个数变成p,或者不修改.求最大子矩阵最小,输出最小值. 思路:请看下图 ...
- DRF 权限的流程
DRF 权限的流程 django rest framework,入口是 dispatch,然后依次 --->>封装请求--->>处理版本--->>>认证--- ...
- C++使用初始化列表的方式来初始化字段
几个月之前,接触Android recovery源代码的时候,看ScreenRecoveryUI类的时候,那时候C++基础还不是特别好,一直不明白以下的初始化方式: 下面这个是Recovery的一个构 ...
- 【转载】取得系统中网卡MAC地址的三种方法
From:http://blog.csdn.net/zhangting1987/article/details/2732135 网卡地址这个概念有点混淆不清.因为实际上有两个地址,mac地址和物理地址 ...
- win10如何在局域网中设置一台电脑的固定ip地址
在工作和生活中,经常要遇到远程访问一台电脑的情况,但是在局域网中如果不进行设置,通常一台电脑的ip是自动生成的,,没有固定,这就导致下次访问这个地址时,不能正常访问,下面就交大家如何在win10系统中 ...
- Arcgis Desktop连接GIS Servers报错“Proxy server got bad address from remote server ...”
今天打开Arcgis Desktop时突然发现连接GIS Servers报错“Proxy server got bad address from remote server ...” 网上查找到解决方 ...
- 有状态与无状态 cookie session
服务器所维护的与客户交互活动的信息称为状态信息.不保存任何状态信息的服务器称为无状态服务器(stateless server),反之则称为有状态服务器(stateful server). 面向连接对应 ...
- ul li 水平居中
li的float:left方法显然有一个问题,就是无法居中(水平),只能使用padding-left或margin-right的方法方法来固定其居中.但这样可能在宽屏与窄屏的显示不一致.使用这种方法主 ...