美文网首页前端出版社Web前端之路
Javascript面向对象编程指南(四)——函数也是数据

Javascript面向对象编程指南(四)——函数也是数据

作者: 跟Y死磕 | 来源:发表于2017-02-11 15:39 被阅读35次

    函数也是数据

    在Javascript中,函数实际上也是数据。这概念对于我们日后的学习至关重要。也就是说,我们可以把一个函数赋值给一个变量。

    var f = function () {
      return 1;
    }
    

    上面这种定义方式通常被叫做函数标识记法

    function (){return 1;}是一个函数表达式。表达式可以被命名,成为命名函数表达式所以以下这种情况也是合法的。虽然我们不常常用到(在这里,myFunc是函数的名字,而不是变量)

    var f = function myFunc(){
      return 1;
    }
    

    这样看起来,似乎命名函数表达式和函数声明没有什么区别。但他们其实是不同的。两者的差别表现于它们的上下文。(以后会举例来阐明这些概念)

    如果我们对函数变量调用typeof,操作符返回的字符串将会是"function"

    所以,Javascript中的函数也是一种数据,只不过这种特殊的数据类型有两个重要的特性:

    • 它们所包含的是代码
    • 它们是可以执行的(或者说是可以调用的)

    由于函数也是赋值给变量的一种数据,所以函数的命名规则与一般变量相同——即函数名不能以数字开头,并且可以由任意的字母、数字、下划线和$组合而成。

    匿名函数

    我们可以这样定义一个函数;

    var f = function (a){
    return a;
    }
    

    通过这种方式定义的函数常被称为匿名函数(即没有名字的函数),特别是当它不被赋值给变量单独使用的时候。在这种情况下,此类函数有两种优雅的用法:

    • 你可以将匿名函数作为参数传递给其他函数,这样接收方函数就能利用我们所传递的函数来完成某些事情。
    • 你可以定义某个匿名函数来执行某些一次性任务。

    接下来,我们来看两个具体的应用示例。通过其中的细节来进一步了解匿名函数。

    回调函数

    既然函数可以与任何可以被赋值给变量的数据是相同的,那么它当然可以像其他数据那样被定义、删除、拷贝,以及当成参数传递给其他函数。

    在下面的示例中,我们定义了一个函数,这个函数有两个函数类型的参数,然后它们会分别执行这两个参数所指向的函数,并返回它们的返回值之和。

    function invokeAdd(a,b) {
      return a() + b();
    }
    

    下面我们来简单定义一下这两个参与加法运算的函数(使用函数声明模式),它们只是单纯地返回一个固定值:

    function one() {
      return 1;
    }
    
    function two() {
      return 2;
    }
    

    现在,我们只需将这两个函数传递给目标函数invokeAdd(),就可以得到执行的结果了。

    invokeAdd(one, two);  // 3
    

    事实上,我们也可以直接用匿名函数(即函数表达式)来代替one()和two(),以作为目标函数的参数,例如:

    invokeAdd(
      function () { return 1; },
      function () { return 2; }
    );
    

    当我们将函数A传递给函数B,并又B来执行A时,A就成了一个回调函数(callback function),如果这时A还是一个无名函数,我们就称它为匿名回调函数。

    那么,应该什么时候使用回调函数呢?下面我们将通过几个应用实例来示范下回调函数的优势,包括:

    • 它可以让我们在不做命名的情况下传递函数(这意味着可以节省变量的使用)
    • 我们可以将一个函数调用错做委托给另一个函数(这以为着可以节省一些代码编写工作)
    • 它们也有助于提升性能

    回调示例

    在编程过程中,我们通常需要将一个函数的返回值传递给另一个函数。在下面的例子中,我们定义了两个函数;第一个是multiplyByTwo(),该函数会通过一个循环将其所接受的三个参数分别乘以2,并一数组的形式返回结果;第二个函数addOne()只接受一个值,然后将它加1并返回。

    function multiplyByTwo(a,b,c){
      var i, arr = [];
      for(i = 0; i < 3; i++){
        arr[i] = arguments[i] * 2;
      }
      retuen arr;
    }
    
    function (a) {
      return a + 1;
    }
    

    现在我们来测试下这两个函数,结果如下:

    multiplyByTwo(1,2,3)  // [2,4,6]
    addOne(100)  // 101
    

    接下来,假设我们又三个元素,我们要实现这三个元素在两个函数之间的传递。这需要定义另一个数组,用于存储来自第一步的结果。我们先从multiplyByTwo()的调用开始。

    var myarr = [];
    myarr = multiplyByTwo(10,20,30);
    

    然后,用循环来遍历每个元素,并将它们分别传递给addOne().

    for(var i = 0; i < 3; i++){
      myarr[i] = addOne(myarr[i]);
    }
    myarr;  // [21,41,61]
    

    如你所见,这段代码可以工作,但是显然还有一定的改善空间。特别是我们这里使用了两个循环,如果数据量很大或循环操作很复杂的话,开销一定不小。因此,我们需要将他们合二为一。这就需要对multiplyByTwo()函数做一些改动,使其接受一个回调函数,并在每次迭代操作中调用它。具体如下:

    function multipslyByTwo(a,b,c,callback){
      var i, arr = [];
      for(i = 0; i < 3; i++){
        arr[i] = callback(arguments[i] * 2);
      }
      return arr;
    }
    

    函数修改完成之后,之前的工作只需要一次函数调用就够了,我们只需要像下面这样同时将初始值和会调函数传递给它:

    myarr = multipslyByTwo(1,2,3,addOne);
    // [3,5,7]
    

    同样,我们还可以用匿名函数来代替addOne(),这样做可以节省一个额外的全局变量。

    multipslyByTwo(1,2,3,function (a){
      return a + 1;
    })
    // [3,5,7]
    

    而且,使用匿名函数也更抑郁随时根据需求来调整代码。

    即时函数

    目前我们已经讨论了匿名函数在回调方面的应用。接下来,我们来看匿名函数的另一个应用示例——这种函数可以在顶以后立即调用。比如:

    (
      function () {
        alert('Bob')
      }
    ) ();
    

    这种语法看上去有点吓人,但其实很简单——我们只需要将匿名函数的定义放进一对括号中,然后再紧跟一堆括号即可。其中,第二队括号起到的“立即调用”的作用,同时它也是我们向匿名函数传递参数的地方。

    (
      function (name) {
        alert('Hello' + name + ' ! ');
      }
    ) ('dude');
    

    另外,你也可以将第一对括号闭合于第二对括号之后。这两种做法都有效。

    (
      function () {
        // dosomething
      } () )
    
    //vs
    
    (function () {
      //dosomething
    }) ()
    

    使用即时(自调)匿名函数的好处是不会产生任何全局变量,当然,缺点在于这样的函数是无法重复执行的(除非你将它放在某个循环或者其他函数中),这样也使得即时函数非常适合执行一些一次性的或初始化的任务。

    如果需要的话,即时函数也可以有返回值,虽然并不常见。

    内部(私有)函数

    函数与其他类型的值本质上是一样的,因此,没有什么理由可以阻止我们在一个函数内部定义另一个函数。

    function outer(param) {
      function inner(theinput) {
        return theinput * 2;
      }
      return 'The result is' + inner(param)
    }
    

    当我们调用全局函数outer()时,本地函数inner()也会在其内部调用。由于inner()是本地函数,它在outer()以外的地方是不可见的,所以我们也能将它称为私有函数。

    outer(2)  // The result is 4
    inner(2)  // inner is not defined
    

    使用私有函数的好处有以下几点:

    • 有助于我们确保全局名字空间的纯净性(这意味着命名冲突的机会很小)
    • 确保私有性——这使我们可以选择只讲一些必要的函数暴露给“外部世界”,而保留属于自己的函数,使他们不为该应用程序的其他部分所用。

    返回函数的函数

    正如之前所提到的,函数始终都会有一个返回值,即便不是显示返回,它也会隐式返回一个undefined。既然函数能返回一个唯一值,那么这个值也有可能是另一个函数。例如:

    function a() {
      alert('A!');
      return funtion () {
        alert('B!');
      }
    }
    

    在这个例子中,函数a()会在执行它工作(弹出A!)之后返回另一个函数。而所返回的函数又会去执行另外一些事情(弹出B!)。我们只需将返回值赋值给某个变量,然后就可以像使用一般函数那样调用它了。

    var newFunc = a();
    newFunc();
    

    上面第一行执行的是alert('A!'),第二行才是alert('B!')。

    如果你像让返回的函数立即执行,也可以不用将它赋值给变量,直接在该调用后面再加一对括号,效果是一样的:

    a() ();
    

    能重写自己的函数

    由于一个函数可以返回另一个函数,一次我们可以用新的函数来覆盖旧的。例如在之前的例子中,我们也可以通过a()的返回值来重写a()函数自己:

    a = a();
    

    当然这句话依然只执行alert('A!'),但如果我们再次调用a(),他就会执行alert('B!')了。这对于要执行某些一次性初始化工作的函数来说会非常有用。这样一来,该函数可以在第一次被调用后重写在即,从而避免了每一次调用时重复一些不必要的操作。

    在上的例子中,我们实在外面来重定义该函数的——即我们将函数返回值赋值给函数本身。但是们也可以让函数从内部重写自己。例如:

    function a(){
      alert("A!");
      a = function () {
        alert("B!")
      }
    }
    

    这样一来,当我们第一次调用该函数时,会有如下情况发生:

    • alert('A!')将会被执行(可以视之为一次性的准备操作)。
    • 全局变量a将会被重定义,并赋予新的函数。

    而如果该函数被调用的话,被执行就是alert('B!')了。

    下面我们来看一个组合型的应用示例,其中有些技术我们会在以后的章节中讨论。

    var a = (function () {
      function someSutup () {
        var setup = 'done';
      }
      function actualWork() {
        alert('worky-worky')
      }
      someSetup();
      return actualWork; 
    } () );
    

    在这个列子中有如下情况:

    • 我们使用了私有函数——someSetup()和actualWork().
    • 我们也使用了即时函数——函数a()的定义后面有一对括号,因此它会执行自调。
    • 当函数第一次被调用时,它会调用someSetup(),并返回函数变量actualWork的引用。请注意,返回值中是不带括号的,因此该结果仅仅是一个函数的引用,并不会产生函数调用。
    • 由于这里的执行语句是以var a = ...开头的,因而该自调函数所返回的值会重新赋值给a。

    好了,我们可以尝试一下以下的问题:上面的代码在以下情景中分别会alert()什么内容?

    • 当它最初被载入时。
    • 之后再次调用a()时。

    这项技术对于某些浏览器相关的操作会相当有用。因为在不同的浏览器中,实现相同任务的方法可能不同,我们都知道浏览器的特性不可能行为函数调用而发生任何改变,因此,最好的选择就是让函数根据其当前所在的浏览器来从新定义自己。这就是所谓的“浏览器兼容性探测”技术。

    相关文章

      网友评论

        本文标题:Javascript面向对象编程指南(四)——函数也是数据

        本文链接:https://www.haomeiwen.com/subject/jqngittx.html