让前端飞程序员前端面试

day6(12.21)函数表达式

2017-12-20  本文已影响21人  留白_汉服vs插画

函数表达式的特征

使用函数表达式递归

使用闭包定义私有变量


定义函数的方式有两种:函数声明,类似变量声明用var,函数声明用function,如 function fun1(arg0,arg1){ }

同样类似于变量声明提升,函数也会有函数声明提升。具体是:可以先执行函数,如fun1(),然后在定义函数function fun1(){ }. 有name属性,返回函数名。

第二种函数定义的方式是函数表达式,var fun1 = function(arg0,arg1){},这个就是看起来的常规的变量表达式赋值,因此就是函数表达式。function后面没有名字,所以也叫匿名函数或者拉姆达函数。把函数当一个值来使用的时候,都可以使用匿名函数。

表达式没有声明提前,所以先执行后定义会有错误的。匿名函数是没有name属性的。

if语句中可以有函数表达式,但是不能有函数声明,因为声明会提前,if-else中两个声明,都提前了,用哪一个呢?不同浏览器会有不同操作。大部分用else中的声明。如果是函数表达式就没有问题了。

函数可以赋值给变量,同样也可以作为另外一个函数的返回值。

递归

递归函数就是在于一个函数中,通过名字调用自身的情况下构成。如递归阶乘函数中return num*fun(num-1)

但是有一个问题,如果fun1是一个递归函数,var fun2=fun1 ; fun1=null ; 这时候执行fun2是导致错误。引用类型,fun1指向的对象已经是空了,fun2指向相同的地址,同样也是空,所以不能执行。

有两个方法解决这个问题:

arguments.callee: 定义函数中,比如递归,返回的是  return num*arguments.callee(num-1);  callee是指被调用的函数。将通过使用arguments.callee代替函数名,这种方式能保证比使用函数名保险。但是严格模式下不支持这个属性。因此还可以使用命名函数表达式达成相同的结果。如下

var fun = (function f(num){return num*f(num-1)})   ,就是通过创建了一个名为f()的命名函数表达式形式,然后将其赋值给fun。即使吧函数赋值给另一个变量,函数的名字f仍然有效,所以递归调用照样能完成。这样改变fun 为null也不会出错,严格模式下同样支持。

闭包:

有权访问另一个函数作用域中的变量的函数。创建闭包常见就是在一个函数中,创建另一个函数。比如函数fun1中有一个变量 var a,在函数fun1中定义另外一个函数fun2,因为内部函数fun2的作用域链包含了外部函数fun1()的作用域链,所以fun2可以访问fun1中的a。无论内部函数是匿名函数还是命名函数。 

一般情况下,全局执行环境始终存在,执行函数fun1的时候,作用域链有两个变量对象,本地和全局变量对象,放在scope中,scope是一个指针列表,只引用,但不包含变量对象。指向这两个变量对象,这两个变量对象又分别包含对应环境中的变量和函数。如果函数执行完了之后,局部活动对象会被销毁,只保留全局的变量对象。但是fun1中加入了访问fun1中变量a的函数fun2,成了闭包,就有所不同了。内部函数会将外部函数的变量对象添加到自己的作用域链中,如果在fun1中返回fun2,那么fun2中初始化为包含fun1函数的活动对象和全局变量对象。那么fun2就可以访问fun1中的变量了,另外由于fun2作用域链仍然在引用这个活动对象,所以fun1的活动对象不会被销毁。也就是fun2返回后,执行环境的作用域链被销毁了,但是活动对象仍然留在内存中,一直到fun2函数被销毁之后,fun1的活动对象才会被销毁。

自己的理解:闭包是为了访问外部函数的变量,所以一般要返回,比如在fun1中返回fun2,如果var fun = fun1,这时候返回的fun2的作用域链中包含fun1中的变量a,所以执行fun的时候,fun1活动对象仍在内存中,一直到fun2被销毁,fun1的活动对象才会被销毁。

