JavaScript函数表达式及运用
JavaScript的函数本身也是值,可以赋给变量,这是其强大功能之一,就像布尔、数字、字符串、数组、对象一样,可以作为一个对象的属性,也可以作为参数传递给另一函数,当然可以作为函数的值返回。这个是函数的最美丽特征。
定义函数有两种方式:一种是通过函数声明
的方式,另一种是通过函数表达式
的方式。
第一种函数声明具有以下形式:
function sayHello(arg){
alert(“Hello!” + arg);
}
函数名称位于function
关键字之后,这就是指定函数名称的函数声明方式。主流浏览器都给函数定义了一个非标准的name属性,此值始终等同于紧随function
关键字之后的标识符。
函数声明,它的一个主要特征就是函数声明的提升
,即在代码执行之前先读取函数声明。 这意味着函数声明语句可能会在调用它的语句代码之后出现并仍然有效:
sayHello("张三"); // 此处不会引发错误,因为函数声明是在代码开始执行之前首先读取的。
function sayHello(arg){
alert("Hello!" + arg);
}
函数声明,它的工作方式是:声明一个名为sayHello的变量,并用一个函数对象引用来初始化该变量,而变量名的标识符sayHello又作为函数对象的非标准的name属性的值。通过函数声明引入的变量遵循不同于普通变量的作用域规则。具体来说,函数声明只能出现在代码脚本中的全局位置,或者出现在一个函数体的顶级
,不允许出现在语句内部。
这里的戒律是: 即便浏览器允许,也绝对不要将函数声明放在一条语句内。
创建函数的第二种方法是使用函数表达式。 在将一个函数对象引用赋给一个变量时,代码如下:
var sayHi = function(){
alert(“Hi!”);
};
函数表达式的这种模式看起来像普通的变量赋值语句,即创建一个函数并将其引用给变量sayHi。 创建的函数被认为是匿名函数,因为在function
关键字后没有标识符。 (匿名函数有时也称为lambda
函数)匿名函数的name属性是空字符串。
函数表达式的行为与其他表达式类似,在使用前必须先赋值,要不然将导致错误:
sayHi(); //错误 – 函数尚不存在
var sayHi = function(){
alert(“Hi!”);
};
创建函数
后再引用赋值给变量,使得将函数作为其他函数的参数
传递,也能够把函数作为其它函数的值
返回。
考虑到函数声明放置位置的限制,专用的作用域规则,以及函数声明方式会模糊变量定义,使函数看起来不同于其它类型的对象的事实,在应用中倾向于避免使用函数声明的方式,总是可以使用函数表达式
。
如果希望直接调用一个函数表达式(这个函数是一个匿名函数 ),则必须在调用的前后添加小括号
。
(function (arg){
alert("Hello, " + arg +"!");
})("张三")
当函数通过名称调用自身时,通常会形成一个递归函数。
var factorial = (function f(num){
if (num <= 1){
return 1;
} else {
return num * f(num-1);
}
});
在此代码中,将创建一个命名函数表达式f()
并将其引用赋值给变量factorial。 即使将函数引用赋值给另一个变量,函数名称f
仍然有效,因此递归调用将始终正确执行。
术语匿名函数和闭包经常错误地混合使用。 闭包不等同于匿名函数,闭包
是有权访问另一个函数作用域中的变量的函数
。 创建闭包的常见方式就是在一个函数内部创建另一个函数。
通常情况,当函数执行完毕后,局部对象就会被销毁,内存中仅保存全局作用域的变量对象。不过,闭包的情况有所不同。在一个函数内部定义的函数会被包含函数(即外部函数)的活动对象(变量)添加到其作用域链中。由于闭包会携带它的包含函数的作用域,因此会比其它函数占用更多的内存。
function createComparisonFn(propertyName) {
return function(obj1, obj2) {
var value1 = obj1[propertyName];
var value2 = obj2[propertyName];
if(value1 < value2) {
return -1;
} else if(value1 > value2) {
return 1;
} else {
return 0;
}
};
}
var compareNames = createComparisonFn("name"); // 创建函数
var result = compareNames({name: "a"}, {name: "b"}); // 调用函数
console.log(result); // 输出:-1
在 createComparisonFn() 函数内部定义的匿名函数的作用域链中,实际将会包含外部函数 createComparisonFn() 的活动对象。在匿名函数从 createComparisonFn() 中被返回之后,它的作用域链被初始化为包含 createComparisonFn() 函数的活动对象和全局变量对象。这样匿名函数就可以访问在 createComparisonFn() 中定义的所有变量。更为重要的是,createComparisonFn() 函数在执行完毕之后,其活动对象也不会销毁,因为匿名函数的作用域链仍然在引用这个活动对象,换句话说,当 createComparisonFn() 函数返回之后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中。直到匿名函数被销毁后,createComparisonFn() 的活动对象才会被销毁。
① 闭包与变量
闭包只能取得包含函数中任何变量的最后一个值。
function fn() {
var result = new Array();
//给数组的前10项各自创建一个函数表达式,而在函数表达式的匿名函数内部,又创建了一个访问 num 的闭包
for(var i = 0; i < 10; i++) {
result[i] = function(num) {
return function() {
return num;
}
}(i);
}
return result;
}
var fn1 = fn();
for(var j = 0; j < fn1.length; j++) { //依次输出数组 result 的元素
console.log(fn1[j]());
}
② 闭包中使用this对象
this 对象是在运行时基于函数的执行环境绑定的,在全局函数中,this 等于 window,而当函数被作为某个对象的方法调用时, this 等于那个对象。
var name = "张三,我是老大";
var obj = {
name: "李四,我是老二",
//函数作为某个对象的方法,返回一个匿名函数,匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。
sayName1: function() {
return function() {
return this.name;
}
},
// 把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。
sayName2: function() {
that = this;
return function() {
return that.name;
}
}
}
console.log(obj.sayName1()()); //张三,我是老大
console.log(obj.sayName2()()); //李四,我是老二
③ IE内存泄漏
闭包的工作方式在Internet Explorer 9之前的版本中会引起特别的问题。 具体来说,如果闭包的作用域链中保存着一个HTML的元素对象,则该元素对象将无法被销毁。可用的解决方案如下:
function assignHandler(){
var element = document.getElementById(“someElement”);
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
在以上的代码中,把element.id的一个副本存储在闭包中使用的变量中,从而消除了循环引用。 但是,闭包引用了包含函数的整个活动对象,其中包含了element。 即使闭包不直接引用element,引用仍会存储在包含函数的活动对象中。 因此,有必要将element变量设置为null。 这将取消引用DOM对象并减少其引用计数,从而确保可以在适当时回收内存。
模块是一种可以将许多相关实体打包在一起的对象。用作块作用域(通常称为私有作用域)的匿名函数的基本语法如下:
(function(){
//这里是块级作用域代码
})();
此语法定义了一个立即调用的匿名函数,有时也称为立即调用的函数。 将函数声明的内容包括在一对圆括号中,表示它实际上是一个函数表达式。 然后通过最后的第二对圆括号立即调用此函数。
这种技术通常在全局作用域中被用做函数外部,以限制添加到全局范围的变量和函数的数量。 通常,要避免向全局范围添加变量和函数,尤其是在具有多个开发人员的大型应用程序中,以避免命名冲突。 私有范围使每个开发人员都可以使用自己的变量,而不必担心污染全局作用域。
这种模式可以减少闭包占用的内存问题,因为没有指向匿名函数的引用变量。 因此,其作用域链可以在函数执行完成后立即销毁。
模块举例:
(function() {
var weeks = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
var now = new Date();
var week= weeks[now.getDay()] // 获取当前星期X(0-6,0代表星期天)
var y = now.getFullYear(); // 获取完整的年份(4位)
var m = now.getMonth()+1; // 获取当前月份(0-11,0代表1月)
var d = now.getDate(); // 获取当前日
var message = "今天是:" + week + ", " + y + "-" + m + "-" +d;
return (function(){
alert(message);
})();
})();
将所有的代码包裹在它的局部作用域中,不会让任何变量泄露成全局变量。
严格来说,JavaScript没有私有成员的概念。 所有对象属性都是公共的。 但是,存在私有变量的概念, 在函数内部定义的任何变量都被视为私有变量,因为在该函数外部无法访问该变量。 这包括函数参数,局部变量以及在函数内部定义的其他函数。 考虑以下:
function add(num1, num2){
var sum = num1 + num2;
return sum;
}
在此函数中,有三个私有变量:num1,num2和sum。 这些变量可以在函数内部访问,但不能在函数外部访问。 如果要在此函数内部创建闭包,则它将可以通过其作用域链访问这些变量。 据此,可以创建可以访问私有变量的公共方法。
通过模块扩展基本单例对象,以允许私有变量和访问私有变量的特权方法,采用以下格式:
var singleton = function(){
// 私有变量和函数
var privateVariable = 10;
function privateFunction(){
return false;
}
// 特权/公共方法和属性
return {
publicProperty: true,
publicMethod : function(){
privateVariable++;
return privateFunction();
}
};
}();
清单 11. 账户余额数据维护,防止接受一个负值
var account = (function() {
var bval = 0;
return Object.create(Object.prototype, {
balance: {
get: function() {
alert("有人请求账户余额");
return bval;
},
set: function(newvalue) {
if(newvalue < 0) {
throw "不允许负值";
}
bval = newvalue;
},
enumerable: true
}
});
})();
Object.preventExtensions(account); // 使得account对象变的不可扩展,不能再添加新的属性。
alert(account.balance); // 调用 get, 提示0;
account.balance = 50; // 调用 set;
alert(account.balance); // 调用 get, 提示50;
try {
account.balance = -20; // 调用 set, 抛出异常;
} catch(err) {
alert(err);
}
alert(account.balance); // 调用 get, 仍然提示50;
account.bval = 500; // 没有效果;
alert(account.balance); // 调用 get, 仍然提示50;
博文最后更新时间: