为什么JavaScript没有块级作用域


Posted in Javascript onMay 22, 2016

最近在看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]赋值了一个函数,当我们执行这个函数时,情况如下:

为什么JavaScript没有块级作用域

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这个值.如下图所示:

为什么JavaScript没有块级作用域

在回到我们之前的代码.

为什么JavaScript没有块级作用域

我们在图中的箭头出是可以正确的访问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]();

为什么JavaScript没有块级作用域

看~我们的解决方法和ES6的解决方法是不是很像.这里我们的立即执行函数相当于生成的ES5代码中的_loop函数以及_loop(i)的执行.

Javascript 相关文章推荐
Javascript实现的分页函数
Feb 07 Javascript
在IE6下发生Internet Explorer cannot open the Internet site错误
Jun 21 Javascript
AngularJS 中的事件详解
Jul 28 Javascript
jQuery动态修改字体大小的方法【测试可用】
Sep 09 Javascript
Bootstrap3 Grid system原理及应用详解
Sep 30 Javascript
JavaScript和jQuery获取input框的绝对位置实现方法
Oct 13 Javascript
express文件上传中间件Multer详解
Oct 24 Javascript
php 修改密码实现代码
May 24 Javascript
详解vue项目打包步骤
Mar 29 Javascript
jquery UI实现autocomplete在获取焦点时得到显示列表功能示例
Jun 04 jQuery
三种方式清除vue路由跳转router-link的历史记录
Apr 10 Vue.js
vue的项目如何打包上线
Apr 13 Vue.js
全面解析Bootstrap中nav、collapse的使用方法
May 22 #Javascript
全面解析bootstrap格子布局
May 22 #Javascript
Bootstrap模块dropdown实现下拉框响应
May 22 #Javascript
基于Bootstrap实现图片轮播效果
May 22 #Javascript
基于Vue.js的表格分页组件
May 22 #Javascript
js正则表达式replace替换变量方法
May 21 #Javascript
深入解析JavaScript中的立即执行函数
May 21 #Javascript
You might like
PHP 删除一个目录及目录下的所有文件的函数代码
2010/05/26 PHP
php禁止直接从浏览器输入地址访问.php文件的方法
2014/11/04 PHP
四个PHP非常实用的功能
2015/09/29 PHP
PHP的PDO操作简单示例
2016/03/30 PHP
JS 控件事件小结
2012/10/31 Javascript
JavaScript mapreduce工作原理简析
2012/11/25 Javascript
jQuery 淡入淡出 png图在ie8下有黑色边框的解决方法
2013/03/05 Javascript
setInterval计时器不准的问题解决方法
2014/05/08 Javascript
使用CamanJS在Web页面上处理图像的技巧
2015/08/18 Javascript
jQuery实现的鼠标滑过弹出放大图片特效
2016/01/08 Javascript
jQuery头像裁剪工具jcrop用法实例(附演示与demo源码下载)
2016/01/22 Javascript
玩转JavaScript OOP - 类的实现详解
2016/06/08 Javascript
jQuery实现淡入淡出的模态框
2017/02/09 Javascript
js实现仿购物车加减效果
2017/03/01 Javascript
帝国cms首页列表页实现点赞功能
2017/10/30 Javascript
vue实现某元素吸顶或固定位置显示(监听滚动事件)
2017/12/13 Javascript
vue中v-for加载本地静态图片方法
2018/03/03 Javascript
基于jQuery实现无缝轮播与左右点击效果
2018/05/13 jQuery
vue实现点击选中,其他的不选中方法
2018/09/05 Javascript
Vue源码解析之数组变异的实现
2018/12/04 Javascript
在vue中created、mounted等方法使用小结
2020/07/21 Javascript
python连接sql server乱码的解决方法
2013/01/28 Python
python微信跳一跳游戏辅助代码解析
2018/01/29 Python
pandas数据框,统计某列数据对应的个数方法
2018/04/11 Python
浅谈Python采集网页时正则表达式匹配换行符的问题
2018/12/20 Python
Python实现定期检查源目录与备份目录的差异并进行备份功能示例
2019/02/27 Python
Python二元赋值实用技巧解析
2019/10/25 Python
Python 实现自动完成A4标签排版打印功能
2020/04/09 Python
关于python3.9安装wordcloud出错的问题及解决办法
2020/11/02 Python
认识深刻的检讨书
2014/02/16 职场文书
2014年教师党员自我评议
2014/09/19 职场文书
2015年春节标语口号
2014/12/09 职场文书
大学毕业典礼致辞
2015/07/29 职场文书
Java面试题冲刺第十六天--消息队列
2021/08/07 面试题
Mysql分析设计表主键为何不用uuid
2022/03/31 MySQL
用Python仅20行代码编写一个简单的端口扫描器
2022/04/08 Python