理解JavaScript的执行上下文栈,可以应用堆栈信息快速定位问题(原文文档

  1、什么是执行上下文:

  简而言之,执行上下文就是当前JavaScript代码被解析和执行时所在环境的抽象概念,JavaScript中运行任何的代码都是在执行上下文中运行。

  2、执行上下文类型:

  执行上下文总共有三种类型:

    1)全局执行上下文

      这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1.  创建一个全局对象,在浏览器中这个对象就是window对象。2. 将this指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。

    2)函数执行上下文

      每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用时才会被创建,一个程序中可以存在任意数量的函数执行上下文,每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤。

    3)Eval函数执行上下文

      运行在eval函数中的代码也获得了自己的执行上下文,但是JavaScript中不常用eval函数,这里不再详细叙述。

  3、执行上下文生命周期:

  执行上下文的生命周期包括三个阶段:创建阶段 -> 执行阶段 -> 回收阶段

    1)创建阶段:

      当函数被调用,但未执行任何其内部代码之前,会做三件事:

      ① 创建变量对象:首先初始化函数的参数 arguments ,提升函数声明和变量声明。

       ② 创建作用域链:在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JavaScript始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一级的父作用域中查找,直到找到该变量。

         ③ 确定 this 指向:包含多种情况,下文详细叙述。

      在一段JS脚本执行之前,会先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个全局的执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来。变量先赋值为undefined,函数则先声明好可使用,这一步做完了,然后开始正式执行程序。

      另外一个函数在执行之前会先创建一个函数执行上下文环境,跟全局上下文差不多,不过函数执行上下文中会出现 this arguments 和函数的参数。

    2)执行阶段:

      执行变量赋值、代码执行

    3)回收阶段:

      执行上下文出栈等虚拟机回收执行上下文

  4、变量提升和this指向细节

    1)变量名提升:

    大部分编程语言都是先声明变量后使用,但是JS中不太一样:

console.log(a); // undefined
var a = 10;

    上述代码输出undefined而不是Uncaught ReferenceError: a is not defined,这是因为声明提升,相当于如下代码:

var a; //声明 默认值是undefined “准备工作”
console.log(a);
a = 10; //赋值

    2)函数声明提升:

    创建函数的方法有两种,一种是通过函数声明function foo(){};另一种是通过函数表达式var foo = function(){},那这两种函数函数提升有什么区别?

console.log(f1); // function f1(){}
function f1() {} // 函数声明
console.log(f2); // undefined
var f2 = function() {}; // 函数表达式

    example1:

function test() {
foo(); // Uncaught TypeError "foo is not a function"
bar(); // "this will run!"
var foo = function() {
// function expression assigned to local variable 'foo'
alert("this won't run!");
};
function bar() {
// function declaration, given the name 'bar'
alert("this will run!");
}
}
test();

    上面例子foo调用时报错,而bar正常调用。

    前面说变量名和函数都会上升,而遇到函数表达式var foo = function(){}时,首先会将var foo上升到函数体顶部,而此时的foo的值为undefined,所以执行foo()报错。

    而对于函数bar(), 则是提升了整个函数,所以bar()才能够顺利执行。

    有个细节必须注意:当遇到函数和变量同名且都会被提升的情况,函数声明优先级比较高,因此变量声明会被函数声明所覆盖,但是可以重新赋值。

alert(a); //输出:function a(){ alert('我是函数') }
function a() {
alert("我是函数");
} //
var a = "我是变量";
alert(a); //输出:'我是变量'

    function 声明的优先级比 var 声明高,也就意味着当两个同名变量同时被 function 和 var 声明时,function 声明会覆盖 var 声明

    这代码等效于:

function a() {
alert("我是函数");
}
var a; //hoisting
alert(a); //输出:function a(){ alert('我是函数') }
a = "我是变量"; //赋值
alert(a); //输出:'我是变量'

    example2:

