时间:2021-05-25
最近在看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对象的过程来模拟.修改过后的代码如下:
下面结合一段程序,我们来看看这些这变量的相对内存地址
(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());// 0for(; i < 10; i = new Number(i+1),i.id()){ a[i] = function(){ console.log(i.id()); console.log(i.toString()); }}a[6](); // 10 10a[7](); // 10 10a[8](); // 10 10a[9](); // 10 10console.log(i.id())// 10这边我们的i的整个的”赋值”的效果我们确实是模拟出来了,i的相对地址从0变到10(最后还需要加一次才可以跳出for循环).
在看i的相对地址的同时,我们发现一个问题:a[x](x:0~9)对应的函数在执行的时候,所引用的i的相对地址都为10.为什么呢?
这里就要牵扯出块级作用域问题来,这里我们引用阮一峰在ES6入门中的一段话:
ES5只有全局作用域和函数作用域,没有块级作用域.
ES5就是大家使用最广泛的JS的版本.这句话说在javascript中,是不存在块作用域的.只存在全局作用域和块级作用域.
怎么理解呢?举个例子
直观的看,我们觉得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.如下:
由于这个立即执行函数引用着数值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)的执行.
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
一、变量提升在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域。变量提升即将变量声明提升到它所在作
Javascript中一个最重要的特性就是闭包的使用。因为闭包的使用,当前作用域总可以访问外部的作用域。因为Javascript没有块级作用域,只有函数作用域,
在之前的介绍中,我们已经知道Javascript没有块级作用,只有函数级作用域。复制代码代码如下:functiontest(){//ascopefor(vari
在之前的介绍中,我们已经知道Javascript没有块级作用,只有函数级作用域。复制代码代码如下:functiontest(){//ascopefor(vari
1在js中只有两种作用域a:全局作用域b:函数作用域在ES6之前,js是没有块级作用域。首先来解释一下什么是没有块级作用域?所以此时是可以打印输出变量a