定义函数方式:
1.函数声明
- 函数声明提升:执行代码前会先读取函数声明
- name属性:可以访问函数名
2.函数表达式
- 匿名函数(拉姆达函数)
- name属性是空字符串
可以创建函数赋值给变量,也能把函数作为其他函数的值返回
一 递归
递归函数是在一个函数通过名字调用自身的情况下构成的
function factorial(num){
if(num<=1){
return 1;
}
else{
return factorial(num-1);
}
}
var another=factorial;
factorial=null;
alert(another(4)); //出错
由于调用时,factorial不是函数,会出错。解决方案:arguments.callee(一个指向正在执行的函数的指针)
function factorial(num){
if(num<=1){
return 1;
}
else{
return num*arguments.callee(num-1);
}
}
二 闭包
概念:闭包是指有权访问另一个函数作用域中的变量的函数
创建方式:常见方式就是在一个函数内部创建另一个函数
function createComparison(propertyName){
return function(object1,object2){
var value1=object1[property];
var value2=object2[property];
if(value1<value2){
return -1;
}else if(value1>value2){
return 1;
}else{
return 0;
}
}
}
内部函数访问了外部函数中的变量,是因为内部函数的作用域链中包含外部函数的作用域
- 当某个函数被调用时,会创建一个执行环境和相应的作用域链。之后,使用arguments和其他命名参数的值来初始化函数的活动对象。
函数执行时,会在作用域链中查找变量
function compare(value1,value2){
if(value1>value2){
return -1;
}
else if(value1<value2){
return 1;
}
}
var result=compare(5,10);
compare()函数执行时的作用域链:
作用域链本质:一个指向变量对象的指针列表,只引用但不实际包含变量对象
一般函数执行完,局部活动对象会被销毁。但闭包不同。
- 在createComparison()函数中,匿名函数会将包含函数的活动对象添加到它的作用域链中。
- 在匿名函数从外部函数被返回后,它的作用域链被初始化为包含外部函数的活动对象和全局变量对象
- 外部函数执行完后,其活动对象也不会销毁,因为匿名函数的作用域链仍然在引用这个活动对象(作用域链会被销毁,但活动对象仍会留在内存中)
var compareNames=createComparison("name");
var result=compareNames({name:"Nicholas"},{name:"Greg"});
//解除对匿名函数的引用(以便释放内存)
compareNames=null;
调用compareNames()过程中的作用域链:
2.1 闭包与变量
作用域链引出一个缺点:闭包只能取得包含函数中任何变量的最后一个值。(闭包保存的是整个变量对象,而不是某个特殊的值)
2.2 关于this对象
- 匿名函数的执行环境具有全局性,其this对象指向window
几种情况下,this值可能会改变
var name="the window";
var object={
name:"My object",
getName:function(){
return this.name;
}
}
object.getName(); //My object
(object.getName)(); //My object
(object.getName=object.getName)(); //the window
相当于定义了一个object.getName的变量
(object.getName=function(){
return this.name;
})()
//立即执行函数内部的this对象非严格模式下指向window
三 模仿块级作用域
- JS没有块级作用域的概念
function outputNumber(count){
for(var i=0;i<count;i++){
alert(i);
}
var i; //重新声明变量
alert(i); //计数
}
//无视后续声明
匿名函数可以模仿块级作用域避免以上问题
- 用作块级作用域的匿名函数的语法
(function(){
//块级作用域
})();
//定义了一个匿名函数,并立即调用
function(){
//块级作用域
}(); //error
JS将function关键字当作一个函数声明的开始,而函数声明后不能跟括号。
function putputNumbers(count){
(function(){
for(var i=0;i<count;i++){
alert(i);
}
})();
}
这种方法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链。
四 私有变量
私有变量:任何在函数中定义的变量,都可以认为是私有变量。
私有变量包括函数的参数,局部变量和在函数内部定义的其他函数。
公有方法:在函数内部创建一个闭包,那么闭包通过自己的作用域链可以访问私有变量。利用这一点可以创建用于访问私有变量的公有方法。
特权方法:有权访问私有变量和私有函数的方法。
在对象上创建特权方法的方式:
- 在构造函数中定义特权方法
function(){
//私有变量和私有函数
var privateVariable=10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod=function(){
privateVariable++;
return privateFunction;
}
}
利用私有和特权成员,可以隐藏不该被直接修改的数据
function Person(name){
this.getName=function(){
return name;
};
this.setName=function(){
name=value;
}
}
var person=new Person("Nicholas");
alert(person.getName()); //Nicholas
person.setName("Greg");
alert(person.getName()); //Greg
在构造函数中定义特权方法的缺点:必须使用构造函数模式达到这个目的。而对构造函数模式,,针对每个实例都会创建同样一组新方法。
使用静态私有变量可以避免该问题
4.1 静态私有变量
- 通过在私有作用域定义私有变量或函数,创建特权方法
(function(){
//私有变量和私有函数
var privateVariable=10;
function privateFunction(){
return false;
}
//构造函数
MyObject=function(){ //全局变量
};
//特权方法
MyObject.prototype.publicMethod=function(){
privateVariable++;
return privateFunction();
}
})();
与在构造函数中定义特权方法的主要区别:私有变量和函数是由实例共享的。由于特权方法是在原型上定义的,因此所有实例都使用同一个函数。这个特权函数,作为一个闭包,总是保存着对包含作用域的引用。
4.2 模块模式
单例:只有一个实例的对象
模块模式:为单例创建私有变量和特权方法。
- JS一般以对象字面量的方法创建单例对象
var singleton={
name:value,
method:function(){
}
}
- 模块模式通过为单例添加私有变量和特权方法能够使其得到增强
var singleton=function(){
//私有变量和私有函数
var privateVariable=10;
function privateVariable(){
return false;
}
//特权方法
return{ //将一个对象字面量作为函数的值返回
publicProperty:true,
piblicMethod:function(){
privateVariable++;
return privateFunction();
}
}
}
本质上:上面对象字面量定义的是单例的公共接口
一些闭包的笔试题:
function a() { //外部闭包域 ,一个名为 a 的 Function 对象
var p = 0; //私有变量 p
var innerA = function () { //内部闭包域 ,一个名为 innerA 的 Function 对象
console.log(p);
//对外部闭包域的私有变量进行了引用,故 innerA 对象的 function scope 会产生一个名为 closure 的对象属性,closure 对象内含有一个名为 p 的引用
}
innerA();//0
p++;
innerA();//1
}
a();
function a(){
var n = 0;
function inc(){
n++;
console.log(n);
}
inc();
inc();
}
a(); //1 2
function a(){
var n = 0;
this.inc = function () {
n++;
console.log(n);
};
}
var c = new a();
c.inc(); // 1
c.inc(); // 2
function a(){
var n = 0;
function inc(){
n++;
console.log(n);
}
return inc;
}
var c = a();
c(); // 1
c(); // 2
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0).fun(1);a.fun(1); a.fun(2); a.fun(3) //undefined 0 0 0
var b = fun(0).fun(1).fun(2).fun(3); //undefined 0 1 2
var c = fun(0).fun(1); c.fun(2); c.fun(3) //undefined 0 1 1