JS之作用域链以及变量的声明提升
JavaScript的作用域
- 在Java,c等语言当中,作用域为for语句、if语句或{}内的一块区域,成为作用域
- 而在JavaScript中,作用域为
function(){}
的区域,称为函数作用域,JS主要通过函数划分作用域 - JS中的作用域又分为
局部作用域
和全局作用域
- 全局作用域:在script顶级写的变量和函数都是全局的,在外部文件顶级位置属性的变量和函数也是全局的。
<script>
var num=80 /*这是全局作用域*/
alert('num在外面输出的为'+num) /*调用了全局作用域的num 值为80*/
function func(){
alert('num在里面输出的为'+num)
}
func()
</script>
- 局部作用域:函数内部是独立的作用域
函数内的形参本质也是局部变量
函数的形参也只能在函数内访问,函数的外部我们无法访问
<script>
var num=80
alert('num在外面输出的为'+num)
function func(){
var num =100 /*这是局部作用域*/
alert('num在里面输出的为'+num) /*调用了局部作用域的num 值为100*/
}
func()
</script>
JavaScript的变量声明提升
JS中有变量声明这一个机制,当js在执行的时候,会分为两个阶段,一个是预解析,一个是执行,js预解析的时候会将所有用var声明的变量以及函数声明提升到当前作用域的最顶端,而赋值的语句在原地等待执行,预解析之后,再从上往下地逐行解析代码。
- 平时我们写的
var num=100
;虽然代码只有1行,但是js会分成2个部分进行读取
//声明变量,并提升到作用域的最前面
var num;
//在原位进行赋值
num=100;
- 我们写的代码,以为是从上往下执行的
var num =100
function func(){
console.log("第一次输出",num) //第一次输出 undefined
var num=200
console.log("第二次输出",num) //第二次输出 200
}
func()
- 其实js真正的运行状态如下所示
- 先把var 和function 声明预解析给提前了,再执行,而不是按照书写顺序从上往下执行
var num;
function func(){
var num;
console.log("第一次输出",num) //第一次输出 undefined
num=200
console.log("第二次输出",num) //第二次输出 200
}
num=100;
func();
注意:
- 在JavaScript中,
函数声明与变量声明
经常被JavaScript引擎隐式地提升到当前作用域的顶部 - 声明语句中的赋值部分并不会被提升,只有
名称
被提升了 - 预解析的过程调试代码的时候断点并不会经过,因为断点调试的时候,预解析的代码已经运行完毕了
- 函数声明的优先级会高于变量,如果变量名与函数名相同且未被赋值,则函数的声明会覆盖变量的声明
- 如果函数有多个同名的参数,那么最后一个参数(即使没有定义)也会覆盖前面的同名参数
作用域链
作用域链:就是变量生效的范围和查找的规则
//全局变量 0级链
var num=123
function func1(){
// 1级链
var num=456
function fun2(){
//2级链
var num=789
}
}
练习代码
var num=111;
function func1(){
var num=222;
console.log(num) ; /*num=????*/
function func2(){
num=333;
console.log(num); /*num=????*/
}
func2()
console.log(num) /*num=????*/
}
console.log(num) /*num=????*/
func1()
答案:
//0级链
var num=111;
function func1(){
//1级链
var num=222;
console.log(num) ; /*num=222*/
function func2(){
//2级链
num=333; /*这里直接覆盖了1级链的内容*/
console.log(num); /*num=333*/
}
func2()
console.log(num) /*num=333*/
}
console.log(num) /*num=111*/
func1()
- 函数内部优先访问函数内部的局部变量
- 如果函数内部没有,就会往上一层作用域查找
- 如果查找到了全局作用域,如果全局作用域都没有,就会报错xxx is not defined ,xxx变量没有定义。
总结:就近原则
函数内部声明的变量,只能给函数内部来使用
为了解决预解析的问题
可以使用let变量和const变量两个新语法
var
-声明变量,所有的浏览器都支持,但是小问题交多,比如预解析问题,变量名和函数名重名覆盖问题- Es6中新增了
let
声明变量和const
声明常量,谷歌支持,IE8不支持 let
和const
并不会参与预解析,只有var
有预解析
let变量和const变量
let
:变量可以先进行声明,后赋值,或者声明的时候同时赋值。好处是–不参与预解析,代码从上往下按书写顺序执行,生成一个块级作用域。
const
:常量声明的时候必须要进行赋值,赋值后值就不能被修改,修改的时候浏览器会出现报错。好处是防止未知的覆盖冲突,因为变量的覆盖没有任何的提示。