Web/03-JavaScript(二)/06-作用域和闭包.md
2018-03-15 23:14:46 +08:00

338 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 前言
面试问题:
- 说一下对变量提升的理解
- 说明this的几种不同的使用场景
- 创建10个`<a>`标签,点击的时候弹出来对应的序号
- 如何理解作用域
- 实际开发中闭包的应用
涉及到的知识点:
- 执行上下文
- this
- 作用域
- 作用域链
- 闭包
## 执行上下文
执行上下文主要有两种情况:
- 全局代码: 一段`<script>`标签里,有一个全局的执行上下文。所做的事情是:变量定义、函数声明
- 函数代码每个函数里有一个上下文。所做的事情是变量定义、函数声明、this、arguments
PS注意“函数声明”和“函数表达式”的区别。
### 全局执行上下文
在执行全局代码前将window确定为全局执行上下文。
1对全局数据进行预处理并没有赋值
- var定义的全局变量==>undefined, 添加为window的属性
- function声明的全局函数==>赋值(fun), 添加为window的方法
- this==>赋值(window)
2开始执行全局代码
![](http://img.smyhvae.com/20180311_1100.png)
### 函数执行上下文
在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)。
1对局部数据进行预处理
- 形参变量==>赋值(实参)==>添加为执行上下文的属性
- arguments==>赋值(实参列表), 添加为执行上下文的属性
- var定义的局部变量==>undefined, 添加为执行上下文的属性
- function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
- this==>赋值(调用函数的对象)
2开始执行函数体代码
### 执行上下文栈
- 1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
- 3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
- 4. 在当前函数执行完后,将栈顶的对象移除(出栈)
- 5. 当所有的代码执行完后, 栈中只剩下window
## this
this指的是**调用函数的那个对象**。
需要特别提醒的是this的指向在函数定义时无法确认只有函数执行时才能确定。
![](http://img.smyhvae.com/20180311_1117.png)
this的几种场景
- 1、作为构造函数执行
例如:
```javascript
function Foo(name) {
//this = {};
this.name = name;
//return this;
}
var foo = new Foo();
```
- 2、作为对象的属性执行
```javascript
var obj = {
name: 'A',
printName: function () {
console.log(this.name);
}
}
obj.printName();
```
- 3、作为普通函数执行
```javascript
function fn() {
console.log(this); //this === window
}
fn();
```
- 4、call apply bind
## 作用域
它是静态的(相对于上下文对象), 在编写代码时就确定了
作用域的分类:
- 全局作用域
- 函数作用域
- 没有块作用域(ES6有了)
作用:隔离变量,不同作用域下同名变量不会有冲突
```javascript
if (true) {
var name = 'smyhvae';
}
console.log(name);
```
上方代码中,并不会报错,因为:虽然 name 是在块里面定义的,但是 name 是全局变量。
### 作用域与执行上下文的区别
区别1
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
- 函数执行上下文是在调用函数时, 函数体代码执行之前创建
区别2
- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
- 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
联系:
* 执行上下文(对象)是从属于所在的作用域
* 全局上下文环境==>全局作用域
* 函数上下文环境==>对应的函数使用域
### 作用域链
外部函数定义的变量可以被内部函数所使用,反之则不行。
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
//只要是函数就可以创造作用域
//函数中又可以再创建函数
//函数内部的作用域可以访问函数外部的作用域
//如果有多个函数嵌套,那么就会构成一个链式访问结构,这就是作用域链
//f1--->全局
function f1(){
//f2--->f1--->全局
function f2(){
//f3---->f2--->f1--->全局
function f3(){
}
//f4--->f2--->f1---->全局
function f4(){
}
}
//f5--->f1---->全局
function f5(){
}
}
</script>
</head>
<body>
</body>
</html>
```
理解:
- 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
- 查找变量时就是沿着作用域链来查找的
查找一个变量的查找规则:
```javascript
var a = 1
function fn1() {
var b = 2
function fn2() {
var c = 3
console.log(c)
console.log(b)
console.log(a)
console.log(d)
}
fn2()
}
fn1()
```
- 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
- 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
- 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
## 闭包
闭包就是能够读取其他函数内部变量的函数。
只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
### 如何产生闭包
**当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量或函数时, 就产生了闭包。**
### 闭包到底是什么?
> 使用chrome调试查看
理解一: 闭包是嵌套的内部函数(绝大部分人)
理解二: 包含被引用变量 or 函数的对象(极少数人)
注意: 闭包存在于嵌套的内部函数中。
### 产生闭包的条件
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)。
## 常见的闭包
- 1. 将函数作为另一个函数的返回值
- 2. 将函数作为实参传递给另一个函数调用
### 闭包1将函数作为另一个函数的返回值