posts - 7, comments - 10, trackbacks - 0, articles - 1
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

2011年9月4日

javascript中代码经常用来封装代码,好的闭包使用能使代码清晰明了,起到意想不到的功效,但是如果使用不当,闭包就很容易因其内存泄露,这篇文章,主要对闭包的如何形成及函数的作用域链做一个简单描述。

我们先来看wiki上对于闭包(closure)的解释,闭包是词法闭(lexical closure)包的简称,是引用了自由变量的函数。这个被引用的自由变量和函数一起存在,既然已经离开了创建它的环境也不例外。因此,另一种说法认为闭包是由函数和其相关的引用环境组合成的实体。

在一些语言中,在函数中定义了另一个函数时,如果内部的函数引用了外部的函数,则可能产生闭包。在外部函数运行时,就产生了闭包,闭包中包含了内部函数,以及所需外部函数中变量的引用(这个变量即是我们前面说的自由变量),其中的引用成为上值(upvalue)。

以上的定义时维基百科中对闭包的定义,其中有关于javascript中闭包形成的一个例子,这个解释比javascript中闭包官方的解释简单易懂得多了。

我们来看一个实例

function createIdFunc(){
    var id=0;
    return function(){
         return id++;
    }    
}

函数createIdFunc的返回值也会一个函数。当createIdFunc被调用时,会形成一个闭包,并把这个闭包返回。这个闭包就是内部的函数以及他所引用的自由变量id。

我们来看一下这个函数的调用:

var getId = createIdFunc();
var id0 = getId();
var id1 = getId();
var id2 = getId();

 

我们执行这几行代码,可以看到id0的值为0,而id1的值为1,id2的值为2,如果继续执行getId()函数,我们会发现它的返回值会依次递增,也就是闭包中所引用的自由变量一直在递增,闭包保留了自由变量的状态。

为什么呢?这就是我们刚才说到的createIdFunc被调用时,形成了一个闭包,就是createIdFunc中定义的内部函数以及内部函数引用的自由变量,这个闭包以函数的形式返回,被我们赋值给getId,并一直存在,直到getId被释放掉。因此每次调用getId,我们可以认为对这个闭包的调用(这个说法并不正确,欢迎大家给出一个合适并易于理解的描述),而这个闭包中的自由变量id并没有随着createIdFunc调用的结束而被释放,每次的调用都会造成闭包中自由变量id的递增。

注:我们删除了函数getId时,闭包占用的内存并未释放,因此可以证明闭包并未删除。
delete getId; //delete function getId, but the closure is still exist

 

我们再来看另一个例子:

function func(){
    var i = 0;
    alert(i);
    return function(){
        alert(++i);
    }
}

varfunc1 = func();
func1();
func1();
 

var func2 = func();
func2();
func2();

func1();
func1();
func2();

你先想一下,这段代码被执行之后,浏览器会弹出什么样的值呢?如果你真的理解了闭包,你就能准确的推测出结果。我们看下结果吧:

0//调用func时,弹出了0,并形成了闭包1,这个闭包依存于func1,同时包含一个自由变量0 
1//调用func1,弹出1,闭包中的自由变量保存了状态,因此递增时是在原来值0的基础上,此时值为1 
2//调用func1,弹出2,原来自由变量值为1,原因与上边一样,执行后值为2 

0//调用func,弹出0,并形成一个新的闭包2,函数被付给了func2,同样包含自由变量值为0,现在已经有两个闭包了 
1//调用func2,弹出1,闭包2中的自由变量递增,此时值为1 
2//调用func2,弹出2,闭包2中的自由变量递增,此时值为2 

3//调用func1,弹出3,func1使用了闭包1中的自由变量,原来值为2,计算后为3 
4//调用func1,弹出4,同样是因为闭包1的缘故 
3//调用func2,弹出3,调用func2,关联的闭包2中的自由变量上次计算后的值为2,此次计算后为3 

与你预想的是否一样呢?从上边的例子,我们至少可以看出:

1)每次调用func都会形成一个闭包,第一次是func1与自由变量构成了一个闭包,第二次是func2与自由变量构成了闭包;

2)每次外部函数执行结束后,其局部变量已经释放,每次调用func,弹出的都是0,局部变量重新初始化

3)闭包中使用的自由变量其实是在外部函数执行时,被拷贝了一个副本,保存于闭包当中,与外部函数(这个示例中就是func)已经没有关系,示例中func1和func2闭包中自由变量值的变化是互不影响的。

 

