JavaScript基础–作用域

JavaScript基础–作用域

什么是作用域

作用域定义了变量在哪查找怎样查找的一系列规则。通常我们在JS中定义的变量具有一定调用范围,比如全局变量可以在任意位置调用;而这个范围通常就是我们所指的作用域,JS中的作用域不同于C/C++,那么有哪些不同呢,请先和我一起了解一下JS简单的编译原理

编译原理

JS虽然是一门脚本语言,但是事实上它也是需要编译的(ps.通常脚本语言只需要“解释”,而不是“编译”)。但与其他传统的编译语言(例如java)不同的是,JS并不会提前编译好代码等待执行,而是很快被编译,然后再迅速执行(通常在几微秒以内)。即使有这样的差异,实际上它们的大致的编辑过程差不多,主要分为三个阶段: 
1. 分词/词法分析 
分词就是将表达式进行拆分,比如var a = 5;,编译引擎会将其分解为vara=5;。词法分析与分词差不多,它的作用是分析表达式中各个组成部分的意思,类似于一个句子中什么是主语、谓语等。 
2. 解析代码 
第二步,解析代码。在这个过程中会生成一个抽象语法树(AST,Abstract Syntax Tree),这个树上嵌套了第一步分解的各个“小元件”。还是以上面的表达式为例子,var a = 5;,这时根节点为var,它有两个子节点,一个是a,一个是==下面还有一个子节点5。 
 
3. 代码生成 
这个过程生成可以执行的代码,是面向机器的。这个过程中机器开始给变量分配内存,构建作用域等等。

理解作用域

JS代码在编译和执行的整个过程中,有三个“人物”扮演了重要的角色,分别是引擎、编译器和作用域。怎么理解这三个“人物”呢?引擎相当于这三者中的老大,负责JS代码的编译和执行;编译器负责解析代码、代码生成等;作用域负责管理代码中各个变量应该活动的范围。三人各司其职,保障JS在平台上的顺利运行。

工作流程

引擎、编译器和作用器各自承担成自己的责任,那么三者是如何协调工作的呢?以var a = 5;为例。 
1. 首先编译阶段,编译器会询问作用域那边是否有变量a,如果有,则编译器忽略这句声明,如果没有,则在作用域中生成一个变量a
2. 在代码生成过程中,编译器就将5赋给变量a; 
3. 最后在引擎在执行代码过程中,也会先问作用域哪里有没有a,有就取它的值来用,如果没有,就去别的地方找(具体在哪里,继续往下看)。

查找方式

引擎在执行代码的过程中,比如var a = 5;,很多书和博客将会搬出两个概念:左查找(LHS)和右查找(RHS),然后又解释了很多,其实左查找就是查找变量,右查找就是查找变量的值,如果这样理解的话,你就很容易知道什么时候用的左查找,什么时候用的右查找。 
后来我自己研究了一下,发现左查找和右查找的唯一作用是标识引擎查找返回错误,如果找不到变量,就报ReferenceError,找不到变量的值(属性),就报TypeError。如果还有其他作用,欢迎补充。

嵌套作用域

嵌套作用域比较好理解,不过这里要注意两点:一个认识到JS是函数作用域,作用域以函数为单位来嵌套的;第二个是区分嵌套作用域与闭包的不同。

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

上面的代码中就是一个简单的嵌套作用域的例子,全局定义了b,在函数里定义了a,被嵌套在全局作用域里的函数foo可以调用b,但全局作用域里不能调用a。 
引擎在查找变量时,是先从所执行的代码的作用域里开始查找的,如果找不到,再一级一级往外层的作用域里查找。

进一步理解作用域

在编译的阶段,作用域就形成,在引擎执行代码时,作用域的规则就开始限定引擎要怎么取变量或者值了。我们举个例子。 
 
在上面的例子中,嵌套了三层作用域,第一层是全局作用域,第二层是函数foo的作用域,第三层是函数bar的作用域。引擎在执行某段代码时,查找的规则是这样的:

  • 首先在当下的作用域里找,找的到就直接调用,找不到就想上一层作用域继续找,再找不到就再往上,直到全局作用域;
  • 如果在当下的作用域下定义了一个与外层作用域相同的变量,以当下的作用域的为准。

在第一条规则中,定义变量时,需要在变量的前面加上var,如果不加,在非严格的模式下,会在全局作用域创建这个变量,在严格模式下会报错。 
在第二条规则中,如果在当下作用域中依然想调用全局那个重复的变量,可以这样定义window.a = 5;,调用的时候直接去window.a就可以了。

注:调用witheval()改变执行环境作用域并不推荐使用,因为它们会带来性能问题,这里不详细解释,大家可以参考文章后面参考文献。

扩展:JS中的块级作用域

JS中的函数作用域带来了灵活性,但在一些地方有存在不方便,比如在forif语句中,我们可能只需要在它们的{}里定义一些临时的变量,并不希望污染当前的作用域。基于这些问题,ES6中新增了letconst这两个关键字来实现了块级作用域定义变量。 
let的用法和var一样,只不过是作用域不同罢了例如:

    for (let i =0; i<3; i++){
        console.log(i);
    } // 0,1,2
    cosole.log(i); //i is undefined

