函数定义
在JS中定义函数有函数声明和函数表达式的两种方式,函数声明会被提到代码执行之前,所以函数声明可以放在函数的调用之后;函数表达式看起来像变量赋值语句,通常是创建一个匿名的函数对象并把它赋值给某个变量,如果在赋值之前调用函数表达式会导致错误。
println("Hello World"); // OK
function println(str) {
alert(str);
}
println("Hello Wolrd") // error
var println = function(str) {
alert(str)
}
闭包
闭包是指有权访问另外一个函数作用域中的变量的函数,创建闭包的常见方式就是在一个函数内部创建另外一个函数。通常来说当函数执行完毕局部上下文对象就会被销毁,内存中仅包含全局作用域的变量对象,但是闭包会将外部函数活动对象添加到它的作用域里,外部函数即使执行完毕也不会销毁它的活动对象;直到返回的匿名闭包函数被销毁,外部函数的活动对象才会被销毁。
function createPrintln() {
var x = 10;
return function() {
x++;
alert(x);
}
}
var println = createPrintln()
println() // 11
println() // 12
上面的例子中println指向的匿名函数引用了createPrintln函数的活动对象,因而x变量在createPrintln执行完成之后依然保存在内存中。通过前面的分析我们知道局部变量x是保存在内存中,这样闭包取得的包含函数中的变量的值总是最后一个。
function createPrintln() {
var result = new Array()
for (var x = 0; x < 10; x++) {
result[x] = function() {
return x;
}
}
return result;
}
var array = createPrintln()
for (var i = 0; i < array.length; i++) {
alert(array[i]()); // 全部都是10
}
这时因为在createPrintln函数的最后x的最新值就是10,每个闭包函数查询x值的时候都会引用到这个最新值,如果想要返回不同的值需要闭包函数引用不同的外部函数才可以。
function createPrintln() {
var result = new Array()
for (var x = 0; x < 10; x++) {
result[x] = function(num) {
// 闭包函数引用的外部函数是这个function(num)匿名函数,
// 不再是createPrintln
return function() {
return num
}
}(x);
}
return result;
}
var array = createPrintln()
for (var i = 0; i < array.length; i++) {
alert(array[i]());
}
闭包最后需要注意的一条是this对象的引用,在对象函数里this代表的就是当前对象,如果对象方法返回的是一个闭包,这个闭包的this就是全局对象,因为查询变量通常在查到第一个VO变量对象里的变量就不再向其他VO变量对象查询,this在自己的变量对象总一定能查得到,就会出现问题。
var name = "World"
var object = {
name: "Hello",
getName: function() {
return function() {
return this.name
}
}
}
alert(object.getName()()) // World
为了能够正确地访问对象里的this,可以在访问之前用that属性将对象this保存下来,之后再访问对象this就通过that变量访问。
var name = "World"
var object = {
name: "Hello",
getName: function() {
var that = this;
return function() {
return that.name
}
}
}
alert(object.getName()()) // Hello
模仿块级作用域
JS中没有块级作用域的概念,也就是说语句块中定义的变量其实是在包含函数中创建,如果在函数内部多次声明同一个变量,JS会对后续的声明视而不见,不过会执行初始化方法,匿名函数可以用来模拟块级作用域。
var sum = 0;
// i的作用域在全局作用域,在if块外也可以访问
for (var i = 0; i < 5; i++) {
sum += i;
}
alert(i); // 5
var i = 3; // 声明会被忽略但是初始化还是被执行
alert(i); // 3
// 块级作用域语法
(function() {
var num = 100
alert(num)
})(); // num的作用域仅在匿名函数块内
alert(num); // error
私有变量
JS中没有私有成员的概念,所遇的对象属性都是公开的,不过变量却可以是私有的。任何函数中定义的变量都可以认为是私有变量,因为无法在函数外部访问这些变量。如果函数返回闭包对象,那么闭包对象就可以访问函数内部定义的变量,闭包对象就作为访问私有变量的公共方法,也称为特权方法。
可以使用构造函数内部方法访问构造函数的变量,除了内部定义的函数可以访问变量,外部无法访问这个变量。不过用构造函数定义的方法在每个对象内部都会创建一组新的方法,使用静态私有变量就可以解决这个问题。
function Person(name) {
this.getName = function() {
return name;
}
this.setName = function(value) {
name = value;
}
}
var tom = new Person('Tom');
alert(tom.getName()) // Tom
var jerry = new Person('Jerry');
alert(jerry.getName()) // Jerry
为了保存私有数据可以使用前面的模拟块级作用域定义变量,同时外部访问函数定义在构造函数的prototype对象上,这样每个新生成的对象都共享同一份变量和方法。
(function() {
var name;
// 注意前面没有var,这个Person构造函数是全局的
Person = function(value) {
name = value;
}
Person.prototype.getName = function() {
return name;
}
Person.prototype.setName = function(value) {
name = value
}
})();
var tom = new Person('Tom')
alert(tom.getName()) // Tom
var jerry = new Person('Jerry')
alert(tom.getName()) // Jerry
alert(jerry.getName()) // Jerry
前面实现都可以创建多个对象,有些对象在系统中可能只有一个,也就是常说的单例对象,对于这种对象的创建可以将它的数值私有化,将操作数值的方法暴露给用户。
var singleton = (function() {
var name;
var object = new Object();
object.getName = function() {
return name;
}
object.setName = function(value) {
name = value;
}
return object;
})();
singleton.setName('Tom');
alert(singleton.getName());
singleton.setName('Jerry');
alert(singleton.getName());