我们再来看作用域链的定义及形成(此部分完全引自李松峰翻译的理解javascript闭包,详细内容可以查看原译文及英文,因此下边部分我这里只是简单介绍,原文描述的非常清晰)

Javascript代码都运行在一个执行环境中。当一个javascript函数被执行时,就会进入这个函数的执行环境中,如果其中调用了另一个函数,则会创建一个新的执行环境,保存当前的执行环境,并进入新的执行环境,直到被调用的函数执行完成,则会返回保存的原始执行环境。在此过程中,运行中的javascript代码构成了一个执行环境中,与大多数的语言中的函数调用一样。

在创建执行环境的过程中,会按照先后顺序,完成一系列的操作,首先会创建一个活动对象,活动对象是一种拥有可访问的命名属性,但没有prototype且不能被javascript代码直接引用的特殊对象;其次,会创建arguments对象,然后把arguments对象的引用赋值给活动对象的arguments属性(关于活动对象,arguments对象,大家可以自行查阅ECMA规范描述,及相关资料,此处很难叙述清楚);然后,为执行环境分配作用域,作用域由对象列表组成,每一个函数都有一个内部的[scope]对象,这个对象也由对象列表组成。指定给一个函数执行环境的作用域,由函数所用引用的[scope]对象列表组成,活动对象也会被添加到该对象列表的顶端;再之后,会执行可变对象的变量实例化过程,此时使用活动对象作为可变对象,会将函数的参数作为可变对象的命名属性,然后将函数的局部变量(包括函数)也作为可变对象的命名属性;最后为this关键字赋值。

执行环境调用函数时创建的执行环境包含一个作用域链,由执行环境的活动对象添加到到执行环境所调用函数的[scope]对象列表的顶端组成的。

posted @ 2011-09-04 23:38 微夜风 阅读(1104) 评论(0) 编辑

2011年7月28日

前一篇中我们讲到,Javascript中任何对象o instanceof Object,都会返回true,因此,从这个层面来看,我以认为Javascript中一切都是对象。但是Javascript并不是一种完全面向对象的编程语言,因此对于简单数据类型,我们这样的讨论并没有太大的意义。我们这里所说的对象,主要是指一些复杂的对象,包括函数对象。

我们先从最常见的说起,

var o = new Object()

这个语句定义了一个最普通的对象o,这也是面向对象语言中最长见的对象定义方法。Javascript中所有的对象都可以通过这种方式来定义:

var o = new TypeDef()

这里TypeDef指的是类型的定义,在Javascript内置的类型定义有以下几种:Object,Function,Date,Array(还有Boolean,这里我们不讨论这种简单类型)

除此之外,一些类型还有一些简单的定义方式,或者可以称之为初始化方式,定义的同时进行初始化,例如:

var array1 = [];//初始化一个空数组
var array2 = [1, 2, 3];//初始化一个包含1、2、3三个元素的数组

var json1 = {};//和json1 = new Object();的结果是一样的 
var json2 = {
    name: "json2",
    mytype: "object"
};

一种是数组的初始化方式,一种是现在现在常见的Json写法定义。其中json2是一个对象,包含了name和mytype两个属性,当然,我们也可以在声明之后再为它添加其他的成员,包括属性成员和行为(函数)成员,如下所示:

json1.newProperty = "This is json1's new property";
json2.newProperty = "This is json2's new property";
json1.newFunc = function(){
     // json1's action
};
json2.newFunc = function(){
    // json2's action
};


这样,我们变为json1和json2分别都增加了一个newProperty属性和一个newProperty方法。到这里我们找到了一个自定义复杂对象的途径,实例化一个Object的对象,然后为其添加属性成员和行为成员,或者是通过Json的声明方式,来初始化一个包含属性成员和行为成员的对象,或者是混合两种方式都是可以的。但是这种对象有一个缺点就是无法复用,因为它不是类型,本身就是一个实例,所以说只存在这样一个实例,很多情况下满足不了我们的需求,这个我们会在后续的章节中谈论到。现在我们继续这个对象,我们来使用Json完整定义一个:

var myObj = {
    name: "my object",
    howToDefined: "Json",
    showMyName: function(){
        return this.name;
    }
}

当我们通过myObj.showMyName时,会返回”my object“。

我们看到,在myObj的showMyName的function定义中,我们引入了this关键字,这是面向对象编程中最常用到的一个关键字,而且,在Javascript中也很难理解。在上面得例子中,就是表示myObj这个对象,在这种单一的对象中,理解起来要容易的多,因为它就代表的是myObj对象,所以这个地方return this.name也可以用return myObj.name 来代替的。让人迷惑的是在函数(类型)的定义中使用this,这个以后会讲到,我们现在先举个小例子:

