Showing posts with label Scope. Show all posts
Showing posts with label Scope. 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, August 08, 2008

scope of javascript anonymous function


var foo = "test in global window";
function Constructor() {
this.foo = 'test in Constructor';
this.local = (function() {
alert(this.foo);
return "local";
}).apply(this);
this.globals = (function(){
alert(this.foo);
return "global";
})();
}
new Constructor();

In javascript, scope of anonymous functions is global.
匿名函数中的作用对象是全局的window对象,一般是不需要注意这点,但当在匿名函数中使用使用this就要小心,这个this是指向window的,如上所示可以用apply或者call来指定匿名函数作用于哪个对象上,但匿名函数如果在setTimeout/setInterval中使用的话则需要将this对象用别名如_self/self/_this/that替代后在匿名方法中使用,如下面二个参考文章所示。
Reference:http://yuweijun.blogspot.com/2008/05/test-scope-of-this-in-closure-and-in.html
http://www.dustindiaz.com/scoping-anonymous-functions/

Thursday, July 31, 2008

Scope and Eager Loading of Tapestry5 Service

在T5中多数service的作用域为"singleton"的,当其interface被使用,则会为此service创建一个代理proxy,此时处于"virtual"阶段,当service中任一方法被调用,即进行"realization"阶段。
这种作用域的service需要注意线程安全,可能会多线程同时调用此service。相对应的还有种service作用域为"perthread"。

当service定义时用了@EagerLoad,Tapestry在Register被创建的时候就会实例化这些service。
Reference: http://tapestry.apache.org/tapestry5/tapestry-ioc/service.html

Thursday, January 10, 2008

ActionScript 变量作用域说明

与 C++ 和 Java 中的变量不同的是,ActionScript 变量没有块级作用域。代码块是指左大括号 ({) 与右大括号 (}) 之间的任意一组语句。在某些编程语言(如 C++ 和 Java)中,在代码块内部声明的变量在代码块外部不可用。对于作用域的这一限制称为块级作用域,ActionScript 中不存在这样的限制,如果您在某个代码块中声明一个变量,那么,该变量不仅在该代码块中可用,而且还在该代码块所属函数的其它任何部分都可用。例如,下面的函数包含在不同的块作用域中定义的变量。所有的变量均在整个函数中可用。
有趣的是,如果缺乏块级作用域,那么,只要在函数结束之前对变量进行声明,就可以在声
明变量之前读写它。这是由于存在一种名为“提升”的方法,该方法表示编译器会将所有的
变量声明移到函数的顶部。例如,下面的代码会进行编译,即使 num 变量的初始 trace() 函
数发生在声明 num 变量之前也是如此。
trace(num); // NaN
var num:Number = 10;
trace(num); // 10
但是,编译器将不会提升任何赋值语句。这就说明了为什么 num 的初始 trace() 会生成 NaN(而非某个数字), NaN 是 Number 数据类型变量的默认值。这意味着您甚至可以在声明变量之前为变量赋值,如下面的示例所示:
num = 5;
trace(num); // 5
var num:Number = 10;
trace(num); // 10


function blockTest(testArray:Array) {
var numElements:int=testArray.length;
if (numElements > 0) {
var elemStr:String="Element #";
for (var i:int=0; i < numElements; i++) {
var valueStr:String=i + ": " + testArray[i];
trace(elemStr + valueStr);
}
trace(elemStr,valueStr,i);// 仍定义了所有变量
}
trace(elemStr,valueStr,i);// 如果 numElements > 0,则会定义所有变量
}
blockTest(["Earth","Moon","Sun"]);

Monday, January 07, 2008

WARNING DON’T LEAVE OUT THE DOT WHEN IT’S NEEDED [R4R page 186]

In one situation, you must use the full object-dot-message notation, even if you’re sending the message to the current self: when the method is a setter method—a method whose name ends with an equal sign. You have to do self.venue = "Town Hall" rather than venue = "Town Hall", if you want to call the method venue=. The reason is that Ruby always interprets the sequence: bareword = value as an assignment to a local variable. To call the method venue= on the current object, you need to include the explicit self. Otherwise, you’ll end up with a variable called venue and no call to the setter method.

Friday, October 26, 2007

Javascript 中变量的声明和变量的作用域说明

一、全局作用域和局部作用域
在全局环境里:
var a = 1;

a = 1;
的作用是相同的。但是如果是在一个函数体内这二者就不同了,前者是声明了一个函数体内的局部变量,而后者在此函数被运行一次之后就会生成一个全局变量 a 。
一般在声明变量时尽可能的加上var。
二、delete与变量关系:
按JavaScript权威指南书中所言,一个变量一旦被 var 声明之后(未初始化)就有一个默认值'undefined',并delete运算符不能删除这些变量,不然会引发一个错误。不过在Firefox中测试是可以对声明后的变量进行delete,并返回true,在操作之后再引用就会报未定义错误,说明变量正常删除。在IE7里进行delete是的确返回false,无法删除,不过也没有引发错误。
三、JavaScript没有块级作用域
这个不同于C/C++/Java,Javascript的变量只要声明了就会在整个函数体中都有定义,而不管声明的前后位置,会覆盖全局的同名变量。


function test(o) {
var i = 0; // i is defined throughout function
if (typeof o == "object") {
var j = 0; // j is defined everywhere, not just block
for(var k=0; k < 10; k++) { // k is defined everywhere, not just loop
document.write(k);
}
document.write(k); // k is still defined: prints 10
}
document.write(j); // j is defined, but may not be initialized
}

