Web/04-JavaScript基础/19-作用域和变量提升.md
2021-10-26 14:21:55 +08:00

283 lines
7.9 KiB
JavaScript
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.

---
title: 19-作用域和变量提升
publish: true
---
<ArticleTopAd></ArticleTopAd>
> 作用域变量提升的知识点面试时会经常遇到
## 作用域Scope的概念
- **概念**通俗来讲作用域是一个变量或函数的作用范围作用域在**函数定义**就已经确定了
- **目的**为了提高程序的可靠性同时减少命名冲突
### 作用域的分类
JS 一共有两种作用域ES6 之前
- 全局作用域作用于整个 script 标签内部或者作用域一个独立的 JS 文件
- 函数作用域局部作用域作用于函数内的代码环境
### 作用域的访问关系
在内部作用域中可以访问到外部作用域的变量在外部作用域中无法访问到内部作用域的变量
代码举例
```javascript
var a = 'aaa';
function foo() {
var b = 'bbb';
console.log(a); // 打印结果aaa。说明 内层作用域 可以访问 外层作用域 里的变量
}
foo();
console.log(b); // 报错Uncaught ReferenceError: b is not defined。说明 外层作用域 无法访问 内层作用域 里的变量
```
### 变量的作用域
根据作用域的不同变量可以分为两类全局变量局部变量
**全局变量**
- 在全局作用域下声明的变量全局变量在全局作用域的任何一地方都可以访问这个变量
- 在全局作用域下使用 var 声明的变量是全局变量
- 特殊情况在函数内不使用 var 声明的变量也是全局变量不建议这么用
**局部变量**
- 定义在函数作用域的变量局部变量
- 在函数内部使用 var 声明的变量是局部变量
- 函数的**形参**也是属于局部变量
从执行效率来看全局变量和局部变量
- 全局变量只有浏览器关闭时才会被销毁比较占内存
- 局部变量当其所在的代码块运行结束后就会被销毁比较节约内存空间
### 作用域的上下级关系
当在函数作用域操作一个变量时它会先在自身作用域中寻找如果有就直接使用**就近原则**如果没有则向上一级作用域中寻找直到找到全局作用域如果全局作用域中依然没有找到则会报错 ReferenceError
在函数中要访问全局变量可以使用window对象比如说全局作用域和函数作用域都定义了变量a如果想访问全局变量可以使用`window.a`
## 全局作用域
直接编写在script标签中的JS代码都在全局作用域
- 全局作用域在页面打开时创建在页面关闭时销毁
- 在全局作用域中有一个全局对象window它代表的是一个浏览器的窗口由浏览器创建我们可以直接使用
在全局作用域中
- 创建的**变量**都会作为window对象的属性保存比如在全局作用域内写 `var a = 100`这里的 `a` 等价于 `window.a`
- 创建的**函数**都会作为window对象的方法保存
### 变量的声明提前变量提升
使用var关键字声明的变量 比如 `var a = 1`**会在所有的代码执行之前被声明**但是不会赋值但是如果声明变量时不是用var关键字比如直接写`a = 1`则变量不会被声明提前
**举例1**
```javascript
console.log(a);
var a = 123;
```
打印结果undefined注意打印结果并没有报错而是 undefined说明变量 a 被提前声明了只是尚未被赋值
**举例2**
```javascript
console.log(a);
a = 123; //此时a相当于window.a
```
程序会报错`Uncaught ReferenceError: a is not defined`
**举例3**
```javascript
a = 123; //此时a相当于window.a
console.log(a);
```
打印结果123
**举例4**
```javascript
foo();
function foo() {
if (false) {
var i = 123;
}
console.log(i);
}
```
打印结果undefined注意打印结果并没有报错而是 undefined这个例子再次说明了变量 i 在函数执行前就被提前声明了只是尚未被赋值
例4中 `if(false)`里面的代码虽然不会被执行但是整个代码有**解析**的环节解析的时候就已经把 变量 i 给提前声明了
**总结**
既然JS中存在变量提升的现象那么在实战开发中为了避免出错建议先声明一个变量然后再使用这个变量
### 函数的声明提前
**函数声明**
使用`函数声明`的形式创建的函数`function foo(){}`**会被声明提前**
也就是说整个函数会在所有的代码执行之前就被**创建完成**所以在代码顺序里我们可以先调用函数再定义函数
代码举例
```javascript
fn1(); // 虽然 函数 fn1 的定义是在后面,但是因为被提前声明了, 所以此处可以调用函数
function fn1() {
console.log('我是函数 fn1');
}
```
**函数表达式**
使用`函数表达式`创建的函数`var foo = function(){}`**不会被声明提前**所以不能在声明前调用
很好理解因为此时foo被声明了这里只是变量声明且为undefined并没有把 `function(){}` 赋值给 foo
所以说下面的例子会报错
![](http://img.smyhvae.com/20180314_2145.png)
## 函数作用域
**提醒1**在函数作用域中也有声明提前的特性
- 函数中使用var关键字声明的变量会在函数中所有的代码执行之前被声明
- 函数中没有var声明的变量都是**全局变量**而且并不会提前声明
举例
```javascript
var a = 1;
function foo() {
console.log(a);
a = 2; // 此处的a相当于window.a
}
foo();
console.log(a); //打印结果是2
```
上方代码中执行foo()函数里面的打印结果是`1`如果去掉第一行代码执行foo()函数里面的打印结果是`Uncaught ReferenceError: a is not defined`
**提醒2**定义形参就相当于在函数作用域中声明了变量
```javascript
function fun6(e) { // 这个函数中,因为有了形参 e此时就相当于在函数内部的第一行代码里写了 var e;
console.log(e);
}
fun6(); //打印结果为 undefined
fun6(123);//打印结果为123
```
## JavaScript 没有块级作用域ES6之前
在其他编程语言中 JavaC#存在块级作用域`{}`包括起来比如在 Java 语言中if 语句里创建的变量只能在if语句内部使用
```java
if(true){
int num = 123;
system.out.print(num); // 123
}
system.out.print(num); // 报错
```
但是 JS 中没有块级作用域ES6之前举例如下
```javascript
if(true){
var num = 123;
console.log(123); //123
}
console.log(123); //123可以正常打印
```
## 作用域链
引入
- 只要是代码就至少有一个作用域
- 写在函数内部的局部作用域
- 如果函数中还有函数那么在这个作用域中就又可以诞生一个作用域
基于上面几条内容我们可以得出作用域链的概念
**作用域链**内部函数访问外部函数的变量采用的是链式查找的方式来决定取哪个值这种结构称之为作用域链查找时采用的是**就近原则**
代码举例
```javascript
var num = 10;
function fn() {
// 外部函数
var num = 20;
function fun() {
// 内部函数
console.log(num);
}
fun();
}
fn();
```
打印结果20
## 我的公众号
想学习<font color=#0000ff>**更多技能**</font>****id`qianguyihao`
扫一扫你将发现另一个全新的世界而这将是一场美丽的意外
![](http://img.smyhvae.com/2016040102.jpg)