Showing posts with label closures. Show all posts
Showing posts with label closures. Show all posts

Tuesday, June 08, 2010

javascript: closures, lexical scope and scope chain

闭包的定义(javascript权威指南)如下:
JavaScript functions are a combination of code to be executed and the scope in which to execute them. This combination of code and scope is known as a closure in the computer science literature. All JavaScript functions are closures.
javascript的function定义了将要被执行的代码,并且指出在哪个作用域中执行这个方法,这种代码和作用域的组合体就是一个闭包,在代码中的变量是自由的未绑定的。闭包就像一个独立的生命体,有其自身要运行的代码,同时其自身携带了运行时所需要的环境。
javascript所有的function都是闭包。

闭包中包含了其代码运行的作用域,那这个作用域又是什么样子的呢,这就引入了词法作用域(lexical scope)的概念:
词法作用域是指方法运行的作用域是在方法定义时决定的,而不是方法运行时决定的。
所以在javascript中,function运行的作用域其实是一个static scope。但也有二个例外,就是with和eval,在这2者中的代码处于dynamic scope中,这给javascript带来额外的复杂度和计算量,因而也效率低下,避免使用。

当闭包在其词法作用域中运行过程中,如何检索其中的变量名?这就再引入了一个概念,作用域链(scope chain):
当一个方法function定义完成,其作用域链就是固定的了,并被保存成为方法内部状态的一部分,只是这个作用域链中调用对象的属性值不是固定的。作用域链是"活"的。
当一个方法在被调用时,会生成一个调用对象(call object or activation object),并将此call object加到其定义时确认下来的作用域链的顶端。
在这个call object上,方法的参数和方法内定义的局部变量名和值都会存在这个call object中,如果调用结束,这个call object会从作用域链的顶端移除,再没有被其他对象引用,内存也会被自动回收。
在此call object中使用的变量名会先从此方法局部变量和传入参数中检索,如果没有找到,就会向作用域链上的前一个对象查询,如此向上追溯,一直检索到global object(即window对象上),如果在整个作用域链上没有找到此变量名,则会返回undefined(没有指定对象直接查询变量名,没找到则抛出异常变量未定义)。
如此通过作用域链,javascrip就实现了call object中变量名检索。

在全局对象中一个方法调用完成之后,生成的call object会被回收,这看不出闭包(即当前被调用的方法)有什么功用。但是当一个外部方法的内部返回一个嵌套方法,并且返回的嵌套方法被全局对象引用时,或者是外部方法内将嵌套方法赋给全局对象的属性(jQuery构造方法就是在匿名方法内设置在window.jQuery上),外部方法调用生成的call object就会引用这个嵌套方法,而同时嵌套方法被全局对象引用,所以这个外部方法调用产生的call object及其属性就会继续生存在内存中,这时闭包(外部方法)的功用才被显示出来,下面以jQuery.fn.animation()方法调用过程为例进行说明:

1、当载入整个jquery.js文件时,会运行最外面的匿名方法(通过这个匿名方法形成一个命名空间,所有的变量名都是匿名方法内部定义的局部变量名):


(function( window, undefined ) {
// ......jQuery source code;
// Expose jQuery to the global object
window.jQuery = window.$ = jQuery;
})(window);
2、因为匿名方法内部有一个内部方法jQuery被全局对象window的属性jQuery和$引用,这里变量名很搞,一个是匿名方法内嵌套的构造方法jQuery,另一个window对象的属性名jQuery。因为这个匿名方法内部的jQuery构造方法被全局对象window.jQuery引用,所以外围的匿名方法在运行时产生的call object会继续生存在内存中。此时,这个call object可以利用Firebug或者Chrome的debug工具可以看到,在Firebug中的scopeChain中称之为"Object",在Chrome的console中称之为"Closure",该对象中记录了当前这个最外围的匿名方法被调用后生成的call object上变量的值,这些变量是未绑定的,是自由的,其值可以被修改并保存在作用域链上。运行此匿名方法时,会将其call object置于global object之上,形成作用域链。
这里注意一点,这匿名方法是一个闭包,但运行方法生成的call object对象只是作用域链顶端的一个对象,记录了方法中的变量名和值。闭包不但包括这个运行的作用域,还包括其运行所需的代码。
3、页面不关闭,这个匿名方法调用生成的call object就会一直驻在内存中,接下来当页面发生了一个jQuery.fn.animate()方法的调用,这个时候javascript又会为.animate()方法生成一个call object,这个对象拥有传进来的参数名和值,以及在.animate()方法内部定义的一个局部变量opt和它的值。
同时,javascript会将生成的这个call object置于其作用域链(scope chain)的最前端,即此时的作用域链为:global object->anonymous function call object->animate call object。
4、接下来会调用jQuery.fn.queue()->jQuery.fn.each()->jQuery.fn.dequeue(),在这些方法调用过程也都会接触到第2步中所提到的那个匿名方法调用后生成的闭包,这中间过程略过,当运行到最后传参给.queue(function)的function时,因为这个匿名方法是定义在jQuery.fn.animate()方法内部的,所以其作用域链(scope chain)也就已经确定了,即global object->anonymous function call object->animate call object,当此匿名方法调用生成一个call object,会将此call object再置于animate call object之上。
5、对于最后的匿名function运行完成之后,如果这个匿名function对象还被其他element的queue数组引用,则第3步中运行.animate()方法生成的闭包将继续生存在内存之中,直到所有的效果方法运行完成,此匿名function没有其他引用时,.animate()调用生成的call object就会被回收。