由于闭包会携带包含它的函数的作用域,因此比其他函数占用更多内存。

 this对象 

 this对象时在运行时基于函数的执行环境绑定:1、在全局函数中,this指的是window;2、函数当某个对象方法使用的时候,等于这个对象;3、new一个对象的时候,this指的是新建的那个对象;4、匿名函数的执行环境具有全局性,this对象通常指向window。5、衍生:注意在外部函数中,返回一个匿名函数,这个匿名函数访问了外部函数的某变量,这时候就是闭包了。闭包里面的this指的也是window,可能不是那么容易认出。比如

 var name = “The Window”; 

var Object = {

         name:“my object”; 

         getNameFunction:function(){ 

                 return function(){ 

                         return this.name ; } } };

 alert(object.getNameFunc()()); // The Window 

返回的匿名函数中的this并不是Object,而是window,根据“this对象时在运行时基于函数的执行环境绑定”更容易理解,函数getNameFunction返回的是一个匿名函数,这个匿名函数执行的时候tihs执行执行环境,当然是window了。 

 var name = “The Window”; 

var Object = {

         name:“my object”;

         getNameFunction:function(){ 

                 var that = this; 

                 return function(){ 

                         return that.name ; } } }; 

alert(object.getNameFunc()()); // my object 

在函数返回之后,that仍然引用着object,所以调用alert(object.getNameFunc()());返回的是my object。 对象Object中的tihs指的是Object啊,但是Object函数中的this要等到执行时候才能确定。

 var name = “The Window”; 

var Object = { 

name:“my object”;

getNameFunction:function(){ return this.name ; } }; 

object.getName()返回的是my object,这里的函数没有返回,所以执行环境是object啦。 闭包可能会有内存泄漏的问题,

 function fun1(){ 

     var a = document.getElementById(“someElement”); 

     a.onclick = function(){ alert(a.id)     

 } } 

创建了一个a事件处理程序的闭包,这个闭包又创建了一个循环引用。所以a的内存无法回收,如下修改: 

 function fun1(){ 

        var a = document.getElementById(“someElement”); 

        var id = element.id;

        a.onclick = function(){ 

            alert(id) //这里的闭包就不直接引用a了。消除了循环引用 

a = null; // 包含函数中活动对象中仍然有一个对a的引用,所以设置为null。消除对DOM对象的引用。

 } 

 模仿块级作用域

没有块级作用域,所以块语句中定义的变量,实际上就是在包含函数中创建。比如

i虽然在块语句中,但是实际上是在函数中定义的。

匿名函数可以模仿块级作用域, 称作私有作用域: (function(){ //这里是块级作用域 })();

第一个括号是表达一个函数表达式,第二个是立刻执行。

推导:var someFunction = function(){ //这里是块级作用域 }; someFunction(); JavaScript 将 function 关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。因此有   (function(){ //这里是块级作用域 })();

无论在什么地方,只要临时需要一些变量,就可以使用私有作用域,例如:

function outputNumbers(count){

    (function () {

        for (var i=0; i < count; i++){ alert(i); }

})(); // 这时候i就是私有作用域里了,结束后会销毁。

alert(i); //导致一个错误! }

这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。

私有变量

任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。 私有变量包括函数的参数、局部变量和在函数内部定义的其他函数

通过闭包,有权访问私有变量和私有函数的公有方法称为特权方法。

模块模式和增强模块模式看不懂啊。

小结

使用函数表达式可以无须对函数命名, 从而实现动态编程。匿名函数,也称为拉姆达函数。函数表达式的特点。

  函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表 达式也叫做匿名函数。

  在无法确定如何引用函数的情况下,递归函数就会变得比较复杂;

  递归函数应该始终使用 arguments.callee 来递归地调用自身,不要使用函数名——函数名可能会发生变化。

当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理 如下。

  在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。

  通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。

  但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。

使用闭包可以在 JavaScript 中模仿块级作用域(JavaScript 本身没有块级作用域的概念),要点如下。

  创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。

  结果就是函数内部的所有变量都会被立即销毁——除非将某些变量赋值给了包含作用域(即外部作用域)中的变量。

闭包还可以用于在对象中创建私有变量,相关概念和要点如下。

  即使 JavaScript 中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量。

  有权访问私有变量的公有方法叫做特权方法。

  可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模块模式来实现单例的特权方法。

JavaScript 中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。不过,因为创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存。

上一篇 下一篇

猜你喜欢

热点阅读