引出闭包案例
for(var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i); // ?
});
}
console.log('a');
// 结果是什么:?
2.事件轮询机制(Event Loop)
事件轮询(Event Loop)是一个很重要的概念,指的是计算机系统的一种运行机制。JavaScript语言就是采用的这种机制,来解决单线程运行带来的一些问题。
-
同步和异步任务分别进入不通的执行“场所”,同步的进入主线程,异步的进入Event Table并注册函数
-
当指定的事情完成时,Event Table会将这个函数移入Event Queue(事件队列/任务队列/消息队列)
-
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行
-
上述过程会不断重复,也就是常说的Event loop(事件循环)
进程:
-
进程是指在系统中正在运行的一个应用程序,程序一旦运行就是进程。
-
一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。
线程:
-
线程是进程的一个实体,是进程的一条执行路径。
-
线程是CPU独立运行和独立调度的基本单位。
任务队列:
-
将要被执行的任务,存放在浏览器的任务队列中
单线程:
-
每次只能干一件事
多线程:
-
多线程是指从软件或者硬件上实现多个线程的并发技术
-
优势:
-
使用多线程可以把程序中占据时间长的任务放到后台去处理,如图片、视屏的下载
-
发挥多核处理器的优势,并发执行让系统运行的更快、更流畅,用户体验更好
-
举例:你早上上班,正要打卡的时候,手机响了。。你如果先接了电话,等接完了,在打卡,就是单线程。 如果你一手接电话,一手打卡, 就是多线程。 2件事的结果是一样的。。你接了电话且打了卡。
事件轮询:
-
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)
2.1 JS是单线程还是多线程
javaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。 为什么不允许js可以实现多线程?因为如果实现了多线程,一个线程创建了一个div元素,而另外一个线程删除了这个div元素,那么这个时候浏览器应该听谁的? 所以为了避免出现这种互相冲突的操作,js从一开始就是单线程的,这就是它的核心特征。 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
2.2 任务队列
2.2.1 什么是宏任务,什么是微任务
在js的异步任务中,分为宏任务和微任务
宏任务(由宿主也就是浏览器和node引发): setTimeout,setInterval,Ajax(网络请求),DOM事件
微任务(js引擎发布的任务): Promise async/await promise本身是同步的,then/catch的回调函数是异步的
注意:微任务比宏任务的执行时间要早
2.2.2 宏任务和微任务的执行顺序
-
同步代码
-
微任务的异步代码(promise等)
-
宏任务的异步代码(setTimeout,setInterval等)
执行顺序
2.2.3 案例
console.log(1);
setTimeout(function(){
console.log(2);
},100)
let pro = new Promise((resolve,reject)=>{
console.log(3)
resolve(1000);
console.log(4)
})
pro.then(data=>{
console.log(data);
})
console.log(5); // 1 3 4 5 1000 2
2.3 解决方案
-
使用闭包
for(var i = 0; i < 5; i++) {
//这个匿名函数生成了闭包的效果,新建了一个作用域,这个作用域接收到每次循环的i值保存了下来,即使循环结束,闭包形成的作用域也不会被销毁
(function(i) {
setTimeout(function () {
console.log(i);
});
})(i)
}
console.log('a');
-
使用let变量
for(let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
});
}
3.闭包
特性:
-
函数嵌套函数
-
函数内部可以引用函数外部的参数和变量
-
参数和变量不会被垃圾回收机制回收【延长变量作用域】
闭包是js开发惯用的技巧:能够访问另一个函数作用域的变量的函数
清晰的讲:闭包就是一个函数,这个函数能够访问其他函数的作用域中的变量 闭包可以用在许多地方。它的最大用处有两个
-
一个是前面提到的可以读取函数内部的变量
-
另一个就是让这些变量的值始终保持在内存中。
function func1(){
var num = 1;
function func2(){
return num;
}
return func2; // 闭包
}
var num = func1()();
console.log(num);
注意:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
3.1 闭包的作用
闭包可以延长作用域链
function person(name){
var name = name;
function sayName(){
console.log(name)
}
return sayName;
}
var fun = person('王小明');
fun();// 王小明
fun 是一个 全局函数,但可以访问到
person里的
局部变量 name,这是因为
fun的值是从
person函数中返回的
sayName函数,而sayName 函数是可以访问到局部变量 name 的;
3.2 复杂闭包案例分析
function outer(){
// 局部变量
var a = '我是outer里面的变量';
// var c = 'one'
// console.log(a);
function twoinner(){
var b = '我是twoinner的变量';
var c = 'two'
// console.log(b);
return a;
// function thrinner(){
// var c = '我是thrinner';
// console.log(c);
// }
// thrinner();
// return thrinner;
}
return twoinner; // 函数体: twoinner函数就是一个闭包, 因为它能够访问到其他函数中的变量的作用域
}
// console.log(outer());
// console.log(outer()());
// outer()()();
var n = outer()();
console.log(n);
3.3 闭包能够解决的问题
问题:给下面5个li添加点击事件,点击显示当前元素的下标
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
var oli = document.getElementsByTagName('li');
/* 输出的全都是5 */
for(var i=0;i<oli.length;i++){
oli[i].onclick = function(){
console.log(i);
}
}
</script>
使用闭包解决
/* 闭包方案解决: */
for(var i=0;i<oli.length;i++){
oli[i].onclick = (function(i){
return function(){
console.log(i);
}
})(i); // 闭包
}
使用let解决
// let 方案解决
for(let i=0;i<oli.length;i++){
oli[i].onclick = function(){
console.log(i);
}; // 闭包
}
分析执行过程
/* 分析执行过程 */
oli[0].onclick = (function(0){
return function(){
console.log(0);
}
})(0);
// => 第一次循环 闭包函数自执行完毕
oli[0].onclick = function(){
console.log(0);
};
3.4 闭包有三个特性:
-
函数嵌套函数
-
函数内部可以引用外部的参数和变量
-
参数和变量不会被垃圾回收机制回收
-
自执行函数
(function a(){alert('我是自执行函数')} ()); // 用括号把整个表达式包起来
(function a(){alert('我是自执行函数')}) (); //用括号把函数包起来
!function a(){alert('我是自执行函数')}(); // 求反,我们不在意值是多少,只想通过语法检查。
+function a(){alert('我是自执行函数')}();
-function a(){alert('我是自执行函数')}();
~function a(){alert('我是自执行函数')}();
void function a(){alert('我是自执行函数')}();
new function a(){alert('我是自执行函数')}();
自执行函数通常都是定义之后立即执行,以后都不再会调用,所以声明时可以省略函数名,因此自执行函数又叫匿名函数
。
(function(){console.log('我是匿名函数')})();// 我是匿名函数
如果上一行代码没有使用 分号 ' ; '结束,可能会导致匿名函数通不过语法检查,所以通常会在小括号前加个分号
;(function(){console.log('我是匿名函数')})()// 我是匿名函数
4.1自执行函数的效果
自执行函数可以用来保存变量的作用域,防止污染全局变量