Reference: JavaScript函数调用时的作用域链和调用对象是如何形成的及与闭包的关系

Friday, October 03, 2008

JavaScript函数调用时的作用域链和调用对象是如何形成的及与闭包的关系

1、javascript解析器启动时就会初始化建立一个全局对象global object,这个全局对象就拥有了一些预定义的全局变量和全局方法,如Infinity, parseInt, Math,所有程序中定义的全局变量都是这个全局对象的属性。在客户端javascript中,Window就是这个javascript的全局对象
2、当javascript调用一个function时,会生成一个对象,称之为call object(调用对象),function中的局部变量和function的参数都成为这个call object的属性,以免覆写同名的全局变量。
调用对象: ECMAScript规范术语称之为activation object(活动对象)。
3、javascript解析器每次执行function时,都会为此function创建一个execution context执行环境,在此function执行环境中最重要的一点就是function的作用域链scope chain,这是一个对象链,由全局对象调用对象构成,对象链具体构成过程见下面说明。
4、当javascript查询变量x的值时,就会检查此作用域链中第一个对象,可能是调用对象或者是全局对象,如果对象中有定义此x属性,则返回值,不然检查作用域链中的下一个对象是否定义x属性,在作用域链中没有找到,最后返回undefined。
5、当javascript调用一个function时,它会先将此function定义时的作用域作为其作用域链,然后创建一个调用对象,置于作用域链的顶部,function的参数及内部var声明的所有局部变量都会成为此调用对象的属性。
6、this关键词指向方法的调用者,而不是以调用对象的属性存在,同一个方法中的this在不同的function调用中,可能指向不同的对象。
7、The Call Object as a Namespace
(function() {
// 在方法体内用var声明的所有局部变量,都是以方法调用时创建的调用对象的属性形式存在。
// 这样就避免与全局变量发生命名冲突。
})();
8、javascript中所有的function都是一个闭包,但只有当一个嵌套函数被导出到它所定义的作用域外时,这种闭包才强大。如果理解了闭包,就会理解function调用时的作用域链和调用对象,才能真正掌握javascript。
9、当一个嵌套函数的引用被保存到一个全局变量或者另外一个对象的属性时,在这种情况下,此嵌套函数有一个外部引用,并且在其外围调用函数的调用对象中有一个属性指向此嵌套函数。因为有其他对象引用此嵌套函数,所以在外围函数被调用一次后,其创建的调用对象会继续存在,并不会被垃圾回收器回收,其函数参数和局部变量都会在这个调用对象中得以维持,javascript代码任何形式都不能直接访问此对象,但是此调用对象是嵌套函数被调用时创建的作用域链中的一部分,可以被嵌套函数访问并修改。

Wednesday, August 13, 2008

javascript 闭包妙用

在javascript中,程序是一行行执行的,在上一行返回结果后执行下一行的语句。如果在执行一条要耗时的操作,而接下来的程序操作与此条执行语句并无关系,则可将此语句放在一个闭包里去执行,用setTimeout(function(){}, 0)来激活此闭包运行,这个技巧可用于加速页面渲染,同时对多个dom对象操作。


<html>
<head>
<title>Yield demo</title>
<script type="text/javascript">
<!--
function doSave(id, doYield) {
document.getElementById(id).innerHTML = '<span style="font-style: italic;">Saving...</span>';
if (doYield) {
var startTime = (new Date()).getTime();
setTimeout(function() { doSaveImpl(id); }, 0); // runing in clousres
var endTime = (new Date()).getTime();
alert("yield time is:" + (endTime - startTime));
} else {
var startTime = (new Date()).getTime();
doSaveImpl(id);
var endTime = (new Date()).getTime();
alert(endTime - startTime); // firebug console.log()
}
}
function doSaveImpl(id) {
var numIters = 10000000;
for (var i = 0; i < numIters; i++) {
var j = Math.sqrt(i); // slow operation
}

document.getElementById(id).innerHTML = '<span style="color: #090">Saved!</span>';

setTimeout(function() { reset(id); }, 3000);
}
function reset(id) {
document.getElementById(id).innerHTML = 'Ready';
}
//-->
</script>
<style type="text/css">
.button {
border: 1px solid black;
background: #ffff9f;
padding: 5px;
font-size: 14pt;
color: black;
text-decoration: none;
-moz-border-radius: 8;
}
.status_msg {
color: #999;
margin-top: 0.5em;
font-size: 18pt;
}
</style>
</head>
<body>

<h1>Yield Demo</h1>

<h2>Without yielding:</h2>

<a href="javascript:;" onclick="doSave('noyield', false); return false;" class="button">Save</a>
<div id="noyield" class="status_msg">Ready</div>

