函数表达式
函数声明 VS 函数表达式
function functionName(arg0, arg1){ //函数体 }
函数声明具有一个重要的特性——函数声明提升,即执行代码前会先读取函数声明(意味着函数声明可以位于函数调用后面)。
var functionName = function(arg0, arg1){ //函数体 };
函数表达式是将匿名函数(又称拉姆达函数,name属性是空字符串)赋值给变量,只有在代码运行至该函数表达式时才会声明函数。在把函数当成值来使用的情况下,都可以使用匿名函数。
递归
arguments.callee是一个指向正在执行的函数的指针,在非严格模式下可以使用其实现对函数的递归调用以确保调用的正确性。
function factorial(num){
if(num <= 1) {
return 1;
}else {
return num * arguments.callee(num-1);
}
}
在严格模式下,则可使用函数表达式实现相同的效果。
var factorial = (function f(num){
if(num <= 1) {
return 1;
}else {
return num * f(num-1);
}
});
闭包
闭包是指有权访问另一个函数作用域中变量的函数。创建闭包的常见方式就是在一个函数内部创建另一个函数。在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是当函数返回一个闭包时,这个函数的作用域会一直存在到闭包被销毁为止。
function createComparisonFunction(propertyName){
return function(obj1, obj2){
//访问了外部函数的变量
var val1 = obj1[propertyName];
var val2 = obj2[propertyName];
if(val1 < val2){
return -1;
}else if(val1 > val2){
return 1;
}else{
return 0;
}
};
}
//创建函数
var compareNames = createComparisonFunction("name");
//调用函数
var result = compareNames({name: "Lou"}, {name: "V"});
//解除对匿名函数的引用(以便释放内存)
compareNames = null;
⚠️ 闭包所保存的是整个变量对象,而不是某个特殊的变量
function createArray(){
var result = new Array();
for(var i = 0; i < 5; i++){
result[i] = function(){
return i;
};
}
return result;
}
var ans = createArray();
console.log(ans); //[f, f, f, f, f]
function createArray(){
var result = new Array();
for(var i = 0; i < 5; i++){
result[i] = function(){
return i;
}();
}
return result;
}
var ans = createArray();
console.log(ans); //[0,1,2,3,4]
⚠️ 匿名函数的执行环境具有全局性,其this对象通常指向window
var name = "window";
var obj = {
name: "obj",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
console.log(obj.getNameFunc()()); //undefined
var name = "window";
var obj = {
name: "obj",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
console.log(obj.getNameFunc()()); //"obj"
console.log((obj.getNameFunc = obj.getNameFunc)()()); //undefined
块级作用域
JavaScript没有块级作用域的概念,意味着在块语句中定义的变量实际上是在包含函数中而非语句中创建的。
var func = function(){
//这里是块级作用域
};
(function(){
//这里是块级作用域
})();
以上第二种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
私有变量
class Person {
constructor(name) {
this.getName = function () {
return name;
};
this.setName = function (val) {
name = val;
};
}
}
var person = new Person("Nick");
console.log(person.getName());
person.setName("Greg");
console.log(person.getName());
私有变量name仅可通过getName()获取,仅可通过setName()修改。但构造函数模式的缺点是针对每个实例都会创建同样一组新方法,使用静态私有变量则可以避免这个问题。
(function() {
var name = "";
Person = function(val){
name = val;
};
Person.prototype.getName = function(){
return name;
};
Person.prototype.setName = function(val){
name = val;
};
})();
var person = new Person("Nick");
console.log(person.getName()); //"Nick"
person.setName("Greg");
console.log(person.getName()); //"Greg"
var bb = new Person("Lou");
console.log(bb.getName()); //"Lou"
console.log(person.getName()); //"Lou"
私有变量和函数是由实例共享的。
模块模式
道格拉斯所说的模块模式是为单例创建私有变量和特权方法。所谓单例是指只有一个实例的对象。通常,JS以对象字面量的方式来创建单例对象。
var app = function(){
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());
//公共
return {
getComponentCount: function(){
return components.length;
}
};
}();
增强模块模式
var application = function(){
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());
//局部副本
var app = new BaseComponent();
//公共
app.getComponentCount: function(){
return components.length;
};
return app;
}();
JS中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。不过,因为创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存。