资讯专栏INFORMATION COLUMN

javascript执行机制<一>

codeKK / 365人阅读

摘要:提示,很显然,出了循环的的大括号对应的作用域之后,就会被自动销毁。那么呢,也是这样么我们来看个例子这段代码执行结果是估计有人也会比较奇怪。这边我解释下执行这段代码的过程。提出了用关键字来代替关键字,具体的话可以参考阮一峰的而教程。

从一道题说起

最近又有人问我下面这道题目,题目是这样的,首先是一个DOM结构如下:




    
1
2
3
4
5

非常easy的dom结构,在来一小段js,如下:

var nodes = document.getElementsByTagName("div");
for(var i = 0,len = nodes.length; i < len; i++){
    nodes[i].onclick = function(){
        console.log(i);
    }
}

好了,问题来了,依次点击div,结果是多少?答案并不是1,2,3,4,5,而是点击任何一个div都会输出5.

分析

先来说下为什么最后执行的结果都是5.首先我们要明白,js中没有块级作用域,讲人话,就是js中不存在{}这种代码块的东西。各位估计会反驳我说,上面例子中不是明明白白的写的for(){}这种代码,怎么这边就开始说js不存在{}这种东西呢?我先举个C++的例子吧

int arr[] = {1,2,3,4,5};
vector v = vector(arr,arr+sizeof(arr)/sizeof(int));
for(int i = 0; i < v.size(); i++){
    std::cout << i << std::endl;
}

这么写是没有问题的,下面我再加点东西

int arr[] = {1,2,3,4,5};
vector v = vector(arr,arr+sizeof(arr)/sizeof(int));
for(int i = 0; i < v.size(); i++){
    std::cout << i << std::endl;
}
std::cout << i;

这么写,编译器直接就报错了。提示 error: use of undeclared identifier "i",很显然,出了for循环的{}的大括号对应的作用域之后,i就会被自动销毁。那么JS呢,也是这样么?我们来看个例子

for(var i = 0;i< 5;i++){
    console.log(i);
}
console.log(i);

这段代码执行结果是0,1,2,3,4,5.估计有人也会比较奇怪。这边我解释下JS执行这段代码的过程。
首先是变量提升,js把var i = 0;分解成两句话,var i;i =0;并且把var i;提到最近一个function的顶部,这个时候,这段代码就变成了这样

var i;
for(i=0;i<5;i++){
    console.log(i);
}
console.log(i);

这样各位对于上面执行出来的0,1,2,3,4,5估计就没啥疑问了。
看完这个例子之后,我也希望各位注意下我前面说的js没有块级作用域,以及js会做变量提升,把变量的申明提升到最近的一个function的顶部
由于js会做变量提升,自动将变量的申明提升到最近的一个function的顶部,所以{}根据不会构成所谓的块级作用域,对js里面的变量而言,只有function才会是其作用域。

好了,讲完js的变量提升,我们再回头来看最开始的这个问题。首先是变量提升,提升之后我们得到

var nodes = document.getElementsByTagName("div");
var i;
for(i = 0,len = nodes.length; i < len; i++){
    nodes[i].onclick = function(){
        console.log(i);
    }
}

执行过程中,我们对每个node[i]节点都绑定了一个onclick事件,但是for循环执行的过程中,我们并没有出发这个click事件,for循环执行结束之后,i变为5。当用户点击div的时候,这个时候执行对应的onclick函数,也就是console.log(i),这个时候,会自动找到被js变量提升过的i,所以大家都会输出5.

解决

总结下,上面的问题之所以会产生,就是因为所有的onclick事件都去引用被js变量提升的i,那么如果我们想要解决这个问题,应该怎么办呢。一个就是我们可以通过JS的IIFE(immediately-invoked-function-expression)来构造一个作用域,让onclick函数引用我们构造出来作用域里面的i。ok,我们来解决下

var nodes = document.getElementsByTagName("div");
for(var i = 0,len = nodes.length; i < len; i++){
    (function(i){
        nodes[i].onclick = function(){
            console.log(i);
        }
    })(i)
    
}

这种做法把整个绑定事件的过程都给包起来了,由于IIFE会马上执行,for循环的i相当于一个输入参数,在绑定完事件只有,也形成了一个作用域,并且这个作用域中存在一个i的值。