<h2>With yielding:</h2>

<a href="javascript:;" onmousedown="doSave('yield', true); return false;" class="button">Save</a>
<div id="yield" class="status_msg">Ready</div>

</body>
</html>

Reference: http://josephsmarr.com/oscon-js/yield.html

Wednesday, May 07, 2008

test scope of this in closure and in anonymous functions

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>test scope of this in closure and in anonymous functions</title>
<script type="text/javascript">
var foo = 'this is window.foo!';
var d = [1, 2, 3];
// timeout functions
function Constructor() {
this.foo = 'this is Constructor.foo!';
var that = this;
this.timerId = window.setTimeout(function() {
// alert(this); // will get [object Window]
alert("this.foo = " + this.foo);
alert("that.foo = " + that.foo);
} , 1000);
}
// local functions
Constructor.prototype.getFoo = function() {
alert(this); // [object Object]
var getExternalFoo = (function() {
// alert(this); // will get [object Window]
return d.concat(this.foo)
})();
return getExternalFoo;
};
// using Function.call(object)
Constructor.prototype.getBar = function() {
var getInternalFoo = (function() {
// alert(this); // will get [object Object]
return d.concat(this.foo)
}).call(this);
return getInternalFoo;
};
var f = new Constructor();
document.write(f.getFoo());
document.write("<br />");
document.write(f.getBar());

</script>
</head>
<body>
</body>
</html>

在window.setTimeout和全局环境中的匿名方法中的this是指向window对象的,所以在调用过程中可以将此匿名方法做为实际的某个对象(如this对象)的方法来调用.

Sunday, January 27, 2008

方法名做为另外一个函数的参数-闭包用法一

<html>
<head><title> test cloures </title></head>
<body>
<script type="text/javascript" charset="utf-8">
var a = b = 1; // var a = (b = 1), variable b is no declared(like var b, the scope of b is global.)
function test() {
alert("a=" + a); // 1
alert("b=" + b); // 10
return a + b;
}

function testClosures(funcName) {
var a = b = 10; // var a = (b = 10); b is a global variable.
return funcName();
}
alert(testClosures(test));
</script>
</body>
</html>

Monday, January 21, 2008

keywords of this in javascript methods

<html>
<head>
<title> cloures </title>
<script src="/lib/prototype/prototype.js" type="text/javascript"></script>

<script type="text/javascript">
var o = {};
var obj = {
name: 'A nice demo in obj',
fx: function() {
return this.name + " " + $A(arguments).join(', ');
},
rf: function() {
return function() { return this.name + " " + $A(arguments).join(', '); }
}
};
alert(obj.fx);
window.name = 'I am such a beautiful window!';
function runFx(f) {
return f();
}
var fx2 = obj.fx.bind(obj, 'otherArg1', 'otherArg2');
var rf2 = obj.rf.bind(obj, 'otherArg1', 'otherArg2');
alert(fx2);
document.write(runFx(obj.fx));
document.write("<br />");
document.write(runFx(fx2));
document.write("<br />");
document.write(obj.rf()());
document.write("<br />");
document.write(rf2()());
document.write("<br />");
</script>
</head>
<body>
Javascript的闭包函数(如将函数做为另一个函数/方法的参数或者是返回结果为函数)中的this会因为闭包函数使用环境不同而变化。
<br />
prototype.js里加了一个bind方法可以把this重新绑回到原对象上(如fx方法所示),但如果返回的是闭包函数,则其中的this会指向新的object(如rf2方法所示指向了window)
</body>
</html>

Monday, October 15, 2007

JavaScript中的闭包closures简单说明

<script type="text/javascript">
uniqueID = (function() { // The call object of this function holds our value
var id = 0; // This is the private persistent value
// The outer function returns a nested function that has access
// to the persistent value. It is this nested function we're storing
// in the variable uniqueID above.
return function() { return id++; }; // Return and increment
})(); // Invoke the outer function after defining it, and return a function: function() { return id++; }

alert(uniqueID()); // alert(function() { return id++; }());
alert(uniqueID());
alert(uniqueID());
// JavaScript函数是将要执行的代码以及执行这些代码的作用域和作用域的arguments一起构成的一个综合体,即使函数包含相同的JavaScript代码,并且每段代码都是从相同的作用域调用的,还是可以返回不同的结果的。因为JavaScript中的函数是在当时定义它们的作用域里运行的,而不是在执行它们的作用域里运行的。这种代码和作用域的综合体叫闭包。所有的JavaScript函数都是闭包。
</script>

当一个嵌套函数被导出到它所定义的作用域外时,这种闭包才有意思。当一个嵌套的函数以这种方式使用时,通常被明确的叫做一个闭包。
uniqueID的函数体为function() { return id++; },它是从一个function literal中返回得到,并包含了导出后的作用域,包含了变量名和值等,也就是从这个匿名函数是返回了一个闭包。
在uniqueID被函数运算符()调用时,已经在函数定义的作用域外,所有调用操作会影响闭包内的变量并仍会被此闭包继续保存。

Ruby和Perl中有个lambda方法也可以生成一个闭包。

更多关于javascript的闭包说明请查看此处