目录
执行上下文栈
- 在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
- 在函数执行上下文创建后,将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后,栈中只剩下window
var a=10
var bar=function(x){
var b=5
foo(x+b)
}
var foo=function(y){
var c=5
console.log(a+c+y);
}
bar(10)
测试
console.log('gb'+i);
var i=1
foo(1)
function foo(i){
if(i==4){
return
}
console.log('fb'+i);
foo(i+1)//递归调用:在函数内部调用自己
console.log('fe'+1);
}
console.log('ge'+i);
输出结果:
面试题
1.
function a(){}
var a;
console.log(typeof a);
注意:先执行变量提升,后执行函数提升。因此,typeof a为‘function’,而不是undefined
2.
if(!(b in window)){
var b=1
}
console.log(b);
在变量中,var会变量提升。所以,b定义了但没有赋值。在if语句中,if语句不管前置条件达不达到,后置语句中的变量会声明提升,所以结果为undefined
3.
var c=1;
function c(c){
console.log(c);
var c=3
}
c(2)//报错
注意:在提升之前,函数已经执行完了。因此,可以看成以下代码
var c
function c(c){
console.log(c);
}
c=1
c(2)
作用域
1.理解
- 就是一块“地盘”,一个代码所在的区域
- 它是静态的(相对于上下文对象),在编写代码时就确定了
2.分类
- 全局作用域
- 函数作用域
- 没有块作用域(ES6有了)
3.作用
- 隔离变量,不同作用域下同名变量不会有冲突
作用域与执行上下文
区别1:
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后,JS代码马上执行创建
- 函数执行上下文是在调用函数时,函数体代码执行之前创建
区别2:
- 作用域是静态的,只要函数定义好了就一直处在,且不会再变化
- 执行上下文是动态的,调用函数时创建,函数调用结束时就会自动释放
联系
- 执行上下文(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数作用域
作用域链
面试题
var x=10
function fn(){
console.log(x);
}
function show(f){
var x=20
f()
}
show(fn)
由作用域划分可得以下,fn()会向上找x,因此为x=10
2.
var fn=function(){
console.log(fn);
}
fn();
在全局作用域里面找变量fn,所以输出为function(){console.log(fn); }
3.
var obj={
fn2:function(){
console.log(this.fn2);
console.log(fn2);
}
}
obj.fn2()
调用obj中的fn2属性,属性的内容为function,输出this.fn2是在obj中找到fn2.function(){console.log(this.fn2);console.log(fn2);} 注意fn2是属性,不是变量,在查找fn2时,window里面没有fn2这个属性,就会报错
闭包
引入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script>
window.onload = function () {
var btns = document.getElementsByTagName("button");
// 遍历加监听
for (var i = 0, length = btns.length; i < length; i++) {
var btn = btns[i];
btn.index = i;
btn.onclick = function () {
alert("第" + (this.index+1) + "个");
//alert("第"+(i+1)+"个");
};
}
for(var i=0,length=btns.length;i<length;i++){
(
function (i){
var btn=btns[i]
btn.onclick=function(){
alert('第'+(i+1)+'个')
}
}
)(i)
}
};
</script>
</head>
<body>
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
</body>
</html>
问:alert("第"+(i+1)+"个");为什么执行出来是i=3?
答:因为事件的响应函数是点击的时候才触发调用的,在点击之前只是声明了但并没有调用过响应函数
问:for (var i = 0, length = btns.length; i < length; i++)为什么不写var =0;i<btns.length;i++?
答:当循环执行一次,btns.length就需要重新计算,因此需要计算多次,这样会提高系统的复杂度。当令length=btns.length时,就只要计算一次。
1.如何产生闭包?
- 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
2.闭包是什么?
- (使用chrome调试查看)
- 理解1:闭包是嵌套的内部函数(绝大部分人)
- 理解2:包含被引用变量(函数)的对象(极少数人)
- 注意:闭包存在于桥套的内部函数中
3.产生闭包的条件?
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
常见的闭包
1.将函数作为另一个函数的返回值
function fn1(){
var a=2
function fn2(){
a++
console.log(a);
}
return fn2
}
var f=fn1()
f()//3
f()//4
2.将函数作为实参传递给另一个函数调用
function showDelay(msg,time){
setTimeout(function(){
alert(msg)
},time)
}
showDelay('atguigu',2000)
闭包的作用
- 使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部都可以操作(读写)到函数内部的数据(变量/函数)
问题:
1.函数执行完,函数内部声明的局部变量是否还存在?
- 一般是不存在的。存在于闭包中的变量才可能存在。fn1返回的是fn3的数据,fn3内部数据是地址值指向它在堆空间的对象,fn1将fn3的地址值赋值给了f,所以函数就算释放出栈了,f也能自己去访问到fn3的对象
2.在函数外部能直接访问函数内部的局部变量吗?
- 不能,但我们可以通过闭包让外部操作它
总结:闭包也是会被回收的,需要定义一个变量来一直指向闭包函数体的地址值,让闭包一直存在
闭包的生命周期
产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
死亡:在嵌套的内部函数称为垃圾对象时
function fn1(){
// 此时闭包就已经产生了(函数提升,内部函数对象已经创建了)
var a=2
function fn2(){
a++
console.log(a);
}
return fn2
}
var f=fn1()
f()//3
f()//4
f=null//闭包死亡(包含闭包的函数对象成为垃圾对象)
闭包的应用——自定义JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包含n个对象或函数
- 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="92.myModule.js"></script>
<script>
var module=myModule();
myModule.doSomething();
</script>
</head>
<body>
</body>
</html>
function myModule(){
// 私有数据
var msg='My atguigu'
// 操作数据的函数
function doSomething(){
console.log('doSomething()'+msg.toUpperCase());
}
function doOtherthing(){
console.log('doOtherthing()'+msg.toLowerCase());
}
// 向外暴露对象(给外部使用的方法)
return {
doSomething:doSomething,
doOtherthing:doOtherthing
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="92.myModule2.js"></script>
<script>
var myModule2=myModule2()
myModule2.doSomething();
</script>
</head>
<body>
</body>
</html>
(
function (){
// 私有数据
var msg='My atguigu'
// 操作数据的函数
function doSomething(){
console.log('doSomething()'+msg.toUpperCase());
}
function doOtherthing(){
console.log('doOtherthing()'+msg.toLowerCase());
}
window.myModule2={
doSomething:doSomething,
doOtherthing:doOtherthing
}
})()
闭包的缺点及解决
缺点
- 函数执行后,函数内的局部变量没有释放,占用内存事件会变长
- 容易造成内存泄漏
解决
- 能不用闭包就不用
- 及时释放
内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时,就抛出内存溢出的错误
内存泄漏
- 占用的内存没有被及时释放
- 内存泄漏积累多了就会内存溢出
常见的内存泄漏
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
// 内存泄漏
function fn(){
a=new Array(100000)
console.log(a);
}
fn()
var intervalId=setInterval(function(){
console.log('--------');
},1000)
// clearInterval(intervalId)
function fn1(){
var a=4
function fn2(){
console.log(++a);
}
return fn2;
}
var fn=fn1()
f()
// f=null
注意:this的指向,当以方法调用时this指向方法所在的对象,当以函数调用时,this就是全局