精华内容
下载资源
问答
  • 一、执行上下文的概念当JS引擎解析到可执行代码片段(通常是函数调用阶段)的时候,就会先做一些执行前的准备工作,这个 ...函数执行上下文存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一...

    8c2cb2490268f4730a427026164e5037.png

    一、执行上下文的概念

    JS引擎解析到可执行代码片段(通常是函数调用阶段)的时候,就会先做一些执行前的准备工作,这个 “准备工作”,就叫做 "执行上下文(execution context 简称 EC)"

    二、执行上下文的类型

    全局执行上下文

    只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。

    函数执行上下文

    存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。

    eval函数执行上下文

    指的是运行在 eval 函数中的代码,很少用而且不建议使用。

    三、执行上下文栈

    作用

    用于管理执行在代码执行期间创建的执行上下文

    特点

    1.单线程,在主进程上运行
    2.同步执行,从上往下按顺序执行
    3.全局上下文只有一个,浏览器关闭时会被弹出栈
    4.函数的执行上下文没有数目限制
    5.函数每被调用一次,都会产生一个新的执行上下文环境

    每当有函数被调用时,引擎都会为该函数创建一个新的函数执行上下文然后推入栈中。 当栈顶的函数行完毕后,所对应的上下文出栈,继续执行栈顶的函数

    四、执行上下文的生明周期

    创建阶段

    1、This Binding

    1)全局执行上下文中 this 的值指向全局对象,在浏览器中this 的值指向 window 对象,而在nodejs中指向这个文件的module对象。

    2)函数执行上下文中this 的值取决于函数的调用方式。 具体有:默认绑定、隐式绑定、显式绑定、new绑定、箭头函数。

    2、创建词法环境(LexicalEnvironment)

    「词法环境有两个组成部分」

    1)环境记录:存储变量和函数声明的实际位置

    2)对外部环境的引用:可以访问其外部词法环境

    「词法环境有两种类型」

    1)全局环境 :是一个没有外部环境的词法环境,其外部环境引用为 null。拥有一个全局对象(window 对象)及其关联的方法和属性(例如数组方法)以及任何用户自定义的全局变量,this 的值指向这个全局对象。

    2)函数环境 :用户在函数中定义的变量被存储在环境记录中,包含了arguments 对象。对外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。

    创建变量环境(VariableEnvironment)

    1)变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性。

    2)在 ES6 中,词法 环境和 变量 环境的区别在于前者用于存储函数声明和变量 letconst 绑定,而后者仅用于存储变量var绑定。

    执行阶段

    「执行阶段主要做三件事情」

    1、变量赋值

    2、函数引用

    3、执行其他的代码

    注意 :如果 Javascript 引擎在源代码中声明的实际位置找不到 var 变量的值,那么将为其分配 undefined 值。

    销毁阶段

    执行完毕出栈,等待回收被销毁

    五、实例讲解

    let a = 20;  
    const b = 30;  
    var c;
    
    function multiply(e, f) {  
     var g = 20;  
     return e * f * g;  
    }
    
    c = multiply(20, 30);
    
    // ----------------------------------------
    GlobalExectionContext = {
        // this绑定
        ThisBinding: < Global Object > ,
        // 创建词法环境
        LexicalEnvironment: {
            EnvironmentRecord: {
                Type: "Object",
                // 标识符绑定在这里  
                a: < uninitialized > ,
                b: < uninitialized > ,
                multiply: < func >
            }
            outer: < null >
        },
        // 创建变量环境
        VariableEnvironment: {
            EnvironmentRecord: {
                Type: "Object",
                // 标识符绑定在这里  
                c: undefined,
            }
            outer: < null >
        }
    }
    // 函数执行上下文
    FunctionExectionContext = {
        // this绑定
        ThisBinding: < Global Object > ,
        // 创建词法环境
        LexicalEnvironment: {
            EnvironmentRecord: {
                Type: "Declarative",
                // 标识符绑定在这里  
                Arguments: {
                    0: 20,
                    1: 30,
                    length: 2
                },
            },
            outer: < GlobalLexicalEnvironment >
        },
        // 创建变量环境
        VariableEnvironment: {
            EnvironmentRecord: {
                Type: "Declarative",
                // 标识符绑定在这里  
                g: undefined
            },
            outer: < GlobalLexicalEnvironment >
        }
    }
    

    「变量提升的原因」: 在创建阶段,函数声明存储在环境中,而变量会被设置为 undefined(在 var 的情况下)或保持未初始化(在 let 和 const 的情况下)。所以这就是为什么可以在声明之前访问 var 定义的变量(尽管是 undefined ),但如果在声明之前访问 let 和 const 定义的变量就会提示引用错误的原因。这就是所谓的变量提升。

    六、执行上下文总结

    对于 ES5 中的执行上下文,我们可以用下面这个列表来概括程序执行的整个过程:

    1.程序启动,全局上下文被创建

      1.1创建全局上下文的词法环境

        1.1.1创建对象环境记录器它用来定义出现在全局上下文中的变量和函数的关系(负责处理 let 和 const 定义的变量)

        1.1.2创建外部环境引用,值为 null

      1.2创建全局上下文的变量环境

        1.2.1创建对象环境记录器,它持有变量声明语句在执行上下文中创建的绑定关系(负责处理 var 定义的变量,初始值为 undefined 造成声明提升)

        1.2.2创建外部环境引用,值为 null

        1.2.3确定 this 值为全局对象(以浏览器为例,就是 window )

    2.函数被调用,函数上下文被创建

      2.1创建函数上下文的词法环境

        2.1.1创建声明式环境记录器,存储变量、函数和参数,它包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理 let 和 const 定义的变量)

        2.1.2创建外部环境引用,值为全局对象,或者为父级词法环境(作用域)

      2.2创建函数上下文的变量环境

        2.2.1创建声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理 var 定义的变量,初始值为 undefined 造成声明提升)

        2.2.2创建外部环境引用,值为全局对象,或者为父级词法环境(作用域)

      2.3确定 this 值

    3.进入函数执行上下文的执行阶段:

      3.1在上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。

    参考

    JavaScript进阶-执行上下文(理解执行上下文一篇就够了) JavaScript执行上下文-执行栈

    展开全文
  • 执行上下文 变量提升(Hoisting)被认为是, Javascript执行上下文 (特别是创建和执行阶段)工作方式的一种认识 编译阶段,JavaScript会为上述代码创建一个执行上下文和可执行代码。 执行上下文是JavaScript...

    前言

    众所周知,JavaScript是单线程语言。所以JavaScript是按顺序执行的!

    先编译再执行

    变量提升

    请看下面的例子:

    console.log(cat)
    catName("Chloe");
    var cat = 'Chloe'
    function catName(name) {
        console.log("我的猫名叫 " + name);
    }
    

    按照得出的结论:"JavaScript是按顺序执行的"来看,步骤如下:

    • 执行第一句的时候,cat并没有定义,结果应该是抛出一个错误,然后结束执行。
    Uncaught ReferenceError: cat is not defined
    

    但实际的执行结果并不是这样:

    不仅可以执行,catName()执行结果也输出了。

    4fda87e6c5429612a1420aec5081a049.png

    这种现象就是: 变量提升

    从概念的字面意义上说,“变量提升”就是把变量和函数的声明移动到代码的最前面,变量被提升后,会给变量设置默认值--undefined。

    调整之后的执行顺序如下:

    • 首先执行var cat = undefined和function catName(){}
    • 然后执行console.log(cat) // undefined
    • 接着调用catName()
    • 最后给cat赋值cat = 'Chloe'

    移动一词容易造成误解。实际在物理层面上代码的位置并没有改变。JavaScript是解析执行的语言,在执行前会先经过编译阶段。造成这种现象的原因是:JavaScript引擎在编译阶段中将变量和函数的声明放在了内存中。

    执行上下文

    变量提升(Hoisting)被认为是, Javascript中执行上下文 (特别是创建和执行阶段)工作方式的一种认识

    在编译阶段,JavaScript会为上述代码创建一个执行上下文和可执行代码。

    18e3978af4d58677a7fd4ec34c7b22b5.png

    执行上下文是JavaScript执行一段代码时的运行环境,包含this、变量、对象以及函数等。

    1、在编译阶段

    • JavaScript引擎会将var变量声明和函数声明等的变量提升内容放在变量环境中。
    • 接下来JavaScript引擎会把声明以外的代码编译为字节码--可执行代码。

    2、执行阶段

    • 执行到console.log(cat)时,JavaScript引擎在变量环境中查找cat这个变量,由于变量环境存在cat变量,并且其值为undefined,所以这时候就输出undefined。
    • 当执行到catName函数时,引擎在变量环境中查找该函数,由于变量环境中存在该函数的引用,所以引擎执行该函数,并输出执行结果。
    • 执行cat赋值,引擎在变量环境查找到cat变量,并进行赋值。

    创建执行上下文的三种情况:
    1、全局执行上下文:JS引擎在编译全局代码时,创建全局执行上下文。在当前页面中,全局执行上下文仅有一个。

    2、函数执行上下文:在调用一个函数时,JS引擎会创建一个函数执行上下文。一般情况下,当函数执行完毕后就会销毁此函数执行上下文。

    3、eval函数执行上下文:执行eval函数时,也会创建一个执行上下文。

    调用栈

    JS引擎通过栈的数据结构来管理多个执行上下文。

    栈是计算机科学中的一种抽象数据类型,只允许在有序的线性数据集合的一端(称为堆栈顶端,英语:top)进行加入数据(英语:push)和移除数据(英语:pop)的运算。因而按照后进先出(LIFO, Last In First Out)的原理运作

    2a2b2cf3b3bd2bf7ef29c5600dddd111.png

    在一个执行上下文创建好后,JS引擎就会它压进栈中。管理执行上下文的栈结构就称为调用栈,或者执行上下文栈。

    请看下面例子:

    function foo() {
        var a = 0
        console.log(a)
    }
    function bar() {
        var b = 1
        foo()
        console.log(b)
    }
    bar()
    

    步骤如下:

    1、创建全局执行上下文,并将其压入栈底。

    2、执行全局代码:bar()。调用bar函数时,JS引擎会编译bar函数,并为其创建一个函数执行上下文。最后将其执行上下文压入栈中,并且将变量b赋予默认值undefined。

    e8a2d8e14b633ba0d2e1f72ce9f3bfa4.png

    3、执行bar函数内部的代码。先执行b = 1的赋值操作,然后调用foo函数。JS引擎编译foo函数,并为其创建一个函数执行上下文。最后将其执行上下文压入栈中,并且将变量a赋予默认值undefined。

    c65838bea9ac7612c12710c0ff830e46.png

    4、执行foo内部的代码。执行a = 1赋值操作,然后输出a的值。foo函数执行完毕后,调用栈就将其执行上下文从栈顶弹出。接着执行bar函数。

    5、执行完bar函数后,调用栈就将其执行上下文从栈顶弹出。剩下全局执行上下文

    整个JavaScript流程执行就到此结束了。

    调用栈是JS引擎追踪函数执行的一个机制,当一次有多个函数被调用时,通过调用栈就能够追踪到哪个函数正在被执行以及各函数之间的调用关系。

    var缺陷与块级作用域

    变量提升带来的问题

    1、变量被覆盖

    var cat = "foo"
    function catName(){
      console.log(cat);
      if(false){
       var cat = "bar"
      }
      console.log(cat);
    }
    catName()
    

    调用catName时,调用栈如下图所示:

    0b916d3c0245f9e7344417b5c9f03f5f.png
    • 创建catName执行上下文时,JavaScript引擎会将var变量声明cat提升内容放在变量环境中,赋予默认值undefined。
    • 执行到catName内部的console.log(cat)时,在catName执行上下文中的变量环境找到了cat的值,输出undefined。
    • if判断为false,不执行。
    • 执行console.log(cat),参照第二步,输出undefined。

    2、变量没被销毁

    function foo () {
        for (var i=0; i<10; i++){}
        console.log(i)
    }
    foo()
    

    直观的来说,会以为for循环结束后,i会被销毁。结果并非如此,console.log(i)输出10。

    原因也是变量提升,在创建foo执行上下文时,i被提升了。所以for循环结束后,i并没有被销毁。

    块级作用域

    存储变量中的值以及对这个值进行访问或修改,是编程语言的基本功能。而 作用域 则是如何存储变量以及如何访问这些变量的规则。

    在ES6前,JavaScript只支持两种方法创建作用域:

    • 全局作用域
    • 函数作用域

    而其他编程语言则都普遍支持块级作用域。

    块级作用域 就是使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域。

    简单来讲,在块级作用域内部定义的变量在其块级作用域外部是访问不到的,并且等该内部代码执行完成之后,其定义的变量会被销毁。

    由于JavaScript不支持块级作用域,所以才会有变量提升带来的问题。

    幸好,ES6改变了现状,引入了新的let和const关键字,提供了除var以外的另一种变量声明方式。

    let和const关键字可以将变量绑定到所在的任意作用域中(通常是{}内部)。换句话说,let为其声明的变量创建了块作用域。

    块级作用域的作用,请看下面例子:

    var cat = "foo"
    function catName(){
      if(true){
       var cat = "bar"
       console.log(cat);
      }
      console.log(cat);
    }
    catName()
    

    在这段代码中,有两处声明了cat变量,一处在全局作用域,一处在catName函数作用域中的if语句里面。

    在执行if语句内部时,调用栈如下图所示:

    62b8b189b6971f7430119e8390ab01dc.png

    从图中可看出两处console.log(cat)都输出bar。

    使用let改写上面代码

    var cat = "foo"
    function catName(){
      if(true){
       let cat = "bar"
       console.log(cat);
      }
      console.log(cat);
    }
    catName()
    

    if语句执行结束后,let声明的cat变量就会被销毁,第二处的console.log(cat)就会输出foo

    44ba57e822ed2ad64ff2eb9351af4d93.png

    JavaScript内部实现块级作用域

    请看下面的例子

    function foo(){
        var a = 1
        let b = 2
        {
          let b = 3
          var c = 4
          let d = 5
          console.log(a)
          console.log(b)
        }
        console.log(b) 
        console.log(c)
        console.log(d)
    }   
    foo()
    

    步骤如下:

    1、第一步创建全局执行上下文

    2、执行foo(),创建foo函数的执行上下文

    1f37c0f114dbb7d2338ece7f5b25c016.png
    • 在函数内部使用var声明的变量都放在变量环境中,并赋予一个默认值undefined。
    • 在函数内部使用let声明的变量被放在词法环境中,没有赋予一个默认值。
    • 在函数内部中的{}内部使用let声明的变量没有放在词法环境中。

    3、执行foo函数内部的{}块,此时a和b的已经初始化了,并且进入作用域块时,作用域块中通过let声明的变量,会被存放在词法环境的一个单独的区域中,这个区域中的变量并不影响作用域块外面的变量。

    af1d3b4bbd472688792f869aecf98c32.png

    在词法环境内部维护了一个栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压入栈中;当作用域执行完成之后,该作用域的let和const声明的变量就会从栈顶弹出。

    4、作用域块执行结束后,词法环境的栈结构就把其信息从栈顶弹出。

    5967f88ff3d59fe41c8665f394875d46.png

    使用let或const声明的变量,在达到声明处之前都是无法访问的,试图访问会导致一个 引用错误,即使在通常是安全的操作时(例如使用typeof运算符)也是如此。示例如 下:

    if (true) {
        console.log(typeof value); // 引用错误
        let value = 'blue'
    }
    

    因为value位于暂时性死区(temporal dead zone, TDZ)的区域内--该名称并没有在ECMAScript规范中被明确命名,但经常被用于描述let或const声明的变量为何在声明之前无法被访问。

    总结

    1、JavaScript代码是先编译再执行的。

    2、执行是按顺序一段一段执行的,一段代码是指一个执行上下文。

    3、执行上下文有三种情况:

    • 全局执行上下文
    • 函数执行上下文
    • eval执行上下文

    4、let和const支持块级作用域

    作者:zhangwinwin
    链接:JavaScript代码是怎么执行的?
    来源:github
    展开全文
  • 通过一些代码的执行顺序与经验我们知道:执行过程,若使用了未声明的变量,那么 JavaScript 执行会报错。一个变量定义之前使用它,不会出错,但是该变量的值会为 undefined,而不是定义时的值。一个函数定义...
    a347472013bf1291b171cb5d755ee536.png

    |变量提升:JavaScript代码是按顺序执行的吗?


    本节主要讲解执行上下文相关的内容。

    通过一些代码的执行顺序与经验我们知道:

    在执行过程中,若使用了未声明的变量,那么 JavaScript 执行会报错。在一个变量定义之前使用它,不会出错,但是该变量的值会为 undefined,而不是定义时的值。在一个函数定义之前使用它,不会出错,且函数能正确执行。

    变量提升

    所谓的变量提升,是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined.
    之所以会发生变量提升,是因为一段JavaScript代码在执行之前,需要被JavaScript引擎编译,编译完成之后,才会进入执行阶段。也就是说在编译阶段,变量和函数的声明提升到了开头。

    |调用栈:为什么JavaScript代码会出现栈溢出?


    一般有三种情况,当一段代码执行的时候JS引擎对其进行编译并创建执行上下文:

    当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份.当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。当使用 eval 函数的时候,eval 的代码也会被编译,并创建执行上下文。

    小结

    每调用一个函数,JavaScript 引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后 JavaScript 引擎开始执行函数代码。如果在一个函数 A 中调用了另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶。当前函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈。当分配的调用栈空间被占满时,会引发“堆栈溢出”问题。

    | 块级作用域:var缺陷以及为什么要引入let和const


    作用域

    作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。
    ES6出现之前,JS的作用域只有两种:全局作用域和函数作用域。 ES6出现,引入了块级作用域。

    在同一段代码中,ES6 是如何做到既要支持变量提升的特性,又要支持块级作用域的呢?

    当一段代码里面既有var声明的变量也有let声明的变量的时候:

    函数内部通过var声明的变量,在编译阶段全都被存放到变量环境里面.通过let声明的变量,在编译阶段会被存放到词法环境中。在函数作用域内部,通过let声明的变量并没有被存放到词法环境中。
    也就是说:通过理解词法环境的结构和工作机制,块级作用域是通过词法环境的栈结构来实现的,而变量提升是通过变量环境来实现的,通过两者的结合,JavaScript引擎也就同时支持了变量 提升和块级作用域了。

    | 作用域和闭包:代码中出现相同的变量,JavaScript引擎是如何选择的


    作用域链

    理解作用域链是理解闭包的基础,而闭包在JavaScript中无处不在,同时作用域和作用域链还是作用语言的基础,所以我们先来学习一下作用域链。 理解了调用栈、执行上下文、词法环境、变量环境等概念,那么你理解起来作用域链也会很容易,看下面一段代码:

    function bar() { console.log(myName) } function foo() { var myName = "局部变量" bar() } var myName = "全局变量" foo() 复制代码

    通过上面的代码,我们知道最终打印出来的结果是:”全局变量“。
    这是因为,当一段代码使用了一个变量后,JavaScript引擎会首先在“当前的执行上下文”中去查找该变量。若没有找到,由于每个执行上下文都包含一个外部引用指向外部执行上下文,所以bar函数中的变量会去全局上下文中区域查找。我们把这个查找的链条就称为作用域链。

    词法作用域

    foo 函数调用的 bar 函数,那为什么 bar 函数的外部引用是全局执行上下文,而不是 foo 函数的执行上下文?了解这个问题我们继续来学习词法作用域:
    词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。
    然后,根据词法作用域,foo 和 bar 的上级作用域都是全局作用域,所以如果 foo 或者 bar 函数使用了一个它们没有定义的变量,那么它们会到全局作用域去查找。也就是说,词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系。

    闭包

    在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。 在使用闭包的时候,要尽量注意一个原则:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。

    | this:从JavaScript执行上下文的视角讲清楚this


    首先我们要知道,在对象内部的方法中使用对象内部的属性是一个非常普遍的需求,但是JavaScript作用域机制并不支持这一点,基于这个需求,JavaScript搞出了一套this机制。

    在前几节中,我们提到执行上下文中包含了:变量环境、词法环境、外部环境、还有一个没有提及的this,this是和执行上下文绑定的,每个执行上下文都有一个this。
    在08节我们总结了执行上下文主要分三种:全局执行上下文、函数执行上下文和eval执行上下文。
    对应的this也只有这三种:全局执行上下文中的this、函数执行上下中的this和eval中的this(不做讨论)。

    全局执行上下文中的this:全局执行上下文中的this指向window对象。函数执行上下文中的this:默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象的.通过函数的call方法设置其this指向其他对象(还可以使用bind和apply方法来设置函数执行上下文中的this)。通过对象调用方法设置。(使用对象来调用其内部的一个方法,该方法的 this 是指向对象本身的。在全局环境中调用一个函数,函数内部的this指向的是全局变量window)。通过构造函数中设置。

    this的设计缺陷以及应对方案

    嵌套函数的this不会从外层函数中继承。==> 1⃣️、将this保存一个self变量,利用变量作用域机制传递给嵌套函数。2⃣️、将乔套函数改为箭头函数。普通函数中的this默认指向全局对象window。==>可以通过设置JavaScript的“严格模式”来解决。

    展开全文
  • (execution context)当 JavaScript 代码执行一段可执行代码(executable code, EC)时,会创建对应的执行上下文有三种执行上下文:全局执行上下文:这是默认或者说基础的上下文,任何不在函数内部的代码全局上下...

    dd347344e3ee9cb4bff51970e1a19bd5.png

    执行上下文 (execution context)

    当 JavaScript 代码执行一段可执行代码(executable code, EC)时,会创建对应的执行上下文

    有三种执行上下文:

    • 全局执行上下文:这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文
    • 函数执行上下文:每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序执行一系列步骤
    • eval 函数执行上下文:执行在 eval 函数内部的代码也会有它属于自己的执行上下文

    通过创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文

    执行上下文有三个重要属性:

    • 变量环境(Variable object,VO
    • 作用域链(Scope chain
    • this

    执行上下文会分成两个阶段来处理:

    • 创建
    • this 值的决定
    • 创建词法环境
    • 创建变量环境
    • 执行

    this

    this 所指向的对象:

    • 全局作用域中,上下文总是 window 对象
    • 如果作用域定义在一个对象的方法中,上下文就是这个方法所在的那个对象
    • 如果使用 new 调用函数时,上下文会被设置为调用函数的实例

    在使用严格模式下的全局作用域中调用函数时,上下文默认是 undefined,因为严格模式禁止 this 指向全局对象。

    词法环境

    词法环境是一种持有标识符—变量映射的结构。(这里的标识符指的是变量/函数的名字,而变量是对实际对象[包含函数类型对象]或原始数据的引用)。

    词法环境的内部有两个组件:

    • 环境记录器:环境记录器是存储变量和函数声明的实际位置
    • 外部环境的引用:外部环境的引用意味着它可以访问其父级词法环境(作用域)

    词法环境有两种类型:

    • 全局环境(在全局执行上下文中)是没有外部环境引用的词法环境。全局环境的外部环境引用是 null。它拥有内建的 Object/Array/等、在环境记录器内的原型函数(关联全局对象,比如 window 对象)还有任何用户定义的全局变量,并且 this 的值指向全局对象。
    • 在函数环境中,函数内部用户定义的变量存储在环境记录器中。并且引用的外部环境可能是全局环境,或者任何包含此内部函数的外部函数。

    环境记录器也有两种类型:

    • 声明式环境记录器存储变量、函数和参数。
    • 对象环境记录器用来定义出现在全局上下文中的变量和函数的关系。

    简而言之:

    • 在全局环境中,环境记录器是对象环境记录器。
    • 在函数环境中,环境记录器是声明式环境记录器。

    注意 — 对于函数环境,声明式环境记录器还包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length

    变量环境

    它同样是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。 如上所述,变量环境也是一个词法环境,所以它有着上面定义的词法环境的所有属性。 在 ES6 中,词法环境组件和变量环境的一个不同就是前者被用来存储函数声明和变量(let 和 const)绑定,而后者只用来存储 var 变量绑定。

    全局作用域和全局变量

    如果变量定义在函数之外,则变量就处于全局作用域中:

    // 默认全局作用域
    var a = 1;
    function fn() {}
    

    全局作用域中的变量可以在任何其他作用域中访问和修改:

    var a = 1;
    function fn() {
      console.log(a); // 显示 a
      a = 2; // 修改 a
    }
    

    全局变量的声明有三种方式:

    • 不带 var 无论是在哪里,甚至是在函数内部声明,都会隐式声明成全局变量
    • var 在函数外部声明,这种是显式声明
    • 使用 window 全局对象来声明,全局对象的属性对应也是全局变量

    全局变量的优点: 可以减少变量个数,减少由于实参和形参的数据传递带来的时间消耗

    全局变量的缺点:

    • 全局变量保存在静态存储区,程序开始运行时为其分配内存,程序结束释放内存,和局部变量的动态分配、动态释放相比,生存期比较长,过多的全局变量会占用较多的内存单元
    • 全局变量破坏了函数的封装性能,使得函数对全局变量产生依赖,同时降低了函数的可移植性
    • 全局变量使得函数的可读性降低,由于多个函数可能使用相同的全局变量,对于程序的查错和调试都非常不利

    注:尽可能少的使用全局变量

    局部作用域

    javascriptC++ 之类的语言不同,它的局部作用域是以函数为单位,而不是块级作用域。(注:最新的标准提供了块级作用域)

    每个函数被调用的时候都具有不同的作用域,意味着相同名称的变量可以在不同的函数中使用,这是因为它们都是属于不同的作用域,在其他函数中不可被访问。

    块级作用域

    块级声明包括:

    • if
    • switch
    • for
    • while

    es6 中引入了 letconst ,这些关键字支持在块级声明中创建使用局部作用域。

    生存周期

    全局作用域的生存周期和应用相同,局部作用域只在函数调用执行期间存在。

    作用域链

    当查找变量的时候,会先从当前上下文的变量环境中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量环境中查找,一直找到全局上下文的变量环境,也就是全局对象。这样由多个执行上下文的变量环境构成的链表就叫做作用域链。

    在函数定义的时候创建(作用域在定义的时候决定所以作用域链也是)

    作用域链包含变量环境,作用域链用于解析变量,当解析一个变量时,开始从最内层沿着父级寻找所需的变量或者其他资源,作用域链包含自己执行环境以及父级环境中包含的变量环境

    内层函数(必须是函数声明)可以访问父级作用域的变量等资源。意味着子函数词法绑定到了父级执行环境。

    • 子执行环境可以访问父级变量,反之不行
    • 在不同执行环境中同名变量优先级在执行
    展开全文
  • 本规范旨在为日常Go项目开发提供一个代码的规范指导,...一、 命名规范命名是代码规范很重要的一部分,统一的命名规则有利于提高的代码的可读性,好的命名仅仅通过命名就可以获取到足够多的信息。Go命名时以字...
  • 代码: Lib/tempfile.py该模块用于创建临时文件和目录,它可以跨平台使用。TemporaryFile、NamedTemporaryFile、TemporaryDirectory 和 SpooledTemporaryFile 是带有自动清理功能的高级接口,可用作上下文管理器。...
  • 代码: Lib/tempfile.py该模块用于创建临时文件和目录,它可以跨平台使用。TemporaryFile、NamedTemporaryFile、TemporaryDirectory 和 SpooledTemporaryFile 是带有自动清理功能的高级接口,可用作上下文管理器。...
  • 章节4基础代码一般结构本章节,我们开始使用Vulkan API编写代码。#include <vulkan/vulkan.h> #include <iostream> #include <stdexcept> #include <functional> #include <cstdlib&...
  • 上下文管理器可以使用 with 语句,with语句之所以这么强大,背后是由上下文管理器做支撑的,也就是说刚才使用 open 函数创建文件对象就是就是一个上下文管理器对象。自定义上下文管理器类,模拟文件操作:定义一个...
  • 前言正文开始前,先来看两个JavaScript代码片段。▣ 代码一console.log(a);vara=10;▣ 代码二fn1();fn2();functionfn1(){console.log('fn1');}varfn2=function(){console.log('fn2');}如果你能正确的回答并解释...
  • 新版本还对GUI、效果编译器、Vulkan后端实现等进行了诸多改进,并对DirectX 9和DirectX12的深度缓冲区检测进行了优化。下载地址:https://reshade.me/除了上述这些内容之外,本次更新还添加了其他细节方面的调整,...
  • 创建一个C文件,并Python中调用该C文件中函数 目录首先创建sample.c和sample.h文件,并确保没有错误编译成动态链接库文件(Linux:.so, Windows:.dll)测试库文件Python访问C代码对c库文件进行封装对Python包装模块...
  • 但如果你碰巧要多个脚本使用同一段代码呢? 显然,为了使用一次而每个脚本中都定义同样的函数太麻烦了。为了解决这个问题,!bash shell允许创建函数文件。 接下来就自定义创建自己的函数库文件,然后多个...
  • 第一步:新建一个MFC对话框应用程序。 第二步:创建一个DLL(同一个工作空间,这样方便调试) 对话框的按钮响应函数里,输入如下代码: 运行效果: ...
  • 就可以 <code>IE9</code> 到 <code>IE11</code> 运行 应该是箭头函数的问题 这个箭头函数是整个js文件里面唯一的箭头函数 而且不是业务代码里面的 这个问题只会出现开发环境 编译出来的js是没有这样的问题...
  • 2、通过将函数存储独立文件中,可以让我们把重点放在程序的高层逻辑上。而且不同的程序中,也可以重复使用这个模块。 1、导入整个模块 要让函数是可以导入的,那首先得创建一个模块,把函数放进去。模块是扩展名...
  • 有时候写代码为了简便,会将一些函数...首先创建一个method.h头文件,声明一个求最大值的函数max(int x,int y),然后再创建一个同名的源文件method.cpp,文件中实现函数max(int x,int y)的功能。代码如下: //metho
  • Linux中创建一个新进程的唯一方法是使用fork()函数。fork()函数是Linux中一个非常重要的函数,和以往遇到的函数有一些区别,因为fork()函数看起来执行一次却返回两个值。 fork()函数用于从已存在的进程中创建一个...
  • 如何Hive中创建自定义函数UDF及使用如何Impala中使用Hive的自定义函数UDF函数开发使用Intellij工具开发Hive的UDF函数,进行编译;1.使用Intellij工具通过Maven创建一个Java工程,并添加pom.xml依赖org.apache....
  • 我写了一个代码,打印文件创建和修改日期,一个对24小时前修改过的旧文件进行排序的函数。但是我正在努力创建一个脚本,这个脚本可以将这些特定的文件传输到一个新的文件夹。这是我代码的第一部分:import ...
  • 父进程与子进程   父进程创建子进程,两进程的关系(两种): 父子进程执行不同的代码段:父进程希望子进程继承自己的代码段...  exec函数在进程是一系列函数,其作用是进程执行可执行文件。根据其不同...
  • 项目Js文件需要完成某一功能,但这一功能的大部分代码在另外一个Js文件已经完成,只需要调用这个文件实现功能。那么如何调用:一个Js文件函数中调用另一个Js文件函数的方法? (直接代码说明) 示例demo: 首先创建三个...
  • 自己基础太差,做项目的时候将所有的代码都写main函数中,感觉太low了,因此决定把算法拆分为各个函数。这个是拆分出来的第一个函数,也算是我第一次正儿八经的写自己的函数。 一开始想用return函数,但是自己...
  • 我正在用Java创建一个小板游戏,我想XML文件中实现游戏关卡(即每个级别,关卡,关卡的高度和长度,以及每个单元的定义),然后通过询问用户他想要玩的等级来构建棋盘。我已经Board类中构建了一个经典的构造函数,...
  • MATLAB程序(.mlx)实时脚本文件不支持脚本文件创建function函数,然后我就新建了一个.m文件运行.mlx文件时,系统提示打不开function函数所属的.m文件。 这时候,我把代码检查了一遍没错误。 把function...
  • //函数的分文件编写可以让代码结构更加清晰,方便大型项目开发: //分为四步: //1、创建后缀名为 .h 的头文件; //2、创建后缀名为 .cpp 的源文件; //3、头文件函数的声明; //4、源文件函数的定义; ...
  • python-创建函数

    2020-02-14 16:14:17
    声明和定义有区别的语言中, 往往是因为函数的定义可能和其声明放在不同的文件中。 python将这两者视为一体,函数的子句由声明的标题行以及随后的定义体组成的。 二、函数属性 你可以获得每个 py...
  • 命名空间(PHP V5.3引入)是一种提供PHP类,常量和函数上下文的方法,以便具有相同名称的元素可以理解为唯一。 唯一的名称使您避免命名冲突,当存在两个相同名称的类或函数时,会发生命名冲突。 有时,这些PHP类...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,986
精华内容 1,594
关键字:

在代码文件中创建函数