• JavaScript 执行机制



    JavaScript 执行机制 -- 先编译,再执行

    -------------------变量提升 ----------------------

    var myname = "zd" =>

    var myname = undefined // 声明

    myname = "zd" // 赋值

    function foo () {
    console.log('function')

    } => // 完整的函数声明

    所谓的变量提升,是指在JavaScript代码执行过程中,JavaScript
    引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”
    变量提升后,会给变量设置默认值,这个默认值就是我们熟悉的undefined

    Javascript经过编译后,会生成两部分内容:执行上下文和可执行代码

    执行上下文是Javascript执行一段代码时的运行环境


    总结:
    1、 Javascript代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,
    是因为JavaScript在代码执行之前需要编译
    2、在编译阶段,变量和函数都会被存放到变量环境中,变量的默认值会被设置为undefined
    3、在代码执行阶段,Javascript引擎会从变量环境中查找自定义的变量和函数
    4、在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖之前定义的


    -------------------------------------调用栈 ----------------------------------

    1、调用栈是一种用来管理执行上下文的数据结构,符合后进先出的规则
    ,调用栈是有大小的,当入栈的执行上下文超过一定数目,JavaScript引擎就会报错,we call this issue as 栈溢出


    2、执行上下文 包括--全局执行上下文, 调用函数时会创建执行上下文, 使用eval函数时会创建执上下文
    ---需要代码进行编译时,就会创建执行上下文

    3、总结:

    每调用一个函数,Javascript引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后Javascript
    引擎开始执行函数代码

    如果在一个函数A中调用了另外一个函数B,那么Javascript引擎会为B函数创建执行上下文,并将B函数
    的执行上下文压入栈

    当函数执行完毕后,Javascript引擎会将该函数的执行上下文弹出栈


    --------------------------词法环境 --------------------------

    变量提升,会存在变量覆盖,变量未被销毁,的问题,所以Es6提供let, const关键字,使其Javascript像
    其他语言一样拥有块级作用域

    1、函数中 全局var定义的变量,在编译阶段 --都会放在 变量环境中(变量会被赋值undefined)

    2、let和const声明的变量,在编译阶段,会被存放在词法环境中 (只会有定义,没有进行初始化)

    3、在函数的作用域内部,通过 let声明的变量并没有被存放在词法环境中(只有当执行到作用域代码时,才会单独存放在词法环境中,此区域与其他是隔离的,当作用域代码执行完,就会从词法环境栈中弹出)

    4、【单个执行上下文】中查找 变量查找过程是 现在词法环境中的独立作用域 从栈顶到栈底 依次查找 , 若在词法环境中没有找到,则去 变量环境中查找


    let myname= '极客时间'
    {
    console.log(myname) //报错,因为作用域中的myname只有定义,没有声明,所以在未声明之前访问变量会报错
    let myname= '极客邦'
    }

    ---------------------------作用域链 ------------------------
    把通过作用域查找变量的链条称为作用域

    1、其实在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为outer

    2、当一段代码使用了一个变量,Javascript引擎首先会在“当前的执行上下文”中查找该变量, 如果 没找到,则会在outer所指向的执行上下文中查找

    3、词法作用域--是指作用域是由代码中函数声明的位置来决定的,所以词法作用域就是
    静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符
    So,词法作用域是在代码阶段就决定好的,和函数是怎么调用的没有关系

    4、一个执行上下文可以有一个变量环境,和多个词法环境, 若当前执行上下文未找到变量,则会去outer引用的执行上下文依次查找,直到Outer = Null结束查找


    ------------------ 闭包 -------------

    1、在Javascript中,根据词法作用域规则,内部函数总是可以访问其外部函数中声明
    的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数 引用外部函数的变量依然保存在内存中,我们把这些变量的集合称为闭包。


    function foo() {
    var myName = "极客时间"
    let test1 = 1
    const test2 = 2
    var innerBar = {
    getName:function(){
    console.log(test1)
    return myName
    },
    setName:function(newName){
    myName = newName
    }
    }
    return innerBar
    }

    // we can see , myName和test1是foo函数的闭包

    2、闭包若使用不正确,没有合理回收,则会导致内存泄漏

    2-1、当引用闭包的函数 是一个全局变量,那么闭包会一直存在直到页面关闭;但如果
    这个闭包以后不再使用的话,就会造成内存泄漏

    2-2、当引用闭包的函数是一个 局部变量,则等函数销毁后,在下次Javascript引擎
    执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么Javascript引擎
    的垃圾回收器 就会回收这块内存了

    所以在使用闭包的时候,要注意一个原则:如果该闭包会一直使用,那么它可以作为
    全局变而存在,但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个
    局部变量


    var bar = {
    myName:"time.geekbang.com",
    printName: function () {
    console.log(myName)
    }
    }
    function foo() {
    let myName = "极客时间"
    return bar.printName
    }
    let myName = "极客邦"
    let _printName = foo()
    _printName()
    bar.printName()


    // 分析代码执行过程

    首先判断foo函数是否有闭包--因为bar.printName是全局函数,所以不会引用foo函数内部的变量,所以foo函数不存在闭包

    1、全局上下文 : 变量环境有:bar = undefined, foo = function(){}
    词法环境有:myName , _printName

    2、执行代码:全局上下文 - 词法环境 myName= "极客帮" , _printName = foo()

    3、创建foo函数执行上下文,并压入调用栈,其
    变量环境: 无
    词法环境:myName

    继续执行foo函数,foo函数-词法环境 myName = "极客时间" , 查找bar变量

    当前词法环境没有-->当前变量环境没有 --> outer词法环境没有 -->outer变量环境有
    (找到后,返回找到的值,然后 foo函数执行上下文出栈)

    4、此时 _printName = bar.printName

    5、创建printName函数上下文,并压入调用栈,其
    变量环境无,词法环境无

    6、执行printName函数,查找myName变量,当前词法环境没有-->当前变量环境没有-->outer的词法环境有,所以打印值为 “极客帮”, 然后 printName函数上下文 出栈

    7、bar.printName() -- 同理 --结果是 “极客帮


    -------------------- this机制 -------------------------------

    1、执行上下文中 有 变量环境,词法环境,外部环境(outer)还有this , 所以有几种执行上下文,就有几种this

    2、全局上下文的this指的是window对象

    3、默认情况下,调用一个函数,其执行上下文的this也指向 window对象

    function foo(){
    console.log(this)
    }
    foo()

    如何改变this指向的对象??

    ① 使用call函数

    bar = { "name" : "test" }

    foo.call(bar) // 则foo中的this指向的就是bar

    ② 通过对象调用函数

    A、在全局环境中调用一个函数,函数内部的this指向的是全局变量window

    B、通过一个对象来调用其内部的一个方法,该方法的执行上下文 中的this指向对象本身

    ③ 通过构造函数中设置


    function CreateObj(){
    this.name = "极客时间"
    }
    var myObj = new CreateObj()

    // this指的是新对象

    4、this 设计缺陷 以及解决方案


    var myObj = {
    name : "极客时间",
    showThis: function(){
    console.log(this)
    function bar(){console.log(this)}
    bar()
    }
    }
    myObj.showThis()

    issue -- 嵌套函数中的this不会从 外层函数中继承 , 此case中,bar() --因为默认调用函数,其this指的是window,所以function bar中 的this打印的不是 myObj,而是window


    解决方案:

    ① 将 myObj的this 保存下来

    var myObj = {
    name : "极客时间",
    showThis: function(){
    console.log(this);
    var self = this;
    function bar(){console.log(self)}
    bar()
    }
    }
    myObj.showThis()

    ② 使用箭头函数

    var myObj = {
    name : "极客时间",
    showThis: function(){
    console.log(this);
    var bar = () => { console.log(this) }
    bar()
    }
    }
    myObj.showThis()

    因为箭头函数不会创建单自身的函数执行上下文,所以箭头函数的this 取决于它的外部函数

    5、若使用严格模式 , 则 默认的this不会指向window对象,而是undefined

     

     

     

     

     

     

  • 相关阅读:
    完全备份、差异备份以及增量备份的区别(转)
    Backup Exec Inventory 与Catalog的含义(转载)
    从客户端中检测到有潜在危险的Request.Form值的解决办法
    IQueryable与IEnumberable的区别(转)
    SQL递归查询(with cte as) 物料分解
    Http权威指南笔记(二) Http状态码大全
    Http权威指南笔记(一) URI URL URN 关系
    echarts在.Net中使用实例(二) 使用ajax动态加载数据
    echarts在.Net中使用实例(一) 简单的Demo
    sql显示12个月数据
  • 原文地址:https://www.cnblogs.com/QQ-lala/p/12558028.html
Copyright © 2020-2023  润新知