由 for 轮回典范面试题延长的 js 相干学问

之前工作中碰到一个需求,须要依据从背景猎取到的图片途径取得这些图片的 base64 文件。完成过程当中碰到一个题目,代码以下:

var src=["http://www.w3school.com.cn/i/site_photoref.jpg",
    "http://www.w3school.com.cn/i/site_photoexa.jpg",
    "http://www.w3school.com.cn/i/site_photoqe.jpg"] 
for(var i=0;i<3;i++){
    var img=new Image();
    img.src=src[i];
    img.onload=function(){
        console.log(img)   
        //终究打印出来都是末了一个图片
        //<img src="http://www.w3school.com.cn/i/site_photoqe.jpg">
        //<img src="http://www.w3school.com.cn/i/site_photoqe.jpg">
        //<img src="http://www.w3school.com.cn/i/site_photoqe.jpg">
    }
}

这个题目实在跟之前常常碰到的一个面试题本质上是一致的。我们能够在上面函数中打印索引值,会发明打印出来的值都是3。经由 segmentfault 上网友的点拨,这个题目涉及到 js 中的两个题目,作用域链事宜实行机制

作用域链

在 js 中,每一个函数都有本身的实行环境,每一个实行环境都有一个与之关联的变量对象。当代码在一个环境中实行时,会建立变量对象的一个作用域链。每一个作用域链的出发点都是当前实行代码地点的实行环境的变量对象,作用域链的下一个变量对象来自包含环境,一向延续到全局实行环境(浏览器中是指 window 对象)。
假如实行环境是函数,那末它的变量对象就包含运动对象,运动对象在一最先只包含 arguments 对象(函数的参数对象)。

当实行环境中要用到某个变量或许函数时,会从本身作用域链的出发点也就是本身的变量对象中最先搜刮响应的变量名或许函数名,假如搜刮不到就接着在作用域链的上一级搜刮,一向到找到相干变量名或许到作用域链的末端为止。

js 事宜实行机制

js 是一种单线程言语,在主线程中同一时候只能实行一个使命。

浏览器内核线程

浏览器内核是多线程的,一般包含以下线程:

  • GUI 衬着线程:
    担任衬着网页,当页面须要重绘时,该线程就会实行。

  • JavaScript 引擎线程:
    也就是 JS 内核,担任剖析和运转 JS 代码。

  • 定时器触发器线程:
    经由过程这个线程计时来肯定什么时候触发定时器。

  • 事宜触发线程:
    监控某个事宜是不是触发,事宜触发以后会被添加到使命行列中。

  • 异步 HTTP 要求线程:
    监控 AJAX 的状况变动时,就会把响应的使命添加到使命行列中。

同步和异步

js 中每一个使命的操纵能够简化为提议挪用取得效果两步,依据这两步能够把js 中的使命能够分为同步使命和异步使命。一切使命的实行都在主线程举行。
同步使命:提议挪用以后,马上就会实行来猎取效果的使命。挪用以后会一向守候直到返回效果,在这时期主线程不能举行其他操纵。
异步使命:提议挪用以后,并不会马上实行相干函数,而是须要分外的操纵满足相干前提以后举行触发。相干使命被触发以后会进入使命行列守候主线程使命实行完成后按递次进入主线程,挪用和实行之间的时候能够参与其他异步使命。罕见的异步使命有定时器、ajax和事宜回调等。

事宜轮回机制(event loop)

js 中事宜实行基础根据下面这三步举行轮回。

  • 主线程先根据代码递次实行同步使命

  • 在异步使命被注册以后,浏览器的其他线程(事宜触发线程、定时器触发线程、异步 HTTP 要求线程)监控异步使命的触发前提,根据触发递次把这些异步使命放在使命行列中

  • 主线程上同步使命实行完以后,会顺次实行使命行列中的使命

回到开首

在最上面的例子中,for 轮回是同步使命,会马上实行,图片的 onload 事宜是异步使命,须要等另行触发。这个例子中代码的实行递次是如许:

同步使命:轮回建立三张图片,每一个图片给予各自的 src 值,而且都注册了一个 onload 事宜。
异步使命:三张图片的 onload 事宜顺次触发,回调函数进入使命行列,等主线程的 for 轮回实行终了以后,顺次实行这三个使命。

当最先实行异步使命时,每一个函数都须要用到 img 这个变量,就最先在本身的作用域链上最先寻觅 img,本身变量对象中不存在,接着在包含环境中找到,因为 for 轮回并不会制造一个新的实行环境,所以这个例子中包含环境实在就是全局实行环境。而在 for 轮回完以后,img 变量的值已经由两次掩盖变成了末了一个索引对应的图片。所以每一个图片的 onload 函数都邑打印出同一个 img 。打印 i 值涌现的效果也是一样。
《由 for 轮回典范面试题延长的 js 相干学问》

处理要领

弄清楚涌现这个题目的缘由,处理这个题目能够用下面的要领:

要领1:建立零丁的实行环境

for(var i=0;i<3;i++){
    (function(index){   
        var img=new Image();
        img.src=src[i];
        img.onload=function(){
            console.log(index)
            console.log(img)   
        }
    })(i)
}

这个要领完成的道理是:for 轮回中马上实行函数每次都邑建立一个新的实行环境,三张图片的 onload 事宜函数的作用域链的包含环境分别是这三个马上实行函数,这三个马上实行函数内里保留的是差别的 img 变量和差别的参数 index,当三个 onload 回调函数实行时,分别在本身的作用域链上寻觅各自对应的 img 变量。应用一样的道理,也能够写成如许:

for(var i=0;i<3;i++){
    var img=new Image();
    img.src=src[i];
    img.onload=function(index,img){
        return function(){
            console.log(index)
            console.log(img) 
        }  
    }(i,img)
}

也能够不经由过程传参,而是在马上实行函数内部建立一个变量来吸收每次轮回中的 i 值,道理都是一样的。

要领2:接见事宜触发节点

for(var i=0;i<3;i++){
    var img=new Image();
    img.src=src[i];
    img.onload=function(){
        console.log(this)   
    }
}

这个要领的道理是:函数内部在实行过程当中会有一个默许的 this 变量会把函数的挪用对象保留起来,经由过程函数内部的 this 就能够接见挪用函数的对象。或许能够经由过程 event 事宜对象的 currentTarget 属性接见到事宜触发节点,道理是一样的。

要领3:ES6 的新语法 let

for(let i=0;i<3;i++){
    let img=new Image();
    img.src=src[i];
    img.onload=function(){
        console.log(img)   
        console.log(i)
    }
}

在 ES6 中划定了一个新的变量声明敕令 let,let 会建立一个块级作用域,用 let 声明的变量只在 let 地点的代码块中有用。这个例子中,三次轮回会建立三个块级作用域,每一个块级作用域中有各自的变量 i 和 img,相互自力,每一个 onload 回调函数实行时都邑猎取各自代码块中的 i 和 img ,终究能完成我们想要的效果。

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