JavaScript百炼成仙 1.20 函数七重关之二 (作用域)

简介: JavaScript百炼成仙 1.20 函数七重关之二 (作用域)

“咳咳,那我接下来继续长话短说了。要回答之前的那个问题,我必须把作用域的概念再说一说。这便是我所总结的函数七重关里面的第二重关。”叶小凡继续讲解,这些概念自己在叶老的教导下早就已经不知道折腾了多少遍。


“首先,作用域如果要深究的话,还是比较复杂和晦涩难懂的,我就用通俗的话来说明作用域的问题吧。在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.<anonymous> (G:\JavaScript百炼成仙\\hello.js:5:13)
    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变量其实就是一个摆设。”


“好小子,回答得不错。那你再说说如果函数有参数传递的时候,会怎样?”尹曾琪点点头,认可了叶小凡的回答。


场外的观众一下子又沸腾了,要知道,在千鹤宗,尹曾琪可是出了名的抠门,每次考试都喜欢各种鸡蛋里面挑骨头,很少会像现在这样去赞同一个弟子。


“弟子遵命。关于函数的传参,其实我是把它归结到函数七重关里面的第三重关的。”


“哈哈哈,好,好,好。那你就讲讲你的第三重关吧!”尹曾琪哈哈大笑,赞赏之意更浓。


叶小凡抬头看了林元青一眼,得到授意后,就开始讲起了第三重关。


相关文章
|
1天前
|
JavaScript 前端开发
在JavaScript中,函数原型(Function Prototype)是一个特殊的对象
【5月更文挑战第11天】JavaScript中的函数原型是一个特殊对象,它为所有函数实例提供共享的方法和属性。每个函数在创建时都有一个`prototype`属性,指向原型对象。利用原型,我们可以向所有实例添加方法和属性,实现继承。例如,我们定义一个`Person`函数,向其原型添加`greet`方法,然后创建实例`john`和`jane`,它们都能调用这个方法。尽管可以直接在原型上添加方法,但推荐在构造函数内部定义以封装数据和逻辑。
18 2
|
1天前
|
前端开发 JavaScript 数据处理
在JavaScript中,异步函数是指什么
【5月更文挑战第9天】JavaScript中的异步函数用于处理非立即完成的操作,如定时器、网络请求等。它们可通过回调函数、Promise或async/await来实现。示例展示了如何使用async/await模拟网络请求:定义异步函数fetchData返回Promise,在另一异步函数processData中使用await等待结果并处理。当fetchData的Promise解析时,data变量接收结果并继续执行后续代码。注意,调用异步函数不会阻塞执行,而是会在适当时间点继续。
11 0
|
1天前
|
自然语言处理 JavaScript 前端开发
在JavaScript中,this关键字的行为可能会因函数的调用方式而异
【5月更文挑战第9天】JavaScript中的`this`关键字行为取决于函数调用方式。在非严格模式下,直接调用函数时`this`指全局对象,严格模式下为`undefined`。作为对象方法调用时,`this`指向该对象。用`new`调用构造函数时,`this`指向新实例。通过`call`、`apply`、`bind`可手动设置`this`值。在回调和事件处理中,`this`可能不直观,箭头函数和绑定方法可帮助管理`this`的行为。
11 1
|
1天前
|
JavaScript 前端开发 网络架构
JavaScript中的箭头函数是一种新的函数表达形式
【5月更文挑战第9天】JavaScript的箭头函数以简洁语法简化函数定义,其特性包括:1) 不绑定自身this,继承上下文的this,适合回调和事件处理;2) 没有arguments对象,需用剩余参数语法访问参数;3) 不能用作构造函数,无new调用;4) 没有prototype属性,不支持基于原型的继承。箭头函数在特定场景下优化了this处理,但使用时要注意与普通函数的差异。
11 2
|
1天前
|
JavaScript 前端开发
js的一些内置函数
js的一些内置函数
7 1
|
1天前
|
JavaScript 前端开发 索引
js的includes函数
js的includes函数
13 1
|
1天前
|
JavaScript 安全 前端开发
js的map函数
js的map函数
7 0
|
1天前
|
JavaScript 前端开发
js的filter函数
js的filter函数
11 1
|
1天前
|
JavaScript 前端开发
js的函数
js的函数
10 0
|
1天前
|
JavaScript 前端开发
js的join函数
js的join函数
9 1