setTimeout闭包疑问


代码如下:


 js


 var div = $('#appendHere');

$('#clickMe').on('click', function () {
    var that = this;
    div.append(checkForWindow(this));

    setTimeout(function () {
        div.append(checkForWindow(this));
        div.append('<strong>that</strong> is the ' + that.tagName + '<br/>');
    }, 300);
});


function checkForWindow(elm) {
    if (elm instanceof Window) {
        return 'this is the Window<br/>';
    } else if (elm.tagName) {
        return ('this is the ' + elm.tagName + '<br/>');
    } else {
        return ('this is ' + elm + '<br/>');
    }
}

输出结果:(另见: http://jsfiddle.net/dposin/okjr81ev/light/ )

this is the BUTTON
this is the Window
that is the BUTTON

问题来了,setTimeout中的 checkForWindow(this) 为什么没有形成闭包,而 that 又形成闭包了呢?

这样写就能形成闭包?

本来我以为是要这样写的:


 js


 for (var i = 0; i < 10; i++) {
  setTimeout((function(i) {
    console.log(i);
  })(i), 0);
}

这两种写法有区别吗?

注:我又理解了一下,发现两种都是闭包,只不过所处的上下文环境不一样。但是 checkForWindow(this) 中的 this 不是取闭包中的 this ,而是取了 window 呢?也是闭包啊,闭包中上下文也有 this 啊,为什么就要取你 setTimeout 中的上下文 this 呢?

=======================================

好文链接:
setTimeout:
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setTimeout

闭包:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
http://www.zhihu.com/question/20019257

JavaScript 闭包

tom34 8 years, 9 months ago

我觉得例子的 setTimeout 中的匿名函数还是有闭包。因为 setTimeout 是在所有正常(普通)代码执行后再执行的,直接使用 setTimeout 时,不管setTimeout定义在哪里,setTimeout执行时所处的上下文为全局上下文,但是在我的例子中, that是一个局部变量,这个that局部变量执行完,但是被保存了,没有被回收。难道这不算是闭包?

nethack answered 8 years, 9 months ago

其实JS中的所有function均为(lexical) closure,所以直接理解了函数的定义与执行过程自然就理解closure的特点了。
function在定义的时候(就是生成Function Object的)会将当前EC的Scope Chain作为自身的[[Scope]]的属性值,其中that变量位于当前EC的AO中,因此自然也在settimeout延迟执行函数的[[Scope]]属性中。
然后在settimeout的延迟执行函数执行时会先构建自己的EC,新EC中的Scope Chain = AO + [[Scope]],然后在延迟函数中访问that时则在Scope Chain解析这个引用,那自然能找到之前设置的值。
而this是关键字,跟变量是不同的,因此不会在Scope Chain上解析。在EC中有一个Context Object的属性,我们访问this其实就是访问EC的Context Object,这个属性是根据函数的调用方式来决定的(暂且这样理解吧,其实底层有一套比较难理解的规范的)


 function callMeBaby(){console.log(this)}

callMeBaby() // 显示[object Window],this指向window

host = {callMeBaby: callMeBaby}
host.callMeBaby() // 显示[object Object],this指向host

上面可以理解为this就是指向函数调用时的所属对象,如果没有所属对象则指向全局对象(window)。下面示例就只能理解底层原理后才能理解透了,先留个坑吧


 (1, host.callMeBaby)() // 显示[object Window],this指向window

火星防卫小兵 answered 8 years, 9 months ago

完全同意 @斑驳光影 的回答,就是回调函数中 this 的指向问题。

xysyc answered 8 years, 9 months ago

函数的上下文不是运行时的上下文而是函数定义时所在的上下文。
闭包就是根据上边的特点,封装在一个函数中作为返回值。
你这个例子里checkForWindow 这个函数 本来就定义在全局中。 哪来的闭包。

还有闭包不是this和that的区别。 闭包到底是啥,你再看看犀牛书。

你需要理解的是第一句话。你就明白了checkForWindow(this),为何打印的是window。而checkForWindow(that),打印的是 BUTTON,原因是你给他传了$('#clickMe') 对象进去。

这本来是一个上下文问题和传参问题。根本就不是一个闭包问题。

工口D大魔王 answered 8 years, 9 months ago

说说个人的理解,楼主可能对闭包的理解有些问题,闭包是指返回的函数能够保留对其父函数中变量的引用
而楼主说的问题,其实是this的指向问题,和闭包没有什么关系,现在说说我对上面现象的理解:

div.append(checkForWindow(this)); 这个里面的this是调用触发当前函数的element,也就是BUTTON;
然后 var that = this; 这里将 element赋值给that,这里的that就是个普通的变量名,不是关键字,没有任何特殊意义;
接下来到 setTimeout 语句,其中传入的为一个匿名函数,这个不是什么闭包就是个函数,然后这个定时器在300ms之后被调用了,里面又调用了 div.append(checkForWindow(this)); ,这个时候,this的指向已经变了, 根据this的定义,this是指向调用当前函数的对象 ,而对于这种匿名函数的调用,这时this指向的就是顶级的 window ;因此打印出来就是Window;
最后的 that,这个和闭包也没有什么关系,这个根据作用域的知识,可以知道在匿名函数中没有这个变量,因此去更加高一级去寻找这个变量,也就是指向BUTTON的this;

最后说说楼主的这个函数


 for (var i = 0; i < 10; i++) {
  setTimeout((function(i) {
    console.log(i);
  })(i), 0);
}

这个其中的


 (function(i) {
    console.log(i);
  })(i)

这个叫立即执行函数,其中的i会被存储起来,是一个闭包,因此会打印出 0到9 的数字,但是这个写法对于定时器来说也是有问题的,你会发现不论你的时间间隔设置多长, 都是瞬间完成,因为这个函数直接执行了,其实赋值给定时器是一个undefined,也就是说,setTimeout其实没有执行函数,你看到的打印数字是在你定义setTimeout的时候打印出来的

正确的写法是


 for (var i = 0; i < 10; i++) {
  setTimeout((function(i) {
    return function(){
       console.log(i);
    }      
  })(i), xxtime);
}

这样写会在xxtime ms之后连续执行10个定时器

稳如pooi answered 8 years, 9 months ago

只要内部函数有权访问外部函数的作用域中的变量,就形成了闭包。是否闭包与this指向的是哪个对象没有关系。

任何情况下,this指向的是函数的所“挂靠”的对象。若函数是以对象的方法的”角色“被创建,即object.fun =function(){},那么函数fun的执行环境中的this关键词指向函数属对象;

若用var关键词创建,这里又要分两种情况:

若函数执行环境不是全局执行环境,而是作为子函数在父函数中被嵌套调用,则该函数的执行环境的this关键词与其父函数的this指向同一个对象。

在你代码中,setTimeout的回调函数是匿名函数,所有匿名函数都是window对象的perperty,在不矫正回调函数执行环境的前提下,this关键词都指向window对象。

畸智的傻逼 answered 8 years, 9 months ago

Your Answer