为什么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 相关文章推荐
jquery通过select列表选择框对表格数据进行过滤示例
May 07 Javascript
Jquery效果大全之制作电脑健康体检得分特效附源码下载
Nov 02 Javascript
微信小程序 wxapp画布 canvas详细介绍
Oct 31 Javascript
利用CSS、JavaScript及Ajax实现图片预加载的三大方法
Jan 22 Javascript
js实现图片粘贴上传到服务器并展示的实例
Nov 08 Javascript
使用Angular CLI进行单元测试和E2E测试的方法
Mar 24 Javascript
vue服务端渲染缓存应用详解
Sep 12 Javascript
vue环形进度条组件实例应用
Oct 10 Javascript
微信小程序学习笔记之跳转页面、传递参数获得数据操作图文详解
Mar 28 Javascript
微信小程序非跳转式组件授权登录的方法示例
May 22 Javascript
记录vue做微信自定义分享的一些问题
Sep 12 Javascript
在weex中愉快的使用scss的方法步骤
Jan 02 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
PHP return语句另类用法不止是在函数中
2014/09/17 PHP
Codeigniter购物车类不能添加中文的解决方法
2014/11/29 PHP
php数组添加与删除单元的常用函数实例分析
2015/02/16 PHP
Thinkphp5.0框架视图view的循环标签用法示例
2019/10/12 PHP
通用javascript脚本函数库 方便开发
2009/10/13 Javascript
基于Jquery的简单图片切换效果
2011/01/06 Javascript
JavaScript 的继承
2011/10/01 Javascript
JS代码优化技巧之通俗版(减少js体积)
2011/12/23 Javascript
js有序数组的连接问题
2013/10/01 Javascript
jquery设置text的值示例(设置文本框 DIV 表单值)
2014/01/06 Javascript
JS实现可点击展开与关闭的左侧广告代码
2015/09/02 Javascript
运用jQuery写的验证表单(实例讲解)
2017/07/06 jQuery
深入理解es6块级作用域的使用
2019/03/28 Javascript
vue+element搭建后台小总结 el-dropdown下拉功能
2020/04/10 Javascript
微信小程序页面渲染实现方法
2019/11/06 Javascript
js实现点击上传图片并设为模糊背景
2020/08/02 Javascript
JavaScript缓动动画函数的封装方法
2020/11/25 Javascript
python时间整形转标准格式的示例分享
2014/02/14 Python
python连接oracle数据库实例
2014/10/17 Python
Python合并多个装饰器小技巧
2015/04/28 Python
Django中URL视图函数的一些高级概念介绍
2015/07/20 Python
python如何实现excel数据添加到mongodb
2015/07/30 Python
使用python实现生成用户信息
2017/03/20 Python
解决PyCharm不运行脚本,而是运行单元测试的问题
2019/01/17 Python
Django页面数据的缓存与使用的具体方法
2019/04/23 Python
浅谈Tensorflow 动态双向RNN的输出问题
2020/01/20 Python
python opencv圆、椭圆与任意多边形的绘制实例详解
2020/02/06 Python
PyCharm 无法 import pandas 程序卡住的解决方式
2020/03/09 Python
Python控制鼠标键盘代码实例
2020/12/08 Python
Stio官网:男女、儿童户外服装
2019/12/13 全球购物
应届大学生的推荐信
2013/11/20 职场文书
旅游网创业计划书
2014/01/31 职场文书
总经理工作职责范文
2014/03/14 职场文书
辅导员评语
2014/05/04 职场文书
2015年师德师风承诺书
2015/01/22 职场文书
2015年社区居委会工作总结
2015/05/18 职场文书