函数表达式

最近一直在复习巩固知识,之前写的笔记如今也在看,为了勘误以前的理解,加深印象,把markdown上所写的又拿下来再叙述一遍,章节顺序都是按照当时看《高程》的顺序整理的,若有不对的地方还请拍砖指教,感谢!前端

========================================================================浏览器

函数表达式

递归、闭包、模仿块级做用域、私有变量安全


定义函数有两种方式,函数声明和函数表达式,函数声明的语法是:markdown

function functionName(arguments){
        //函数体;
    }

函数声明的一个重要特征就是函数声明提高,在执行代码以前,都会读取函数声明,如下的例子不会出现错误:闭包

sayHi();
    function sayHi(){
        alert("hi");
    }

函数表达式的语法是:app

var functionName = function(arguments){
        //函数体;
    }
这种状况下建立的函数是匿名函数,匿名函数的name属性是空字符串,若是以这样的方式调用会出现错误:
sayHi();//错误,函数还不存在;
    var sayHi = function(){
        alert("hi");    
    }
若是使用if...else语句判断一个条件,执行同一个functionName的函数声明,JavaScript的引擎会尝试修正错误,将其转换为合理的状态,而修正的机制不同,大多数会在condition为true时返回第一个声明,少部分会为第二个声明,所以在if...else中最好使用函数表达式,它会根据condition赋值给变量返回正确的函数结果。经过condition以后,函数赋值给变量,再选择执行函数。
var sayHi;
    if(condition){
        sayHi = function(){
            alert("Hi");
        }
    }else{
        sayHi = function(){
            alert("Yo!");
        }
    }
    sayHi();

可以建立函数赋值给变量,固然也能够把函数做为其它函数的值返回。如下:函数

function compare(propertyName){
        return function(object1,object2){
            var value1 = object1[propertyName];
            var value2 = object2[propertyName];
            if(value1 < value2){
                return -1;
            }else if(value1 > value2){
                return 1;
            }else{
                return 0;
            }
        }
    }
  • 递归
    递归函数即本身调用本身,一个阶乘函数:优化

function sum(num){
        if(num<=1){
            return 1;
        }else{
            return num * sum(num-1);
        }
    }

这样一看好像没有问题,可是若是把函数存在变量中,而后改变函数的引用为null,那么在执行函数就会出错。this

var other = sum;
    sum = null;
    other(4);//sum isn't a function;

使用callee()方法能够解耦,callee保存的是拥有这个arguments参数的函数。prototype

function sum(num){
        if(num<=1){
            return 1;
        }else{
            return num * arguments.callee(num-1);
        }
    }

而在strict模式下,callee是不可用的,而且考虑到代码安全的因素callee和caller已经被弃用了,因此尽可能不要使用这个方法。可使用函数表达式的形式,将结果赋值给变量,即便把函数赋值给了另外一个变量,函数的名字依然有效,因此递归依然能够正常运行。

var factorial = function sum(num){
        if(num<=1){
            return 1;
        }else{
            return num * sum(num-1);
        }
    }
    var other = factorial;
    factorial = null;
    other(7);//
  • ES6中关于尾递归的优化

    函数调用会在内存造成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。若是在函数A的内部调用函数B,那么在A的调用记录上方,还会造成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。若是函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。全部的调用记录,就造成一个"调用栈"-------摘自阮一峰老师的博客
function fac(n,total=1){
        if(n == 1){
            return total;
        }
        return fac(n - 1,n * total);
    }

2.闭包

执行环境定义了全部变量或函数有权访问的其余数据,决定了它们各自的行为。每一个执行环境都有与之关联的变量对象,环境中定义的因此变量和函数都保存在这个对象中。当代码在执行环境中运行时,会建立变量对象的一个做用域链(保证对执行环境有权访问的全部变量和函数的有序访问)。
    当某个函数被调用时,会建立一个执行环境及相应的做用域链,而后arguments和其余命名参数的值来初始化函数的活动对象,但在做用域链中,外部函数的活动对象处于第二位,在它以外的函数处于第三位,...直至做为做用域终点的全局执行环境。
function compare(value1,value2){
        if(value1 < value2){
            return -1;
        }else if(value1 > value2){
            return 1;
        }else{
            return 0;
        }
    }
    var result = compare(5,10);
在这段代码中,当调用compare()函数时,会建立一个包含arguments、value一、value2的活动对象,而全局执行环境的变量对象result、compare则在这个做用域链的第二位。
    每一个执行环境都有一个表示变量的对象-----变量对象,全局环境的对象会一直存在,而compare函数里的局部环境的变量对象,只在函数执行时存在,建立compare函数时,会建立一个预先包含了全局变量对象compare、result的做用域链,此后又有一个活动对象被建立并被推入做用域链的前端。compare()的活动对象是arguments[5,10],value1 : 5,value2 : 10。因此此时的做用域链包含了本地活动对象和全局变量对象。做用域链是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

