前言:
程序设计语言主要分为两种 1、基于存储原理(冯*诺依曼体系)的命令式编程语言,如C/C++、Java、Object Pascal(DELPHI)。
2、根据阿隆左*丘琦的lambda演算而产生的函数式编程语言,如Lisp、Scheme。
这两种语言互不干涉的时代(javascript出现以前),程序=数据结构+算法
Javascript被称为具有C的语法的Lisp,完美结合了这两个体系(C的语法,Lisp的内核)。
本书分为两部分 1、Javascript语言的核心概念:javascript的对象、函数、数组、闭包
2、Javascript支持的编程范式:javascript引擎、javascript在java、C、C++等应用中的使用,服务器端的javascript应用。
第一章 概述
1.1 javascript简史
Mocha(1996) -- LiveScript -- JavaScript -- ECMAScript
JSP、ASP可以为页面提供动态内容,但是如果没有javascript则无法在服务器返回之后动态地在前端修改页面,也没有页面特效。
1.2 javascript语言特性
动态的、弱类型、基于原型的脚本语言。
彻底的“一切皆对象” :即使作为代码本身载体的函数(function)也是对象,数据与代码的界限相当模糊。
1.2.1 动态性:
1.为一个属性赋值,不必事先创建一个字段,只要在使用时直接赋值
1 var obj = new object();//定义一个对象 2 obj.name= "an object";//动态创建属性name 3 obj.sayHi = function(){return "Hi";};//动态创建属性sayHi 4 5 delete obj.name;//删除对象的属性
在静态语言中,需要预先定义好对象的属性以及属性自身的类型。定义完成后,对象的结构就定下来了,之后都保持这种结构无法更改。通常对象会继承一些自己用不到的方法,并且无法删除。
2.动态地访问一个javascript对象的属性
1 var key = "property";
2 print(key);
3
4 var obj = {property:"myproperty"}
5 print(obj[key]);
这个特性可以使得代码更加简洁清晰,比如可以动态地从代码中生成对象的属性名,然后去除属性值等。
1.2.2 弱类型:数据类型无需在声明时指定,解释器会根据上下文对变量进行实例化。
在javascript中,类型是和值关联的,而不是和变量关联。弱类型具有很大的灵活性,在定义变量时无需显示声明。
弱类型也有其不利的一面,例如开发面向对象javascript时,没有类型的判断,但是可以通过其他途径解决。
1.2.3 面向对象:在javascript中一切都是对象,甚至用以表发逻辑的函数/代码本身也是对象,比如代码本身可以作为参数传递给其他代码
1 var array = [1,2,3,4,5];
2 array.map(function(item){return item*2});
3
4 var staff = [{name:'lilei',age:25},{name:'hanmeimei',age:24},{name:'hobo',age:25}];
5 staff.map(function(item){return item.name.toUpperCase});
6 staff.filter(function(){returen item.age>24});
1.2.4 解释与编译:浏览器中,javascript是解释型语言
Rhino(java版的javascript解释器)中,javascript编译型(可以被编译为java字节码)
Google V8引擎中,直接将javascript代码编译为本地代码,无需解释。
解释型 优:可以随时修改代码,无需编译,刷新页面即可重新解释
劣:由于每一次都需要解释,程序开销大。
编译型 优:仅需要编译一次,每次都运行编译过的代码即可
劣:丧失动态性
1.3 javascript应用范围
现在的javascript大多数运行于客户端。
部分运行于服务器端,如servlet、asp等
作为独立语言运行于其他应用程序中,如Rhino(java),SpiderMonkey(C)
1.3.1 客户端javascript
随着Ajax的复兴,客户端的javascript得到了很大的发展,出现大量javascript库,如JQuery、ExtJS、Backbone.js、Mootools等。
1.3.2 服务器端javascript
Node.js,Helma,Apache Sling等。
Jaxer的例子:
1 <script>
2 jQuery(function(){
3 $("form").submit(function(){
4 save($("textarea").val());
5 return false;
6 });
7 });
8 </script>
9 <script runat="server">
10 function save(text)
11 {
12 Jaxer.File.write("temp.txt",text);
13 }
14 save.proxy = true;
15
16 function load()
17 {
18 $("textarea").val(
19 Jaxer.File.exists("tmp.txt") ? Jaxer.File.read("tmp.txt") : ""
20 );
21 }
22 </script>
23 <script>
24 var rs = Jaxer.DB.execute("SELECT * FROM table");
25 var field = rs.rows[0].field;
26 </script>
node.js的例子:node.js运行实现了javascript的模块规范ComminJS,使得大规模javascript开发成为可能,一个模块可以使用其他任何模块,或者将自己需要公开的函数导出,提供给别的模块使用。
<script>
var sys = require('sys');
http = require('http');
http.createServer(function(){
setTimeout(function(){
res.sendHeader(200,{'Content-Type':'text/plain'});
res.sendBody('Hello World');
res.finish();
},2000)
}
).listen(8000);
sys.puts('Server running at http://127.0.0.1:8000/');
</script>
<script>
var tcp = require('tcp');
var server = tcp.createServer(function(socket){
socket.setEncoding("utf8");
socket.addListener("connect",function(){
socket.send("hello\r\n");
});
socket.addListener("receive",function(data){
socket.send(data);
});
socket.addListener("eof",function(){
socket.send("goodbye\r\n");
socket.close();
});
});
</script>
第二章 基本概念
2.1 数据类型
javascript中数据类型分为 1、基本数据类型:在必要时会隐式转换为对象
2、对象类型:对象、数组、函数
2.1.1 数据类型:string、number、boolean、undefined、null、object(6种)
2.1.2 对象类型:这里提到的对象不是对象本身,而是指一种类型。
包括对象(属性的集合,即键值的散列表)、数组(有序的列表)、函数(包含可执行的代码)。
对象类型是一种复合的数据类型,其基本元素由基本数据类型组成,但不嫌友基本类型,比如对象类型中的值可以使其他的对象类型实例。
2.1.3 基本类型与对象间的转换
基本类型转换为对象:
基本数据类型在做一些运算时,会临时包装一个对象,做完运算后,又自动释放该对象。
对象转换为基本类型:
通过调用对象的valueOf()方法来取得对象的值,如果和上下文的类型匹配,则使用该值。如果valueOf()取不到值的话,则需要调用对象的toString()方法,而如果上下文为数值型,则又需要将此字符串转换为数值。valueOf()的优先级要高于toString()。
事实上这种转换规则会导致很多问题,比如所有非空对象在布尔值环境下都会被转换为true
if(datamodel.item){...}else{...}
datamodel.item在转换为布尔值的同时判断是否为空,简化了if(datamodel.item != null)的写法
2.1.4 类型的判断
例子:当函数的参数为函数时需要判断这个参数的类型,使用typeof操作符和instanceof操作符结合判断
1 function handleMessage(message,handle) 2 { 3 return handle(message); 4 } 5 6 //在调用前判断handle是否是function 7 function handleMessage(message,handle) 8 { 9 if(typeof handle == "function") 10 {return handle(message);} 11 else 12 {throw new Error("the 2nd argument should be a function");} 13 }
当typeof相同时:
1 var obj = {}; 2 var array = ["one","two","three"]; 3 4 print(typeof obj);//object 5 print(typeof array);//object 6 7 print(obj instanceof Array);//false 8 print(array instanceof Array);//true
2.2 变量
变量是对值的存储空间的引用,通过一个名字将一个值关联起来,以后通过变量就可以引用到该值。
2.2.1 基本类型和引用类型
基本类型在内存中有固定的大小。
引用类型原则上无大小限制,我们通过它们的引用来访问它们。(引用是一个地址,指向真实存储复杂对象的位置)
1 var x = 1; 2 var y = x; 3 print(x);//1 4 print(y);//1 5 6 x = 2; 7 print(x);//2 8 print(y);//1 9 10 var array = [1,2,3,4,5]; 11 var arrayRef = array; 12 13 array.push(6); 14 print(arrayRef );//1,2,3,4,5,6
引用指向的是地址,不会指向引用本身,而是指向该引用所对应的实际对象。
2.2.2 变量的作用域
变量被定义的区域。
会引入活动对象及作用域链的概念。
2.3 运算符
一些比较古怪的语法现象需要运算符的结合率或者其作用来进行解释。javascript中要特别注意运算符。
2.3.1 中括号运算符([])
中括号运算符可用在数组对象和对象上,从数组中按下标取值。
[]同样可以作用于对象,一般对象中的属性通过点(.)运算符来取值,在我们对对象中属性的键(key)一无所知的时候,可以用[]运算符。
var object = { field:"self"; printInfo:function(){print(this.field);} } for(var key in object) { print(key + ":" + object[key]); }
2.3.2 点运算符(.)
点运算符的左边为一个对象(属性的集合),右边为属性名。但点运算符并不总是能用。
var obj = { id:"referencel", "self.rec":ref }
访问"self.rec"时要写成obj["self.rec"]。
建议在不知道对象的内部结构的时候,一定要使用中括号运算符。
2.3.3 相等与等同运算符
1.相等 == (会对两边的操作数做类型转换)
如果操作数具有相同的类型,判断其等同性,如果两个操作数值相等,则返回true,否则返回false
如果操作数类型不同,则按照这样的情况来判断:
- null和undefined相等;
- 其中一个是数字,另一个是字符串,则将字符串转换为数字,再做比较;
- 其中一个是true,先转换成1(false是0)再做比较;
- 如果一个值是对象,另一个是数字/字符串,则将对象转换为原始值(通过toString()或者valueOf()的方法);
- 其他情况,则直接返回false。
2.等同 === (不会对两边的操作数做类型转换)
如果操作数的类型不同,则不进行值的判断,返回false。
如果操作数类型相同,则按照这样的情况来判断:
- 都是数字时,如果值相同,则两者等同(NaN与NaN不等同),否则不等同;
- 两个操作数引用同一个对象,返回true;
- null/undefined与自身等同。
推荐使用===来避免bug
第三章 对象
javascript对象与传统面向对象中的对象几乎没有相似之处。
传统面向对象语言中创建一个对象必须先有对象的模板--类,类中定义了对象的属性和操作这些属性的方法。通过实例化来构建一个对象,然后使用对象间的协作来完成一个功能,通过功能的集合完成整个工程。
javascript中没有类的概念,借助javascript的动态性,我们创建一个空的对象,通过向对象动态的添加属性来完善对象的功能。
JSON是一种轻量级数据交换格式。
3.1 javascript对象
javascript对象就是属性的集合(以及一个原型对象)。这里的集合与数学中的集合等价,具有确定性、无序性、互异性(如果同名则后声明的覆盖先声明的)
javascript对象本身就是一个字典(directory),或者java中的map。或关联数组(通过键来关联一个对象)。这个对象本身又可以是一个对象。
javascript对象可以表示任意复杂的数据结构。
3.1.1 对象的属性
属性是由键-值对组成的。键是一个字符串,值可以为任意javascript对象
3.1.2 属性与变量
事实上,对象的属性和变量是一回事。
javascript引擎在初始化时,会构建一个全局对象,在客户端环境中,这个对象即为window。如果在其他javascript环境中需要引用这个全局对象,只需要在顶级作用域(即所有函数声明之外的作用域)中声明。
var global = this;
我们在顶级作用域声明的变量将作为全局对象的属性被保存,从这一点上来看,变量其实就是属性。
var v = "globle"; var array = ["Hello","World"]; function fun(id){var element = document.getElementById(id);} 等价于 window.v = "globle"; window.array = ["Hello","World"]; window.fun = function(id) {var element = document.getElementById(id);}
变量就是全局对象(global)的属性
3.1.3 原型对象及原型链
原型(prototype)是javascript特有的一个概念。
通过使用原型,javascript可以建立其传统面向对象语言中的继承,从而体现对象的层次关系。
javascript本身也是基于原型的,每个对象都有一个prototype属性,这个prototype本身也是一个对象,因此它本身也可以有自己的原型,这样就构成了一个链结构。
访问一个属性时,解析器从下向上遍历这个链结构,直到遇到该属性,则返回属性对应的值。
或者遇到原型为null的对象(javascript的基对象Object的构造器的默认prototype有一个null原型)。
如果没有该属性则返回undefined。
function Base(name)//声明一个对象base { this.name=name; this.getName = function(){return this.name;} } function child(id)//声明一个对象Child { this.id = id; this.getId = function(){return this.id;} } Child.prototype = new Base("base");//将child原型指向一个新的base对象 var c1 = new Child("child"); print(c1.getId()); print(c1.getName());
由于遍历原型链时由下而上,最先遇到的属性值最先返回。
通过这种机制可以完成继承及重载等传统面向对象机制。这个机制并非基于类,而是基于原型。
function Person(name,age) { this.name = name; this.age = age; this.getName = function(){return this.name;} this.getAge = function(){return this.age;} } var tom = new Person("Tom",38); var jerry = new Person("Jerry",6);
3.1.4 this指针
this表示当前上下文(context),即对调用者的引用。
var jack = { name:"jack"; age:26; } var abruzzi= { name:"abruzzi"; age:26; } function printName(){return this.name;} print(printName.call(jack));//设置printName的上下文为jack,此时this为jack print(printName.call(abruzzi));//call是Function对象的一个内置方法
this的值并非由函数如何声明而确定,而是由函数如何被调用而确定,这一点与传统面向对象语言截然不同。
3.2 使用对象
对象是javascript的基础。
对象声明有三种方式:
1、通过new操作符作用于Object对象,构造一个新的对象,然后动态地添加属性,从无到有地构建一个对象。
2、定义对象的“类”:原型,然后使用new操作符来批量构建新的对象。
3、使用对象字面量。
function Address(street,xno) { this.street = street || 'ShangHai Road'; this.xno = xno | | 135; this.toString = function(){return "street:"+this.street+",No"+this.xno;} } function Person(name,age,addr) { this.name = name || 'unknow'; this.age = age; this.addr = addr || new Address(null,null); this.getName = function(){retuen this.name';} this.getAge = function(){retuen this.name';} this.getAddr= function(){retuen this.name';} } var jack = new Person('jack',26,new Address('QingHai Road',123)); var abruzzi = new Person('abruzzi',26);
当使用new操作符来操作Person函数时,引擎会创建一个空的对象,这个对象的原型链的constructor指向函数自身,函数执行完成后,返回这个对象
3.3 对象字面量
使用字面量,简洁、没有冗余的中间变量:
var obj = { name:"sharmun", age:23, birthday: new Date(1992,3,12); addr:{ street:"luzhuang", xno:"1233" } }
当一个函数拥有多个返回值时,传统面向对象语言中,需要组织一个对象然后返回。
javascript不必这么麻烦,直接构建一个匿名的新对象即可:
function point(left,top) { this.left = left; this.top = top; return{x:this.left,y:this.top} }
当我们需要遍历一个一无所知的对象:
for(var item in json) { //item key //json[item] value }
var style = { border:"solid 1px #fff", color:"#r32" } for (var item in style)//动态为元素添加样式(对style一无所知) { $("div#element").css(item,style[item]);}
function customize(options) { this.default = { width:100, height:100, background:"#ccc" } this.settings = $.extend(this.default,options); } customize({background:"#rrr"}) //此时this.settings = { width:100, height:100, background:"#rrr" }
3.4 JSON
json作为前端与服务器端的数据交换的标准格式:前端通过Ajax发送json到后端,服务器端脚本对json进行解析,还原成服务器端对象。
json一定程度上比XML更高效,冗余更小。
第四章 函数
在C语言之类的过程式语言中,是顶级的尸体。
在java/c++之类的面向对象的语言中,则是被对象包装起来,一般称为对象的方法。
在javascript中,函数本身也与其他任何的内置对象在地位上是没有任何区别的,也就是说函数本身也是对象。
在javascript中函数可以:
- 被赋值给一个变量;
- 被赋值为对象的属性;
- 作为参数传入别的函数;
- 作为函数的结果被返回;
- 用字面量来创建。
4.1 函数对象
4.1.1 创建函数
创建javascript函数的一种不常用的方式是通过new操作符来作用与Function“构造器”:
var funcName = new Function([argname1,...,[...,argNameN]],body);
javascript提供了一种语法糖,通过字面量来创建函数
function add(x,y) { return x+y; } var add = function(x,y){ return x+y; }
function关键字会调用Function来new一个对象,并将参数表和函数体准确地传递给Function的构造器。
通常来说,在全局作用域内声明一个对象,只不过是对一个属性赋值而已。
function p()//虽然P引用了一个匿名函数(对象),但同时又可以拥有自己的属性,完全跟其他对象一样。 { print("invoke p by()"); } p.id = "func"; p.type = "function"; print(p); print(p.id+":"+p.type); print(p());
4.1.2 函数的参数
javascript在处理函数的参数时,与其他编译型的语言不一样,解释器传递给函数的是一个类似于数组的内部值:arguments(对象,值都是string),这个内部值在函数对象生成的时候就被初始化了。(没有传入的形参为undefined)
4.2 函数的作用域
4.2.1 词法作用域
javascript中的变量作用域为函数体内有效,而无块作用域。
javascript的函是在局部作用域内运行,在局部作用域内运行的函数体可以访问其外层的变量和函数,其作用域在定义时就确定下来,而并非在执行时确定。
var str = "global"; function scopeTest(){ print(str); //undefinded var str = "local"; print(str);//local }
在词法分析结束后,构造作用域链的时候,会将函数内定义的var变量放入该链,因此str在整个函数scopeTest内都是可见的(从函数体的第一行到最后一行)。
4.2.2 调用对象
在javascript中,在所有函数之外声明的是全局变量。在函数内部声明的是局部变量。
全局变量是全局对象的属性。
在执行一个函数时,函数的参数和其局部变量会作为调用对象的属性进行存储。同时,解释器会为函数创建一个执行器上下文(context),与上下文对应起来的是一个作用域链。作用域链是关于作用域的链,通常实现为一个链表,链表的每个项都是一个对象。在全局作用域中,该链中有且只有一个对象,即全局对象。对应的,在一个函数中,作用域链上会有两个对象,第一个为调用对象(首先访问到),第二个为全局对象。如果函数需要用到某个变量,则解释器会遍历作用域链。
作用域链随着嵌套函数的层次会变得很长,但是查找变量的过程依旧是遍历作用域链(链表),一直向上查找,直到找出该值。如果遍历完作用域链仍然没有找到对应的属性,则返回undefined。
4.3 函数上下文
在java或c++/c等语言中,方法只能依附于对象存在,而不是独立的。
而在javascript中,函数也是一种对象,并非其他任何对象的一部分。(javascript的函数式编程范式)
函数的上下文是可以变化的,对应的,函数内的this也是可以变化的,函数可以作为一个对象的方法,也可以同时作为另一个对象的方法。
函数本身是独立的,可以通过Function对象上的call或者apply函数来修改函数的上下文。
4.4 call和apply
call和apply通常用来修改函数的上下文,函数中的this指针将被替换为call或者apply的第一个参数。
var jack = { name:"jack", age:26 } var abruzzi = { name:"abruzzi", age:26 } function printName(){ return this.name; } alert(printName.call(jack));//设置printName的上下文为jack,此时this为Jack alert(printName.call(abruzzi));
4.5 使用函数
4.5.1 赋值给一个变量
通常的编程语言中,我们会将函数/方法的返回值赋值给其他变量,但是无法做到将函数/方法本身赋值给其他变量,因为在这些语言中没有一种数据类型来表示函数。
4.5.2 赋值为对象的属性
4.5.3 作为参数传递
当我们想要处理一些对象,但是又不确定以何种形式来处理,则完全可以将“处理方式”作为一个抽象的粒度来进行包装(即函数)。
4.5.4 作为函数的返回值
function currying(){ return function(){ alert("curring"); } } currying()();//第一个括号操作符调用currying本身,此时返回值为匿名函数。第二个括号操作符调用这个返回值。
第五章 数组
javascript的同一个数组中,可以有各种完全不同类型的元素。
方法 | 描述 |
concat() | 连接两个或更多的数组,并返回结果数组 |
join() | 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔 |
pop() | 删除并返回数组的最后一个元素 |
push() | 向数组的末尾添加一个或更多元素,并返回新的长度 |
reverse() | 颠倒数组中元素的顺序 |
shift() | 删除并返回数组的第一个元素 |
slice() | 从某个已有的数组返回选定的元素 |
sort() | 对数组的元素进行排序(按字母的字典顺序) |
splice() | 删除元素,并向数组添加新元素 |
unshift() | 向数组的开头添加一个或更多元素,并返回新的长度。 |
valueOf() | 返回数组对象的原始值 |
javascript的数组对象比较复杂,还有pop、push等类似栈的操作,又有reverse、sort这样类似的列表的操作。
5.1 数组的特性
数组包括一些属性和方法,其中最常用的属性为length。
length表示数组的当前长度,与其他语言不同的是,这个变量并非只读属性。
var array = new Array(1,2,3,4,5); alert(array.length);//5 array.length = 3;
alert("length:3,array3:"+array[3]);//undefined alert("length:3,array:"+array);//1,2,3 array.length = 5; alert("length:5,array:"+array);//1,2,3,,
另一个与其他语言数组不同的是,字符串也可以作为数组的下标。
如果是数字作为下标,则与其他程序设计语言中的数组一样,可以通过index来进行访问;
使用字符串作为下标,就会采用访问javascript对象的属性的方式进行(javascript内置的Array是从Object上继承下来的)。
var stack = new Array(); stack['first'] = 3.141592654; stack['second'] = "okay then"; stack['third'] = new Date(); for(var item in stack) { alert(typeof stack[item]); // number string object }
alert(stack.length);//0
因为数组本身也是对象,所以这些字符串(下标)会被解释为对象的属性。但是数组本身的length属性不会发生变化。
5.2 使用数组
5.2.1 数组的基本方法使用
创建数组:
var array = new Array(); var array = new Array(10); var array = new Array("apple","balala","wuha"); var array = []; var array = ["q",12,"342",false];
排序:
function sorter(a,b) { return a-b; } var array = [10,20,22,365,2]; array.sort(sorter); print(array);
sort根据sorter的返回值来进行排序。
array.sort(function(a,b){return a - b});//正序 array.sort(function(a,b){return b - a});//逆序
5.2.2 删除数组元素
2.2.3 遍历数组
for...in对数组同样适用
var array = [1,2,3,4]; for(var item in array)//但这种方式并不总是有效,比如我们扩展了内置对象Array:Aarray.prototype.useless = function(){} { alert(array[item]); }
下面这种for循环通过访问array的length属性来完成内部元素访问,而不会访问array对象上的其他属性。
for(var i=0;i<array.length;i++) { alert(array[i]); }
除非必要,尽量不要对内置对象进行扩展,因为对内置对象的扩展会造成所有继承链上都带上“烙印”,而有时候这些“烙印”会成为滋生bug的温床。
第六章 正则表达式
正则表达式是对字符串结构进行形式化描述,简洁优美,功能强大。
RegExp是javascript中的内置类。
6.1 正则表达式基础概念
javascript的正则表达式对象实现了perl正则表达式规范的一个子集
6.1.1 元字符与特殊字符
元字符是一些数学符号,在正则表达式中有特定含义。
如果需要使用元字符的字面意思,则需要转义
元字符 | 含义 |
^ | 串的开始 |
$ | 串的结束 |
* | 零到多次匹配 |
+ | 一到多次匹配 |
? | 零或一次匹配 |
\b | 单词边界 |
特殊字符,主要指注入空格,制表符,其他进制(十进制之外的编码方式)等,它们的特点是以转义字符(\)为前导。
如果需要使用特殊字符的字面意思,则需要转义。
字符 | 含义 |
字符本身 | 匹配字符本身 |
\r | 匹配回车 |
\n | 匹配换行 |
\t | 制表符 |
\f | 换页 |
\x# | 匹配十六进制数 |
\cX | 匹配控制字符 |
6.1.2 范围及重复
标识符 | 含义 |
[...] | 在集合中的任一个字符 |
[^...] | 不在集合中的任一个字符 |
. | 除\n之外的任一个字符 |
\w | 所有的单字,包括字母,数字及下划线 |
\W | 不包括所有的单字,\w的补集 |
\s | 所有的空白字符,包括空格、制表符 |
\S | 所有的非空白字符 |
\d | 所有的数字 |
\D | 所有的非数字 |
\b | 退格字符 |
第七章 闭包
由于在javascript中,函数是对象,对象是属性的集合,而属性的值又可以是对象,则在函数内定义函数成为理所当然,如果在函数func内部声明函数inner,然后在函数外部调用inner,这个过程即产生了一个闭包。
7.1 闭包的特性
var outter = []; function clouseTest() { var array = ["one","two","three","four"]; for(var i = 0; i<array.length; i++) { var x = {}; x.no = i; x.text = array[i]; x.invoke = function(){alert(i);} outter.push(x); } } clouseTest(); outter[0].invoke();//4 outter[1].invoke();//4 outter[2].invoke();//4 outter[3].invoke();//4
其实在每次迭代的时候,x.invoke = function(){alert(i);}并没有被执行,只是为x的invoke属性构建了一个函数体为alert(i)的函数对象。最后执行outter[0].invoke()时,i的值为4
var outter = []; function clouseTest2() { var array = ["one","two","three","four"]; for(var i = 0; i<array.length; i++) { var x = {}; x.no = i; x.text = array[i]; x.invoke = function(no) { return function(){alert(i);} }(i); outter.push(x); } } clouseTest2(); outter[0].invoke();
闭包允许你引用存在于外部函数中的变量。然而,它并不是使用该变量创建时的值,相反,它使用外部函数中该变量最后的值。
7.2 闭包的用途
闭包有很多用途,比如模拟面向对象的代码风格。
7.2.1 匿名自执行函数
所有的变量如果不加上var关键字,则会默认添加到全局对象上去。这样的临时变量加入对全局对象有很多坏处:其他函数误用这些变量;全局对象过于庞大,影响访问速度(变量的取值需要从原型链上遍历)。
有的函数只需要执行一次,其内部变量无需维护,比如初始化UI,可以使用闭包:
var datamodel = { table:[], tree:{} }; (function(dm){ for(var i = 0; i < dm.table.rows; i++){ var row = dm.table.row[i]; for(var j = 0; j < row.cells; i++) { drawCell(i,j);} }//build dm.tree })(datamodel);
创建一个匿名函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,最主要的是这种机制不会污染全局对象。
7.2.2 缓存
有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么如果我们需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到直接返回。
闭包正是可以做到这一点,因为它不会释放外部的引用,函数内部的值可以得以保留。
7.2.3 实现封装
7.3 应该注意的问题
7.3.1 内存泄露
7.3.2 上下文的引用