本文不进行系统的讲解,只枚举及个特殊的例子。
例1
<script>
var str1 = "hello";
var str2 = "world";
function t1() {
console.log(str1);
console.log(str2);
var str2 = "toby";
console.log(str2);
}
//这里会输出什么?
t1();
</script>
结果:
- hello
- undefined
- tbody
你一定奇怪为什么是undefined,因为根据函数ti来说,他是有自己的str2变量的,只不过声明在试用之后,所以是undefined。
例2
<script>
function t(userName) {
console.log(userName);//这里输出什么?
function userName() {
console.log('tom');
}
}
t('toby');
</script>
你一定以为这里输出的是tbody,但实际处出结果是:
function userName() {
console.log('tom');
}
执行t(‘toby’)的时候,会开始两个阶段,一个是分析阶段,分析完就到执行阶段
分析阶段:
函数运行的瞬间,会生成一个Active Object对象(以下简称AO对象),一个函数作用域内能找到的所有变量,都在AO上,此时用代码表示为: t.AO = {}
分析参数: 接收参数,以参数名为属性,参数值为属性值,因为没有参数,因此分析结果用代码表示为: t.AO = {userName : toby}
分析var声明: t函数内没有var声明,略过
分析函数声明: 这个函数声明有个特点,AO上如果有与函数名同名的属性,则会被此函数覆盖,因为函数在JS领域,也是变量的一种类型,因此用代码表示为: t.AO = { userName : function userName() {console.log(‘tom’);}}
执行阶段:
执行t(‘toby’)的时候,当执行到console.log(userName)时,就调用t.AO.userName,所以,最后的输出结果是function userName() {console.log(‘tom’);}
所以重点就在于,同一域内,同名的函数和变量,函数优先,也就是函数会覆盖变量。
例3:
<script>
function t(userName) {
console.log(userName);//这里输出什么?
var userName = function () {
console.log('tom');
}
console.log(userName);//这里输出什么
}
t('toby');
</script>
结果:
- tbody
- function () {
console.log(‘tom’);
}
分析阶段:
创建AO对象,t.AO = {}
分析参数: t.AO = {userName : toby}
分析var声明: 在AO上,形成一个属性,以var的变量名为属性名,值为undefined,(因为是先分析,后执行,这只是词法分析阶段,并不是执行阶段,分析阶段值都是undefined,如果执行阶段有赋值操作,那值会按照正常赋值改变),也就是说代码应该表示为:t.AO = {userName : undefined},但是还有另外一个原则,那就是如果AO有已经有同名属性,则不影响(也就是什么事都不做),由于分析参数时,AO上已经有userName这个属性了,所以按照这个原则,此时什么事都不做,也就是说,此时按照分析参数时的结果t.AO = {userName : toby}
分析函数声明: 此时没有函数声明,略过
执行阶段:
调用t.AO.userName,所以,最后的输出结果是toby
例4:
<script>
t();
t2();
function t() {
console.log('toby');//这里会输出什么?
}
var t2 = function () {
console.log('hello toby');//这里会输出什么?
};
</script>
结果:tbody 报错。因为第一个已解析的时候已经将整个函数提升到顶部,但是第二个相当于变量,只有声明,没有定义。
例5:
<script>
function t(userName) {
console.log(userName);//这里输出什么?
function userName() {
console.log(userName);//这里输出什么?
}
userName();
}
t('toby');
</script>
结果:
- function userName() {console.log(userName);}}
- function userName() {console.log(userName);}}
t(‘toby’)的分析和执行阶段
分析阶段:
创建AO对象,t.AO = {}
分析参数: t.AO = {userName : toby}
分析var声明: 有同名属性,不做任何事,还是t.AO = {userName : toby}
分析函数声明: 有同名属性,覆盖: 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);}}
例6:
<script>
function t(userName) {
console.log(userName);//这里输出什么?
var userName = function () {
console.log(userName);//这里输出什么?
}
userName();
}
t('toby');
</script>
结果:
- toby
- function () {
console.log(userName);//这里输出什么?
}
t(‘toby’)的分析和执行阶段
分析阶段:
创建AO对象,t.AO = {}
分析参数: t.AO = {userName : toby}
分析var声明: 有同名属性,不做任何事,还是t.AO = {userName : toby}
分析函数声明: 无,略过
执行阶段:
执行console.log(userName);时调用t.AO.userName 输出为toby,执行完后,代码继续往下执行,那么就到了进行var的赋值操作(var的分析和执行的区别看例子2中我有解释),此时t.AO = {userName : function userName() {console.log(userName);}}},代码继续往下执行,接着就执行到了userName()
userName()的分析和执行阶段
分析阶段:
创建AO对象,userName.AO = {}
分析参数: 无,略过
分析var声明: 无,略过
分析函数声明: 无,略过
执行阶段:
按照例子4我们知道userName.AO是个空对象,所以会往上调用t.AO.userName,所以输出为:function userName() {console.log(userName);}}}
总结:
后面的变量与前面的变量同名,如果后面变量的值是undefined,则不会覆盖前面的,也就是只有执行到了才赋值,赋了值才能覆盖。也就是说,用这个同名变量时,看之前谁最后一次赋的值,就是那个值。(赋值为undefined也算赋值)
var x=1;
console.log(x);//1
var x;
console.log(x);//1(这样未赋值的undefine就覆盖不了之前的,如果这里给x=undefined,可以覆盖,输出结果就为undefined)
var x=3;
console.log(x);//3
JavaScript作用域会先在自己的AO上找,找不到就到父函数的AO上找,再找不到再找上一层的AO,直到找到window.这样就形成一条链,这条AO链就是JavaScript中的作用域链.JavaScript中有两条很重要的链,一条是作用域链,一条是原型链。