您的当前位置:首页JS执行环境及作用域分析

JS执行环境及作用域分析

2023-11-30 来源:华拓教育

执行环境:定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

全局执行环境:是最外围的一个执行环境。在web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。

执行流的机制:每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而当函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

!!当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一只延续到全局环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。

延长作用域链

执行环境类型:全局和局部。

有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。

  • try-catch语句的catch块(会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明);

  • with语句(会将指定的对象添加到作用域中)。

  • 没有块级作用域

    对于JavaScript来说,由for语句创建的变量i即使在for循环执行结束后,也依旧会存在于循环外部的执行环境中。

    1.变量声明

    使用var声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境;在with语句中,最接近的环境是函数环境。如果初始化变量时没有使用var声明,该变量会自动被添加到全局环境。

    2.查询标识符

    当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域的前端开始,向上逐级查询与给定名字匹配的标识符。

    小编还为您整理了以下内容,可能对您也有帮助:

    如何更好的理解javascript变量类型以及变量作用域


    变量的类型
    Javascript和Java、C这些语言不同,它是一种无类型、弱检测的语言。它对变量的定义并不需要声明变量类型,我们只要通过赋值的形式,可以将各种类型的数据赋值给同一个变量。例如:
    i=100;//Number类型
    i="variable";//String类型
    i={x:4};//Object类型
    i=[1,2,3];//Array类型JS的这种特性虽然让我们的编码更加灵活,但也带来了一个弊端,不利于Debug,编译器的弱检测让我们维护冗长的代码时相当痛苦。

    全局变量和局部变量
    当JS解析器执行时,首先就会在执行环境里构建一个全局对象,我们定义的全局属性就是做为该对象的属性读取,在顶层代码中我们使用this关键字和window对象都可以访问到它。而函数体中的局部变量只在函数执行时生成的调用对象中存在,函数执行完毕时局部变量即刻销毁。因此在程序设计中我们需要考虑如何合理声明变量,这样既减小了不必要的内存开销,同时能很大程度地避免变量重复定义而覆盖先前定义的变量所造成的Debug麻烦。
    变量作用域
    任何程序语言中变量的作用域都是一个很关键的细节。JS中变量的作用域相对与JAVA、C这类语言显得更自由,一个很大的特征就是JS变量没有块级作用域,函数中的变量在整个函数都中有效,运行下面代码:
    <SCRIPT LANGUAGE="JavaScript" type="text/javascript">
    //定义一个输出函数
    function outPut(s){
    document.writeln(s)
    }
    //全局变量
    var i=0;
    //定义外部函数
    function outer(){
    //访问全局变量
    outPut(i); // 0
    //定义一个类部函数
    function inner(){
    //定义局部变量
    var i = 1;
    // i=1; 如果用隐式申明那么就覆盖了全局变量i
    outPut(i); //1
    }
    inner();
    outPut(i); //0
    }
    outer();
    </SCRIPT>输出结果为0 1 0,从上面就可以证明JS如果用var在函数体中声明变量,那么此变量在且只在该函数体内有效,函数运行结束时,本地变量即可销毁了。
    由于上面的这个JS特性,还有一个关键的问题需要注意。此前一直使用ActionScript,虽然它和JS都是基于ECMA标准的,但在这里还是略有不同的。例如下面代码:
    <SCRIPT LANGUAGE="JavaScript" type="text/javascript">
    //定义一个输出函数
    function outPut(s){
    document.writeln(s)
    }
    //全局变量
    var i=0;
    //定义外部函数
    function outer(){
    //访问全局变量
    outPut(i); // 0
    //定义一个类部函数
    function inner(){
    outPut(i); //undefiend
    var i=1;
    outPut(i); //1
    }
    inner();
    outPut(i); //0
    }
    outer();
    </SCRIPT>JS变量作用域
    <script language ="javascript" type ="text/javascript" >
    var a = "change";
    function fun() {
    alert(a);//输出undefined
    var a = "改变了";
    alert(a);//输出改变了
    }
    alert(a);//输出change
    fun();
    </script>var定义的是一个作用域上的变量,在第一次输出a之前,JS在预编译分析中已经将a赋值为change,所以第一次输出change,当调用到fun()函数的时候,JS创建一个新的作用域,在输出a之前,初始化所有var变量的值为undefined,所以fun()中第一次输出的是undefined,第二次输出已经给a赋值了,所以输出新的值;两个a在函数里面和外面是不同的两个变量,如:
    <script language ="javascript" type ="text/javascript" >
    var b;
    function fun() {
    b = "change";
    }
    alert(b);//输出undefined
    </script>变量b在函数外面已经定义了,在函数中有给b赋值,但输出的却是undefined。

    如何更好的理解javascript变量类型以及变量作用域


    变量的类型
    Javascript和Java、C这些语言不同,它是一种无类型、弱检测的语言。它对变量的定义并不需要声明变量类型,我们只要通过赋值的形式,可以将各种类型的数据赋值给同一个变量。例如:
    i=100;//Number类型
    i="variable";//String类型
    i={x:4};//Object类型
    i=[1,2,3];//Array类型JS的这种特性虽然让我们的编码更加灵活,但也带来了一个弊端,不利于Debug,编译器的弱检测让我们维护冗长的代码时相当痛苦。

    全局变量和局部变量
    当JS解析器执行时,首先就会在执行环境里构建一个全局对象,我们定义的全局属性就是做为该对象的属性读取,在顶层代码中我们使用this关键字和window对象都可以访问到它。而函数体中的局部变量只在函数执行时生成的调用对象中存在,函数执行完毕时局部变量即刻销毁。因此在程序设计中我们需要考虑如何合理声明变量,这样既减小了不必要的内存开销,同时能很大程度地避免变量重复定义而覆盖先前定义的变量所造成的Debug麻烦。
    变量作用域
    任何程序语言中变量的作用域都是一个很关键的细节。JS中变量的作用域相对与JAVA、C这类语言显得更自由,一个很大的特征就是JS变量没有块级作用域,函数中的变量在整个函数都中有效,运行下面代码:
    <SCRIPT LANGUAGE="JavaScript" type="text/javascript">
    //定义一个输出函数
    function outPut(s){
    document.writeln(s)
    }
    //全局变量
    var i=0;
    //定义外部函数
    function outer(){
    //访问全局变量
    outPut(i); // 0
    //定义一个类部函数
    function inner(){
    //定义局部变量
    var i = 1;
    // i=1; 如果用隐式申明那么就覆盖了全局变量i
    outPut(i); //1
    }
    inner();
    outPut(i); //0
    }
    outer();
    </SCRIPT>输出结果为0 1 0,从上面就可以证明JS如果用var在函数体中声明变量,那么此变量在且只在该函数体内有效,函数运行结束时,本地变量即可销毁了。
    由于上面的这个JS特性,还有一个关键的问题需要注意。此前一直使用ActionScript,虽然它和JS都是基于ECMA标准的,但在这里还是略有不同的。例如下面代码:
    <SCRIPT LANGUAGE="JavaScript" type="text/javascript">
    //定义一个输出函数
    function outPut(s){
    document.writeln(s)
    }
    //全局变量
    var i=0;
    //定义外部函数
    function outer(){
    //访问全局变量
    outPut(i); // 0
    //定义一个类部函数
    function inner(){
    outPut(i); //undefiend
    var i=1;
    outPut(i); //1
    }
    inner();
    outPut(i); //0
    }
    outer();
    </SCRIPT>JS变量作用域
    <script language ="javascript" type ="text/javascript" >
    var a = "change";
    function fun() {
    alert(a);//输出undefined
    var a = "改变了";
    alert(a);//输出改变了
    }
    alert(a);//输出change
    fun();
    </script>var定义的是一个作用域上的变量,在第一次输出a之前,JS在预编译分析中已经将a赋值为change,所以第一次输出change,当调用到fun()函数的时候,JS创建一个新的作用域,在输出a之前,初始化所有var变量的值为undefined,所以fun()中第一次输出的是undefined,第二次输出已经给a赋值了,所以输出新的值;两个a在函数里面和外面是不同的两个变量,如:
    <script language ="javascript" type ="text/javascript" >
    var b;
    function fun() {
    b = "change";
    }
    alert(b);//输出undefined
    </script>变量b在函数外面已经定义了,在函数中有给b赋值,但输出的却是undefined。

    前端:如何理解 JS 的作用域和作用域链?说说闭包的两个应用场景

    ES6 之前 JS 没有块级作用域。例如

    从上面的例子可以体会到作用域的概念,作用域就是一个独立的 地盘 ,让变量不会外泄、暴露出去。上面的name就被暴露出去了,因此,JS 没有块级作用域,只有全局作用域和函数作用域

    全局作用域就是最外层的作用域,如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样的坏处就是很容易撞车、冲突。

    这就是为何 jQuery、Zepto 等库的源码,所有的代码都会放在(function(){....})()中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。这是函数作用域的一个体现。

    附:ES6 中开始加入了块级作用域,使用let定义变量即可,如下:

    首先认识一下什么叫做 自由变量。如下代码中,console.log(a)要得到a变量,但是在当前的作用域中没有定义a(可对比一下b)。当前作用域没有定义的变量,就称为 自由变量。自由变量如何得到 —— 向父级作用域寻找。

    如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是 作用域链

    通过例子来理解闭包。

    自由变量将从作用域链中去寻找,但是 依据的是函数定义时的作用域链,而不是函数执行时,以上这个例子就是闭包。闭包主要有两个应用场景:

    1.函数作为返回值,上面的例子就是

    2.函数作为参数传递,看以下例子

    前端:如何理解 JS 的作用域和作用域链?说说闭包的两个应用场景

    ES6 之前 JS 没有块级作用域。例如

    从上面的例子可以体会到作用域的概念,作用域就是一个独立的 地盘 ,让变量不会外泄、暴露出去。上面的name就被暴露出去了,因此,JS 没有块级作用域,只有全局作用域和函数作用域

    全局作用域就是最外层的作用域,如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样的坏处就是很容易撞车、冲突。

    这就是为何 jQuery、Zepto 等库的源码,所有的代码都会放在(function(){....})()中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。这是函数作用域的一个体现。

    附:ES6 中开始加入了块级作用域,使用let定义变量即可,如下:

    首先认识一下什么叫做 自由变量。如下代码中,console.log(a)要得到a变量,但是在当前的作用域中没有定义a(可对比一下b)。当前作用域没有定义的变量,就称为 自由变量。自由变量如何得到 —— 向父级作用域寻找。

    如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是 作用域链

    通过例子来理解闭包。

    自由变量将从作用域链中去寻找,但是 依据的是函数定义时的作用域链,而不是函数执行时,以上这个例子就是闭包。闭包主要有两个应用场景:

    1.函数作为返回值,上面的例子就是

    2.函数作为参数传递,看以下例子

    Js基础27:作用域

    函数和变量的有效范围就是作用域

    1、作用域的概念

    这是因为js中存在作用域的概念。

    作用域:

    作用域就是指定一个变量或者一个函数的作用范围。

    能在页面的任何位置都可以访问,称为 全局作用域

    只能在局部(函数内)访问,称为为 局部作用域

    上述代码中,a是全局变量,b是局部变量

    ES5中只有函数才有作用域,所谓是局部作用域也可以叫函数作用域。

    作用域的作用就是为了把数据进行保护,不让外部的数据对我们的数据进行污染

    以下①②③④打印什么?

    但是结果却大出所料,这里得到的结果是undefined。

    ③处的结果也和我们最初的认识是不一样的,结果为f对应的函数对象。

    造成这个结果是因为变量和函数的作用域提升的原因,什么意思呢?

    JS是解释性语言,JS引擎对代码的处理分为两步:

    4、作用域链和访问规则

    在JavaScript里面,函数内部是可以包含另一个函数的

    此时函数b就被函数a包含越来了,这样就形成了两层作用域。

    如果有以下代码:三个同名变量放在三个作用域内

    会依次输出:10,20,30

    虽然多个变量x同名,但是不同作用域内优先使用自己内部作用域的变量x。

    如果代码做一下修改:删除函数b的局部变量x

    依次输出:10,20,20

    函数b内部没有变量b,会向自己的外面的作用域查找x变量,函数a内的x变量离函数b最近,会优先得到函数a的变量x

    代码再做修改:再删除a的局部变量x

    会依次输出:10,10,10

    函数b内部没有x变量,会向函数a的作用域查找,但是函数a内部也没有x变量,会向函数a的上一层作用域再查找,直到查找到了全局作用域。

    代码再次变化:全局的变量x也删除

    函数b内部没有变量x,会顺着上层作用域一层一层地查找,直到全局作用域也没有,就会报错。

    总结:

    理解什么是JS作用域,闭包和基本的JS作用域面试题

    作用域:它是指对某一变量和方法具有访问权限的代码空间, 在JS中, 作用域是在函数中维护的。表示变量或函数起作用的区域,指代了它们在什么样的上下文中执行,亦即上下文执行环境。Javascript的作用域只有两种:全局作用域和本地作用域,本地作用域是按照函数来区分的。

    闭包:在js中的我的理解就是函数嵌套函数,当内部函数在定义它的作用域的外部被引用时,就创建了该内部函数的闭包 ,如果内部函数引用了位于外部函数的变量,当外部函数调用完毕后,这些变量在内存不会被 释放,因为闭包需要它们.

    使用闭包要注意:

    1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

    2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便

    改变父函数内部变量的值。

    本文如未解决您的问题请添加抖音号:51dongshi(抖音搜索懂视),直接咨询即可。