function test(arg) {
// 1. 形参 arg 是 "hi"
// 2. 因为函数声明比变量声明优先级高,所以此时 arg 是 function
console.log(arg);
var arg = "hello"; // 3.var arg 变量声明被忽略, arg = 'hello'被执行
function arg() {
console.log("hello world");
}
console.log(arg);
}
test("hi");
/* 输出:
function arg(){
console.log('hello world')
}
hello
*/

    这是因为当函数执行的时候,首先会形成一个新的私有的作用域,然后依次按照如下的步骤执行:

    ① 如果有形参,先给形参赋值

    ② 进行私有作用域中的预解释,函数声明优先级比变量声明高,最后后者会被前者所覆盖,但是可以重新赋值

    ③ 私有作用域中的代码从上到下执行

    3)确定this的指向:

    this 的值是在执行的时候才能确认,定义的时候不能确认。因为this是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候:

    example:

// 情况1
function foo() {
console.log(this.a) //
}
var a = 1
foo()
// 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window
// 情况2
function fn(){
console.log(this);
}
var obj={fn:fn};
obj.fn(); //this->obj
// 对于 obj.fn() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 fn 函数中的 this 就是 obj 对象
// 情况3
function CreateJsPerson(name,age){
//this是当前类的一个实例p1
this.name=name; //=>p1.name=name
this.age=age; //=>p1.age=age
}
var p1=new CreateJsPerson("尹华芝",48);
// 在构造函数模式中,类中(函数体中)出现的 this.xxx=xxx 中的 this 是当前类的一个实例
// 情况4
function add(c, d){
return this.a + this.b + c + d;
}
var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
// call、apply 和 bind:this 是第一个参数
// 情况5
<button id="btn1">箭头函数this</button>
<script type="text/javascript">
let btn1 = document.getElementById('btn1');
let obj = {
name: 'kobe',
age: 39,
getName: function () {
btn1.onclick = () => {
console.log(this);//obj
};
}
};
obj.getName();
</script>
// 箭头函数 this 指向:箭头函数没有自己的 this,看其外层的是否有函数,如果有,外层函数的 this 就是内部箭头函数的 this,如果没有,则 this 是 window。

    4)执行上下文栈:

    函数多了就有多个函数执行上下文,每次调用函数创建一个新的执行上下文,如何管理创建的那么多执行上下文?

    JavaScript引擎创建了执行上下文栈来管理执行上下文,可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出的原则

   

    从上面的流程图,我们需要记住几个关键点:

    ① JavaScript 执行在单线程上,所有的代码都是排队执行。

    ② 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。

    ③ 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。

    ④ 浏览器的 JS 执行引擎总是访问栈顶的执行上下文。

    ⑤ 全局上下文只有唯一的一个,它在浏览器关闭时出栈。

    example:

var color = "blue";
function changeColor() {
var anotherColor = "red";
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors();
}
changeColor();

    上述代码运行步骤:

    1、当上述代码在浏览器中加载时,JavaScript 引擎会创建一个全局执行上下文并且将它推入当前的执行栈

    2、调用 changeColor 函数时,此时 changeColor 函数内部代码还未执行,js 执行引擎立即创建一个 changeColor 的执行上下文(简称 EC),然后把这执行上下文压入到执行栈(简称 ECStack)中。

    3、执行 changeColor 函数过程中,调用 swapColors 函数,同样地,swapColors 函数执行之前也创建了一个 swapColors 的执行上下文,并压入到执行栈中。

    4、swapColors 函数执行完成,swapColors 函数的执行上下文出栈,并且被销毁。

    5、changeColor 函数执行完成,changeColor 函数的执行上下文出栈,并且被销毁。

     

