ES6 之前,js 只有两种作用域:
- 全局作用域
- 函数作用域
是没有 块级作用域的
全局作用域变量:
- 不是在函数内部定义的
- 未使用 var 声明的
函数作用域变量:
只要在函数中定义的变量,函数内部都可以访问到。即使是后定义的,表现为提升到函数顶部,所以函数任何位置都可以访问该变量
什么是 变量提升(hoisting):
因为变量申明是在任意代码执行前处理的(注意,只是声明),在代码区中任意地方申明变量和在最开始(最上面)的地方申明是一样的。也就是说,看起来一个变量可以在申明之前被使用!这种行为就是所谓的“hoisting”,也就是变量提升,看起来就像变量的申明被自动移动到了函数或全局代码的最顶上。
示例:
输出: undefined
解析:运行之前,先处理所有变量声明,全局变量会提升到文件开头,函数内声明变量会提升到函数开头。所以 var tmp = 'hello' 会将 var tmp 提升至函数开头,但是此刻并不会赋值。所以 console 输出的是 undefined
理解了上面,那么下面这个就好理解了:
所以,建议变量声明 全局或者函数最顶上,可读性好。
继续:
输出: 1 2 2.
变量被声明后,不会再次声明,相当于:
函数和变量同时声明:
输出: function foo(){}
如果改成下面形式:
输出: undefined
输出: ReferenceError, 因为调用的时候, foo 为 undefined
如果还有一个同名foo 函数放到第一个 foo 后面:
function foo () {
console.log('foo2')
}
输出: ‘foo2'
为什么?
函数提升分为两种情况:
- 函数声明: function foo(){}
- 函数表达式: var foo = function(){}
第二种就是声明一个 foo 变量,然后将一个匿名函数赋值给 foo, 和上面变量声明是一样的。所以输出 undefined
第一种会将函数声明整个提升到开头,相当于:
并且函数声明优先于变量声明,所以,输出: function foo(){}
同名的函数声明会使用最后一个
- 首先,解释器扫描Function Declarations,也就是function name{},解释器将对每个声明创建一个函数并作为Window的一个变量
- 解释器扫描var declarations,作为window的属性。但是此时变量并没有被赋值,所有的变量此时都是undefined。
要彻底理解JS的作用域和Hoisting,只要记住以下三点即可:
1、所有申明都会被提升到作用域的最顶上
2、同一个变量申明只进行一次,并且因此其他申明都会被忽略
3、函数声明的优先级优于变量申明,且函数声明会连带定义一起被提升
继续下面的示例:
输出: 'foo'
解析: 函数声明优先于变量声明,再次声明都被忽略。
下面输出什么?
输出结果:
先弹框:
然后输出 2
然后又弹框:
最后:
解析:
函数声明比表达式赋值优先级高,所以第一个alert 弹出 声明的函数
a() 输出 2
接下来解析到变量 a 被赋值 var a = function(){},虽然这个时候 a 已经被函数声明赋值,但是可以被后续的赋值覆盖掉,所以之后 a 的值是第一个函数
所以,接下来弹出 1
因为存在函数作用域,所以内部定义的变量,外部是访问不到的,所以报错 d 未定义
alert(c) 由于报错执行不到
解析:
a = null 将 function a() 置成 null 了,不再是函数了
let, const 都存在变量提升,但是有点不一样。后续会讲到。
关注我,每天更新技术干货!