javascript实行机制<一>

从一道题提及

近来又有人问我下面这道题目,题目是如许的,起首是一个DOM组织以下:

<html>
<head></head>
<body>
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
</body>
</html>

异常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<int> v = vector<int>(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<int> v = vector<int>(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的代码。

<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
   <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
   <script type="text/babel">
        let nodes = document.getElementsByTagName('div');
        console.log('exec');
        for(let i = 0,len = nodes.length; i < len; i++){
            nodes[i].onclick = function(){
                    console.log(i);
                }
        }
   </script>>
</body>
</html>

<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>援用了一个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的事宜绑定历程,跟我们上面的处理方法实际上是一样的!

    原文作者:warjiang
    原文地址: https://segmentfault.com/a/1190000008859224
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