同样的道理,我再给一种解法,如下:

var nodes = document.getElementsByTagName("div");
for(var i = 0,len = nodes.length; i < len; i++){
    nodes[i].onclick = (function(i){
        return function(){
            console.log(i);
        }
    })(i)
}

除此之外,我们可能会想到,如果js能够有这种块级作用于就好了,我们绑定的事件一定是在{}作用域下面,一定可以引用到for循环中的每个i,而不是应用哪个被变量提升的i。ES6提出了用let关键字来代替var关键字,具体的话可以参考阮一峰的而ES6教程。上个代码,这边代码用了一个inbrowser的es6转码器,可以测试用,如果想要生产环境中使用需要提前将es6代码编译成es5的代码。






    
1
2
3
4
5
>

引用了一个inbrower级别的es6转码器。具体可以参考babel-standalone项目.改进后的代码与原来的代码的区别在于,将var i = 0换成了let i = 0.
下面我在看下,通过转码之后,到底生成了什么样的js代码,通过es6转码器,我们最终生成了如下的代码

var nodes = document.getElementsByTagName("div");

var _loop = function _loop(i, len) {
    nodes[i].onclick = function () {
        console.log(i);
    };
};

for (var i = 0, len = nodes.length; i < len; i++) {
    _loop(i, len);
}

原来ES6帮我们构造了一个function的作用域报过了node[i].onclick的事件绑定过程,跟我们上面的解决方法其实是一样的!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/82225.html

相关文章

  • 详细解说JavaScript内存管理和GC算法

      JavaScript在创建变量(数组、字符串、对象等)是自动进行了分配内存,而且当它没有被使用的状态下,会自动的释放分配的内容;其实这样基层语言,如C语言,他们提供了内存管理的接口,比如malloc()用于分配所需的内存空间、free()释放之前所分配的内存空间。  释放内存的过程称为垃圾回收,例如avaScript这类高级语言可以提供了内存自动分配和自动回收,其实这个自动储存不会占用太多空间...

    3403771864 评论0 收藏0
  • 解析关于JavaScript事件循环同步任务与异步任务

      学习一门知识,有些内容必须要提前明白,比如在学习js中同步异步的问题前,需要明白,js是单线程的,为什么它得是单线程的呢?现在先从它应用的场景来说,就是用来让用户与页面进行交互的吧。假如有js是多线程的,那在这个线程里面,用户点击某个按钮会增加一个DOM节点,在另一个线程里面,用户点击这个按钮又会删除一个DOM节点,那么此时js就不知道该听谁的了。这就是为什么会出现同步异步。假设没有异步,那么...

    3403771864 评论0 收藏0
  • JavaScript数据结构与算法

      学习JS,就应该知道数据结构与算法这个词。现在我们就说说:  数据结构与算法在编程中是十分需要,主要是没有很好的数据结构与算法的功底,就影响后续学习和工作,这是为什么那?是因为随着项目的复杂,数据量也随之变大,数据结构与算法可以更优雅的处理这些数据。  程序=数据结构+算法,是计算机科学界的一个经典名句,这句话也体现了一个应用程序是与数据结构和算法密不可分的。  数据结构  其实数据结构简单说...

    3403771864 评论0 收藏0
  • 同源策略

    一、浏览器安全无风险的世界不存在,包括浏览器,我们知道Web世界是开放的,包容的。但是开放和风险是对立的。Web 世界会是开放的,任何资源都可以接入其中,我们的网站可以加载并执行别人网站的脚本文件、图片、音频 / 视频等资源,甚至可以下载其他站点的可执行文件。比如你打开了一个银行站点,然后又一不小心打开了一个恶意站点,如果没有安全措施,恶意站点就可以做很多事情:修改站点的 DOM、CSSOM 等信...

    社区管理员 评论0 收藏0
  • useEffect支持async及await如何运用

    背景  在使用useEffect中用啦回调函数中使用 async...await... 这时候就会报错。  上面代码可以看到,在报错,effect function 应该返回一个销毁函数(effect:是指return返回的cleanup函数),如果 useEffect 第一个参数传入 async,返回值则变成了 Promise,结果就是会导致 react 在调用销毁函数的时候报错。  React...

    3403771864 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<