function myType(){
    this.name = "my type";
}
var o = new myType();// 监视o可以看到o.name = "my type",此时this代表的是对象o

//然后我们直接调用myType(),作为一个函数调用
myType();//监视window.name可以发现window.name = "my type",此时this代表window对象

//然后我们清空window.name ,并通过caller调用myType
window.name = undeifned; //监视发现window.name = undeifined
var caller = {};
myType.call(caller);// 监视caller.name = "my type", window.name = undefined

如果有对call或者apply不熟悉的,可以查看下相关资料,此处不做深入描述。上边的例子我们大致可以看出,this在函数中真是代表什么,简而言之,就是调用者。直接在浏览器客户端调用,通常表示window,通过call,apply调用,则代表指定的调用对象,而实例化上作为方法使用,它的调用者就是实例化出来的对象。

需要说明的是,在客户端编程中,在事件或者setTimeInterval等中指定的obj.func,他们的调用者并不是obj,只是指定了一个委托而已。我曾经遇到过这样的问题,因此在这儿给说明下。

posted @ 2011-07-28 00:12 微夜风 阅读(1352) 评论(1) 编辑

2011年7月27日

做了多年的程序员,虽然不是做前端,但却一直也在和Javascript脚本打交道。我对Javascript的看法可以用两个字来概括“灵活”,因其灵活,所以入手容易,真正的掌握理解却很难,因此使用中总是有许多的不确定。在使用中,我一直遇到种种的问题,有的解决了并理解了,有的就就直接借鉴(或者说Copy)了许多其他的一些优秀的库的代码,但却因种种缘故,没有深入理解其设计思想、实现思路,自己也懒于梳理,所以,总是有些问题反复出现,然后又去反复寻找答案。这系列博客,主要是针对自己以往学习的一个整理。

Javascript是一种弱类型脚本语言,弱类型并不代表没有类型。和其他的程序设计语言一样,Javascript中的数据也是有类型的,大的可以分为简单类型与复杂类型。

 

如果按照typeof 函数的计算:

简单类型有 number,string,boolean

复杂类型有object和 function

下边的例子可以说明:

typeof 1 //echo "number"
typeof 1.1 //echo "number"
typeof "hello" //echo "string"
typeof true //echo "boolean" 
 
typeof new Object() // echo "object"
typeof new Date() //echo "object"
typeof new Array() //echo "object"

typeof new Function() //echo "function"
typeof Object //echo "function"
typeof Date //echo "function"
typeof Array //echo "function"
typeof Function //echo "function"

上边的代码我们大致可以看出,使用typeof函数可以得出的Javascript数据类型有哪些,复杂类型只有object和function,而简单类型也只有三种。其实这并不准确,不论复杂类型还是简单类型,使用typeof得出的都不完全,下边我们将详细点描述。

对于简单类型,”number”可以分为integer和float两种,这两种类型在计算乘除的时候,是不一样的,这句有点废话,其实我想说的是,float类型在和int类型计算的时候,会有许多莫名奇妙的问题,就是因为float的精度问题。而对于复杂类型,就相对复杂些,javascript内置的一些类型从上边的例子我们可以看到,常见的有Date,Object,Array,Function(应该还有其他的吧,写这个的时候我想不起来),虽然他们使用typeof 计算时,返回的只有object换个function两种,但是他们其实是不一样的,如何不一样,就无需赘述。但是由上边的例子我们也可以看出,object和function两种类型比较特殊,我将他们其称之为基础类型。有个简单的区分方法,通常由new创建的都是object类型,通过function关键自定义的都是function类型,但是还有例外,就是new Function,它所创建的就是一个function,这个可能会在以后将函数的定义时提到吧。object类型,大致都能差不多理解,容易让人疑惑的是function类型,作为一种类型来描述时,其实应该描述为大写Function。Function本身的类型就是Function,由Function类型创建(包括使用function关键字定义)出来的function(函数)类型也是Function。这句话需要仔细理解。但是,还有一点,定义的function也是可以实例化的(这个词用这儿可能不太合适,不过便于理解),如下所示:

function functionType()
{
    //echo I am a function
}
var func = new functionType();

我们分别再使用typeof来检查functionType和func的类型:

typeof functionType // echo "function"
typeof func // echo "object"

