你不知道的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 ...
随机推荐
- LeetCode OJ:Combination Sum (组合之和)
Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C wher ...
- react-redux: counter
store: import {createStore,applyMiddleware, compose} from "redux"; import thunk from " ...
- CentOS 7下sqlite3的问题修复
Centos7下的nltk启动问题 CentOS 7, Python 3.6,ipython 6.0.0 问题描述 ipython 启动ipython命令 import nltk 爆出以下的错误信息: ...
- DotNet Core Console 程序使用NLog
参考:https://github.com/NLog/NLog/wiki/Tutorial 步骤: 1. 使用Nuget安装NLog.Extensions.Logging Install-Packag ...
- SpringMVC使用session实现简单登录
1.首先为了能直观地在jsp页面体现session的内容,我使用了jstl表达式,首先在pom.xml中引入jstl的依赖 <!-- jstl所需要的依赖 --> <dependen ...
- SCARA——OpenGL入门学习四(颜色)
OpenGL入门学习[四] 本次学习的是颜色的选择.终于要走出黑白的世界了~~ OpenGL支持两种颜色模式:一种是RGBA,一种是颜色索引模式. 无论哪种颜色模式,计算机都必须为每一个像素保存一些数 ...
- call()方法和apply()方法用法总结
1. 每个函数都包含两个非继承而来的方法:call()方法和apply()方法. 2. 相同点:这两个方法的作用是一样的. 都是在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖 ...
- C#枚举Enum[轉]
枚举是一个指定的常数,其基础类型可以是除 Char 外的任何整型.如果没有显式声明基础类型,则使用 Int32.编程语言通常提供语法来声明由一组已命名的常数和它们的值组成的枚举.定义默认基数从O开始, ...
- 笔记:使用 Protel 99 SE 改一块车充 PCB
笔记:使用 Protel 99 SE 改一块车充 PCB Protel 99 SE N 多年前用过,之前就再没有碰过了. 今天由于特殊原因又使用了一下. 还好有些有印象,现记录一下以免忘记. 元件转向 ...
- javaMail邮件接收解析内容及附件 及删除邮件
参考自: http://blog.csdn.net/xyang81/article/details/7675160 package com.szy.project.utils; import jav ...