js基础阵营:变量篇之变量作用域

641 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

上面已经介绍完了变量的定义,说完定义肯定要说下变量的常见的问题。其实变量的常见问题说来说去也就那么几个,变量作用域,变量提升。这就跟前面说到张无忌的乾坤大挪移一样,用来用去也就那么几招。那么我们首先来看变量作用域

1.变量作用域

为了理解什么是变量作用域,我们首先要搞明白一个概率-'执行环境'。故名思议,执行环境就是我们要处理一件事所处的环境,举个栗子:我们去TAM取款机取钱,那么ATM机器就是一个执行环境,一个执行取钱命令的环境。那么这个环境中包括哪些东西呢?ATM取款机这个执行环境中呢就包括钱,以及如何取钱这个操作。那么我们类比到我们的js中,我们就不难明白什么是执行环境,执行环境就是定义了变量(钱)或者函数(取钱操作)的一个范围。

理解了执行环境,那么我们变量作用域也很好明白了,那就是变量所能操作(读取)的一个执行环境。那么我们常见的变量作用域有哪些呢?在ES5时代变量的作用域有两种:全局作用域以及函数作用域,在ES6时代引入了块级作用域的概率,下面我们来一个一个来剖析

1.1全局作用域

全局作用域就是在最外层的一个执行环境,js代码运行宿主不同(js可以在浏览器运行也可以在node环境中运行)表示的执行环境也不同,在浏览器中我们说到的全局作用域就是指window对象,因为所有的变量以及函数都是作为window对象的属性和方法来创建的,我们来看一下代码

var name = '二锅头';
console.log('window.name的值为:' + window.name);

我们把代码复制到浏览器的控制台就能得到一下结果:

image.png

这个时候我们就能看到其实我们定义的变量name变成了window的一个属性。所以只要能访问到window的地方都能使用我们的name属性。这也就是为什么叫全局作用域的原因。

1.2函数作用域

函数作用域,说简单点就是函数的执行环境。还是用前面银行取钱的例子,我们取钱这个动作可以理解为一个函数,我们在这个取钱(函数)中,我们进行了输入密码,插卡等等操作,那么我们输入密码这个只能是在取钱这个动作中有效。你把卡放到另一个环境中这个密码就无效了对吧。所以函数作用域就是属于这个函数的全部变量都可以在这个函数内部使用。当脱离了这个函数,函数中原有的函数变量都无效。我们来看下代码

  var number= 1;
  function sayHello() {
     console.log(number)
     var testName= '二锅头';
     console.log(testName)
  }
 sayHello();
 console.log(number);
 console.log(testName);

我们来看下结果是什么

image.png

我们可以清洗的看到在函数sayHello中定义的变量name,在外部输出打印的时候会报错,提示没有定于,那么说明了什么?说明了这个name仅仅存在于sayHello这个函数中。形成了一种很奇特的块作用域。

1.3块作用域

顾名思义,块作用域是在一个固定的区域内有效,在js中通常是一个{}\color{blue}{\{\\}\}内部中为一个块作用域。我们来看下下面的代码

   var password = '123456';
   if(password  === '123456') {
     let testName = '二锅头';
    console.log(testName + '你的密码是对的!');
  }
console.log(testName);

image.png

所以我们可以看到,testName当前仅仅在if这个块({})中有用,在外部的时候依旧会提示未定义。所以在我个人的理解里,ES6通过let以及const相当于'劫持'了变量,从而胁迫变量成为一个块作用域。可以理解你去取钱,有人抢银行,把你的密码抢走了,胁迫你取钱了

深入探讨下1

至此,js的三大作用域都结束了,但是真的结束了吗?深究下,为什么会出现以上作用域,原理是什么呢?

image.png

开头我们已经说过了,执行环境。我们可以理解为没一个作用域都是有对应的执行环境,全局作用域是最大的执行环境,每个函数有函数的执行环境,每个块级作用域有自己的执行环境,当程序执行定义全局变量的时候,会将全局环境中的变量推入到一个全局环境中,当执行完成后,弹出,返回之前的执行环境,因此我们可以用下图来表示

image.png

我们在深入下,程序是如何知道怎么处理的呢?在js中,我们永远逃脱不了一个东西---链。同样的在作用域中同样有一个链,那就是作用域链。当程序在一个环境中执行的时候,就会创建一个作用域链,通过这个作用域链来保证在此环境中有权访问所有的变量和函数。作用域链的前端永远是当前执行环境的变量对象,例如:当环境是一个函数的时候,其作用域链的前端是arguments对象。作用域链的下一个是下一个环境中,直到全局环境中。我们看下面例子。通过例子来了解

var a = '全局环境';
function test(){
   var b = "test环境";
   test1();
  function test1() {
      var c = 'test1环境'
  }
  var d = "test环境"
}
test();
var e = "全局环境"

那么作用域链是什么样的呢? 初始化的时候作用域链是全局(window),然后执行到test环境中的arguments,,然后是test中的b,然后是test1环境中的arguments,然后是test1环境中的C,然后进入test环境中的d,然后进入全局环境中的e。

深入探讨2

既然我们上面也说到了作用域链的问题,那么我们是否可以人为的改变作用域链呢?思考下,怎么改变呢?那就是当执行到某一环境的时候,人为的去将环境给改掉。js提供三种方法 #####eval(string) 此方法,传入一字符串,将其中的内容执行,并写入执行的地方,在写的代码中的程序中生成并运行,就好像此代码就在此位置,如下代码

function test(str,a) {
    eval(str);
    console.log(a,b)
}
var b = 2;
test('var b = 3;',1)

输出结果为

image.png

with(obj)

此方法,通常被当做应用同一个对象多个属性的快捷方法,如下代码

var obj = {
  a:1,
  b:2
}
function test(obj) {
   with(obj) {
      a = 2;
  }
}
test(obj);
console.log(obj)

结果为: 我们来将上面的代码稍微处理下

var obj = {
  a:1,
  b:2
}
function test(obj) {
   with(obj) {
      c = 2;
  }
}
test(obj);
console.log(c)

这个时候我们会发现什么?会发现C值为2,也就是出现了泄漏问题,泄漏到了全局环境中去了

try catch

抛出异常,如下代码

try{
  undefined();
}catch(err){
  console.log(err)
}
console.log(err);

结果是

image.png

所以catch中的作用域为一个独立的块级作用域 至此关于作用域的问题也讲完了。下一章,我们将讲解变量提升问题