JavaScript中的作用域
下面一个简单的问题:
<script>
var str1 = "hello";
var str2 = "world";
function t1() {
console.log(str1);
console.log(str2);
var str2 = "肥朝";
console.log(str2);
}
//这里会输出什么?
t1();
</script>
这是一个很简单的JS作用域问题,但是你越是强调简单
这两个字,就越容易使人放松警惕,所以导致有些同学不假思索的回答输出
- hello
- world
- 肥朝
但是结果是输出
- hello
- undefined
- 肥朝
那么这就奇怪了,为什么会有undefined
呢,不是应该是world
吗?首先我们要明白,变量的寻找会遵循就近原则
,所以js会先在函数中找,找不到才会向外找,而函数内有str2,但是运行到console.log(str2)
时str2未定义,所以就出现了undefined
##词法分析
知其然还必须其所以然,那么我们再来看几个例子
例子1
<script>
function t(userName) {
console.log(userName);//这里输出什么?
function userName() {
console.log('tom');
}
}
t('肥朝');
</script>
输出的结果是什么,这个例子好像和上面不一样,有种回到高中数学,题型一变就懵逼的感觉,这个时候可能有些同学会觉得是肥朝
,但是实际输出是
function userName() {
console.log('tom');
}
为什么是function呢?其实这种作用域的问题,都是可以通过"套公式"
来得出,这个公式,就是JS中的词法分析
,JS中函数执行前,必须要做的一项工作就是词法分析,那么究竟要什么什么呢?分析参数
,分析变量声明
,分析函数声明
,那么我们就拿这道题来套一下公式
执行t('肥朝')
的时候,会开始两个阶段,一个是分析阶段
,分析完就到执行阶段
分析阶段:
-
函数运行的瞬间,会生成一个
Active Object
对象(以下简称AO对象),一个函数作用域内能找到的所有变量,都在AO上,此时用代码表示为:t.AO = {}
-
分析参数: 接收参数,以参数名为属性,参数值为属性值,因为没有参数,因此分析结果用代码表示为:
t.AO = {userName : 肥朝}
-
分析var声明: t函数内没有var声明,略过
-
分析函数声明: 这个函数声明有个特点,AO上如果有与函数名同名的属性,则会被此函数覆盖,因为函数在JS领域,也是变量的一种类型,因此用代码表示为:
t.AO = { userName : function userName() {console.log('tom');}}
执行阶段:
执行t('肥朝')
的时候,当执行到console.log(userName)
时,就调用t.AO.userName
,所以,最后的输出结果是function userName() {console.log('tom');}
例子2
<script>
function t(userName) {
console.log(userName);//这里输出什么?
var userName = function () {
console.log('tom');
}
}
t('肥朝');
</script>
那这里的输出又是什么呢?这个好像又和上面的例子不一样,又再次陷入懵逼状态?别怕麻烦,坚定的按照公式再走一次流程(上面的的例子写得比较详细,下面的分析就简单写)
分析之前,首先要弄明白两个概念,一个叫函数声明
,一个叫函数表达式
//这个叫函数声明
function userName() {
console.log('tom');
}
//这个叫函数表达式
var userName = function () {
console.log('tom');
}
分析阶段:
-
创建AO对象,
t.AO = {}
-
分析参数:
t.AO = {userName : 肥朝}
-
分析var声明: 在AO上,形成一个属性,以var的变量名为属性名,值为
undefined
,(因为是先分析,后执行,这只是词法分析阶段,并不是执行阶段,分析阶段值都是undefined,如果执行阶段有赋值操作,那值会按照正常赋值改变),也就是说代码应该表示为:t.AO = {userName : undefined}
,但是还有另外一个原则,那就是如果AO有已经有同名属性,则不影响(也就是什么事都不做),由于分析参数时,AO上已经有userName这个属性了,所以按照这个原则,此时什么事都不做,也就是说,此时按照分析参数时的结果t.AO = {userName : 肥朝}
-
分析函数声明: 此时没有函数声明,略过
执行阶段:
调用t.AO.userName
,所以,最后的输出结果是肥朝
例子3
<script>
t();
t2();
function t() {
console.log('肥朝');//这里会输出什么?
}
var t2 = function () {
console.log('hello 肥朝');//这里会输出什么?
};
</script>
那么我们再来看一个例子,这下彻底回到高中时代,做了两个例子好像感觉掌握了,结果考试你给来看这个?
答案是,t()
输出为肥朝,t2()
则会报错.这又是为什么?
-
t()
可以调用是因为在词法分析的过程,就已经完成了t函数的分析,所以可以调用 -
t2()
不能调用是因为在词法分析的阶段,分析到有一个t2声明,在AO上只是形成了一个属性,但是值为undefined
例子4
<script>
function t(userName) {
console.log(userName);//这里输出什么?
function userName() {
console.log(userName);//这里输出什么?
}
userName();
}
t('肥朝');
</script>
函数里面套函数,这次竟然又和前面不一样了...这次我不说答案了,直接先套公式走一波
t('肥朝')
的分析和执行阶段
分析阶段:
-
创建AO对象,
t.AO = {}
-
分析参数:
t.AO = {userName : 肥朝}
-
分析var声明: 有同名属性,不做任何事,还是
t.AO = {userName : 肥朝}
-
分析函数声明: 有同名属性,覆盖:
t.AO = {userName : function userName() {console.log(userName);}}
执行阶段: t.AO.userName
输出为function userName() {console.log(userName);}}
userName()
的分析和执行阶段
这里也要搞清楚两个概念
//执行userName()分析的是
function () {
console.log(userName);
};
//而不是
var userName = function () {
console.log(userName);
};
分析阶段:
-
创建AO对象,
userName.AO = {}
-
分析参数: 无,略过
-
分析var声明: 无,略过
-
分析函数声明: 无,略过
执行阶段: 因为此时userName.AO = {}
是个空对象,无法执行userName.AO.userName
,所以会向上一层找,所以输出t.AO.userName
的结果,也就是function userName() {console.log(userName);}}
例子5
<script>
function t(userName) {
console.log(userName);//这里输出什么?
var userName = function () {
console.log(userName);//这里输出什么?
}
userName();
}
t('肥朝');
</script>
好吧,我保证这个是最后一道...这个输出结果是什么呢?我们只要坚定公式没问题,就一定能得出结果,那么再套公式走一波
t('肥朝')
的分析和执行阶段
分析阶段:
-
创建AO对象,
t.AO = {}
-
分析参数:
t.AO = {userName : 肥朝}
-
分析var声明: 有同名属性,不做任何事,还是
t.AO = {userName : 肥朝}
-
分析函数声明: 无,略过
执行阶段: 执行console.log(userName);
时调用t.AO.userName
输出为肥朝
,执行完后,代码继续往下执行,那么就到了进行var的赋值操作(var的分析和执行的区别看例子2中我有解释),此时t.AO = {userName : function() {console.log(userName);}}
,代码继续往下执行,接着就执行到了userName()
userName()
的分析和执行阶段
分析阶段:
-
创建AO对象,
userName.AO = {}
-
分析参数: 无,略过
-
分析var声明: 无,略过
-
分析函数声明: 无,略过
执行阶段: 按照例子4
我们知道userName.AO
是个空对象,所以会往上调用t.AO.userName
,所以输出为:function () {console.log(userName);}
总结
JavaScript作用域会先在自己的AO上找,找不到就到父函数的AO上找,再找不到再找上一层的AO,直到找到window.这样就形成一条链,这条AO链就是JavaScript中的作用域链.