一、函数声明与函数表达式
定义函数的方式有两种:函数声明和函数表达式
函数声明:
function functionName()
{
}
- 函数声明提升:执行代码前会先读取函数声明。可以把函数声明放在调用它的语句后面。
- 两个同名函数声明,后出现的生效
函数表达式
var functionName = function()
{
}
- 因为function关键字后没有函数名,所以创建的函数叫匿名函数(或拉姆达函数)
- 函数表达式在使用前必须赋值,和其他表达式一样
var sayHi;
if(condition)
{
sayHi = function(){alert("Hi");};
}
else
{
sayHi = function(){alert("Yo");};
}
//只能用函数表达式实现,不能用函数声明
二、函数递归
function factorial(num)
{
if(num<=1)
{
return 1;
}
else
{
return num*arguments.callee(num-1);
}
}
- arguments.callee指向正在执行的函数的指针
- 在严格模式下访问arguments.callee属性会导致错误
var factorial = function f(num)//命名函数表达式
{
if(num<=1)
{
return 1;
}
else
{
return num*f(num-1);
}
}
- 严格模式下用命名函数表达式实现递归
三、闭包
- 在一个函数内部创建的另一个函数,会将包含函数的活动对象添加到它的作用域中,因此有权访问包含函数中的变量和方法。
- 外部函数在执行完毕后,其作用域链会被销毁,但其活动对象也不会销毁,因为内部函数的作用域链仍在引用这个活动对象。
- 闭包会比其他函数占用更多的内存,只在绝对必要时在考虑使用闭包
- 闭包保存的是整个活动对象,而不是某个时刻的变量值,它只能取得包含函数的变量的最后一个值。
function createFunctions()//每个函数返回自己在数组中的索引值
{
var result = new Array();
for(var i=0; i<10; i++)
{
result[i]= (function(num)
{
return function()
{
return num;
}
})(i);//函数参数是按值传递的,会将变量i的瞬时值赋给num
}
return result;
}
this对象
- 在全局函数中this = window
- 函数被作为某个对象的方法调用时,this = 那个对象
- call() , apply() 改变函数执行环境时,this指向指定的对象
- 每个函数在调用时都会自动取得两个变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其自己的活动对象为止,因此不能直接访问其外部函数的这两个变量。可以将外部函数的这两个变量保存在其他变量里,内部函数利用闭包来访问。
var name = "the window";
var object = {
name : "My object",
getNameFunc : function(){
return function()//匿名函数执行环境具有全局性,其this对象通常指向window
{
return this.name;
};
}
};
alert(object.getNameFunc()());//the window
var name = "the window";
var object = {
name : "My object",
getNameFunc : function(){
var that = this;
return function()
{
return that.name;
};
}
};
alert(object.getNameFunc()());//My object
- 特殊情况
var name = "the window";
var object = {
name : "my object",
getName:function()
{
return this.name;
}
};
(object.getName = object.getName)();//the window
- 先赋值,再调用赋值后的结果。因为赋值表达式的值是函数本身,this的值不能得到维持。(???)
内存泄露
- IE9之前对JScript和COM对象使用不同的垃圾收集例程,COM对象使用引用计数方式。如果闭包的作用域链中保存着一个HTML元素,该元素将无法被销毁。
- 解决方法如下:
function assignHandler()
{
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){alert(id);};
element = null;//有必要解除对DOM对象的引用
}
模仿块级作用域
- 匿名函数模仿块级作用域
(function(){
//块级作用域
})();
- 函数声明后不能加(),函数表达式后可以加()
- 这种技术经常在全局作用域中被用在函数的外部,限制向全局作用域中添加过多的变量和函数。
私有变量
- javascript中所有对象属性都是公有的
- 函数中定义的变量和方法、函数的参数可以认为是私有的
- 用闭包创建访问私有变量和方法的公有方法:特权方法
- 创建特权方法:
- 在构造函数中定义特权方法
但是每个实例都会创建同样的方法,不能复用方法
function myObject()
{
var privateVariable = 10;
function privateFunction(){return false;}
//特权方法
this.publicMethod = function(){
privateVariable++;
return privateFunction();
};
}
- 用静态私有变量实现特权方法
(function(){
var privateVariable = 10;
function privateFunction(){return false;}
//构造函数用函数表达式,因为函数声明只能创建局部函数,而这里的MyObject是全局变量
//但严格模式下给未经声明的变量赋值会导致错误
MyObject = function(){};
//特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();
- 这里的私有变量和函数变成了静态的,是由实例共享的
- 模块模式为单例创建特权方法
- 单例:只有一个实例的对象
- javascript以对象字面量来创建单例对象
var singleton = function()
{
var privateVariable = 10;
function privateFunction(){return false;}
return {
publicProperty : true,
publicMethod : function(){
privateVariable++;
return privateFunction();
};
}();
- 增强的模块模式
- 单例必须是某种类型的实例,同时还必须添加某些属性和方法对其加以增强的情况。
var singleton = function()
{
var privateVariable = 10;
function privateFunction(){return false;}
var object = new CustomType();
object.publicProperty = true;
object.publicMethod : function(){
privateVariable++;
return privateFunction();
};
return object;
}();