![](https://img.kancloud.cn/39/6e/396eb6fba29c47803f3524c342d288b3_469x314.png)
![](https://img.kancloud.cn/eb/4e/eb4e42959777480fdab8a4a3a591293f_683x129.png)
![](https://img.kancloud.cn/08/c2/08c2db58fa6d69facbfb50727b0901d8_616x417.png)
![](https://img.kancloud.cn/f4/83/f4831954354850936d1619e16f73fb5d_721x269.png)
![](https://img.kancloud.cn/3d/c3/3dc39a93cc5bb4df6ac7eb62721e935a_844x292.png)
![](https://img.kancloud.cn/26/25/2625d02a2ae6f426b45e4d7de6aef645_763x226.png)
作用域和闭包是前端面试中,最可能考查的知识点。例如下面的题目:
> 题目:现在有个 HTML 片段,要求编写代码,点击编号为几的链接就`alert`弹出其编号
~~~
<ul>
<li>编号1,点击我请弹出1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
~~~
一般不知道这个题目用闭包的话,会写出下面的代码:
~~~
var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i++) {
list[i].addEventListener('click', function(){
alert(i + 1)
}, true)
}
~~~
实际上执行才会发现始终弹出的是`6`,这时候就应该通过闭包来解决:
~~~
var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i++) {
list[i].addEventListener('click', function(i){
return function(){
alert(i + 1)
}
}(i), true)
}
~~~
要理解闭包,就需要我们从「执行上下文」开始讲起。
### 执行上下文
先讲一个关于**变量提升**的知识点,面试中可能会遇见下面的问题,很多候选人都回答错误:
> 题目:说出下面执行的结果(这里笔者直接注释输出了)
~~~
console.log(a) // undefined
var a = 100
fn('zhangsan') // 'zhangsan' 20
function fn(name) {
age = 20
console.log(name, age)
var age
}
console.log(b); // 这里报错
// Uncaught ReferenceError: b is not defined
b = 100;
~~~
在一段 JS 脚本(即一个`<script>`标签中)执行之前,要先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个**全局执行上下文**环境,先把代码中即将执行的(内部函数的不算,因为你不知道函数何时执行)变量、函数声明都拿出来。变量先暂时赋值为`undefined`,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。再次强调,这是在代码执行之前才开始的工作。
我们来看下上面的面试小题目,为什么`a`是`undefined`,而`b`却报错了,实际 JS 在代码执行之前,要「全文解析」,发现`var a`,知道有个`a`的变量,存入了执行上下文,而`b`没有找到`var`关键字,这时候没有在执行上下文提前「占位」,所以代码执行的时候,提前报到的`a`是有记录的,只不过值暂时还没有赋值,即为`undefined`,而`b`在执行上下文没有找到,自然会报错(没有找到`b`的引用)。
另外,一个函数在执行之前,也会创建一个**函数执行上下文**环境,跟**全局上下文**差不多,不过**函数执行上下文**中会多出`this``arguments`和函数的参数。参数和`arguments`好理解,这里的`this`咱们需要专门讲解。
总结一下:
* 范围:一段`<script>`、js 文件或者一个函数
* 全局上下文:变量定义,函数声明
* 函数上下文:变量定义,函数声明,`this`,`arguments`
### `this`
先搞明白一个很重要的概念 ——**`this`的值是在执行的时候才能确认,定义的时候不能确认!**为什么呢 —— 因为`this`是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候。看如下例子
~~~
var a = {
name: 'A',
fn: function () {
console.log(this.name)
}
}
a.fn() // this === a
a.fn.call({name: 'B'}) // this === {name: 'B'}
var fn1 = a.fn
fn1() // this === window
~~~
`this`执行会有不同,主要集中在这几个场景中
* 作为构造函数执行,构造函数中
* 作为对象属性执行,上述代码中`a.fn()`
* 作为普通函数执行,上述代码中`fn1()`
* 用于`call``apply``bind`,上述代码中`a.fn.call({name: 'B'})`
下面再来讲解下什么是作用域和作用域链,作用域链和作用域也是常考的题目。
> 题目:如何理解 JS 的作用域和作用域链
### 作用域
ES6 之前 JS 没有块级作用域。例如
~~~
if (true) {
var name = 'zhangsan'
}
console.log(name)
~~~
从上面的例子可以体会到作用域的概念,作用域就是一个独立的地盘,让变量不会外泄、暴露出去。上面的`name`就被暴露出去了,因此,**JS 没有块级作用域,只有全局作用域和函数作用域**。
~~~
var a = 100
function fn() {
var a = 200
console.log('fn', a)
}
console.log('global', a)
fn()
~~~
![](https://img.kancloud.cn/34/a4/34a4ef0ad0a122d9b6d642f6ce12e7b6_251x147.png)
全局作用域就是最外层的作用域,如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样的坏处就是很容易撞车、冲突。
~~~
// 张三写的代码中
var data = {a: 100}
// 李四写的代码中
var data = {x: true}
~~~
这就是为何 jQuery、Zepto 等库的源码,所有的代码都会放在`(function(){....})()`中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。这是函数作用域的一个体现。
附:ES6 中开始加入了块级作用域,使用`let`定义变量即可,如下:
~~~
if (true) {
let name = 'zhangsan'
}
console.log(name) // 报错,因为let定义的name是在if这个块级作用域
~~~
### 作用域链
首先认识一下什么叫做**自由变量**。如下代码中,`console.log(a)`要得到`a`变量,但是在当前的作用域中没有定义`a`(可对比一下`b`)。当前作用域没有定义的变量,这成为**自由变量**。自由变量如何得到 —— 向父级作用域寻找。
~~~
var a = 100
function fn() {
var b = 200
console.log(a)
console.log(b)
}
fn()
~~~
如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是**作用域链**。
~~~
var a = 100
function F1() {
var b = 200
function F2() {
var c = 300
console.log(a) // 自由变量,顺作用域链向父作用域找
console.log(b) // 自由变量,顺作用域链向父作用域找
console.log(c) // 本作用域的变量
}
F2()
}
F1()
~~~
### 闭包
讲完这些内容,我们再来看一个例子,通过例子来理解闭包。
~~~
function F1() {
var a = 100
return function () {
console.log(a)
}
}
var f1 = F1()
var a = 200
f1()
~~~
![](https://img.kancloud.cn/6a/bb/6abbde1359c0f513765e563dda6dd7df_261x150.png)
自由变量将从作用域链中去寻找,但是**依据的是函数定义时的作用域链,而不是函数执行时**,以上这个例子就是闭包。闭包主要有两个应用场景:
* **函数作为返回值**,上面的例子就是
* **函数作为参数传递**,看以下例子
~~~
function F1() {
var a = 100
return function () {
console.log(a)
}
}
function F2(f1) {
var a = 200
console.log(f1())
}
var f1 = F1()
F2(f1)
~~~
![](https://img.kancloud.cn/4b/ea/4bea5fbd9089c2c6183829ad72b70229_289x195.png)
至此,对应着「作用域和闭包」这部分一开始的点击弹出`alert`的代码再看闭包,就很好理解了。
- 文档说明
- 大厂面试题
- HTML
- 001.如何遍历一个dom树
- 002.为什么操作DOM会很慢
- 003.浏览器渲染HTML的步骤
- 004.DOM和JavaScript的关系
- JS
- 001.数组扁平化并去重排序
- 002.高阶函数
- 003.sort() 对数组进行排序
- 004.call 、 apply 和bind的区别
- 006.0.1+0.2为什么等于0.30000000000000004
- 011.var、let、const 的区别及实现原理?
- 010.new操作符都做了什么
- 009.a.b.c.d 和 a['b']['c']['d'],哪个性能更高?
- 016.什么是防抖和节流?有什么区别?如何实现?
- 017.['1', '2', '3'].map(parseInt) what & why ?
- 018.为什么 for 循环嵌套顺序会影响性能?
- 019.介绍模块化发展历程
- 020.push输出问题
- 021.判断数组的三个方法
- 022.全局作用域中,用 const 和 let 声明的变量不在 window 上,那到底在哪里?如何去获取?
- 023.输出以下代码的执行结果并解释为什么
- 024.ES6 代码转成 ES5 代码的实现思路是什么
- 025.为什么普通 for 循环的性能远远高于 forEach 的性能,请解释其中的原因。
- 026.数组里面有10万个数据,取第一个元素和第10万个元素的时间相差多少
- 027.变量类型
- 028.原型和原型链
- 029.作用域和闭包
- 030. 异步
- 031.ES6/7 新标准的考查
- 024.事件冒泡/事件代理
- 025.手写 XMLHttpRequest 不借助任何库
- 026.什么是深拷贝?
- 0027.克隆数组的方法
- 0028.ES6之展开运算符(...)
- 0029.arguments
- 0030. requestAnimationFrame
- 0031.递归爆栈问题与解决
- 021.简单改造下面的代码,使之分别打印 10 和 20
- 032.箭头函数与普通函数
- 033.去除掉html标签字符串里的所有属性
- 034.查找公共父节点
- 035.Promise
- 0036.JSON.stringify ()
- CSS
- 001. BFC
- 002.介绍下 BFC、IFC、GFC 和 FFC
- 003.分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景
- 004.怎么让一个 div 水平垂直居中
- 005.重排重绘
- 006.inline/block/inline-block的区别
- 007.选择器的权重和优先级
- 008.盒模型
- 009.清除浮动
- 010.flex
- 011.nth-child和nth-of-type的区别
- 0012.overflow
- 0013.CSS3中translate、transform和translation的区别和联系
- 0014.flex
- 0015.px、em、rem
- 0016.width:100%
- 网络
- 001.讲解下HTTPS的工作原理
- 002.介绍下 HTTPS 中间人攻击
- 003.谈谈你对TCP三次握手和四次挥手的理解
- 004.A、B 机器正常连接后,B 机器突然重启,问 A 此时处于 TCP 什么状态
- 005.简单讲解一下http2的多路复用
- 006. 介绍下 http1.0、1.1、2.0 协议的区别?
- 007.永久性重定向(301)和临时性重定向(302)对 SEO 有什么影响
- 008.URL从输入到页面展示的过程
- 009.接口如何防刷
- 010.http状态码?
- 0111.跨域/如何解决?
- 012.cookie 和 localStorage 有何区别?
- 013.Fetch API
- 014.跨域Ajax请求时是否带Cookie的设置
- 0015.协商缓存和强缓存
- 性能优化
- 001.前后端分离的项目如何seo
- 002.性能优化的方法
- 003.防抖和节流
- React
- 001.React 中 setState 什么时候是同步的,什么时候是异步的?
- 002.Virtual DOM 真的比操作原生 DOM 快吗?谈谈你的想法。
- 003.Hooks 的特别之处
- 004.元素和组件有什么区别?
- 005.什么是 Pure Components?
- 006.HTML 和 React 事件处理有什么区别?
- 007.如何将参数传递给事件处理程序或回调函数?
- 008.如何创建 refs?
- 009.什么是 forward refs?
- 010.什么是 Virtual DOM?
- 011.什么是受控组件、非受控组件?
- 012.什么是 Fragments ?
- 013.为什么React元素有一个$$typeof属性?
- 014.如何在 React 中创建组件?
- 015.React 如何区分 Class 和 Function?
- 016.React 的状态是什么?
- 017.React 中的 props 是什么?
- 018.状态和属性有什么区别?
- 019.如何在 JSX 回调中绑定方法或事件处理程序?
- 020.什么是 "key" 属性,在元素数组中使用它们有什么好处?
- 021.为什么顺序调用对 React Hooks 很重要?
- 022.setState如何知道该做什么?
- 023.hook规则?
- 024.Hooks 与 Class 中调用 setState 有不同的表现差异么?
- 025.useEffect
- 026.fiber的作用
- 027.context的作用?
- 028.setState何时同步何时异步?
- 029.react性能优化
- 030.fiber
- 031.React SSR
- 异步
- 001.介绍下promise
- 002.Async/Await 如何通过同步的方式实现异步
- 003.setTimeout、Promise、Async/Await 的区别
- 004.JS 异步解决方案的发展历程以及优缺点
- 005.Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?
- 006.模拟实现一个 Promise.finally
- 012.简单手写实现promise
- 015.用Promise对象实现的 Ajax
- 007.简单实现async/await中的async函数
- 008.设计并实现 Promise.race()
- 009.Async/await
- 010.珠峰培训promise
- git
- 001.提交但没有push
- 002.gitignore没有作用?
- Node
- 001.用nodejs,将base64转化成png文件
- Koa
- 001.koa和express的区别
- 数据库
- redux
- 001.redux 为什么要把 reducer 设计成纯函数
- 002.在 React 中如何使用 Redux 的 connect() ?
- 003.mapStateToProps() 和 mapDispatchToProps() 之间有什么区别?
- 004.为什么 Redux 状态函数称为 reducers ?
- 005.如何在 Redux 中发起 AJAX 请求?
- 006.访问 Redux Store 的正确方法是什么?
- 007.React Redux 中展示组件和容器组件之间的区别是什么?
- 008.Redux 中常量的用途是什么?
- 009.什么是 redux-saga?
- 设计模式
- 公司题目
- 001.饿了么
- 001.div垂直水平居中(flex、绝对定位)
- 002.React子父组件之间如何传值
- 003.Emit事件怎么发,需要引入什么
- 004.介绍下React高阶组件,和普通组件有什么区别
- 005.一个对象数组,每个子对象包含一个id和name,React如何渲染出全部的name
- 006.在哪个生命周期里写
- 007.其中有几个name不存在,通过异步接口获取,如何做
- 008.渲染的时候key给什么值,可以使用index吗,用id好还是index好
- 009.webpack如何配sass,需要配哪些loader
- 010.配css需要哪些loader
- 011.如何配置把js、css、html单独打包成一个文件
- 012.监听input的哪个事件,在什么时候触发
- 013.两个元素块,一左一右,中间相距10像素
- 014.上下固定,中间滚动布局如何实现
- 016.取数组的最大值(ES5、ES6)
- 017.apply和call的区别
- 018.ES5和ES6有什么区别
- 019.some、every、find、filter、map、forEach有什么区别
- 020.上述数组随机取数,每次返回的值都不一样
- 021.如何找0-5的随机数,95-99呢
- 022.页面上有1万个button如何绑定事件
- 023.如何判断是button
- 024.页面上生成一万个button,并且绑定事件,如何做(JS原生操作DOM)
- 025.循环绑定时的index是多少,为什么,怎么解决
- 026.页面上有一个input,还有一个p标签,改变input后p标签就跟着变化,如何处理
- 浏览器相关
- 001.性能优化
- 002.web安全
- 003.获取浏览器大小
- 004.从输入 URL 到页面加载完成的过程中都发生了什么事情?
- 后端
- 001.分布式
- zuku
- 字节
- webpack
- webpack的打包原理是什么
- Webpack-- 常见面试题
- webscoket