为什么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实现tab标签浏览效果
Feb 20 Javascript
js setattribute批量设置css样式
Nov 26 Javascript
文本框获得焦点和失去焦点的判断代码
Mar 18 Javascript
常用jQuery选择器总结
Jul 11 Javascript
Angular用来控制元素的展示与否的原生指令介绍
Jan 07 Javascript
jquery获取url参数及url加参数的方法
Oct 26 Javascript
json对象与数组以及转换成js对象的简单实现方法
Jun 24 Javascript
javascript实现根据函数名称字符串动态执行函数的方法示例
Dec 28 Javascript
JS简单实现点击按钮或文字显示遮罩层的方法
Apr 27 Javascript
详解AngularJS脏检查机制及$timeout的妙用
Jun 19 Javascript
在vscode里使用.vue代码模板的方法
Apr 28 Javascript
微信小程序实现跳转的几种方式总结(推荐)
Apr 24 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
DC的38部超级英雄动画电影
2020/03/03 欧美动漫
php防止伪造的数据从URL提交方法
2014/06/27 PHP
PHP实现统计在线人数功能示例
2016/10/15 PHP
整理8个很棒的 jQuery 倒计时插件和教程
2011/12/12 Javascript
JavaScript高级程序设计阅读笔记(十六) javascript检测浏览器和操作系统-detect.js
2012/08/14 Javascript
js如何调用qq互联api实现第三方登录
2014/03/28 Javascript
javascript数组操作方法小结和3个属性详细介绍
2014/07/05 Javascript
使用jQuery实现返回顶部
2015/01/26 Javascript
JavaScript与jQuery实现的闪烁输入效果
2016/02/18 Javascript
jQuery+PHP+MySQL实现无限级联下拉框效果
2016/02/19 Javascript
Node.js+Express配置入门教程详解
2016/05/19 Javascript
js实现复选框的全选和取消全选效果
2017/01/03 Javascript
利用js判断手机是否安装某个app的多种方案
2017/02/13 Javascript
JS打开摄像头并截图上传示例
2017/02/18 Javascript
Angular 4.X开发实践中的踩坑小结
2017/07/04 Javascript
vue中如何实现pdf文件预览的方法
2018/07/12 Javascript
axios简单实现小程序延时loading指示
2018/07/30 Javascript
浅谈Vuex注入Vue生命周期的过程
2019/05/20 Javascript
create-react-app中添加less支持的实现
2019/11/15 Javascript
新手如何快速入门Python(菜鸟必看篇)
2017/06/10 Python
Django 后台获取文件列表 InMemoryUploadedFile的例子
2019/08/07 Python
python socket 聊天室实例代码详解
2019/11/14 Python
django自带的权限管理Permission用法说明
2020/05/13 Python
Python sklearn中的.fit与.predict的用法说明
2020/06/28 Python
Django返回HTML文件的实现方法
2020/09/17 Python
PyCharm 2020.2下配置Anaconda环境的方法步骤
2020/09/23 Python
CSS3使用transition实现的鼠标悬停淡入淡出
2015/01/09 HTML / CSS
alice McCALL官网:澳大利亚时尚品牌
2020/11/16 全球购物
网上常见的一份Linux面试题(多项选择部分)
2015/02/07 面试题
安全生产中长期规划实施方案
2014/02/21 职场文书
广告语设计及教案
2014/03/21 职场文书
学校花圃的标语
2014/06/18 职场文书
中秋节国旗下演讲稿
2014/09/05 职场文书
2014年学校后勤工作总结
2014/12/06 职场文书
SpringCloud Alibaba 基本开发框架搭建过程
2021/06/13 Java/Android
Mysql事务索引知识汇总
2022/03/17 MySQL