通常来讲,当函数执行完毕后,局部活动对象就会销毁,内存中仅保存全局做用域,可是闭包的状况有所不一样。
在一个函数内部定义的函数,会将外部函数的活动对象添加到它的做用域链中。

function createCompare(propertyName){
        return function(object1,object2){
            var value1 = object1[propertyName];
            var value2 = object2[propertyName];
            if(value1 < value2){
                return -1;
            }else if(value1 > value2){
                return 1;
            }else{
                return 0;
            }
        }
    }
    var compare = createCompare("name");
    var result = compare({name : "Nicholas"},{name : "Greg"});
匿名函数在被返回以后,它的做用域链被初始化为外部函数的活动对象以及全局变量对象,所以它能够访问外部函数的全部变量,而且在外部函数执行完毕后,外部函数的做用域链会被销毁,可是它的活动对象仍然保存着,由于此时匿名函数还在引用这个活动对象arguments:"name",propertyName : name。若是:
compare = null;

那么这时就会解除对匿名函数的引用,以便释放内存。

1)闭包与变量
闭包的一个反作用就是只能取得外部函数中任何变量的最后一个值,如下例子能够说明:

function create(){
        var result = new Array();
        for(var i = 0;i<10;i++){
            result[i] = function(){
                return i;
            }
        }
        return result;
    }
    console.log(result);

在这个函数内部返回result时,它能够引用外部函数的活动对象以及全局变量对象,而当create()执行完以后,这个活动对象仍然在被引用,此时i已经变为了10,因此result只会返回同一个i值,即i=10;能够修改成:

function create(){
        var result = new Array();
        for(var i = 0;i<10;i++){
            result[i] = function(num){
                return function(){
                    return num;
                }
            }(i);
        }   
        return result;
    }
    console.log(create());

修改后的函数闭包获得的值并不直接赋值给result,而是经过闭包,使这个匿名函数的内部再返回一个匿名函数,这个匿名函数能够访问外部的活动对象num,再经过给内部的函数传递变量i,赋值给num,因此最后能够获得function(1)、function(2)...function(10),而且他们的内部属性[[scope]]还会包含对应的i值。

2)关于this对象
匿名函数的执行环境具备全局性,所以它的this指向通常指向window

var name = "The Window";
    var object = {
        name : "My object",
        getName : function(){
            return function(){
                return this.name;
            };
        }
    }
    alert(object.getName()());//The Window

以上例子中,匿名函数的的this指向的是window,因此获得的是"The Window",
由于this对象实际上是函数执行时的上下文,与如何定义函数并无关系,Object.getName(),指向的是Object对象,可是继续调用匿名函数,显然this指向的不是这个对象,此时是全局环境下调用的这个函数,所以this.name为"The Window".若是在定义匿名函数以前把this对象赋值给一个变量,那么调用匿名函数时会改变this的指向。

var name = "The Window";
    var object = {
        name : "My object",
        getName : function(){
            var _this = this; 
            return function(){
                return _this.name;
            };
        }
    }
    alert(object.getName()());//My object

3)内存泄漏
在IE9以前的浏览器中,IE中有一部分对象并非原生对象,其DOM、BOM中的对象就是以COM对象的形式实现的,而针对COM对象垃圾回收机制是引用计数,在闭包中很容易出现循环引用的问题,所以可能会出现内存泄漏。

function Handle(){
        var elment = document.getElementById("someElement");
        element.onclick = function(){
            alert(element.id);
        }
    }

只要匿名函数存在,对element的引用数也至少为1,所以它占用的内存永远都不会回收,能够作如下的修改:

function Handle(){
        var elment = document.getElementById("someElement");
        var id = element.id;
        element.onclick = function(){
            alert(id);
        }
        element = null;
    }

3)模仿块级做用域

JS没有块级做用域的概念,也就是说在块语句中定义的变量实际上是在包含函数中建立的。如下例子能够说明:
function outNumbers(){
        for(var i = 0;i<10;i++){
            alert(i);
        }
        var i;
        alert(i);//10
    }

在for循环中i从0-9,i=10会直接给后一个声明赋值,因此会再次计数,固然在ES6中,若是使用let或const声明,for语句就会造成块级做用域。这一节主要讨论的是利用匿名函数自执行造成块级做用域。

若是是匿名函数自执行,那么在它的内部天然就会造成块级做用域,例如:
(function(){
        //块级做用域;
    })();
若是采用函数声明的形式建立函数表达式:
var someFunction = function(){
        //块级做用域:
    };
    someFunction();

