- 函数表达式的特征
- 使用函数实现递归
- 使用闭包定义私有变量
前面我们说到定义函数有两种方式:函数声明、函数表达式。
两者的区别在于函数声明提升,前者在执行之前的上下文环境中直接被赋值,而后者不会。
一、递归
递归函数是一个函数通过名字调用自身的情况下构成的。
function factorial(num){
if(num<1){
return 1;
}else{
return num * arguments.callee(num-1);
}
}
alert(factorial(10)); 二、闭包
闭包的核心概念就是指有权访问另一个函数作用域中变量的函数。
创建闭包的常见方式主要就是在一个函数内部创建另一个函数。
function createCompareFunction(proprtyName){
return function(obj1,obj2){
if(obj1[proprtyName]>obj2[proprtyName]){
return 1;
}else if(obj1[proprtyName]<obj2[proprtyName]){
return -1;
}else{
return 0;
}
}
}
var o1 = {name : 'zjh'};
var o2 = {name : 'azz'};
var f = createCompareFunction('name');
alert(f(o2,o1)); //-1 内部匿名函数中调用的proprtyName就是另一个函数作用域中的变量。当f被赋值后,外层函数的作用域链被销毁,但是他的活动对象仍然在内存中,因为它的活动对象被返回的匿名函数引用了。
所以闭包会占用很多内存,要在必要的时候使用。
2.1闭包与变量
作用域链的这种机制也会有一定的副作用:
function createFunction(){
var array = new Array();
for(var i = 0 ; i < 10 ; i++){
array[i] = function(){
return i;
}
}
i = 20;
return array;
}
var a = createFunction();
alert(a[6]())//20 应该是6,这是却是20。原因就是数组中的每一项都是function(){return i},而此时的i的值是createFunction执行完的20。
function createFunction(){
var array = new Array();
for(var i = 0 ; i < 10 ; i++){
array[i] = function(num){
return num;
}(i)
}
i = 20;
return array;
}
var a = createFunction();
alert(a[6])//20 利用了函数参数按值传递的特性。
2.2关于this对象
this对象时在运行时基于函数执行环境绑定的。在全局函数中,this等于window,而当函数作为某个对象的方法调用时,this等该对象。
匿名函数执行环境具有全局性。因此this通常指向window。
var k = 'window';
var o = {
k : 'object',
getName : function(){
return function(){
return this.k;
}
}
}
alert(o.getName()()) 2.3内存泄漏
在IE浏览器中,因为dom对象和js对象存在浏览器的不同地方,如果闭包的作用域链中保存着一个HTML元素,那么该元素就无法被销毁。
function createF(){
var ele = document.getElementById('aa');
ele.onclick = function(){
alert(ele.value);
}
} 避免泄漏:
function createF(){
var ele = document.getElementById('aa');
var k = ele.value;
ele.onclick = function(){
alert(k);
}
ele = null;
} 三、模仿块级作用域
JS中没有块级作用域,如果要使用需要在函数中定义来模仿。
(function(){
for(var i =0 ; i<10 ; i++){
var k = 10;
}
})()
alert(i) //错误 因为i在块级作用域执行完后 就销毁了。
只要临时需要一些变量,可以使用私有作用域。这种技术经常用在全局作用域中的外部函数,可以限制向全局作用域中添加过多的变量和函数。
也能有效的减少闭包占用内存的问题。因为没有指向匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域了。
四、私有变量
JS中没有私有成员的概念,所以属性都是公有的,但是有一个私有概念,那就是在任何函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。
这些私有变量包括:函数的参数,局部变量,函数内部定义的其它函数。
function add(num1,num2){
var a = '1500';
} 在这个函数中,有三个私有变量,num1,num2,a。我们可以在函数内部访问他们,或者通过函数内部的闭包通过作用域来访问他们。那么我们利用这一点就可以创建用于访问私有变量的特权方法。
有两种方式可以创建特权方法:
4.1私有变量
第一种:利用闭包
function Person(name){
this.setName = function(value){
name = value;
}
this.getName = function(){
return name;
}
}
var p = new Person();
p.setName('zjh');
alert(p.getName()); 所以,利用私有成员和特权成员可以隐藏那些不该被直接修改的数据。
缺点:是必须使用构造函数模式来达到这个目的。前面提到过,构造函数模式的缺点是:每次实例化一个对象,都会创建一组同样的新方法。
4.2静态私有变量
在私有作用域中定义私有变量或者函数,也可以创建特权方法。
var privateVariable = 10;
function privateFuntion(){
return false;
}
MyObject = function(){};
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFuntion();
}
var o = new MyObject();
alert(o.publicMethod()); 这个模式在定义构造函数时没有使用函数声明,而是使用了函数表达式。函数表达式只能创建局部函数,所以我们不使用关键字var 来定义了MyObject ,未经过初始化声明的变量总是会创建一个全局变量。(在严格模式下会错误)
这个模式与在构造函数中定义特权方法的区别在于:私有变量和函数是→实例共享的,由于特权方法是在原型上定义的,因此所有实例都使用同一个函数。而这个特权方法作为一个闭包,总是保存着对包含作用域的引用。
(function(){
var name = '';
Person = function(value){
name = value;
}
Person.prototype.setName = function(value){
name = value;
}
Person.prototype.getName = function(){
return name;
}
})()
var p = new Person('zjh');
alert(p.getName());//zjh
var pp = new Person('zzz');
alert(pp.getName());//zzz
alert(p.getName());//zzz 可见这种方式创建的是静态私有变量。
4.3模块模式
之前的模式是为自定义类型添加私有变量。而这种模块模式是为单例模式的对象添加私有属性。
方法如下:
var singleton = function(){
var privateVar = 10;
function privateFun(){
return false;
}
return {
publicVar : true,
publicMethod : function(){
privateVar++;
return privateFun();
}
}
}()
alert(singleton.publicMethod()); 如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以用到这种模块方式。
4.3增强的模块模式
function Person(){}
var singleton = function(){
var privateVar = 10;
function privateFun(){
return false;
}
var o = new Person();
o.publicVar = true;
o.publicMethod = function(){
privateVar++;
return privateFun();
}
return o;
}()
alert(singleton.publicMethod()); 这种模式 在匿名函数中创建了一个指定类型的对象,用来返回。
五、小结
在JS中,函数表达式是一种非常有用的技术。使用函数表达式无须命名,从而实现动态编程。
特点:
函数表达式不一定有名字,没有名字的函数表达式叫做匿名函数。
在无法确定如何引用函数的情况下,递归函数变得比较复杂。
递归函数中应该用arguments.callee来递归自身,以防止函数名变化。
当在函数内部定义其他函数时就创建了闭包。闭包有权访问 包含函数 的 内部的所有变量,原理:
在后台执行环境中,闭包的作用域链包含它自身的作用域、包含函数的作用域、全局作用域。
通常,函数的作用域及其变量都会在函数执行结束了被销毁。
但是,当函数返回一个闭包时,这个函数的作用域会在内存中一直保存到闭包不存在为止。
使用闭包可以在对象中创建私有变量:
可以使用构造函数模式、原型模式、来实现自定义类型的特权方法;也可以使用模块模式、增强模式的模块模式来实现单利的特权方法。