(内容未稳定,请无视~)
JavaScript Guide
see
https://developer.mozilla.org/cn/Core_JavaScript_1.5_Guide/%E5%85%B3%E4%BA%8E
1. 关于
JavaScript是跨平台面向对象脚本语言。
JavaScript和Navigator的版本对应如下:
JavaScript 1.6对应Firefox 1.5
JavaScript 1.7对应Firefox 2
JavaScript 1.8对应Firefox 3
(注:以Mozilla Firefox 3.5.2火狐中国版测试,下同)
打开Firefox错误控制台,输入3 + 5 * 2回车,输出13
(如果之前留下的警告信息太多,需要先清除一下)
安装Firebug插件。启用“控制台”,然后输入3 + 5 * 2回车,输出13。
输出信息可以使用alert()弹窗,
如果用Firebug可以用console.log()代替(激活“所有”标签页)
2. 什么是JavaScript
JavaScript是脚本语言。
JavaScript核心可以用于客户端和服务器端。
通过LiveConnect可以与Java通信。
Netscape发明了JavaScript,JavaScrips首先应用于Netscape的浏览器。
没有静态类型和强类型检查。
基于原型而非基于类,支持动态继承(不同对象实例的继承可以不同)。
支持不用特别声明的函数,而且它可以有对象属性。
不需要声明所有的变量,类以及方法。
不用关心方法是公有私有或者被保护。
不用实例化接口。
变量,参数以及函数返回值都没有明确的类型。
JavaScript的灵魂来源于动态类型化的语言如HyperTalk和dBASE。
不能自动写入硬盘。
JavaScript的标准化版本叫做ECMAScript。
3. 值
JavaScript支持Number,Boolean,String,null和undefined值。
此外还有Date,Object和Function。
程序执行的过程中数据的类型会根据需要自动的转换。
数值与字符串的“+”操作时,JavaScript会将数值转换成字符串。
使用其他运算符时,JavaScript不将数字转换成字符串(例如减法)。
4. 变量
一个JavaScript标识符必须以字母、下划线(_)或者美元符号($)作为首字符。随后的字符也可以是数字。
JavaScript大小写敏感。
从JavaScript 1.5开始,可以使用ISO 8859-1或者Unicode字母。
定义(声明)变量可以用var x = 42或x = 42,后者不应该使用。
没有初始值的变量的值是undefined。
访问一个未声明的变量,将抛出异常(ReferenceError)。
undefined对于布尔是false。
null对于数值是0,布尔是false。
JavaScript在函数外声明的是全局变量,否则是局部变量。
JavaScript没有块语句作用域(例如if内声明的变量可以作用到if块外部)
可以在变量使用后重新var,不会发生异常。
全局变量实际上是全局对象的属性(网页中是window),例如window.variable。
可以访问另一个window或frame的全局变量,例如parent.phoneNumber。
5. 常量
使用关键字 const 来创建一个只读的常量。
它必须以一个字母或是下划线开头,后跟字母、数字或是下划线。
常量不能在代码运行时重新定义或者重新赋值。
常量的作用域范围和变量的作用域范围相同。
const关键字不可以省略。
同一个作用域内常量与变量名不可重复。
6. 字面值
数组字面值包围在中括号[]内。例如:
var coffees = ["French Roast", "Colombian", "Kona"];
表达式的值在每次使用时都会计算和创建一次(例如函数内)
多余的逗号会预留空位(作为undefined),例如:
var fish = ["Lion", , "Angel"];
而且,末尾附加的逗号会被忽略。
布尔类型有两个常数值:true和false。布尔常量不同于布尔变量。
整数可以表示为十进制、十六进制和八进制,例如:
0、117、-345 (十进制)
015、0001、-077 (八进制)
0x1123、0x00111、-0xF1A7 (十六进制)
八进制整形常数因为兼容性而被JavaScript 1.5保留。
浮点常数的简单语法:[数字][.数字][(E|e)[(+|-)]数字]
对象常数是由大括号{}括起来。
大括号不要用于语句开头以免引起歧义。
大括号内以冒号隔开属性名和值。
属性名可以是数字或字符串常数。
标识符属性名用foo.a或foo["a"]形式访问。
而数字属性名只能用foo["2"]形式访问。
字符串常数由双引号或单引号括起。
字符串常数上调用任何字符串的方法(自动创建临时变量)
例如"John's cat".length
字符串中使用特殊字符需要转义。
\b 退格 \f 换页 \n 换行 \r 回车
\t 水平制表 \v 垂直制表
\' 单引号 \" 双引号 \\ 反斜线(\)
\XXX 三位八进制数
\xXX 两位十六进制
\uXXXX 四位十六进制数表示Unicode字符
除此以外的转义,反斜线将被忽略。
转义常用于反斜杠和引号。
7. Unicode
JavaScript 1.3之前的版本不支持Unicode字符集
8. 表达式与运算符
JavaScript有以下表达式类型:
算术、字符串、逻辑、对象。
JavaScript有以下运算符类型:
赋值、比较、算术、按位、逻辑、字符串、特殊。
除条件是三元以外、其余都是一元和二元。
赋值包括=,+=,-=,*=,/=,%=,<<=,>>=,>>>=,&=,^=,|=
比较包括==,!=,===,!==,>,>=,<,<=
对于不同类型,==会试图转换,但===不会。
所以var var1=3则var1=="3"是true,但var1==="3"是false。
算术包括+,-,*,/,%,++,--,-(一元)
按位包括&,|,^,~,<<,>>,>>>
按位把操作数看作32位二进制数处理。>>保留符号,而>>>填充0。
逻辑包括&&,||,!
逻辑运算符的二元运算不总是返回true或false,而是通过短路计算得到尽可能右面的值。
字符串运算符包括+,+=
特殊元素符包括以下:
条件condition ? val1 : val2
逗号for (var i = 0, j = 9; i <= 9; i++, j--)
删除对象或对象属性(可用于with内)或数组成员delete objectName;
delete可用于删除非var的隐式声明变量。(var变量将失败,返回false)
成功的话对象或属性设置为undefined,返回true。
否则,返回false。(例如var变量或预定义属性)
delete数成员组不同于undefined赋值,例如:
delete trees[3];则if(3 in trees)返回false,
trees[3] = undefined;则if (3 in trees)返回true。
判断是否拥有属性propNameOrNumber in objectName
in左侧是数字时用于数字型下标的数组。
in左侧是字符串时用于无引号属性名的对象。
判断是否某类型的实例objectName instanceof objectType
创建用户定义对象或Array数组var objectName = new objectType([param1, param2, ..., paramN])
当前对象this[.propertyName]
this在事件句柄中用于传递表单元素,例如:
<INPUT TYPE="text" NAME="age" SIZE=3
onChange="validate(this, 18, 99);">
this.form用于表示当前对象的父表单,例如
<FORM NAME="myForm">
Form name:<INPUT TYPE="text" NAME="text1" VALUE="Beluga">
<P>
<INPUT NAME="button1" TYPE="button" VALUE="Show Form Name"
onClick="this.form.text1.value = this.form.name;">
</FORM>
获取类型字符串typeof operand 或typeof (operand)
typeof返回"function","string","object","number","boolean","undefined"
计算但不返回值void (expression)或void expression
例如,取消一个超链接(计算但不被文档加载)
<A HREF="javascript:void(0)">Click here to do nothing</A>
用于提交的超链接
<A HREF="javascript:void(document.form.submit())">
Click here to submit</A>
优先级,可以用括号改变(略,详见:
https://developer.mozilla.org/en/JavaScript/Guide/Expressions_and_Operators最下方)
9. 正则表达式。
正则是对象。
模式应用于RegExp的exec和test方法,以及
String的match,replace,search和split方法。
构建方法有:
var re = /ab+c/;(字面值)
var re = new RegExp("ab+c");(RegExp对象)
/abc/属于精确匹配的模式,
而正则更多用于非直接地匹配,需要使用特殊字符。
\用于转义或者表示正则中特殊字符的原本含义。
^用于匹配输入开始(m选项下还匹配回车后的行首)
$用于匹配输入结束(m选项下还匹配回车前的行尾)
*用于匹配重复0或0次以上。
+用于匹配重复1或1次以上。
?用于匹配重复0或1次。
.用于匹配换行以外的单个字符。
(x)用于匹配并捕获
(?:x)用于匹配但不捕获
x(?=y)用于匹配x(后面有y)但结果不包括y
x(?!x)用于匹配x(后面没有y)
x|y用于匹配x或匹配y(多选一)
{n}用于匹配重复n次的字符
{n,}用于匹配重复n次或n次以上
{n,m}用于匹配n到m次(多于m次的取前m次)
[xyz]用于匹配字符集内的一个字符,如果是[a-d]则相当于[abcd]
[^xyz]用于匹配字符集补集内的一个字符。如果是[^a-d]则相当于[^abcd]
[\b]匹配退格
\b匹配可以分割开标识符的分界,包括输入的开始和结束
\B匹配不能分割开标识符的分界(指\B后面的字符?)
\cX匹配Ctrl-X控制字符
\d匹配数字[0-9]
\D匹配非数字[^0-9]
\f匹配表格回退
\n匹配换行
\r匹配回车
\s匹配单个空白字符[ \f\n\r\t\v\u00A0\u2028\u2029]
\S匹配单个非空白字符[^ \f\n\r\t\v\u00A0\u2028\u2029]
\t匹配表格符
\v匹配纵向表格符
\w匹配字母数字和下划线的单个字符[A-Za-z0-9_]
\W匹配非\w字符[^A-Za-z0-9_]
\0匹配空字符,但后面不能带数字
\xhh匹配两位16进制字符
\uhhhh匹配四位16进制字符
括号用于捕获(匹配字符串的子串)或者类似(?:x)的场合中
JavaScript使用正则的方法有
exec()是RegExp的方法,用于匹配搜索,返回数组。
test()是RegExp的方法,用于匹配测试,返回true或false。
match()是String的方法,用于匹配搜索,返回数组,不匹配则返回null
search()是String的方法,用于匹配测试,返回匹配位置,不匹配则返回-1。
replace()是String的方法,用于匹配搜索并替换。
split()是String的方法,用于切割字串数组。
是否存在模式(速度较快)用test和search。
更多信息用exec和match,例如:
var myRe = /d(b+)d/g;
var myArray = myRe.exec("cdbbdbsbz");
或者
var myArray = /d(b+)d/g.exec("cdbbdbsbz");
或者
var myRe = new RegExp("d(b+)d", "g");
var myArray = myRe.exec("cdbbdbsbz");
这三种方式都是等效的,
其结果myArray和myRe包含匹配的结果:
myArray:匹配串(0下标)和捕获字串数组(其它下标)
myArray.index:匹配位置
myArray.input:输入的原始字符串
myArray[0]:最后匹配的字符串
myRe.lastIndex:用于g选项,开始下一次匹配的位置
myRe.source:正则的模式字符串(创建但未执行时更新)。
注意,上面的myRe是对于显式变量而言(字面值产生的临时变量将无效)
小括号可用于替换,例如交换匹配子串
var re = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(re, "$2, $1");
document.write(newstr);
或用于exec的忽略写法(可能不适用于其它浏览器)
<html>
<script type="text/javascript">
function getInfo(field){
var a = /(\w+)\s(\d+)/(field.value);
window.alert(a[1] + ", your age is " + a[2]);
}
</script>
Enter your first name and your age, and then press Enter.
<form>
<input type="text" name="NameAge" onchange="getInfo(this);">
</form>
</html>
正则还支持flag(选项),例如:
g选项表示全局搜索(可能多于一个结果)
i选项表示大小写不敏感。
m表示多行搜索(换行可以作为^和$匹配的位置)。
y表示严谨搜索(当前位置,Firefox 3开始支持)。
正则选项可以单独使用或一起使用。
使用方式:
var re = /\w+\s/g;
或
var re = new RegExp("\\w+\\s", "g");
选项在正则创建后不可以添加和删除
简单的正则使用示例(注释略)
var names = "Harry Trump ;Fred Barney; Helen Rigby ; Bill Abel ; Chris Hand ";
var output = new Array("---------- Original String<br><br>", names + "<br><br>");
var pattern = /\s*;\s*/;
var nameList = names.split(pattern);
pattern = /(\w+)\s+(\w+)/;
var bySurnameList = new Array();
output.push("---------- After Split by Regular Expression<br>");
var i, len;
for (i = 0, len = nameList.length; i < len; i++){
output.push(nameList[i] + "<br>");
bySurnameList[i] = nameList[i].replace(pattern, "$2, $1")
}
output.push("---------- Names Reversed<br>");
for (i = 0, len = bySurnameList.length; i < len; i++){
output.push(bySurnameList[i] + "<br>")
}
bySurnameList.sort();
output.push("---------- Sorted<br>");
for (i = 0, len = bySurnameList.length; i < len; i++){
output.push(bySurnameList[i] + "<br>")
}
output.push("---------- End<br>");
document.write(output.join("\n"));
正则也可用于输入的验证,例如:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<script type="text/javascript">
var re = /\(?\d{3}\)?([-\/\.])\d{3}\1\d{4}/;
function testInfo(phoneInput){
var OK = re.exec(phoneInput.value);
if (!OK)
window.alert(RegExp.input + " isn't a phone number with area code!");
else
window.alert("Thanks, your phone number is " + OK[0]);
}
</script>
</head>
<body>
<p>Enter your phone number (with area code) and then press Enter.</p>
<form action="">
<input name="phone" onchange="testInfo(this);">
</form>
</body>
</html>
(详见:
https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions)
10. 语句
表达式都是语句。
分号用于分割语句。
块语句用大括号分割,常用于控制流(if,for,while)
JavaScript不支持块作用域,所以不能用大括号来防止对块外变量产生的影响。
条件语句包括:
条件判断if()~else if()~else~
尽量使用大括号的块,尤其是if后的语句。
对于条件中的赋值不应该写成:
if (x = y) {
/* do the right thing */
}
而应该写成:
if ((x = y)) {
/* do the right thing */
}
布尔值不同于布尔对象,例如:
var b = new Boolean(false);
if (b) //得到true
虽然值是false,但在if得到的却是true。
会被计算为true的还有:
undefined, null, 0, NaN, ""
开关语句switch()~case x:~default:
switch语句在大部分情况下都需要在每个case/default后添加break。
否则,控制流会流向下一个case的语句中。
循环语句包括for,do while和while,以及标号(label)
循环中可以使用break或continue语句。
for语句的一般形式:
for([initialExpression];[condition];[incrementExpression])
statement
常用于数组的遍历,例如表单的勾选统计:
<script type="text/javascript">//<![CDATA[
function howMany(selectObject) {
var numberSelected = 0;
for (var i = 0; i < selectObject.options.length; i++) {
if (selectObject.options[i].selected)
numberSelected++;
}
return numberSelected;
}
//]]></script>
<form name="selectForm">
<p>
<strong>Choose some music types, then click the button below:</strong>
<br/>
<select name="musicTypes" multiple="multiple">
<option selected="selected">R&B</option>
<option>Jazz</option>
<option>Blues</option>
<option>New Age</option>
<option>Classical</option>
<option>Opera</option>
</select>
</p>
<p>
<input type="button" value="How many are selected?"
onclick="alert ('Number of options selected: ' + howMany(document.selectForm.musicTypes))"/>
</p>
</form>
do while语句用于至少执行一次的循环。例如:
do {
i += 1;
document.write(i);
} while (i < 5);
while语句用于判断后执行的循环。例如:
n = 0;
x = 0;
while (n < 3) {
n++;
x += n;
}
应尽量避免死循环,例如:
while (true) {
alert("Hello, world");
}
标号语句是指带冒号结束的标识符,可标识语句,但更多是用于标识嵌套循环中的外层循环,例如:
markLoop:
while (theMark == true) {
doSomething();
}
break语句用于循环或switch中表示跳出循环或跳出switch块。
break后面跟标号则表示跳出指定块(通常是嵌套循环的外层)。
continue语句类似于break语句,但不是针对switch块,而且是跳到条件判断以继续下一次循环。
continue后跟标号表示跳到指定块(通常是嵌套循环的外层)的条件判断中。
嵌套的while循环中经常使用标号以使流程更清晰,例如:
checkiandj :
while (i < 4) {
document.write(i + "<br/>");
i += 1;
checkj :
while (j > 4) {
document.write(j + "<br/>");
j -= 1;
if ((j % 2) == 0)
continue checkj;
document.write(j + " is odd.<br/>");
}
document.write("i = " + i + "<br/>");
document.write("j = " + j + "<br/>");
}
对象操作语句包括:for...in, for each...in, 和with语句
for in用于遍历对象的属性(键),例如:
function dump_props(obj, obj_name) {
var result = "";
for (var i in obj) {
result += obj_name + "." + i + " = " + obj[i] + "<br>";
}
result += "<hr>";
return result;
}
要计算值必须使用下标。
虽然for...in可以用于数字型下标的Array对象,但获得的是用户定义属性及数字下标,
如果只想遍历数组的数字下标,需要使用传统的while或者数字型的for。
for each...in用于遍历对象的值,而非属性名称。
with语句用于指定属性名的缺省对象(忽略对象名),
如果属性名不匹配,则使用局部或全局对象(而非属性)。
虽然with可使程序简洁,但是使用不当会让程序变慢。
注释语句包括单行注释//和多行注释/* */
例如:
// This is a single-line comment.
/* This is a multiple-line comment. It can be of any length, and
you can put whatever you want here. */
异常处理语句包括:throw和try...catch语句
异常类型包括:
ECMAScript异常:
Error
EvalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError
DOM异常:
DOMException
EventException
RangeException
... (?)
以及nsIXPCException (XPConnect)
throw语句可以抛出任意表达式,抛出任意的类型。
例如:(UserException的toString()被覆盖)
function UserException (message){
this.message=message;
this.name="UserException";
}
UserException.prototype.toString = function (){
return this.name + ': "' + this.message + '"';
}
throw new UserException("Value too high");
try...catch语句用于捕获块内语句中抛出的异常。
finally块内的语句在try...catch处理完之后但在try块退出之前执行。
例如数组的越界检查:
function getMonthName (mo) {
mo=mo-1;
var months=new Array("Jan","Feb","Mar","Apr","May","Jun","Jul",
"Aug","Sep","Oct","Nov","Dec");
if (months[mo] != null) {
return months[mo]
} else {
throw "InvalidMonthNo"
}
}
try {
monthName=getMonthName(myMonth)
}
catch (e) {
monthName="unknown"
logMyErrors(e)
}
其中catch()内的变量仅在catch块内有效。
如果想处理多个字符串异常对象,在Mozilla中可以这样写:
try {
getCustInfo("Lee", 1234, "[email protected]")
}
catch (e if e == "InvalidNameException") {
bad_name_handler(e)
}
catch (e if e == "InvalidIdException") {
bad_id_handler(e)
}
catch (e if e == "InvalidEmailException") {
bad_email_handler(e)
}
catch (e){
logError(e)
}
但这种写法可能不适用于其它JavaScript引擎(例如V8)
try...catch允许嵌套。
可以使用错误对象的name和message属性。
如果不想自己定义错误类型,可以使用Error的构造函数指定name。
例如:
function doSomethingErrorProne () {
if (ourCodeMakesAMistake()) {
throw (new Error('The message'));
}
else {
doSomethingToGetAJavascriptError();
}
}
....
try {
doSomethingErrorProne();
}
catch (e) {
alert(e.name);
alert(e.message);
}
11. 函数(闭包)
函数包括名称、参数列表和大括号中的JavaScript语句。
例如:
function square(number) {
return number * number;
}
函数内改变的属性值会影响到函数外(引用传递)
function myFunc(theObject) {
theObject.make="Toyota";
}
var mycar = {make:"Honda", model:"Accord", year:1998};
var x=mycar.make; // 返回 Honda
myFunc(mycar);
var y=mycar.make; // 返回 Toyota
但对对象本身的改变不会影响到函数外(值传递)
function myFunc(theObject) {
theObject = {make:"Ford", model:"Focus", year:2006};
}
var mycar = {make:"Honda", model:"Accord", year:1998};
var x=mycar.make; // 返回 Honda
myFunc(mycar);
var y=mycar.make; // 返回 Honda
函数定义可以包裹在if内。
if (num == 0)
{
function myFunc(theObject) {
theObject.make="Toyota"
}
}
函数可以是匿名的。
var square = function(number) {return number * number};
函数可以作为参数传递。
例如把函数作为遍历数组的迭代器:
function map(f,a) {
var result=new Array;
for (var i = 0; i != a.length; i++)
result[i] = f(a[i]);
return result;
}
map(function(x) {return x * x * x}, [0, 1, 2, 5, 10]);
函数定义不会真正执行函数,
但函数调用会以参数传入以执行指定的动作。
函数允许递归,例如计算阶乘:
function factorial(n) {
if ((n == 0) || (n == 1))
return 1;
else {
var result = (n * factorial(n-1) );
return result;
}
}
a=factorial(1); // 返回 1
b=factorial(2); // 返回 2
c=factorial(3); // 返回 6
d=factorial(4); // 返回 24
e=factorial(5); // 返回 120
在函数内可以通过arguments数组访问传入参数,例如:
function myConcat(separator) {
var result = "";
for (var i = 1; i < arguments.length; i++) {
result += arguments[i] + separator;
}
return result;
}
其中arguments[0]是第一个参数(上面直接用separator表示),如此类推。
JavaScript 1.3及其早期版本中arguments是Function对象的属性,可以在前面加上函数名,例如:
functionName.arguments[i]
顶级预定义函数包括:
eval
isFinite
isNaN
parseInt 和 parseFloat
Number 和 String
encodeURI、decodeURI、encodeURIComponent、decodeURIComponent(Javascript 1.5以后可使用)。
eval对字符串中的表达式求值,或对一条以上的JavaScript语句求值,作用域等于调用方的作用域。
不要用eval计算算术表达式(JavaScript会自动求值,这里指非字符串?)
isFinite用于判断值不是NaN或正负无穷大。
isNaN用于判断值是NaN
parseInt 和 parseFloat用于把字符串转为整数或浮点数,否则返回NaN
parseInt(str [, radix])可以指定进制。
Number 和 String函数用于把对象转为数字或字符串。
例如:
var D = new Date (430054663215);
var x = String(D);
escape 和 unescape用于字符串编解码。
常用于服务器中URL名值对的编解码。
scape 和 unescape 在非 ASCII 下不正常。
在JavaScript 1.5及以后,可改用
encodeURI、decodeURI、encodeURIComponent、decodeURIComponent。
闭包可以修改非局部变量(但不是全局变量)的值。
闭包是一种特殊对象,结合函数和函数环境于一体。
闭包在建立(定义)的时候把闭包内使用的外层函数var变量的引用保存在函数(闭包)环境
(注:不同于值参数传递,环境是引用外层函数的var变量而非保存值)中。
以便在异步执行时使用。
例如:
function init() {
var name = "Mozilla";
function displayName() {
alert(name);
}
displayName();
}
init()
在这类displayName执行完后,从init中返回。
但是在下面代码中
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
makeFunc函数返回后,displayName才执行(在myFunc(); 这句中)
虽然如此,效果是一样的(不过,后者是异步执行的)
环境内的值只是闭包建立时的变量值(有点像参数传递)
上面的例子中闭包保存了displayName函数以及值为"Mozilla"的字符串变量name。
闭包可以用于函数模板(函数工厂),例如:
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
print(add5(2)); // 7
print(add10(2)); // 12
makeAdder通过参数定制出不同增量(x作为环境被保存)的闭包。
实际上,在某些对象上使用单一方法时,可能需要使用闭包。
在Web上,通常需要使用回调来处理事件。
定义多个类似的回调会显得繁琐且不容易维护。
例如对于三个改变body字体大小的链接:
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
可以这样处理:
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
function setupButtons() {
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
}
闭包可用于模拟私有方法,例如
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
alert(Counter.value()); // 0
Counter.increment();
Counter.increment();
alert(Counter.value()); // 2
Counter.decrement();
alert(Counter.value()); // 1
Counter获得匿名函数内创建的对象,
其中包含三个闭包函数:increment, decrement, value。
匿名函数就像类(对象工厂),
而由工厂返回的不同对象中虽然拥有相同的界面(公开成员闭包),但拥有不同的环境。
在匿名函数内privateCounter是私有变量,changeBy是私有方法。
私有变量和私有方法只能通过返回的公共界面访问。
用这种方式,可以实现面向对象的数据隐藏和封装性。
要注意,循环中建立闭包可能会产生问题,例如:
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
这里会有问题,首先JavaScript没有块作用域,所以
for循环内的item其实是同一个变量。
其次,闭包共享同一个环境(同一个item),所以闭包的效果将完全一样。
解决办法之一是使用函数造成类似“块作用域”的效果。
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function makeHelpCallback(help) {
return function() {
showHelp(help);
};
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
}
}
只要makeHelpCallback的参数值不同,就可以保证匿名函数的效果不一样。
代价是,匿名函数的环境不是setupHelp的局部变量,而是makeHelpCallback的局部变量。
所以,匿名函数无法获得item.help以外的其它属性值。
解决方法之二,如果使用JavaScript 1.7以上的版本,可以使用带块作用域的变量。
for (var i = 0; i < helpText.length; i++) {
let item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
前提是需要支持let。
不必要时不要使用闭包,因为有性能损失。
使用原型而非在构建时创建闭包可以加快创建速度。
例如:
function MyObject(name, message) {
this.name = String(name);
this.message = String(message);
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}
上面闭包的写法不够快,应该用原型
function MyObject(name, message) {
this.name = String(name);
this.message = String(message);
}
MyObject.prototype = {
getName: function() {
return this.name;
},
getMessage: function() {
return this.message;
}
};
或者
function MyObject(name, message) {
this.name = String(name);
this.message = String(message);
}
MyObject.prototype.getName = function() {
return this.name;
};
MyObject.prototype.getMessage = function() {
return this.message;
};
原型的继承使每个对象共享方法(因为共享prototype)
在对象构建时就不需要重新定义这些共享的方法。
(详见:
https://developer.mozilla.org/zh_tw/Core_JavaScript_1.5_%E6%95%99%E5%AD%B8/%E9%96%89%E5%8C%85%E7%9A%84%E9%81%8B%E7%94%A8)
12. (待续)