为什么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调用WebService的示例
Apr 07 Javascript
js arguments对象应用介绍
Nov 28 Javascript
JQuery 获取json数据$.getJSON方法的实例代码
Aug 02 Javascript
原生javascript实现拖动元素示例代码
Sep 01 Javascript
javascript常用方法汇总
Dec 02 Javascript
javascript实现dom元素可拖动
Mar 21 Javascript
通过jquery-ui中的sortable来实现拖拽排序的简单实例
May 24 Javascript
JS触摸屏网页版仿app弹窗型滚动列表选择器/日期选择器
Oct 30 Javascript
利用CSS、JavaScript及Ajax实现图片预加载的方法
Nov 29 Javascript
vue 使用html2canvas将DOM转化为图片的方法
Sep 11 Javascript
微信小程序动画(Animation)的实现及执行步骤
Oct 28 Javascript
vue 父组件中调用子组件函数的方法
Jun 06 Javascript
全面解析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
东方红 - 来复式再生机的修复
2021/03/02 无线电
用PHP实现 上一篇、下一篇的代码
2012/09/29 PHP
CodeIgniter错误mysql_connect(): No such file or directory解决方法
2014/09/06 PHP
PHP中SSO Cookie登录分析和实现
2015/11/06 PHP
php支付宝在线支付接口开发教程
2016/09/19 PHP
PHP构造函数与析构函数用法示例
2016/09/28 PHP
js 调整select 位置的函数
2008/02/21 Javascript
FileUpload上传图片(图片不变形)
2010/08/05 Javascript
[原创]JQuery 在表单提交之前修改 提交的值
2016/04/14 Javascript
js获取对象、数组的实际长度,元素实际个数的实现代码
2016/06/08 Javascript
jquery实现瀑布流效果 jquery下拉加载新数据
2016/12/12 Javascript
用jQuery.ajaxSetup实现对请求和响应数据的过滤
2016/12/20 Javascript
分享bootstrap学习笔记心得(组件及其属性)
2017/01/11 Javascript
vuex 的简单使用
2018/03/22 Javascript
小程序点击图片实现自动播放视频
2020/05/29 Javascript
[01:08:09]DOTA2上海特级锦标赛主赛事日 - 1 胜者组第一轮#1Liquid VS Alliance第二局
2016/03/02 DOTA
[32:47]完美世界DOTA2联赛 GXR vs IO 第二场 11.07
2020/11/09 DOTA
python网络编程学习笔记(四):域名系统
2014/06/09 Python
Python while、for、生成器、列表推导等语句的执行效率测试
2015/06/03 Python
Python利用前序和中序遍历结果重建二叉树的方法
2016/04/27 Python
Python实现PS滤镜Fish lens图像扭曲效果示例
2018/01/29 Python
python中使用zip函数出现错误的原因
2018/09/28 Python
在unittest中使用 logging 模块记录测试数据的方法
2018/11/30 Python
Python中请不要再用re.compile了
2019/06/30 Python
在Python中实现函数重载的示例代码
2019/12/12 Python
PyTorch中反卷积的用法详解
2019/12/30 Python
Python实现简单的猜单词小游戏
2020/10/28 Python
pycharm中leetcode插件使用图文详解
2020/12/07 Python
Myprotein台湾官方网站:全球领先的运动营养品牌
2018/12/10 全球购物
什么是抽象
2015/12/13 面试题
英语教育专业自荐信
2014/05/29 职场文书
政府法律服务方案
2014/06/14 职场文书
小学关爱留守儿童活动方案
2014/08/25 职场文书
介绍信的格式
2015/01/30 职场文书
自我推荐信格式模板
2015/03/24 职场文书
《富饶的西沙群岛》教学反思
2016/02/16 职场文书