像第一个例子中建立的函数能够利用匿名函数自执行修改成:

function outNumbers(court){
        (function(){
            for(var i = 0;i<court;i++){
                alert(i);
            }   
        })();
        alert(i);//错误
    }

匿名函数自执行并不影响闭包的特性,court仍然能够做为外部函数的活动对象被引用。经过模仿块级做用域能够避免全局变量被污染以及函数的命名冲突,并且能够减小闭包占用的内存问题,由于没有指向匿名函数的引用,只要函数执行完毕,就能够当即销毁做用域链了。

4)私有变量

任何在函数中定义的变量都是私有变量,外部函数不能访问到这些变量,若是在函数内部建立一个闭包,那么闭包能够利用外部函数的活动对象,即外部函数的私有变量,利用这一点能够建立用于访问私有变量的特权方法。如下两种方式均可觉得自定义类型建立私有变量和特权方法。
    第一种是在构造函数中定义特权方法,方法以下:
function MyObject(){
        var private = 10;
        function SomeFun = {
            return false;
        }
        this.public = function(){
            alert(private++);
            return someFun();
        };
    }
    var person = new MyObject();
    console.log(person.public());
在这个方法中必需要建立构造函数,在实例上才能够调用这个特权方法从而访问私有变量,可是这样作会带来构造函数实例标识符重解析以及不一样的做用域链,这一点和构造函数模式相同。

①.静态私有变量

第二种方法为经过私有做用域定于私有变量和函数,依靠原型模式,以下:
(function(){
        var private = 10;
        function PrivateFun(){
            return false;
        }
        MyObject = function(){
        
        }
        MyObject.prototype.public = function(){
            private++;
            return PrivateFun();
        }
    })();
使用函数表达式是由于若是使用函数声明,那么function内部为块级做用域,没法实现实例化对象的目的,从而也就没法达到访问函数内部私有变量和私有函数的目的,而且在匿名函数内部的构造函数也不能声明,这样以来变量就成为了全局变量,可以在这个块级做用域以外被使用到。示例:
(function(){
        var name = "";
        Person = function(value){
            name = value;
        };
        Person.prototype.setName = function(value){
            name = value;
        };
        Person.prototype.getName = function(){
            return name;
        };
    })();
    var person1 = new Person("Nicholas");
    var person2 = new Person("Michael");
    alert(person1.getName());//Michael
    alert(person2.getName());//Michael

Person构造函数与setName()和getName()同样,均可以访问匿名函数的私有变量name,变量name就成了静态的、由全部实例共享的属性。可是因为原型链的特性,一旦更改了name属性,那么全部实例都会受到影响。

闭包和私有变量方法的一个明显不足之处就是,他们都会多查找做用域链的一个层次,这显然会在必定程度上影响查找速度。

②.模块模式

若是是只有一个实例的对象访问私有变量和函数,在必须以对象的形式建立的前提下,并且须要以某些数据初始化,同时还要公开一些可以访问这些私有数据的方法,能够采用这种方式:
var singleton = function(){
        var private = 10;
        function privateFun(){
            return false;
        }
        return{
            public : true,
            publicMethod : function(){
                private++;
                return privateFun();
            }
        }
    }();这一个()至关于  singleton();
    //当使用公有办法时,singleton.publicMethod();

为何使用函数声明?

这仅仅是一个单例,因此不能将它实例化,仅将函数的返回值以对象的形式返回给变量就可使用公有办法访问私有变量、函数。

为何在内部以对象形式返回?

若是采用this对象形式访问,这样至关于构造函数结构,而且在函数声明的前提下,this对象为window(严格模式下为undefined)。

下面的这个例子说明模块模式能够应用的场景:

var application = function(){
        //私有变量和函数
        var components = new Array();
        //初始化
        components.push(new BaseComponent());
        //公共
        return {
            getComponent : function(){
                return components.length;  
            },
            registerComponent : function(component){
                if(typeof component == "object"){
                    components.push(component);
                }
            }
        }
    }();

在这个单例的公共接口中,前者能够返回已注册的组件数目,后者用于注册新组件。

③.加强的模块模式

在返回对象以前加入对其加强的代码,单例是自定义类型的实例,同时还必须添加某些属性或方法对其增强的状况下,可使用加强模块模式。application的例子能够改写为:
var application  = function(){
        var components = new Array();
        components.push(new BaseComponent());
        //建立一个自定义类型实例,做为application的局部副本使用
        var app = new BaseComponent();
        app.getComponent = function(){
            return components.length;  
        };
        app.registerComponent = function(component){
            if(typeof component == "object"){
                components.push(component);
            }
        };
        //返回副本
       return app; 
    }();
相关文章
相关标签/搜索