美文网首页
JavaScript中的词法作用域

JavaScript中的词法作用域

作者: 晴天小雨不感冒 | 来源:发表于2020-03-25 19:52 被阅读0次

词法作用域

变量作用域及其生存期是所有编程语言都必须具备的基本特性。但是在JavaScript中,却有一种新的提法,叫做词法作用域。在《JavaScript权威指南》中,没有对词法作用域进行准确的定义。对于词法作用域的解释包含三个方面的:
1.通过阅读包含变量定义的数行源码就能知道变量的作用域。
2.全局变量在程序中始终都是有定义的。
3.局部变量在声明它的函数体内以及所嵌套的函数体内都是有定义的。
在继续讨论前,我们有必要回顾一下其他语言的关于变量及其作用域的定义。以C语言的作用域为例:

extern int i = 0;
void Fun()
{
    auto int j = 0;
    static int k = 0;
}

在C语言中,extern表示外部变量,表示变量的作用域为整个应用程序,生存期为应用程序启动到应用程序退出。当在函数外部声明变量时,extern可以省略,因为外部变量默认便是extern类型。auto表示内部变量,表示变量的作用域为当前函数内部,生存期为函数执行期间,函数一旦返回即销毁。static如果在函数外部声明,和extern效果一样,如果在函数内部声明,则表示变量作用域为当前函数,但生存期为整个程序运行期间。根据以上特性,上述代码可以简写为如下这样:

int i = 0;
void Fun()
{
    int j = 0;
    static int k = 0;
}

相比较而言,JavaScript的词法作用域似乎更简单,我们把上述代码改为JavaScript版本:

var i = 0;//全局作用域
function Fun()
{
    var j = 0;//函数作用域
    function Kic()
    {
        //此处仍然可以访问i,j
    }
}

我们对比发现,C语言的作用域和JavaScript大同小异,区别在于JavaScript允许函数嵌套定义,内层函数仍然可以访问外层函数中的变量。如果我们把内层函数当作外层函数的返回值,就会发生一种“奇特”的现象,即外层函数中定义的变量明明已经出栈了,却还能在内部函数中访问。这种现象在JavaScript中称之为“闭包”。在之后,我们再详细讨论“闭包”,接下来,我们讨论一下作用域链。

作用域链

在C语言中,一个函数的执行过程代表一个CPU的执行栈,函数内的变量都是定义在CPU的执行栈中,一旦出栈,即所有栈中的变量就都不存在了。JavaScript中的函数却不一样,解释器会将JavaScript中定义的函数处理为一个一个对象,每一个对象都有一个与之关联的作用域对象。当函数需要操作变量时,会查找作用域对象。如果在当前作用域对象中没有找到,即返回到上一级函数作用域对象中查找,再没有找到则继续返回上一级作用域对象,直到顶级,也就是全局作用域对象。当一个函数开始执行,需要注意的细节是,这里的函数执行是由解释器解释执行,而具体的解释器内部的执行机制对用户来说是不可见的。不过,可以肯定的是,由于JavaScript是解释型语言,没有像C语言一样最终形成机器代码,因此,JavaScript函数执行并没有直接形成CPU执行栈,最终执行栈的生成都是由解释器负责执行的。解释器在执行完函数代码后,并没有直接回收掉当前函数作用域对象,而是删除掉了该对象的引用,从而让垃圾回收机制对它进行回收。问题在于,当一个函数返回,函数作用域对象被删除后,是否该对象就会被回收掉呢?根据垃圾回收机制的特点,如果这个对象已经没有别的引用了,则垃圾回收机制会对它进行回收。如果还有别的引用呢?比如,嵌套函数的情况,由于有作用域链,嵌套函数中的内部函数很自然地拥有对外部函数作用域的引用。如果我们又在外部函数中将内部函数return,则系统会删除掉外部函数的作用域引用,但是内部函数仍然保持了该作用域的引用,所以垃圾回收机制仍然不会回收掉外部函数作用域对象。我们称这种现象为“闭包”。

闭包