const也是用来定义变量,不过它定义的是静态变量(常量),一旦定义了,重新赋值就会报错。

   var foo = true;
    if (foo) {
        var a = 2;
        const b = 3; // 定义常量

        a = 3;
        b = 4; // error!
    }

    console.log( a ); // 3
    console.log( b ); // ReferenceError!
 

参考文献

时间: 2024-10-20 07:40:08

JavaScript基础–作用域的相关文章

前端知识体系:JavaScript基础-作用域和闭包-闭包的实现原理和作用以及堆栈溢出和内存泄漏原理和相应解决办法

闭包的实现原理和作用 闭包: 有权访问另一个函数作用域中的变量的函数. 创建闭包的常见方式就是,在一个函数中创建另一个函数. 闭包的作用: 访问函数内部变量.保持函数在环境中一直存在,不会被垃圾回收机制处理 因为函数内部声明 的变量是局部的,只能在函数内部访问到,但是函数外部的变量是对函数内部可见的,这就是作用域链的特点了. 子级可以向父级查找变量,逐级查找,找到为止 因此我们可以在函数内部再创建一个函数,这样对内部的函数来说,外层函数的变量都是可见的,然后我们就可以访问到他的变量了. <scr

前端知识体系:JavaScript基础-作用域和闭包-如何处理循环的异步操作

如何处理循环的异步操作 先看一段代码 function getMoney(){ var money=[100,200,300] for( let i=0; i<money.length; i++){ compute.exec().then(()=>{ console.log(money[i]) //alert(i) }) } } //compute.exec()这是个异步方法,在里面处理一些实际业务 //这时候打印出来的很可能就是300,300,300(因为异步for循环还没有等异步操作返回P

JavaScript基础–闭包

JavaScript基础–闭包 理解闭包的概念对于学习JavaScript至关重要,很多新手(包括我)开始学习闭包时,都会感觉似懂非懂,之前看了一些资料,整理了闭包的一篇博客,若有疏忽与错误,希望大家多多给意见. 概述 理解闭包的概念前,建议大家先回想一下JS作用域的相关知识,如果有疑问的同学,可以参考:JavaScript基础–作用域.闭包的定义如下: Closure is when a function is able to remember and access its lexical s

js基础--javascript基础概念之变量与作用域

js基础--javascript基础概念之变量.作用域 javascript按照ECMA-262 的定义,变量与其他语言变量有所不同.js变量时松散的,不需要事先定义变量类型的.这使得他只是一个保存特定值的一个名称.变量与其数据类型可以在脚本的生命周期内改变. 还有明白几点: JavaScript的变量作用域是基于其特有的作用域链的,JavaScript没有块级作用域. 基本类型和引用类型的值 ECMAScript 的变量有两种不同的数据类型:分别是 基本数据类型值 和 引用类型值 : 基本数据

【javascript基础】3、变量和作用域

原文:[javascript基础]3.变量和作用域 前言 这篇和大家说一下javascript中的变量和作用域,由于是将基础嘛,主要给大家捋一下知识,不想翻开书复习的道友可以看一下,打算刚开始学习javascript的同学可以扫一眼. PS:jQuery源码交流群( 239147101)等你来,群里高手云集,让我受益匪浅,尽量少灌水. 变量 javascript中有两种变量,分别是基本类型和引用类型,基本类型是Null,Undefined,String,Boolean,Number这五种,前面简

js基础--javascript基础概念之语法

掌握一门语言 必须先掌握它的语法! javascript 的语法和C.Java.Perl 的语法有些相似.但是比它们更加宽松. javascript 中的一切都是严格区分大小写的.例如变量: demo 和 Demo 两个变量是完全不同的. javascript 标示符,所谓标示符 是指 变量.函数.属性 的名字或函数的参数.标示符的格式是按照以下规则组合的一个或多个字符. 1.第一个字符必须是字母,下划线,或 $ 符号. 2.其他字符可以是字母.下划线.$ . 或数字. 注意 不能把关键字 保留

js基础--javascript基础概念之语句(二)

js基础--javascript基础概念之语句(二)label,break,continue.. break .  continue 语句. break  continue 语句用于在循环中精确控制代码的执行,其中break语句会立即退出循环,执行循环后面的语句. continue 则退出循环后返回到再次进入循环中. 如: var num = 0; for(var i = 0; i<=100; i++){ if(i >= 10){ break; } num = i; } alert(num);

javascript 的作用域

翻译自:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html 如果以下代码执行,你知道出什么结果吗? var foo = 1; function bar(){ if(!foo){ var foo = 10; } alert(foo); } bar(); 这运行结果是10. 如果你对结果感到惊讶,请看下面例子: var a =1; function b(){ a = 10; return; function a(){}

JavaScript从作用域到闭包

作用域(scope) 全局作用域和局部作用域 通常来讲这块是全局变量与局部变量的区分. 参考引文:JavaScript 开发进阶:理解 JavaScript 作用域和作用域链 全局作用域:最外层函数和在最外层函数外面定义的变量拥有全局作用域. 1)最外层函数和在最外层函数外面定义的变量拥有全局作用域 2)所有末定义直接赋值的变量自动声明为拥有全局作用域,即没有用var声明的变量都是全局变量,而且是顶层对象的属性. 3)所有window对象的属性拥有全局作用域 局部作用域:和全局作用域相反,局部作