再揣摩上边的话,定义的function的类型也是Function,定义的function同样可以被用于实例化,就像系统内置的Date,Array等类型一样,这说明了自定义的function是可以被当做类型使用的,这就找到了一条自定义类型的路,在面向对象的语言中,我们称之为类。现在在Javascript,我们也有定义类方法,即定义function。同时,类也是对象,类对象的类型是Function。

以上可以看出,Javascript中的复杂类型其实由Date,Array,Object,Function,以及我们自定义的类(型)。

既然存在类型,我们就需要区分类型,我们上边说了,typeof不能准确区分,因此,我们引入另一个关键字instanceof,并不是函数,但是它会返回一个boolean类型的值,标明一个对象是否为另一个对象的实例。如下

typeof new Date() instanceof Date //echo true
typeof new Array() instanceof Array //echo true
typeof new Object() instanceof Object //echo true

function myType(){
    // this is my type
}
var instance  = new myType();
instance instanceof myType() //echo true 
myType instanceof Function //echo true
new Date() instanceof myType() // echo false
我们可以看出,instanceof是用来检查一个对象是否为另一个对象的实例的。细心的朋友可能还会这样尝试:

Function instanceof Function //echo true
Function instanceof Object //echo true
Object instanceof Object //echo true
Object instanceof Function //true

老实说,这段代码很让人抓狂。我在前边也说过,Object和Function都为基础类型,我们前边也看到使用typeof时他们两个之间的关系,typeof Object结果为function,我个人认为,这说明了Javascript中所有复杂类型的基础的都是Function,换句话说所有的类型都是基于Function来实现的。另一方面任何对象使用instanceof Object结果都为true,说明了任何对象都是Object的实例。前边一句侧重于类型,后边侧重于实例(Function我们也可以认为它是自己的一个实例)。

这只是类型于实例之间的简单示例,后边会讲到prototype,那是我们会发现类型与实例之间一些更有趣的关系,这些事Javascript中实现面向对象的一个基础结构。

posted @ 2011-07-27 00:27 微夜风 阅读(1813) 评论(9) 编辑

2010年10月14日

<ul>
    <li onmouseover="alert('contains subnode');">
        <span>Hi, move mouse on</span>
    </li>
    <li onmouseover="alert(mouse on li);">
        move mouse on,。。。。。。。。。。撑开
    </li>
</ul>

元素中含有子元素时,onmouseover与onmouseout会在子元素与父元素之间反复触发,这个问题已经有很多解决方案,在此不再讨论。
今天遇到奇怪的问题,在IE6下,上边的代码,第一个li的mouseover只有在鼠标移动到span对象上或者是li的上边框时才会触发,百思不解。
后来发现如果为li添加个“style='height:25px;'”,也就是设置height或者width之后就可以正常触发,猜测可能是inlin元素与block元素的问题,把li设置为display: inline-block,正常工作。查看了下li在不同浏览器下的解释,有半内联之说,不过感觉依然模糊。照我的理解,纵然是inline元素,鼠标移到边框内时,mouseover也应该正常触发。

正常工作代码如下:
<ul>
    <li style="display: inline-block;" onmouseover="alert('contains subnode');">
        <span>Hi, move mouse on</span>
    </li>
    <li onmouseover="alert(mouse on li);">
        move mouse on
    </li>
</ul>

posted @ 2010-10-14 13:32 微夜风 阅读(983) 评论(0) 编辑

2010年8月20日

在 sharepoint 2010中,部署project时可能会发生这样的错误:Both "project.csproj" and "project" contain a file that deploys to the same Package location: project.dll .

意思是包中包含有相同的问题件。

“Project.csproj”项目文件,是因为项目的properties中设置了"include assembly in package" ,这样部署时会自动把项目assembly拷贝到package中。

"project" 就是因为在package的advanced中又手动添加了assembly。这是我开始做sharepoint时参考的步骤,需要添加用到的assembly,问题就再这个地方,sharepoint project中默认的 "include assembly in package",因此会自动拷贝项目的assembly的。

 

解决方法就是不要在package的advanced中又手动添加会自动"include assembly in package"的assembly,或者是设置"include assembly in package" 为 false。

posted @ 2010-08-20 12:42 微夜风 阅读(99) 评论(0) 编辑

2009年2月5日

posted @ 2009-02-05 16:31 微夜风 阅读(852) 评论(0) 编辑

2009年2月4日

posted @ 2009-02-04 13:02 微夜风 阅读(96) 评论(0) 编辑