函数的定义方式有两种:一种是函数声明,另一种是函数表达式。
//函数声明:
function functionName(arg0, arg1, arg2){
// 函数体;
}
函数声明最重要的特征就是函数声明提升,意思就是执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。
sayHi();
function sayHi(){
alert("Hi");
}
以上例子不会报错。
// 函数表达式
var functionName = function(arg0, arg1, arg2) {
// 函数体
}
看起来像是常规的变量赋值语句,即创建一个函数并将它赋值给变量functionName。
以上创建函数的方式叫做匿名函数,也叫拉姆达函数,因为function后面没有标识符,也可以写成
var functionName = function aaa(arg0, arg1, arg2){
//函数体
}
韩式表达式与其他表达式一样,在使用前必须先赋值。
sayHi(); // 报错,函数名不存在
var sayHi(){
alert("Hi");
}
7.1 递归
递归函数是在一个函数通过名字调用自身的情况下构成的。
function factorial(num){
if(num <= 1){
return 1;
} else {
return num * factorial(num - 1);
}
}
一个问题: 先将factorial赋值给一个变量,再将factorial指向null
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)) // 报错!只有第一次anotherFactorial指向factorial,之后factorial指向null
arguments.calllee表示指向正在执行的函数的指针
function factorial(num){
if(num <= 1){
return 1;
} else {
return num * arguments.calllee(num - 1); // 替换factorial
}
}
严格模式不支持arguments.calllee,可使用命名函数方式,达到相同效果:
var factorial = (function f(num){
if(num <= 1){
return 1;
} else {
return num * f(num - 1); // f()函数表达式
}
});
7.2 闭包
闭包:有权访问另一个函数作用域中变量的函数。
创建闭包常见方式:在一个函数内部创建另一个函数
function createComparisonFunction(propertyName){
return function(object1, object2){
var val1 = object1[propertyName];
var val2 = object2[propertyName];
if(val1 < val2){
return 1;
} else {
return 0;
}
}
}
作用域链本质上是指向变量对象的指针列表,它只引用不实际包含变量对象。
一般情况下,函数执行完毕后,局部活动对象会被销毁,内存中仅保存全局作用域【全局执行环境的变量对象】。
但是闭包的情况不同。
在另一个函数内部定义的函数将包含函数(即外部函数)的活动对象添加到它的作用域链中。在createComparisonFunction()函数内部定义的匿名函数的作用域链中,实际上将会包含外部函数createComparisonFunction()的活动对象,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h96IKCPH-1615212121716)(evernotecid://41E469EA-0EFB-49ED-9229-E2CD30653BA7/appyinxiangcom/7805470/ENResource/p112)]
匿名函数从createComparisonFunction()中被返回后,作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunction()中定义的所有变量。
更为重要的,createComparisonFunction()函数执行完毕之后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。
换句话说,当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍会留在内存中;直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。
// 创建函数
var compareNames = createComparisonFunction("name");
// 调用函数
var result = compareNames({name:"Nicoles"},{name:"Greg"});
//解除对匿名函数的引用(以便释放内存)
compareNames = null;
7.2.1 闭包与变量
闭包能取得包含函数中任何变量的最后一个值。
function createFunctions(){
var result = new Array();
for(var i=0; i<10; i++){
result[i] = function(){
return i;
}
}
return result;
}
乍看似乎每个函数应该返回自己的索引值。但实际结果都返回10。
因为每个函数的作用域链中都保存着createFunctions()函数活动对象。,所以他们都是引用的同一个变量i。
通过新增立即执行匿名函数可以解决上述问题:
function createFunctions(){
var result = new Array();
for(var i=0; i<10; i++){
result[i] = function(num){
return function(){
return num;
}
}(i);
}
return result;
}
7.2.2 关于this对象
全局函数中this等于window,而当函数被作为某个对象方法调用时,this等于那个对象。
匿名函数的执行环境具有全局性,因此其this指向window。但有时,编写闭包方式不同,这一点不那么明显。
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function(){
return function(){
return this.name;
}
}
};
alert(object.getNameFunc()()); // "The Window"(非严格模式下)
函数在被调用时,都会自动取得两个特殊变量this,arguments。
内部函数在搜索这两个变量时,只会搜索到其活动的对象为止。
如果把外部作用域的this临时赋给that等变量,使得闭包能够访问得到
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;
}
}
alert(object.getName()); // "My Object"
alert((object.getName)()); // "My Object"
alert((object.getName = object.getName)()); // "The Window"
// 第三行赋值语句,this的值被改变,不再指向object
7.2.3 内存泄漏
闭包在IE9之前版本中,如果闭包的作用域链中保存着一个HTML元素,那么久意味着该元素无法被销毁。
function assignHandlee(){
var element = document.getElementById('someElement');
element.onclick = function(){
alert(element.id);
}
}
通过将element.id指向一个变量id,再闭包中引用这个变量id以消除循环引用。最后将element指向null,就能消除对DOM对象的引用,确保垃圾回收正常进行。
7.3 模仿块级作用域
匿名函数可以用来模仿块级作用域【通常称为私有作用域】并避免这个问题。
(function(){
// 这里是块级作用域
}()) // 立即执行匿名函数
7.4 私有变量
js中没有私有成员的概念,所有对象属性都是公有的。
不过,在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。
私有变量包括:
-
函数的参数
-
局部变量
-
函数内部定义的其他函数
function(num1, num2){
var sum = num1 + num2;
return sum;
}
其中,num1、num2、sum都是私有变量。
能够访问私有变量和私有属性的公有方法叫做特权方法
function MyObj(){
// 私有变量和私有函数
var privateVal = 10;
function privateFunc(){
return false;
}
// 特权方法
this.publicMethod = function(){
privateVal++;
return privateFunc();
}
}
7.4.1 静态私有变量
通过静态私有变量定义私有变量或函数,创建特权方法,不会每次创建同样的新方法。
// ... 私有变量、私有函数声明
MyObj.prototype.publicFunc = function(){
privateVal++;
return privateFunc();
}
7.4.2 模块模式
静态私有变量导致视力共享所有属性,导致实例没有自己的私有变量。
对象字面量方式创建单例对象:
var singleton = {
name: value,
method: function(){
// 这里是代码方法
}
}
模块模式通过为单例添加私有变量和特权方法使其得到增强
var singleton = function(){
// 私有变量和私有函数
var privateVal = 10;
function privateFunc(){
return false;
}
// 特权/公有方法和属性
return {
publicProperty: value,
publicMethod: function(){
privateVal++;
return privateFunc();
}
};
}();
如果必须创建一个对象,并以某些数据对其进行初始化
同时,还要公开一些能够访问这些私有数据的方法就可以使用模块模式。
7.4.3 增强的模块模式
单例必须是某种指定类型的实例,同时还必须添加某些属性和(或)方法对其加以增强。
var singleton = function(){
// 私有变量和私有函数
var privateVal = 10;
function privateFunc(){
return false;
}
// 创建对象
var obj = new CustomType();
// 特权/公有方法和属性
obj.publicProperty = true;
publicMethod: function(){
privateVal++;
return privateFunc();
};
// 返回对象
return obj;
}();