补充内容
什么是块级作用域. JS 没有块级作用域是啥意思.
如何一对花括号中的语句代码集都属于一个块, 在这之中定义的所有变量在代码块外是不可见的, 称为块级作用域. 作用域控制着变量和参数的可见性与生命周期.
块级作用域概念, 任何一对花括号 ({和}) 中的语句集都属于一个块, 在这之中定义的所有变量在代码块外都是不可见的, 我们称之为块级作用域.
在 JS 中作用域区分为全局作用域, 函数作用域. 没有块级作用域的概念, ECMAScript 6(简称 ES6)中新增了块级作用域. 块作用域由 { } 包括, if 语句和 for 语句里面的 { } 也属于块作用域.
都知道在 JS 中是没有块级作用域的, 在 ES6 中添加了块级作用域, 那么块级作用域有什么好处呢?
执行环境
定义变量或函数有权访问的其他数据, 决定了它们各自的行为.
执行环境, 也称 "环境",Execution context, 或执行上下文对象, 统一为用执行上下文表示, 它定义了变量或函数有权访问的其他数据, 决定了他们各自的行为. JS 代码执行时所在的环境.
每个执行环境都有一个与之关联的变量对象, 环境中定义的所以有变量和函数都保存在这个对象中.
执行环境的特点
在 JavaScript 中分为三种执行环境:
第一种为: 全局执行环境, 这是最外围的执行环境, 一旦代码被载入, 引擎最先进入的就是这个环境. 在浏览器中, 全局环境就是 Windows 对象, 所以所有全局属性和函数都是作为 Windows 对象的属性和方法创建. 全局执行环境直到应用程序退出时才会被销毁.
每个函数都有自己的执行环境, 当执行流进入一个函数时, 函数的环境就会被推入到一个环境栈中, 执行完后, 栈将其弹出, 把控制权交给之前的执行环境.
第二种为: 函数执行环境, 当执行流执行一个函数时, JavaScript 会创建一个新的函数执行环境, 函数执行环境中的代码执行完后, 该环境被销毁, 保存在其中的所有变量和函数定义也随之被销毁.
环境是函数, 则将其活动对象作为变量对象.
活动对象在最开始只有一个变量, 就是 arguments 对象, 这个对象在全局环境中是不存在, 只有在函数中.
第三种为: Eval 执行环境.
用 eval 执行的语句应该和普通语句没有区别, 其作用域就是当前的作用域.
默认情况下:
1. eval 内代码可以读取和使用所在作用域的变量
2. eval 中声明的变量也可以在当前作用域中存在
例子:
(function(){Windows.eval("var x=1;");})();alert(x);// 1
x 声明在 Windows 中
(function(){eval("var x = 1;"); alert(x);// 1})();alert(x);// x is not defined
y 声明在闭包中
(function(){"user strict";eval("var y = 1; alert(y);");// 1alert(y);// y is not defined})();alert(y);// y is not defined
严格模式下的 eval 的变量仅存在于 eval 内部, 不外泄.
作用域链的前端, 一直都是当前执行的代码所在环境的变量对象.
执行顺序
- varfoo =function(){
- console.log('foo1');
- }foo();// foo1varfoo =function(){
- console.log('foo2');
- }foo();// foo2
- functionfoo(){
- console.log('foo1');
- }foo();// foo2functionfoo(){
- console.log('foo2');
- }foo();// foo2
JavaScript 引擎不是一行一行地分析和执行程序的, 而是一段一段地分析执行的. 第一个例子中有变量提升, 第二个例子中有函数提升.
JavaScript 中的可执行代码的类型有三种, 全局代码, 函数代码, eval 代码.
执行上下文
在 JavaScript 引擎中创建了执行上下文栈, 来管理执行上下文.
functionfun3(){console.log('fun3')}functionfun2(){ fun3();}functionfun1(){ fun2();}fun1();
当执行一个函数的时候, 就会创建一个执行上下文, 然后把它压入执行上下文栈, 当函数执行完毕后, 就会将函数的执行上下文从栈中弹出.
作用域
作用域是指在程序中定义变量的区域, 作用域规定了如何查找变量, 对当前执行代码对变量的访问权限.
关于词法作用域和动态作用域
词法作用域就是静态作用域, 而相对于词法作用域就是动态作用域. 词法作用域是函数的作用域在函数定义的时候决定的, 而动态作用域是在函数调用的时候决定的.
varvalue =1;functionfoo(){console.log(value);}functionbar(){varvalue =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) {vardada ='dada'; }console.log(dada);// dada 没在 if 块中也可以访问
为什么在 if 语句执行完毕后没有被销毁呢
在 JavaScript 中 if 语句中变量声明会将变量添加到当前的执行环境中.
for(vari =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(Windows.a);//undefinedvara =1;// 会发生变量声明提升 console.log(a);//1})();(function(){vara;//a 是局部变量 a =5;// 局部环境中有 a, 就不会找全局中的 console.log(Windows.a);//undefineda =1;// 这里会发生变量声明提升 console.log(a);//1})();
Eval: 执行字符串内的代码
内建 (built-in) 函数 eval 让我们能够执行字符串内的代码.
语法如下: letresult =eval(code); 比如: letcode ='alert("Hello")';eval(code);// Hello
字符串内的代码在当前词法环境 (lexical environment) 下执行, 因此能访问外部变量:
leta =1;functionf(){leta =2;eval('alert(a)');// 2}f();letx =5;eval("x = 10");alert(x);// 10, 变量的值改变了
总结
全局执行环境是最外围的一个执行环境.
根据所在的宿主环境不同, 表示执行环境的对象也不一样.
在 web 浏览器中, 全局执行环境被认为是 Windows 对象, 所有的全局变量和函数都是作为 Windows 对象的属性和方法创建的.
某执行环境的所有代码执行完毕后, 该环境被销毁, 保存在其中的所有变量和函数定义也随之销毁.
Eval 的执行环境和函数调用的执行环境相同.
执行环境可以说分两种的, 一种全局执行环境, 一种是函数执行环境. 全局执行环境是最外围的一个执行环境, 每个函数都有自己的执行环境, 函数执行环境的变量对象被称为活动对象, 它在最开始只包含一个变量, 即 arguments 对象.
标识符解析是沿着作用域链一级一级地搜索标识符的过程, 从作用域链的前端开始, 向后回溯, 直到找到标识符为止, 找不到, 会导致错误发生.
每次进入到一个新的执行环境中, 都会创建一个用于搜索变量和函数的作用域链.
变量的执行环境有助于确定应该何时释放内存.
补充内容
上下文和作用域, 每个函数的调用都有与之相关的作用域和上下文, 作用域是基于函数, 而上下文时基于变量对象.
当调用一个函数, 通过 new 操作符创建一个对象的实例, this 指向新创建的实例.
作用域是和每次函数调用时变量的访问有关系, 每次调用都是独立的, 上下文总是关键字 this 的值, 是调用当前可执行代码的对象的引用.
☆ END ☆
来源: http://www.jianshu.com/p/844c567f4c51