Js闭包面试解答

面试基本都会问到的问题,面试题基本都会有的一项问题

1、例子

    

产品一

产品二

产品三

产品四

产品五

(1) 需求 : 每次点击对应目标时弹出对应的index( 0~4)

function onMyLoad(){
    var arr = document.querySelectorAll('p')
    for(var i = 0, len = arr.length; i < len; i++) {
        arr[i].onclick = function(){
          alert(i)
        }
    }
}

执行上面的代码,你会发现每次弹出的都是5。为什么会出现这种问题?

原因是i是全局变量,在你点击任意一项的时候,i的值已经变为了5,由于i是全局变量,所以,所有弹出的都是5

2、解决方案
  • 方案一 : 使用let 声明变量
 for(let i = 0; i < arr.length;i++){
    arr[i].onclick = function(){
         alert(i)
    }
}

原理 : 变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算(参见es6阮一峰)。本质就是 let 声明的变量具有块级作用域

  • 方案二 : 立即执行函数+闭包
for(var i = 0; i < arr.length;i++){
    arr[i].onclick = (function(){
        var j = i
        return function() {
            alert(j)
        }
    }())
}
for(var i = 0; i < arr.length;i++){
    arr[i].onclick = (function(i){
        return function() {
            alert(i)
        }
    }(i)) 
}
for(var i = 0; i < arr.length;i++){
    (function(i) {
        arr[i].onclick = function(){
            alert(i)
        } 
    }(i))    
}
for(var i = 0; i < arr.length;i++){
    (function() {
        var j = i;
        arr[i].onclick = function(){
            alert(j)
        }  
    }())    
}

原理 : 上边的四种方法其实就是一种方法。在一个函数中返回另一个函数,并且返回的函数中引用到了父函数作用域中的变量,则该变量不会被自动回收。外部函数还有对此变量的引用。(语言表达能力欠佳,可以参考犀牛书闭包章节,讲的很清楚)

  • 方案三 : 单独设置属性
for(var i = 0; i < arr.length;i++){
    arr[i].i = i;
    arr[i].onclick = function(){
        alert(this.i)
    }     
}

原理 : 为数组的每个元素单独设置对应的属性,此属性是唯一的,不会再是全局变量

  • 方案四 : 通过bind()方法实现
function f(i) {
   alert(i)
}
for(var i = 0; i < arr.length;i++){
        arr[i].onclick = f.bind(null,i)
}

原理 : 通过f.bind(),将f包装返回一个新的函数。 (解释的不行,其实还是闭包的应用,因为bind()方法就是使用闭包实现的)参见下面bind()简单的实现,你就明白了: 在执行f.bind(null, i)的时候,参数传入了闭包函数中,并且返回了一个函数,且返回的这个函数引用到了父函数中的boundArgs(这里面就包含了当前的i)变量,因此在返回函数后,此变量不会被垃圾回收机制回收,在返回的函数中还可以正常引用到boundArgs(这里面就包含了当前的i)。

Function.prototype.fbind = function(obj, /*args*/) {
    let self = this,
        boundArgs = arguments;
    return function() {
        let args = [],
            i;
        for(i = 1, len = boundArgs.length; i < len; i++){args.push(boundArgs[i])};
        for(i = 0, len = arguments.length; i < len; i++) {args.push(arguments[i])};
        return self.apply(obj, args)
    }
}
  • 方案五 : 通过Function实现(用到的不多)
for(var i = 0;i

解释一下上面函数 : 通过 new 使用 Function 的构造函数 创建 Function 实例实现,由于传入的函数体的内容是字符串,故 Function 得到的是一个字符串拷贝,而没有得到 i 的引用(这里是先获取 i.toString()然后与前后字符串拼接成一个新的字符串,Function 对其进行反向解析成 JS 代码

3、 补充一个常见的面试题
//方案一
for (var i = 0; i < 5; i++) {
    setTimeout( (function timer(i) {
        return function() {
            console.log(i);
        }
    }(i)), 0 );
}
//方案二
for (var i = 0; i < 5; i++) {
    (function(i) {
        setTimeout( function() {
            console.log(i);
        }, 1000);
    }(i))
}
......
4、补充一个常见的面试题

Helpful notes will appear here

E-mail:

Name:

Age:

你可能感兴趣的:(Js闭包面试解答)