前端面试题(执行上下文与执行上下文栈作用域与作用域链闭包内存溢出与内存泄露)
Posted 小小白学计算机
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端面试题(执行上下文与执行上下文栈作用域与作用域链闭包内存溢出与内存泄露)相关的知识,希望对你有一定的参考价值。
0、执行上下文与执行上下文栈
0.1 变量提升与函数提升
- 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)
- 函数提升: 在函数定义语句之前, 就执行该函数
- 先有变量提升, 再有函数提升
0.2 如何理解变量提升与函数提升?
- 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
- 执行上下文个数: n(调用了几次函数) + 1
- 全局执行上下文
- 函数执行上下文
0.3 执行上下文栈: 用来管理产生的多个执行上下文
- 分类:
- 全局: window
- 函数: 对程序员来说是透明的
- 生命周期
- 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
- 函数 : 调用函数时产生, 函数执行完时死亡
- 包含哪些属性:
- 全局 :
- 用var定义的全局变量 ==>undefined
- 使用function声明的函数 ===>function
- this ===>window
- 函数
- 用var定义的局部变量 ==>undefined
- 使用function声明的函数 ===>function
- this ===> 调用函数的对象, 如果没有指定就是window
- 形参变量 ===>对应实参值
- arguments ===>实参列表的伪数组
- 全局 :
- 执行上下文创建和初始化的过程
- 全局:
- 在全局代码执行前最先创建一个全局执行上下文(window)
- 收集一些全局变量, 并初始化
- 将这些变量设置为window的属性
- 函数:
- 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
- 收集一些局部变量, 并初始化
- 将这些变量设置为执行上下文的属性
- 全局:
0.3.1 执行上下文栈面试题:
0.3.2 变量提升和函数提升的面试题:
先执行变量提升, 再执行函数提升
一、作用域与作用域链
- 理解:
-
作用域: 一块代码区域, 在编码时就确定了, 不会再变化
-
作用域个数:n(定义了几个函数) + 1
-
-
作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量
-
分类:
- 全局
- 函数
- js没有块作用域(在ES6之前)
-
作用
- 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
- 作用域链: 查找变量
-
区别作用域与执行上下文
- 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
- 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
- 联系: 执行上下文环境是在对应的作用域中的
二、闭包
2.1 闭包的理解:
- 当嵌套的内部函数引用了外部函数的变量时就产生了闭包
- 通过chrome工具得知: 闭包本质是
内部函数中的一个对象
,这个对象中包含引用的变量属性
function fn1() {
var a = 2
var b = 'zep'
return function fn2() {
console.log(a)
}
}
var f = fn1()
f()
2.2 闭包的作用:
-
延长局部变量的生命周期
-
让函数外部能操作内部的局部变量
-
写一个闭包程序
function fn1() {
var a = 2;
function fn2() {
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f();
f();
2.3 常见的闭包
// 常见的闭包:
/* // 1. 将函数作为另一个函数的返回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
// 闭包函数(即fn2())创建了几个?
// 看外部函数执行了几次,即fn1()执行了几次 */
// 2. 将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
setTimeout(function() {
alert(msg)
}, time)
}
showDelay('zep', 2000)
2.4 闭包的生命周期:
2.5 闭包应用:
-
模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为
方式一:
方式二:
-
循环遍历加监听
// 需求: 点击某个按钮,提示“点击的是第n个按钮”
/* var btns = document.getElementsByTagName('button')
// 遍历加监听
for(var i = 0, length = btns.length; i < length; i++) {
var btn = btns[i]
// 将btn所对应的下标保存到btn上
btn.index = i
btn.onclick = function() {
alert('第' + (this.index + 1) + '个')
}
} */
var btns = document.getElementsByTagName('button')
// 遍历加监听
for(var i = 0, length = btns.length; i < length; i++) {
// 利用闭包
(function(i) {
var btn = btns[i]
btn.onclick = function() {
alert('第' + (i + 1) + '个')
}
})(i)
}
- JS框架(jQuery)大量使用了闭包
2.6 闭包的缺点:
- 变量占用内存的时间可能会过长
- 可能导致
内存泄露
(该释放的没有及时释放) - 解决:
- 及时释放 : f = null; //让内部函数对象成为垃圾对象
如何判断是否存在闭包?
需要满足以下三点
:
- 存在函数嵌套
- 子函数中引用(使用)了父函数中定义的变量
- 父函数被调用执行了
例如:下面代码中,
因为闭包可以使得n变量一直存在内存中,而且有a变量指向fun(0)函数的返回值(返回值也是一个函数,函数中存在一个闭包,闭包是一个对象,对象中包含引用的变量属性即n变量
),则返回值就不会被当作是垃圾变量来自动销毁,这样就存在一个问题:闭包可能会导致内存泄漏
2.7 闭包面试题
//代码片段一
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
return function() {
return this.name;
};
}
};
console.log(object.getNameFunc()()); //? the window
//代码片段二
var name2 = "The Window";
var object2 = {
name2: "My Object",
getNameFunc: function() {
var that = this;
return function() {
return that.name2;
};
}
};
console.log(object2.getNameFunc()()); //? my object
三、内存溢出与内存泄露
- 内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
- 内存泄露
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
- 常见的内存泄露:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
以上是关于前端面试题(执行上下文与执行上下文栈作用域与作用域链闭包内存溢出与内存泄露)的主要内容,如果未能解决你的问题,请参考以下文章