浏览器中的js执行机制

一、JS代码执行流程

JS的执行机制:先编译,再执行。js代码在编译阶段,会创建执行上下文,变量和函数会被放到变量环境中,变量初始化为undefiend;在执行阶段,js引擎会从变量环境中查找自定义的变量和函数。

变量提升:js代码执行过程中,js引擎会把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后会给变量设置默认值undefined。

变量提升1.png

变量提升2.png

实际上变量和函数声明在代码里的位置是不会改变的,而是在编译阶段被js引擎放入内存中。(js代码先被js引擎编译,编译完成后进入执行阶段)。具体分析:
一、编译阶段
1、执行上下文:js执行一段代码时的运行环境,在执行上下文中存在一个变量环境的对象(Viriable Environment),该对象中保存了变量提升的内容。
示例:

showName()
console.log(myname)
var myname = '极客时间'
function showName() {
    console.log('函数showName被执行');
}
  • 第 1 行和第 2 行,这两行代码不是声明操作,所以 JavaScript 引擎不会做任何处理;
  • 第 3 行,由于这行是经过 var 声明的,因此 JavaScript 引擎将在环境对象中创建一个名为 myname 的属性,并使用 undefined 对其初始化
  • 第 4 行,JavaScript 引擎发现了一个通过 function 定义的函数,所以它将函数定义存储到堆 (HEAP)中,并在环境对象中创建一个 showName 的属性,然后将该属性值指向堆中函数的位置。
    这样就生成了变量环境对象。接下来 JavaScript 引擎会把声明以外的代码编译为字节码。
    二、执行阶段
    JavaScript 引擎开始执行“可执行代码”,按照顺序一行一行地执行。
js执行流程图.png
  • 注:如果存在同名的函数或者同名的变量,在编译阶段,前者会被后者覆盖。即,最终存储在变量环境中的是最后定义的那个。如果变量和函数同名,编译阶段,变量的声明会被忽略,变量环境中存储的是函数声明,而不论顺序(函数提升比变量提升优先级高)

代码编译时有三种情况会创建执行上下文

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

调用栈:在执行上下文创建好后,js引擎会将执行上下文压入栈中。这种用来管理执行上下文的栈称为执行上下文栈,又称调用栈。
示例:

var a = 2
function add(b,c) {
  return b+c
}
function addAll(b,c) {
var d = 10
result = add(b,c)
return  a+result+d
}
addAll(3,6)
  • 第一步,创建全局上下文,并将其压入栈低


    全局执行上下文压栈.png

    全局执行上下文压入到调用栈后,JavaScript 引擎便开始执行全局代码了。首先会执行 a=2 的赋值操作,执行该语句会将全局上下文变量环境中 a 的值设置为 2。

  • 第二步,调用addAll函数。当调用该函数时,js引擎会编译该函数,并为其创建一个执行上下文,最后将该函数的执行上下文压入栈中


    执行addAll时的调用栈.png

    addAll 函数的执行上下文创建好之后,便进入了函数代码的执行阶段了,这里先执行的是 d=10 的赋值操作,执行语句会将 addAll 函数执行上下文中的 d 由 undefined 变成了 10。

  • 第三步,当执行到 add 函数调用语句时,同样会为其创建执行上下文,并将其压入调用栈


    执行add时的调用栈.png
  • 第四步,当add函数返回,该函数的执行上下文会从栈顶弹出,并将result的值设置为add的返回值


    add执行结束时的调用栈.png
  • addAll执行完,其执行上下文也会从栈顶弹出,此时只剩下全局上下文


    addAll执行结束时的调用栈.png

    整个js流程执行结束

二、js如何支持块级作用域

作用域:全局作用域、函数作用域、块级作用域
ES6通过let/const关键字可以实现块级作用域,在编译阶段,js引擎不会把块级作用域中的变量存放到变量环境中,而是存放到词法环境中,块级作用域是通过词法环境的栈结构来实现的。
示例:

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()
  • 第一步:编译并创建执行上下文
    foo执行上下文.png

    var声明的变量在变量环境
    let/const声明的变量在词法环境
    在作用域块内部,let/const声明的变量不在词法环境
  • 继续执行代码,并赋值,当进入作用域块中,let/const声明的变量会被存放在词法环境的一个单独区域中
    执行到块作用域时的上下文.png

    在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出。
    块级作用域就是通过词法环境的栈结构来实现的,而变量提升是通过变量环境来实现,通过这两者的结合,JavaScript 引擎也就同时支持了变量提升和块级作用域了。
    注:
  • var的创建和初始化被提升,赋值不会被提升
  • let/const的创建被提升,初始化和赋值不会被提升
  • function的创建、初始化和赋值均被提升

三、作用域链和闭包

每个执行上下文的变量环境中都包含了一个外部引用outer,用来指向外部的执行上下文。
当一段代码使用一个变量时,js引擎会先在“当前的执行上下文”中查找,如果在当前的变量环境中没有找到,js引擎会继续在outer所指向的执行上下文中查找,这个查找的链条就称为作用域链作用域是由代码中函数声明的位置来决定的

变量查找顺序.png

闭包:在 JavaScript 中,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。
闭包还可以这样理解:当函数嵌套时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局作用域下可访问时,就形成了闭包。

四、this

this和作用域链属于两套不同的系统。
执行上下文中包含了变量环境、词法环境、外部环境outer,还有一个this


执行上下文.png

this是和上下文绑定的,每个执行上下文都有一个this。
执行上下文分文三种:全局执行上下文、函数执行上下文和eval执行上下文,所以this对应的也有三种。

  1. 全局执行上下文中的this:指向window对象
  2. 函数执行上下文中的this:要取决于函数是如何调用的
    • 函数作为普通函数调用,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;
    • 作为对象的方法调用,this指向该对象
    • 函数使用call、apply或bind方法调用,this由调用时传入的参数决定,指向传入的参数(call、apply和bind都是用于指定函数中的this值的方法)
    • 在构造函数中,this指向即将创建的新对象
    • 在事件处理函数中,this指向触发事件的元素。
    • 嵌套函数不会继承外层的this,也是根据函数时如何调用来决定的,但是箭头函数不会创建其自身的执行上下文,箭头函数中的this,指向外层非箭头函数的this
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容