这篇文章主要介绍了为什么 JavaScript 没有块级作用域的相关资料, 需要的朋友可以参考下
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
最近在看 ES2015 实战, 里面有句话是这么说的
JavaScript 中没有块级作用域
可能会对这个问题大家可能有点不理解, 先看个例子
- var a = []
- for(var i = 0; i < 10; i++){
- a[i] = function(){
- console.log(i);
- }
- }
- a[6]();
我想很多人会觉得这个问题的结果是 6, 然而很不幸, 答案是 10. 在试试别的呢. a[7]()、a[8]()、a[8]() 结果都是 10!!
由于 JS 在处理 primitive 的变量的时候, 很多时候会把 primitive 变量包装成对应的对象来处理, 比如对于 var str = "hello world";str.slice(1).
JS 真正的处理过程大概是 var str = "hello world";new String(str).slice(1). 这样的过程可能会对我们理解问题造成困扰.
这里为了解释这个问题, 同时 i 属于 primitive 类型中的 number 类型, 我就显式的声明为 Number 类型. 由于基本类型的赋值过程就是重新申请内存, 修改变量的指向的过程, 对于这一过程我们也用重新 new Number 对象的过程来模拟. 修改过后的代码如下:
- var a = []
- var i = new Number(0);
- for(; i < 10; i = new Number(i+1)){
- a[i] = function(){
- console.log(i.toString());
- }
- }
- a[6](); // 10
- a[7](); // 10
- a[8](); // 10
- a[9](); // 10
下面结合一段程序, 我们来看看这些这变量的相对内存地址
- (function() {
- var id = 0;
- function generateId() {
- return id++;
- };
- Object.prototype.id = function() {
- var newId = generateId();
- this.id = function() {
- return newId;
- };
- return newId;
- };
- })();
- var a = []
- var i = new Number(0);
- console.log(i.id()); // 0
- for (; i < 10; i = new Number(i + 1), i.id()) {
- a[i] = function() {
- console.log(i.id());
- console.log(i.toString());
- }
- }
- a[6](); // 10 10
- a[7](); // 10 10
- a[8](); // 10 10
- a[9](); // 10 10
- console.log(i.id()) // 10
这边我们的 i 的整个的 "赋值" 的效果我们确实是模拟出来了, i 的相对地址从 0 变到 10(最后还需要加一次才可以跳出 for 循环).
在看 i 的相对地址的同时, 我们发现一个问题: a[x](x:0~9) 对应的函数在执行的时候, 所引用的 i 的相对地址都为 10. 为什么呢?
这里就要牵扯出块级作用域问题来, 这里我们引用阮一峰在 ES6 入门中的一段话:
ES5 只有全局作用域和函数作用域, 没有块级作用域.
ES5 就是大家使用最广泛的 JS 的版本. 这句话说在 javascript 中, 是不存在块作用域的. 只存在全局作用域和块级作用域.
怎么理解呢? 举个例子
- for(var i = 0;i < 10; i++){
- console.log(i);
- }
- console.log(i);//10
- console.log(window.i);//10
直观的看, 我们觉得 for 循环是一个代码块, 应该属于一个块级作用域. 但是这里不仅能正常的输出 0~9, 居然还可以在 for 循环的外部输出 10. 同时我们发现, 虽然我们是在 for 循环上定义的 i, 但是似乎 i 是挂在了全局的 window 对象上 (如果是 nodejs 的执行环境, 就会挂到 global 对象上)
所以说在 JavaScript 中 for 循环之类的 block 并不会起到一个块级作用域的效果, 在 for 循环之类的代码块中定义变量, 跟在当前所在的作用域中直接定义变量没什么区别.
但是我们可以通过函数隔离出作用域出来:
- (function(){
- for(var i = 0;i < 10; i++){
- console.log(i);
- }
- console.log(i);
- })()
- console.log(i);////i is not defined
同时如果执行 console.log(window.i); 会得到 undefined 的结果. 这里我们用一个立即执行函数来形成一个作用域. 起到类似于代码块的作用, 出了这个函数作用域, 就不再可以访问 i 这个变量. 但是在函数作用域内可以任意访问 i.
回到之前的问题, 同时结合 JavaScript 中只有全局作用域和块级作用域再来理解一下. 我们在 for 循环中, 定义的 i 肯定是定义在当前作用域的, 也就是 window 作用域. 在循环体中, 我们给 a[i] 赋值了一个函数, 当我们执行这个函数时, 情况如下:
function 中不存在 i, 于是顺着作用域链去 window 作用域找得到了 i. 我们这个时候输出的 i 就是这个 i. 由于 i 在跳出循环最后一次的 + 1, 使得 i 变成了 10, 所以输出结果一直都是 10. 但是我们真正需要的 i 不是最后的 i, 而是中间过程中的 i. 如果要解决这个问题, 我们需要抛开 i 这个变量 (因为最后的 i 不可避免的变成 10). 我们要让 a[0] 对应的 function 引用 0 这个值, 让 a[1]对应的 function 引用 1 这个值. 如下图所示:
在回到我们之前的代码.
我们在图中的箭头出是可以正确的访问 i(0~9). 这里由于 for 循环并没有自己形成一个块级作用域. 导致了我们顺着作用域链去访问 i 的时候就访问到了 for 循环定义的 i.
这里我们用一个立即执行函数包裹我们的代码, 就可以形成一个作用域, 同时我们为其传值 i. 如下:
- var a = []
- var i = new Number(0);
- console.log(i.id());// 0
- for(; i < 10; i = new Number(i+1),i.id()){
- (function(i){
- a[i] = function(){
- console.log(i.id());
- console.log(i.toString());
- }
- })(i);
- a[6](); // 6 6
- a[7](); // 7 7
- a[8](); // 8 8
- a[9](); // 9 9
- console.log(i.id());// 10
- }
由于这个立即执行函数引用着数值 0~9, 当我们执行函数 a[i] 的时候, 会顺着作用域链先找到这个立即执行函数的作用域. 立即执行函数维护着 0~9 的数值引用, 我们就可以在函数 a[i] 中正确的输出 i 的值. 通过执行结果, 我们可以看到, 不光执行结果是对的, 同时我们引用的值的相对内存地址也都是对的. 接着我们把原来为了测试显式声明的 Number 对象改回去. 如下:
- var a = [];
- for(var i = 0; i < 10; i++){
- (function(i){
- a[i] = function(){
- console.log(i);
- }
- })(i);
- }
最后我们再来看看 ES6 的语法中推荐用 let 代替 var 以及经过 bable 编译生成 ES5 的代码是如何的:
- //ES6代码
- var a = []
- for(let i = 0; i < 10; i++){
- a[i] = function(){
- console.log(i);
- }
- }
- a[6]();
- //babel编译生成的ES5代码
- "use strict";
- var a = [];
- var _loop = function _loop(i) {
- a[i] = function () {
- console.log(i);
- };
- };
- for (var i = 0; i < 10; i++) {
- _loop(i);
- }
- a[6]();
看~ 我们的解决方法和 ES6 的解决方法是不是很像. 这里我们的立即执行函数相当于生成的 ES5 代码中的_loop 函数以及_loop(i) 的执行.
来源: http://www.phperz.com/article/17/0405/265555.html