小茶配音 | 吊打JavaScript之执行环境与作用域的区别与联系​

点击蓝色 “达达前端” 关注我哦!

加个 “星标” ,每天一篇文章,一起学编程

补充内容

什么是块级作用域。js没有块级作用域是啥意思。

如何一对花括号中的语句代码集都属于一个块,在这之中定义的所有变量在代码块外是不可见的,称为块级作用域。作用域控制着变量和参数的可见性与生命周期。

块级作用域概念,任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。

js中作用域区分为全局作用域,函数作用域。没有块级作用域的概念,ECMAScript 6(简称ES6)中新增了块级作用域。块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。

都知道在js中是没有块级作用域的,在ES6中添加了块级作用域,那么块级作用域有什么好处呢?

执行环境

定义变量或函数有权访问的其他数据,决定了它们各自的行为。

执行环境,也称“环境”,Execution context,或执行上下文对象,统一为用执行上下文表示,它定义了变量或函数有权访问的其他数据,决定了他们各自的行为。js代码执行时所在的环境。

每个执行环境都有一个与之关联的变量对象,环境中定义的所以有变量和函数都保存在这个对象中。

执行环境的特点

在JavaScript中分为三种执行环境:

第一种为:全局执行环境,这是最外围的执行环境,一旦代码被载入,引擎最先进入的就是这个环境。在浏览器中,全局环境就是window对象,所以所有全局属性和函数都是作为window对象的属性和方法创建。全局执行环境直到应用程序退出时才会被销毁。

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

第二种为:函数执行环境,当执行流执行一个函数时,JavaScript会创建一个新的函数执行环境,函数执行环境中的代码执行完后,该环境被销毁,保存在其中的所有变量和函数定义也随之被销毁。

环境是函数,则将其活动对象作为变量对象。

活动对象在最开始只有一个变量,就是 arguments 对象,这个对象在全局环境中是不存在,只有在函数中。

第三种为:Eval执行环境。

用eval执行的语句应该和普通语句没有区别,其作用域就是当前的作用域。

默认情况下:

1. eval内代码可以读取和使用所在作用域的变量

2. eval中声明的变量也可以在当前作用域中存在

例子:

(function(){
 window.eval("var x=1;");
})();
alert(x); // 1

x声明在window中

(function(){
 eval("var x = 1;");
 alert(x); // 1
})();
alert(x); // x is not defined

y声明在闭包中

(function(){
 "user strict";
 eval("var y = 1; alert(y);"); // 1
 alert(y); // y is not defined
})();
alert(y); // y is not defined

严格模式下的eval的变量仅存在于eval内部,不外泄。

作用域链的前端,一直都是当前执行的代码所在环境的变量对象。

执行顺序

var foo = function () {
    console.log('foo1');
}


foo();  // foo1


var foo = function () {
    console.log('foo2');
}


foo(); // foo2
function foo() {
    console.log('foo1');
}


foo();  // foo2


function foo() {
    console.log('foo2');
}


foo(); // foo2

JavaScript引擎不是一行一行地分析和执行程序的,而是一段一段地分析执行的。第一个例子中有变量提升,第二个例子中有函数提升。

JavaScript中的可执行代码的类型有三种,全局代码,函数代码,eval代码。

执行上下文

在JavaScript引擎中创建了执行上下文栈,来管理执行上下文。

function fun3() {
    console.log('fun3')
}


function fun2() {
    fun3();
}


function fun1() {
    fun2();
}


fun1();

当执行一个函数的时候,就会创建一个执行上下文,然后把它压入执行上下文栈,当函数执行完毕后,就会将函数的执行上下文从栈中弹出。

作用域

作用域是指在程序中定义变量的区域,作用域规定了如何查找变量,对当前执行代码对变量的访问权限。

关于词法作用域和动态作用域

词法作用域就是静态作用域,而相对于词法作用域就是动态作用域。词法作用域是函数的作用域在函数定义的时候决定的,而动态作用域是在函数调用的时候决定的。

var value = 1;


function foo() {
    console.log(value);
}


function bar() {
    var value = 2;
    foo();
}


bar();

作用域链

作用域链,当代码在一个环境中执行时,会创建变量对象的一个作用域链,这个作用域链确保对执行环境有权访问的所有变量和函数的有序访问

执行流每进入一个执行环境,都会创建一个作用域链。

作用域链由执行环境的变量对象组成,作用域链的前端都是当前执行环境的变量对象,下个变量对象来自外围环境,再下一个变量对象则来自下一个外围环境,一直延续到全局执行环境的变量对象。

所以全局执行环境的变量对象一直都是作用域链中的最后一个对象。

内部环境可以通过作用域链可以访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。这些环境之间的连续是线性的,有次序的。

每个环境都是可以向上搜作用域链的,但是任何环境都不能通过向下搜索作用域而进入另一个执行环境。

当我们在某个环境中需要读取而引用一个标识符代表某种特定含义的时候,必须通过搜索来确定该标识符。搜索由近到远,由局部到全局,如果查询到了相应的标识将停止搜索。

延长作用域链

执行环境类型分两种:一种全局和一种局部。

延长就是可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除,让作用域链加长的方法。

