【学习日记】理解JavaScript的作用域概念

172 阅读7分钟

1.前言

在学习作用域的概念前,我们先来了解一下它是如何被编译的。JavaScript 是一种解释型语言,不同于像 Java 或 C++ 这样的编译型语言,它并不通过传统的编译过程将代码转换为机器码。然而,JavaScript 引擎在执行代码前确实会对代码进行一系列的处理步骤,这个过程可以粗略地分为以下几个阶段:

• 分词/词法分析(Tokenizing/Lexing)

通俗讲就是将字符组成的字符串分解成有意义的代码块,这些代码块就被称为词法单元。例如:

var a = 2;通常会被分解成var、a、=、2、;。而空格是根据他在里面是否有意义来判断他是否被当作词法单元

• 解析/语法分析(Parsing)

解析或语法分析,就像是在阅读一段用外语写的故事时,先要理解每个单词的意思(这一步是由词法分析完成的),然后再搞清楚这些单词是如何组合在一起形成有意义的句子和段落的。在这个编程的上下文中,我们要把一串代码看作是一段“故事”。想象一下,你有一堆乐高积木,每块积木就是一个词法单元,可能是名词、动词或其他零件。解析的过程,就是按照语言的规则,把这些积木正确地组装起来,构建出一个复杂的模型。这个模型,就是“抽象语法树”(AST),它形象地展现了代码的结构和各个部分之间的关系。解释起来就是:

还是拿 var a = 2;来说,一个故事开头我们用var表示说我们在这里声明了一个变量,接着我们遇到故事的主人公“变量a”,然后这里发生了一个我们给变量赋值的事,最后找到这个事件具体内容是给变量赋的值为2

• 代码生成

将AST转换成可执行代码的过程就叫代码生成。这个过程与语言、目标平台等息 息相关。

抛开具体细节,简单来说就是有某种方法可以将 var a = 2; 的 AST 转化为一组机器指 令,用来创建一个叫作 a 的变量(包括分配内存等),并将一个值储存在 a 中。

2.域

介绍完上述内容,接下来就是我们的正题:域。作用域我们分成了两种一中是全局作用域另一种是局部作用域也称函数作用域。现在我们就分开来介绍一下这两种吧。

全局作用域(Global Scope) :他是被定义在最外层的变量和函数所处的作用域,它体内的变量和函数可以从代码任何地方访问到;

var a =2
function foo(){
    console.log(a)
}
console.log(a)
foo();

此段代码变量a它就是所处全局变量中,可以在函数体外被访问也可以被函数体内访问。

局部作用域(Local Scope) :它通常是有函数定义创建的,所以它也叫函数作用域,当一个变量在函数内部定义时,这个变量就只在该函数内部可见,这就是局部作用域。

function foo(a){
    var a = 2
    // console.log(a)
}
console.log(a)
foo();

image.png

运行上面那段代码他就会报错,说a未被定义,这就是变量a是函数foo()的局部作用域。在作用域中有个这个规则:内部作用域可以访问外部作用域,反之则不行;即局部作用域可以访问全局作用域,全局作用域不能访问局部作用域。 要解释这一问题就得贴出下面这张图了

image.png

当js执行时,有一个全局作用域从上往下入栈,每当一个函数调用时一个函数的局部作用域会被创建并进入栈,当代码在某个作用域内执行,在自己体内没找到对应的变量他就会往下作用域寻找而当函数运行完他就会出栈以至于这个局部作用域在栈外而全局作用域还在栈内以至于全局作用域访问不了这个函数作用域。

讲完以上内容我们就要讲一个另类欺骗词法作用域

3.欺骗词法作用域

而讲欺骗词法作用域前我们得先了解一下词法作用域的概念。词法作用域也称为静态作用域,是指在编写代码时(即在词法分析阶段)就能确定的变量访问规则。用代码表示就是:

function foo(a) {
    var b=a*2;
    function bar(c) {
        console.log(a,b,c);
    }
    bar(b*3);
}
foo(2)

函数foo()存在全局作用域中,a、bar、b在foo()这个函数作用域中,c在bar()这个函数作用域中。

欺骗词法作用域有两种:

1.eval 这个函数可以接受一个字符串参数,并将其内容变得天生就在这里一样代码演示:

function foo(str,a){
    console.log(a,b);
}
 var b=2
foo("var b =3",1)

这段应该会输出1,2我们修改一下:

function foo(str,a){
    eval(str);
    console.log(a,b);
}
foo("var b =3",1)

按道理运行它应该会报错,因为不管在foo函数体内还是体外找遍了也是没找到b这个变量,但是当我们运行一下却会输出1,3。这就是eval函数的欺骗了编译器,让编译器以为"var b = 3"这个字符串是定义一个变量b值为3。

2.with当修改对象中有不存在的属性时,这个属性会被泄露到全局,变成全局变量。比如:

var obj = { 
    a: 1, 
    b: 2, 
    c: 3 
   }; 
   // 单调乏味的重复 "obj" 
   obj.a = 2; 
   obj.b = 3; 
   obj.c = 4; 
   // 简单的快捷方式 
   with (obj) { 
    a = 3; 
    b = 4; 
    c = 5; 
   }
   console.log(obj)

以上代码还是一切正常,但当我们修改一下代码呢?

function foo(obj) { 
    with (obj) { 
    a = 2; 
    } 
   } 
   var o1 = { 
    a: 3 
   }; 
   var o2 = { 
    b: 3 
   }; 
   foo( o1 ); 
   console.log( o1.a ); 
   foo( o2 ); 
   console.log( o2.a ); 
   console.log( a );

正常应该是console.log( o1.a ); console.log( o2.a );这两个会输出3 和 undefined 结果却是2 undefined 2;这以为着with内的a=2泄露了被定义成了一个全局变量。

以上两种都是比较特殊的我们了解一下就行

最后我们要讲一下从ES6(ECMAScript 2015)开始,JavaScript引入了新的作用域概念——块级作用域。

4.块级作用域

这主要是es6出现引入的一个新概念,那么在讲这个之前我们先来了解一下let 与 var 的一个区别吧:

console.log(a)
var a = 2

这段代码算是倒反天罡了吧但编译器却会把他们分析成这样:

var a
console.log(a)
a = 2

这就是var会使声明的变量被提升

而es6引入的let就不会出现这样的问题

提了一嘴var和let的一个区别接下来进入我们的主题块

块级作用域

它主要是{}加上let和const来界定的,指变量和绑定仅在指定的代码块内有效的作用域区域。

{
  let blockVar = "我是块级作用域变量";
  console.log(blockVar); // 输出: 我是块级作用域变量
}
// console.log(blockVar); // 这里会报错,因为blockVar在此作用域外不可见

if (true) {
  const insideIf = "我在if语句块中";
}
// console.log(insideIf); // 同样会报错,insideIf只在if块内有效

到这补充一点const与let的区别:const声明的是常量,不可以修改值,而let是可在声明时不进行赋值

const a  // SyntaxError: Missing initializer in const declaration

let b  //这里并不会报错

完结写完这篇文章我翻了提多资料,也写了挺久但也有很多因为我的表达能力不足和理解不够深存在很多知识点没有表达出来欢迎各位大佬评论指点!!!!