“咳咳,那我接下来继续长话短说了。要回答之前的那个问题,我必须把作用域的概念再说一说。这便是我所总结的函数七重关里面的第二重关。”叶小凡继续讲解,这些概念自己在叶老的教导下早就已经不知道折腾了多少遍。
“首先,作用域如果要深究的话,还是比较复杂和晦涩难懂的,我就用通俗的话来说明作用域的问题吧。在JavaScript中,可以简单的理解作用域分为两种,一个是全局作用域,一个是函数作用域。所谓作用域,就是当你要查找某一个变量的时候,可以在什么地方找到这个变量。这个寻找的范围,就是作用域了。不管是全局作用域,还是函数作用域,都是被定义在词法阶段。词法阶段,就是刚才所说JavaScript编译代码的第一个步骤——分词。所以,词法阶段也叫作分词阶段。关于全局作用域,看一个比较简单的例子。”
var a = 10;
function test(){
console.log(a);
}
“a变量和test函数都是直接暴露在外面,因此,他们都属于全局作用域。而test函数的函数体,用花括号包起来的部分,则是函数作用域了。没有错,函数的函数体都属于函数作用域。又因为test函数属于全局作用于,而它自己又拥有一个函数作用域,那么这样一来,就形成了一个作用域的嵌套。也就是说,全局作用域里面嵌套了一个函数作用域。函数作用域里面可以访问全局作用域中的变量,但是反过来就不行。比如刚才的例子,如果我直接调用test函数。”
function test(){
console.log(a);
}
var a = 10;
test();
“答案必然是10,在这个例子中,函数作用域里面的a会先去当前函数作用域里面寻找是否有一个a变量。如果找不到,就去上一层包着它的父级作用域中去寻找。那么,在这个例子里面,不难看出,外面的父级作用域,也就是全局作用域中确实有一个a变量。那么,在执行函数体的时候,就可以访问到外面的a变量啦。但是,如果反过来就不行,比如这样。”
function test(){
var a = 10;
}
console.log(a);
代码运行,结果如下:
ReferenceError: a is not defined
at Object.
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
at startup (internal/bootstrap/node.js:279:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:752:3)
“刚才已经说了,全局作用域包着一个函数作用域,那么函数作用域里面可以访问到全局作用域里面的变量。但是反过来看的话,全局作用域想要去调用里面的函数作用域中定义的变量,却是做不到的。因此当发生作用域嵌套的时候,只能里面的访问外面的,外面的无法访问里面的。而且,需要额外注意一点,那就是作用域嵌套一般是针对全局作用域和函数作用域,或者函数作用域和其他函数作用域而言的。比如,下面这种方式就不算是作用域嵌套。”说着,叶小凡随手就打出了一段代码流。
if(true){
var a = 20;
}
console.log(a);
代码运行,结果是20!
“虽然a变量的定义是写在花括号里面,但是这里并没有函数的出现,因此不算做作用域嵌套。而且我刚才也已经说了,在JavaScript中,只有全局作用域和函数作用域,你可以认为这里的a也属于全局作用域,这样方便理解。既然都是在全局作用域里面,那么console.log方法自然可以访问到同为全局作用域里面的变量a了啊。”
叶小凡讲得有理有据,饶是林元青和尹曾琪也微微点了点头。也难怪叶小凡懂的比其他弟子多,毕竟,可是有一个老怪级别的叶老在教导自己啊。
“接下来,我把代码换一下。”
if(false){
var a = 20;
}
console.log(a);
“瞧,我现在把if判断中的true改为了false,那么你说,下面的a打印出来是多少呢?”
“这,应该是报错吧,因为定义a变量的语句不会执行了啊。”对面弟子想了想,不是很确定地说道。
“错了,你看好。”叶小凡微微一笑,运功将代码执行了一下。得到的结果是undefined!
“什么,竟然是undefined,为什么?”对面弟子惊呼。
“var a = 20;这句话在if判断中,而if判断的条件是false,所以的确不会执行。但是,执行代码是运行阶段的时候,在代码的分词阶段和解析阶段,a变量依然会被获取,并且系统会默认给它一个undefined。又因为a变量不是在某一个函数的函数体中,而是在全局作用域里面,所以console.log方法依然可以访问到这个a变量,而且获取a变量的值就是undefined。”
“接下来可以解释之前的那个问题了。”
console.log(a);
var a = function(){
alert("函数被调用了!");
}
console.log(a);
“第一次执行console.log方法的时候,a变量还没有被赋值为一个函数,但是JavaScript引擎还是会把它提取出来,放入全局作用域中,并且默认给它一个undefined。所以,第一次打印出来的就是undefined。接下来,就是一个赋值语句了。”
var a = function(){
alert("函数被调用了!");
}
“这个赋值语句,把一个匿名函数赋给了变量a,那么从此变量a就指向了这个函数,换句话说,一个名字叫做a的函数就已经产生了。这句话一旦执行,那么a就不再是undefined了,而是一个函数。接下来,执行第二个console.log方法,这个时候a自然是已经有值了,所以打印出来的是一个函数。”
“好,好,好!”尹曾琪连说三个号,显然对叶小凡的实力不吝赞赏,看下叶小凡的眼光中明显多了好几分神采。
“小娃娃,没想到你小小年纪,竟然可以对函数这么了解。既然你提到了作用域分为全局作用域和函数作用域,那么老夫就再来考你一考。”说着,尹曾琪随手一挥,一段代码就显示在了众人眼前。
var a = 1;
function test(){
var a;
var inner = function (){
console.log(a);
}
inner();
}
test();
“这道题,你来回答看看,答案是什么呢?”
“答案是undefined,这是函数作用域里面嵌套了函数作用域,那么最里面的inner函数中要访问一个变量啊,就会优先从inner函数里面去找,结果发现找不到。既然当前函数作用域里面找不到,那么就网上翻一层,去它的父级作用域中,也就是test函数的作用域里面去找,结果发现找到了,test函数里面定义了一个a变量,但是没有赋值,那么a就是undefined。既然已经找到了,那么就不会去全局作用域里面找a变量了。所以,全局作用域里面的a变量其实就是一个摆设。”
“好小子,回答得不错。那你再说说如果函数有参数传递的时候,会怎样?”尹曾琪点点头,认可了叶小凡的回答。
场外的观众一下子又沸腾了,要知道,在千鹤宗,尹曾琪可是出了名的抠门,每次考试都喜欢各种鸡蛋里面挑骨头,很少会像现在这样去赞同一个弟子。
“弟子遵命。关于函数的传参,其实我是把它归结到函数七重关里面的第三重关的。”
“哈哈哈,好,好,好。那你就讲讲你的第三重关吧!”尹曾琪哈哈大笑,赞赏之意更浓。
叶小凡抬头看了林元青一眼,得到授意后,就开始讲起了第三重关。