第一种,try-catch语句的catch块。通过增加一个变量对象在作用域前端,在代码执行结束以后销毁。

第二种, with语句。会将指定的对象添加到作用域链上面来延长作用域链。

try{  
  null.name  
}catch(e) {  
  console.log(e.message);  
}

延长作用域链的特点:

第一,添加的变量对象是临时的,在语句执行完后将被移除。

第二,添加的变量对象不关联执行环境。

示例,如with语句接收一个参数location对象,那么其变量对象中就包含了location对象的所有属性和方法,这个变量对象被添加到了作用域链的前端。

没有块级作用域

因为没有块级作用域,而添加块级作用域,为什么会添加这个功能呢?就得了解ES5没有块级作用域时出现了哪些问题。

第一,在if或者for循环中声明的变量会泄漏成全局变量,第二,内层变量可能会覆盖外层变量。

了解js的同学知道ES5中是没有块级作用域的概念,只有全局作用域和函数作用域,之前js的是用var定义的变量。如果使用了js内部已经定义好的函数名,就会造成了全局污染。

如果使用了相同名称的变量,就会覆盖掉之前的,或函数内层的变量会覆盖掉外层的变量。从没有块级作用域到有块级作用域。

JavaScript没有块级作用域经常会导致理解上的困惑。为什么说js没有块级作用域

if(true) {  
  var dada = 'dada';  
}  
console.log(dada); // dada没在if 块中也可以访问

为什么在if语句执行完毕后没有被销毁呢

在JavaScript中if语句中变量声明会将变量添加到当前的执行环境中。

for(var i = 0;i< 10; i++){
  doSomething(i);
  }
alert(i);    //10

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

JavaScript没有块级作用域,变量的声明周期和执行环境有关。

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

补充内容

块级作用域的出现,es6语法中还有两个定义变量的方法为let和const。

let定义一个变量,允许被改变,只作用在被定义时的作用域下,const也是,但是唯一const为不允许被改变。

执行环境与作用域的区别与联系

作用域链是基于执行环境的变量对象,由所有执行环境的变量对象共同组成。

(function(){
    a= 5;
    console.log(window.a);//undefined
    var a = 1;//会发生变量声明提升
    console.log(a);//1
})();


(function(){
    var a;//a是局部变量
    a = 5;//局部环境中有a,就不会找全局中的
    console.log(window.a);//undefined
    a = 1;//这里会发生变量声明提升
    console.log(a);//1
})();

Eval:执行字符串内的代码

内建(built-in)函数 eval 让我们能够执行字符串内的代码。

语法如下:


let result = eval(code);
比如:


 let code = 'alert("Hello")';
eval(code); // Hello

字符串内的代码在当前词法环境(lexical environment)下执行,因此能访问外部变量:

let a = 1;


function f() {
  let a = 2;


  eval('alert(a)'); // 2
}


f();


let x = 5;
eval("x = 10");
alert(x); // 10, 变量的值改变了

总结

  1. 全局执行环境是最外围的一个执行环境。

  2. 根据所在的宿主环境不同,表示执行环境的对象也不一样。

  3. 在web浏览器中,全局执行环境被认为是window对象,所有的全局变量和函数都是作为window对象的属性和方法创建的。

  4. 某执行环境的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。

  5. Eval的执行环境和函数调用的执行环境相同。

  6. 执行环境可以说分两种的,一种全局执行环境,一种是函数执行环境。全局执行环境是最外围的一个执行环境,每个函数都有自己的执行环境,函数执行环境的变量对象被称为活动对象,它在最开始只包含一个变量,即arguments对象。

  7. 标识符解析是沿着作用域链一级一级地搜索标识符的过程,从作用域链的前端开始,向后回溯,直到找到标识符为止,找不到,会导致错误发生。

  8. 每次进入到一个新的执行环境中,都会创建一个用于搜索变量和函数的作用域链。

  9. 变量的执行环境有助于确定应该何时释放内存。

补充内容

上下文和作用域,每个函数的调用都有与之相关的作用域和上下文,作用域是基于函数,而上下文时基于变量对象。

当调用一个函数,通过new操作符创建一个对象的实例,this指向新创建的实例。

作用域是和每次函数调用时变量的访问有关系,每次调用都是独立的,上下文总是关键字this的值,是调用当前可执行代码的对象的引用。

☆ END ☆

参考文档来源:《JavaScript 高级程序设计》

加群前端交流群

扫码,备注 加群-技术领域-城市-姓名 

目前文章内容涉及前端知识点,囊括Vue、JavaScript、数据结构与算法、实战演练、Node全栈一线技术,紧跟业界发展步伐,将 Web前端领域、网络原理等通俗易懂的呈现给小伙伴。更多内容请到达达前端网站进行学习:www.dadaqianduan.cn

1、你知道多少this,new,bind,call,apply?那我告诉你

2、为什么学习JavaScript设计模式,因为它是核心

3、一篇文章把你带入到JavaScript中的闭包与高级函数

4、大厂HR面试ES6中的深入浅出面试题知识点

觉得本文对你有帮助?请分享给更多人

关注「达达前端」加星标,提升前端技能

这是一个有质量,有态度的公众号

posted @ 2020-02-15 20:17  达达前端  阅读(182)  评论(0编辑  收藏  举报