var scope = "global";
function f( ) {
alert(scope); // Displays "undefined", not "global"
var scope = "local"; // Variable initialized here, but defined everywhere
alert(scope); // Displays "local"
}
f( );

Monday, October 15, 2007

Function() constructor of JavaScript

There are a few points that are important to understand about the Function() constructor:

The Function() constructor allows JavaScript code to be dynamically created and compiled at runtime. It is like the global eval() function (see Part III) in this way.

The Function() constructor parses the function body and creates a new function object each time it is called. If the call to the constructor appears within a loop or within a frequently called function, this process can be inefficient. By contrast, a function literal or nested function that appears within a loop or function is not recompiled each time it is encountered. Nor is a different function object created each time a function literal is encountered. (Although, as noted earlier, a new closure may be required to capture differences in the lexical scope in which the function is defined.)

A last, very important point about the Function() constructor is that the functions it creates do not use lexical scoping; instead, they are always compiled as if they were top-level functions, as the following code demonstrates:


var y = "global";
function constructFunction() {
var y = "local";
return new Function("return y"); // Does not capture the local scope!
}

// This line displays "global" because the function returned by the
// Function() constructor does not use the local scope. Had a function
// literal been used instead, this line would have displayed "local".
alert(constructFunction()()); // Displays "global"

Sunday, August 05, 2007

prototype javascript library ajax example


<div id="ajax_div">
use ajax to update it!
</div>
<br/>
<div id="ajax_failure">
if evalScript set false, sayHi is not defined!
</div>

<input type="button" name="button_ajax" value="ajax update div" id="button_ajax" />

<script type="text/javascript" charset="utf-8">
function doc(argument) {
document.write("<p>\n");
document.write(argument);
document.write("</p>\n");
}
var url = '/prototype/ajax_eval_script';
var options = {
method: 'get',
evalScripts: true,
parameters: 'id=1',
insertion: Insertion.Top
}

//new Ajax.Updater('ajax_div', url, options);
//new Ajax.Request(url, {onComplete: function (req) { alert(req.responseText); }});
var ajax = new Ajax.Request();
doc('ajax.transport type is ' + ajax.transport); //ajax.transport = Ajax.getTransport();
new Ajax.Updater({success: 'ajax_div', failure: 'ajax_failure'}, url, options);

ajax.setOptions(Object.extend(options, {onComplete: function(req) {
$('ajax_div').innerHTML = req + "<br/>\n" +
"req == ajax.transport is " + (req == ajax.transport) + "<br/>\n" +
"Server is " + req.getResponseHeader('Server') + "<br/>\n" +
"ajax request status is " + ajax.transport.status + "<br/>\n";
}
}));
$('button_ajax').onclick = function (event) { ajax.request('/prototype/ajax_eval_script'); };
</script>

/prototype/ajax_eval_script

<script language="javascript" type="text/javascript">
//function sayHi(){ // do nothing, function is not generated in runtime
//var sayHi = function(){ // not use the "var" keyword
sayHi = function(){
alert('Hi');
}
</script>

<input type="button" value="Click Me" onclick="sayHi()"/>
Note that in the previous example we did not use the var keyword to declare the variable. Doing so would have created a function object that would be local to the script block (at least in IE and FireFox). Without the var keyword the function object is scoped to the window, which is our intent.

Saturday, March 24, 2007

Using static variables

Another important feature of variable scoping is the static variable. A static variable exists only in a local function scope, but it does not lose its value when program execution leaves this scope.
static variable只作用于本地function内,在程序执行完后此变量值不会丢失,此方法再次调用,将使用上次操作完成后的此变量值。在singleton pattern里,也是利用static variable,使程序在运行期间只产生一个实例。


<?php
function Test_static_var()
{
static $a = 0;
echo $a, "<br />";
$a++;
}
Test_static_var();
Test_static_var();
Test_static_var();
?>
<?php
function Test()
{
static $count = 0;

$count++;
echo $count;
for($i = 0; $i < $count; $i++)
{
echo " ";
}
echo " before recursive <br />\n";
if ($count < 10) {
Test();
}
$count--;
echo $count;
for($i = 0; $i < $count; $i++)
{
echo " ";
}
echo " after recusive <br />\n";
}
Test();
// 1 before recursive
// 2 before recursive
// 3 before recursive
// 4 before recursive
// 5 before recursive
// 6 before recursive
// 7 before recursive
// 8 before recursive
// 9 before recursive
// 10 before recursive
// 9 after recusive
// 8 after recusive
// 7 after recusive
// 6 after recusive
// 5 after recusive
// 4 after recusive
// 3 after recusive
// 2 after recusive
// 1 after recusive
// 0 after recusive
?>

The global keyword


<?php
$a = 1;
$b = 2;
$GLOBALS['c'] = 3;
echo $c, "<br>";
$d = 4;

function Sum()
{
global $a, $b;

$b = $a + $b + $GLOBALS['c'] + $GLOBALS['d'];
}

Sum();
echo $b;
?>

The above script will output "3", "10". By declaring $a and $b global within the function, all references to either variable will refer to the global version. There is no limit to the number of global variables that can be manipulated by a function.

A second way to access variables from the global scope is to use the special PHP-defined $GLOBALS array.

The $GLOBALS array is an associative array with the name of the global variable being the key and the contents of that variable being the value of the array element. Notice how $GLOBALS exists in any scope, this is because $GLOBALS is a superglobal.