第七章 函数表达式

第七章 函数表达式

7.1 递归

// 递归阶乘
var factorial = (function f(num){
    if(num==1){
        return 1;
    }else{
        return num * f(num-1)
    }
})

7.2 闭包

  • 闭包和匿名函数有区别
  • 闭包是指有权访问另一个函数作用域中的变量的函数
  • [拓展]7.2 闭包

7.2.1 闭包与变量

  • 副作用:闭包只能取得包含函数中任何变量的最后一个值
function createFunctions(){
    var result = new Array();
    for(var i=0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}
var funs = createFunctions(); 
funs[0](); //10
funs[1](); //10
...
  • 改进
function createFunctions(){
    var result = new Array();
    for(var i=0; i < 10; i++){
        result[i] = function(num){
            return function(){
                return num;
            };
        }(i);
    }
    return result;
}
var funs = createFunctions(); 
funs[0](); //0
funs[1](); //1

7.2.2 关于this对象

  • 但函数作为某个对象的方法调用时,this等于那个对象

  • 不过,匿名函数的执行环境具有全局性,因此this通常指向window

  • 外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象

var name = "The Window";
var obj = {
    name: "My Obj",
    getNameFunc: function(){
        return function(){
            return this.name;  
        };
    }
}
console.log(obj.getNameFunc()()); //The Window
var name = "The Window";
var obj = {
    name: "My Obj",
    getNameFunc: function(){
        var that = this;
        return function(){
            return that.name;  
        };
    }
}
console.log(obj.getNameFunc()()); //My Obj
  • this和arguments存在同样的问题
var name = "The Window";
var obj = {
    name: "My Obj",
    getName: function(){
        return this.name;  
    }
}
obj.getName(); //My Obj
(obj.getName)(); //My Obj
(obj.getName = obj.getName)(); //The Window

7.2.3 内存泄漏

  • IE9之前浏览器垃圾回收机制使用引用计数方式(有缺陷)
  • 因此闭包在这些IE版本中,如果闭包的作用域链保存着一个HTMl元素,则该元素无法被销毁,导致内存一直被占用,成为内存泄漏
function assignHandler(){
    var element = document.getElementById("someElement");
    element.onclick = function(){
        console.log(element.id);
    };
}
// 创建了一个element元素时间处理程序的闭包,而闭包创建了循环引用,导致无法减少element的引用数。只要匿名函数存在,element的引用数至少都是1,内存永远不会被回收
  • 改进
function addignHandler(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        console.log(id);
    };
    element = null;
}

7.3 模仿块级作用域

function outputNumbers(count){
    for(var i=0; i < count; i++){
        console.log('->',i);
    }
    console.log('-->',i);
}
outputNumbers(3); 
//-> 0
//-> 1
//-> 2
//--> 3
  • 由来
    var somefunction = function(){
        //块级作用域
    };
    somefunction();
    
    //是否可以使用函数值直接取代函数名,结论:不行
    function(){
        //块级作用域
    }(); //error 因为函数声明后不能加括号
    
    //将函数声明转换成函数表达式
    (function(){
        //块级作用域
    })();
function outputNumbers(count){
    (function(){
        for(var i=0; i < count; i++){
            console.log('->',i);
        }    
    })();
    console.log('-->',i); //error
}
  • 优点:
    • 限制全局作用域中添加过多的变量和函数,适合大型应用程序
    • 减少闭包占用的内存问题,因为没有指向匿名函数的引用,只要函数执行完毕就会销毁其作用域链
(function(){
    var now = new Date();
    if(now.getMonth() == 1 && now.getDate() == 8){
        alert("新年快乐!!");
    }
})();

7.4 私有变量

  • 严格来讲,JS没有私有成员,倒是有私有变量

  • 私有变量:任何在函数中定义的变量,包括函数参数、局部变量和函数内定义的其他函数

  • 特权方法:有权访问私有变量和私有函数的公有方法

    • 创建方式1:在构造函数中定义
    function MeOjb(){
        //私有变量和私有函数
        var privateVariable = 10;
        function privateFunction(){
            return false;
        }
        //特权方法
        this.publicMethod = function(){
          privateVariable++;
          return privateFunction();
        };
    }
    
    function Person(name){
        this.name = name;
        this.getName = function(){
            return this.name;
        };
        this.setName = function(value){
            this.name = value;
        };
    }
    var person = new Person("Tom");
    console.log(person.getName()); //Tom
    person.setName("Bob");
    console.log(person.getName()); //Bob
    
    • 缺点:对于每个实例,都会创建同样一组新方法,可以使用静态私有变量解决

7.4.1 静态私有变量

  • 创建方式2:在私有作用域中定义私有变量或函数
(function(){
    var name = "";
    Person = function(value){
        name = value;
    }; //Person会变为全局变量
    Person.prototype.getName = function(){
        return name;
    };
    Person.prototype.setName = function(value){
        name = value;
    };
})();
var person1 = new Person("Tom");
console.log(person1.getName()); //Tom
person1.setName("Bob");
console.log(person1.getName()); //Bob

var person2 = new Person("Kevin");
console.log(person1.getName()); //Kevin
console.log(person2.getName()); //Kevin
  • 不足:使用闭包和私有变量 会延长作用域链查找 影响查找速度

7.4.2 模块模式

  • 单例:只有一个实例的对象
  • 模块模式:为单例创建私有变量和特权方法
  • 在需要对单例进行某些初始化,同时有需要维护其私有变量时非常有用
//例子:项目组件注册 初始化应用程序集信息
var application = function(){
    //私有变量和函数
    var components = new Array();
    //初始化
    components.push({
        version: '0.0.1'
    })
    //公共
    return {
        getComponentCount: function(){
            return components.length;
        },
        registComponentCount: function(component){
            if(typeof component == "objec"){
                component.push(component);
            }
        }
    }
}();

7.4.3 增强的模块模式

  • 模块模式的增强版
  • 适合那些单例必须是某种类型的实例
var singletom = function(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //插件对象
    var obj = new CustomType();
    //添加特权/公有属性和方法
    obj.publicProperty = true;
    obj.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    }
    //返回这个对象
    return obj;
}();

小结

  • 递归函数使用arguments.callee来递归调用自身,不要使用函数名
  • 当在函数内部创建了其他函数,就创建了闭包。闭包有权访问包含函数内部的所有变量
posted @ 2019-10-08 17:50  KevinTseng  阅读(84)  评论(0编辑  收藏  举报