前端知识体系:JavaScript基础-原型和原型链-理解JavaScript的执行上下文栈,可以应用堆栈信息快速定位问题的更多相关文章

  1. 【前端知识体系-JS相关】10分钟搞定JavaScript正则表达式高频考点

    1.正则表达式基础 1.1 创建正则表达式 1.1.1 使用一个正则表达式字面量 const regex = /^[a-zA-Z]+[0-9]*\W?_$/gi; 1.1.2 调用RegExp对象的构 ...

  2. 【前端知识体系-JS相关】JS基础知识总结

    1 变量类型和计算 1.1 值类型和引用类型的区别? 值类型:每个变量都会存储各自的值.不会相互影响 引用类型:不同变量的指针执行了同一个对象(数组,对象,函数) 1.2 typeof可以及检测的数据 ...

  3. web前端知识体系总结

    1. 前言 大约在几个月之前,让我看完了<webkit技术内幕>这本书的时候,突然有了一个想法.想把整个web前端开发所需要的知识都之中在一个视图中,形成一个完整的web前端知识体系,目的 ...

  4. 自己总结的web前端知识体系大全【欢迎补充】

    1. 前言 大约在几个月之前,让我看完了<webkit技术内幕>这本书的时候,突然有了一个想法.想把整个web前端开发所需要的知识都之中在一个视图中,形成一个完整的web前端知识体系,目的 ...

  5. web前端知识体系大全

    1. 前言 大约在几个月之前,让我看完了<webkit技术内幕>这本书的时候,突然有了一个想法.想把整个web前端开发所需要的知识都之中在一个视图中,形成一个完整的web前端知识体系,目的 ...

  6. web前端知识体系小结(转)

    1. 前言 大约在几个月之前,让我看完了<webkit技术内幕>这本书的时候,突然有了一个想法.想把整个web前端开发所需要的知识都之中在一个视图中,形成一个完整的web前端知识体系,目的 ...

  7. Web前端知识体系精简

    Web前端技术由html.css和javascript三大部分构成,是一个庞大而复杂的技术体系,其复杂程度不低于任何一门后端语言.而我们在学习它的时候往往是先从某一个点切入,然后不断地接触和学习新的知 ...

  8. 从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系!

    前言 见解有限,如有描述不当之处,请帮忙指出,如有错误,会及时修正. 为什么要梳理这篇文章? 最近恰好被问到这方面的问题,尝试整理后发现,这道题的覆盖面可以非常广,很适合作为一道承载知识体系的题目. ...

  9. Web前端知识体系

    看到一篇不错的文章,拿来收藏和分享. 原文:http://mp.weixin.qq.com/s/UFTfdE7LYhHquWEzwZKLCQ Web前端技术由html.css和 javascript三 ...

随机推荐

  1. Shortest Unsorted Continuous Subarray

    Given an integer array, you need to find one continuous subarray that if you only sort this subarray ...

  2. Python 命名规范总结

    Python推荐命名规范: 模块名和包名采用小写字母并且以下划线分隔单词的形式: 如:browser_driver 类名或异常名采用每个单词首字母大写的方式: 如:BasePage, Keyboard ...

  3. Kubernetes---Service(SVC)服务--ingress api

    对于k8s传统的svc来说 它仅支持4层代理,如果遇到7层代理的话,是没有办法去实现的 k8s官方在1.11中推出了ingress api接口,通过ingress达到7层代理的效果 对于ingress ...

  4. javaSE总结(一)-java数据类型和运算符

    一.注释 (1)单行注释: // (2)多行注释:/*  */  (3)文档注释:/**  */ 二.标识符和关键字 (1)分隔符:分号; 花括号{} 方括号[] 圆括号() 空格 圆点(.)     ...

  5. CF1032B Personalized Cup

    一个多月前写的题,既然没人写题解,那蒟蒻就写一篇吧 基本思路 既然是输出任意一个解,那么号的位置在那里是没有关系的.我的思路是默认*号在最后面,然后对输入的字符串输出的行数进行分类. 代码 #incl ...

  6. centos7 yum安装nginx和 编译安装tengine

    说明 我这里给大家演示一下如何安装nginx,nginx我就不多介绍了,然后我再说一点就是,安装的两种方法都可以,编译安装和yum安装,我不能每个都演示两遍呀,所以看到我这博客的你,学会举一反三好吧? ...

  7. 【Python基础】08_Python中的列表

    1.列表的定义 List(列表)是Python中使用的 最频繁 的数据类型,其他语言通常叫数组 专门用于存储 一串信息 列表用 [] 定义,数据 之间用 , 分割 列表的 索引(位置) 从 0 开始 ...

  8. ingress安装配置

    Traefik Traefik 是一款开源的反向代理与负载均衡工具.它最大的优点是能够与常见的微服务系统直接整合,可以实现自动化动态配置.目前支持 Docker.Swarm.Mesos/Maratho ...

  9. Grace模式、Saint模式

    一.probe(后端探针) 探测后端,确定他们是否健康,返回的状态用req.backend.healthy核对 backend b1 { .host = "127.0.0.1"; ...

  10. MySQL Select语句的执行顺序

    源文章:How is a query executed in MySQL? 当执行SQL的Select查询语句时,SQL指令的执行顺序如下: FROM 子句 WHERE 子句 GROUP BY 子句 ...