从作用域链的角度去理解闭包,一切就显得顺理成章。闭包的形成是函数作用域链和垃圾回收机制共同作用的结果。因此在《JavaScript权威指南》中,就有这样的论断:“理论上来说,所有JavaScript函数都是闭包”。因为多数时候,函数作用域对象在函数返回后就删除了,也没有别的地方引用它,毕竟多数时候函数内部变量只在该函数内部有意义,外部并不需要它,因此垃圾回收机制在函数返回后不久就清除了它。但这种清除和我们上文提到的CPU执行栈的内存清除是完全不一样的。这是两种不同的机制。很多对闭包的误解就是从这里开始的。我们需要牢记,JavaScript对函数变量的回收也是通过垃圾回收机制进行回收的,垃圾回收机制只有检测到对象没有任何引用了才会回收它。当一个函数作用域对象在当前函数执行完毕后仍然有地方引用它,就产生了闭包。就是这么简单。那么最后,我们来谈一谈一个可以改变当前函数作用链的关键字:with。

with

with的特殊之处是它可以修改当前函数的作用域链。with的作用细节是:它将操作对象附加到当前函数作用域链的顶端,在它的执行区域内,变量查找也会查找该对象的属性,离开with的执行区域,则作用域链又恢复到with之前的模样。以下面代码为例:

function Fun()
{
    with(window.document.forms[0])
    {
        username = '';
        age = 12;
        tel = 1111;
    }
}

在上述代码中,with会将form对象附加到作用域链顶端,之后再with的执行范围内,就能够通过变量查找的方式直接给form的属性赋值。离开with的执行区域,则作用域链上的form对象又会被删除掉。with在某些情况下确实能节省我们的开发工作,比如上面代码所展示的,它可以避免过长的路径引用。但它提供的便利却非常有限。上述代码我们也可以用下面的代码代替:

function Fun()
{
    var form = window.document.forms[0];
    form.username = '';
    form.age = 12;
    form.tel = 1111;
}

在实际开发中,with总是让代码的可读性变得很差,而且它也不太好优化。with的使用率不高,并不存在不可替代的使用场景,多数开发者对它的作用细节也不甚了了。因此,如果你是进行团队开发,尽量不用with,在适合用with的场景下也尽量用其他的替代方案。这倒不仅仅因为代码优化的问题,还可能因为你的队友一看with就一脸懵逼,因为他从来都没有用过。严格模式下,已经禁用了with,相信它也活不了多久了。那么,忘记它吧,也没什么大不了的。
好了,就到这里这里吧,希望你有所收获。。。

相关文章

  • 词法作用域

    我们知道JavaScript并不具有动态作用域,它只有词法作用域,什么是词法作用域? 一、 词法作用域 词法作用域...

  • JavaScript this 绑定规则

    JavaScript 中的作用域是词法作用域。而JavaScript中的 this 却更加类似于 动态作用域的机制...

  • 2.词法作用域

    JavaScript的作用域模型采用的是词法作用域 词法阶段 查找 欺骗词法作用域 既然词法作用域完全由编写时来决...

  • 「JS」变量作用域

    作用域介绍静态作用域动态作用域 JavaScript 变量作用域词法环境组成创建结构关于词法环境的问题with 语...

  • JS作用域的深入理解

    作用域 作用域是指程序源代码中定义变量的区域,JavaScript 采用词法作用域(lexical scoping...

  • JavaScript深入之词法作用域和动态作用域

    转自:JavaScript深入之词法作用域和动态作用域 作用域: 作用域是指程序源代码中定义变量的区域。 作用域规...

  • JavaScript作用域和变量提升

    一、JS的作用域 1.JS采用词法作用域 首先,我们得知道JavaScript采取的是词法作用域,而不是动态作用域...

  • JavaScript 之 静态作用域与动态作用域

    静态作用域 JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。 函数的作用...

  • 一网打尽 JavaScript 的作用域

    JavaScript 的作用域包括:模块作用域,函数作用域,块作用域,词法作用域和全局作用域。 全局作用域 在任何...

  • javascript基础知识问答-作用域和闭包

    1.理解词法作用域和动态作用域2.理解JavaScript的作用域和作用域链3.理解JavaScript的执行上下...

网友评论

      本文标题:JavaScript中的词法作用域

      本文链接:https://www.haomeiwen.com/subject/mxoxuhtx.html