定义函数有两种方式:
1.函数声明(会发生函数声明提升,即在执行代码前会先读取函数声明)
function functionDeclaration(){
console.log("这是函数声明");
}
2.函数表达式(在这种方式下创建的函数叫做匿名函数,因为function关键字后面没有标识符)
var fcn = function(){
console.log("这是一个匿名函数");
};
【递归】(函数通过名字调用自身)
function factorial(num){
if(num<=1){
return 1;
}
else{
return num * factorial(num-1);
}
}
console.log(factorial(4)); //24
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(4)); //"TypeError: factorial is not a function"
通常使用arguments.callee 这个指向正在执行函数的指针来代替函数名,以防函数名更改。
function factorial(num){
if(num<=1){
return 1;
}
else{
return num * arguments.callee(num-1);
}
}
console.log(factorial(4)); //24
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(4)); //24
但在严格模式下,访问arguments.callee这个属性会报错,解决方案:使用命名函数表达式
//创建了f()命名函数表达式
var factorial = (function f(num){
if(num<=1){
return 1;
}
else{
return num * f(num-1);
}
});
console.log(factorial(4)); //24
【闭包】(有权访问另一个函数作用域中的变量的函数)
创建闭包的常见方法:在函数内部创建另一个函数。即在函数内部定义了其他函数时,就创建了闭包。
[个人对闭包的理解] 理解闭包本质上就是在理解作用域链,闭包之所以能够访问其外部的变量的函数,是因为闭包的作用域链中包含这些变量和函数。在后台执行环境中,闭包的作用域链包括它自己的作用域、其包含函数的作用域、全局作用域。
【TIPS】
1. 包含闭包的函数在执行完毕时,虽然其执行环境的作用域链会被销毁,但其活动对象不会被销毁,因为作为闭包的匿名函数仍然引用着这些活动对象。直到匿名函数被销毁后,其活动对象才会被销毁。
2. 闭包会携带包含它的函数的作用域,会占用更多的内存,所以要慎重使用闭包。
3. 闭包只能取得包含函数中任何变量的最后一个值。因为闭包保存的是整个变量对象,而不是某个特殊的变量。
function createFunction(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(){
return i;
};
}
return result;
}
var array = createFunction();
for(var j=0;j<10;j++){
console.log(array[j]());//输出10个10
}
function createFunction(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
var array = createFunction();
for(var j=0;j<10;j++){
console.log(array[j]()); //输出0到9
}
等同于
function createFunction(){
var result = new Array();
for(var i=0;i<10;i++){
result[i] = i;
}
return result;
}
var array = createFunction();
for(var j=0;j<10;j++){
console.log(array[j]); //输出0到9
}
【this】
- this对象是在运行时基于函数的执行环境绑定的。
- 在匿名函数中,由于匿名函数的执行环境具有全局性,因此this对象通常指向window。
- 但在使用call()或apply()改变函数执行环境的情况,this会指向其他对象。
- 或者把this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象。(对于arguments也是一样,如果想访问作用域中的arguments对象,可以把对arguments的引用保存在闭包能够访问到的变量中)
var name = "the window";
var object ={
name: "My Object",
getNameFun: function(){
//匿名函数的this对象指向window
return function(){
return this.name;
};
}
};
console.log(object.getNameFun()()); //the window
var name = "the window";
var object ={
name: "My Object",
getNameFun: function(){
var that = this;
//把this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象。
return function(){
return that.name;
};
}
};
console.log(object.getNameFun()()); //My Object
var name = "the window";
var object ={
name: "My Object",
getNameFun: function(){
return this.name;
}
};
function test(){
return this.name;
}
console.log(object.getNameFun()); //My Object
console.log((object.getNameFun)());//My Object
console.log((object.getNameFun = object.getNameFun)()); //the window
//该语句先执行赋值,再调用赋值后返回的结果。由于object.getNameFun是函数本身,等同于下面的语句,先把函数赋值给一个变量名,这时的this会发生改变,不再指向object,而是指向全局window
var t = test;
console.log(t()); //the window
【块级作用域/私有作用域】
JavaScript没有块级作用域的概念,因此在块级语句中定义的变量是存在于其包含函数中的。而匿名函数可以模仿(块级作用域/私有作用域)。
语法:
(function(){
//这里是块级作用域
})();
定义并立即执行该匿名函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。由于JavaScript会将function关键字当作函数声明的开始,但是函数声明后面是不能加圆括号立即执行的,所以将函数声明放在一对圆括号中表示它实际上是一个函数表达式。
function UsePrivateScope(count){
(function(){
//for语句是私有作用域,变量i只能在该循环语句中使用
for(var i=0;i<count;i++){
console.log(i);
}
})();
console.log("test:"+i); //ReferenceError: i is not defined
}
UsePrivateScope(3);
当需要一些临时变量时,就可以使用匿名函数模仿私有作用域。在实际开发中,通过创建私有作用域,
1. 可以避免使用过多的全局变量和函数而导致的命名冲突情况。
2. 可以减少闭包占用的内存问题,因为没有指向匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域链。
【私有变量】
任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问到这些变量。
私有变量包括:函数的参数、局部变量、函数内部定义的其他函数
由于闭包通过其作用域链可以访问到其包含函数的所有变量,因此可以利用闭包创建一个访问私有变量的公有方法。一般把这种能访问私有变量和私有函数的公有方法称为“特权方法”。
一、为自定义类型创建私有变量“特权方法”的两种方法:
1. 在构造函数中定义
function MyObject(){
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function(){
console.log(privateVariable);
return privateFunction();
}
}
var instance1 = new MyObject();
console.log(instance1.privateVariable); //undefined
console.log(instance1.privateFunction()); //TypeError: instance1.privateFunction is not a function
console.log(instance1.publicMethod()); //10 false
除了特权方法,没有其他方法可以直接访问privateVariable和privateFunction()。
【缺点】:对于每个实例都会创建同样的一组新方法,不能实现函数复用。
2. 在私有作用域中定义
(function(){
var privateVariable = 10; //私有变量
//私有函数
function privateFunction(){
return false;
}
MyObject = function(){}; //构造函数(使用函数表达式是因为函数声明只能创建局部变量,没有使用var是因为这样会使MyObject成为一个全局变量,能在私有作用域外被访问。)
//特权方法
MyObject.prototype.publicMethod = function(){
console.log(privateVariable);
return privateFunction();
}
})();
var instance1 = new MyObject();
console.log(instance1.publicMethod()); //10 false
该方法的私有变量和私有函数由实例共享,同时,其特权方法在原型上定义,因此所有实例使用同一个特权函数。
【缺点】:每个实例没有自己的私有变量。
二、为单例创建私有变量和特权方法
【单例】:只有一个实例的对象,通常都是作为全局对象存在的。惯例用对象字面量创建单例对象。
1. 作为Object的实例的单例
var singleton = function(){
var privateVariable = 10; //私有变量
//私有函数
function privateFunction(){
return false;
}
return {
//特权方法
publicMethod: function(){
console.log(privateVariable);
return privateFunction();
}
};
}();
console.log(singleton.publicMethod()); // 10 false
2. 作为特定类型的实例的单例
var singleton = function(){
var privateVariable = 10; //私有变量
//私有函数
function privateFunction(){
return false;
}
//创建特定类型的对象
var someObject = new someClass();
//特权方法
someObject.publicMethod = function(){
console.log(privateVariable);
return privateFunction();
};
//返回对象,将这个局部变量赋值给全局变量singleton
return someObject;
}();
console.log(singleton.publicMethod()); // 10 false