JavaScript 基础与提高

JavaScript 忍者秘籍笔记——函数是根基

2017-02-06  本文已影响41人  soojade

第三章 函数是根基

函数的独特之处

函数是第一型(first-class)对象

对象在 javascript 中有如下功能:

在 javascript 中,函数拥有全部这些功能。除了可以像其它对象类型一样使用外,函数还可以被调用。这些调用通常以异步方式进行。

浏览器的事件轮询

桌面应用程序(GUI)大多采用如下方式:

  1. 创建用户界面。
  2. 进入轮询,等待事件触发。
  3. 调用事件的处理程序(监听器[listener])。

浏览器编程唯一的不同就是:代码不负责事件轮询和事件派发,而是浏览器处理。

我们的职责是为浏览器中发生的各种时间建立事件的处理程序(handler)。这些事件在触发时被放置在一个事件队列(先进先出列表[FIFO])中,然后浏览器将调用已经为这些事件建立好的处理程序(handler)。因为这些事件发生的时间和顺序都是不可预知的,所以事件处理函数的调用也是异步的。

浏览器的事件轮询是单线程的。每个事件都是按照在队列中所放置的顺序来处理的。这就是所谓的FIFO(先进先出)列表,或者一个使用古老定时器的筒仓(silo)。每个事件都在自己的生命周期内进行处理,所有其他事件必须等到这个事件处理结束后才能继续处理。执行过程如图所示:

浏览器在单线程中处理事件轮询,并处理每个事件自身的简化视图

浏览器把事件放到队列上的机制是在事件轮询模型之外。确定事件何时发生并把它们放到事件队列上的过程所处的线程,并不参与事件本身的处理。

回调的概念

定义一个函数,以便其它一些代码在适当的时候调用它。

函数声明

声明一个函数时,该名称在整个函数声明范围内时有效的。此外,如果一个命名函数声明在顶层,window 对象上的同名属性则会引用到该函数。

所有的函数都有一个 name 属性,该属性保存的是该函数名称的字符串。没有名称的函数也仍然有 name 属性,只是该属性值为空字符串

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>证明函数声明相关内容</title>
    <style>
        li.pass{color: green;}
        li.fail{color: red;}
    </style>
</head>
<body>
    <ul id="results"></ul>

    <script>
        // 定义assert方法
        function assert(value,desc){
            var li = document.createElement('li');
            li.className = value ? "pass" : "fail";
            li.appendChild(document.createTextNode(desc));
            document.getElementById("results").appendChild(li);
        }

        // 声明一个命名函数,该名称在当前作用域有效,并隐式在window上添加一个同名属性
        function isNimble(){return true;}
        // 判断window属性是确定的
        assert(typeof window.isNimble === "function", "isNimble() defined");
        // 判断函数的 name 属性
        assert(isNimble.name === "isNimble", "isNimble() has a name");

        // 创建一个匿名函数,并赋值给canFly变量
        var canFly = function(){return true;};
        assert(typeof window.canFly === "function", "canFly() defined");
        assert(canFly.name === "", "canFly() has no name");

        // 创建一个匿名函数并引用到window的一个属性上
        window.isDeadly = function(){return true;};
        assert(typeof window.isDeadly === "function", "isDeadly() defined");

        // 在outer函数内定义一个inner函数,测试该inner()在其定义之前和之后都可以访问到,并且没有创建全局的inner()
        function outer(){
            assert(typeof inner === "function", "inner() in scope before declaration");
            function inner(){}
            assert(typeof inner === "function", "inner() in scope after declaration");
            assert(window.inner === undefined, "inner() not in global scope");
        }

        // outer()可以在全局作用域内访问到,而inner()则不可以
        outer();
        assert(window.inner === undefined, "inner() still not in global scope");

        // 这里声明的函数名无效,真正起到控制作用的是变量名
        window.wieldsSword = function swingsSword(){return true;};
        assert(window.wieldsSword.name === 'swingsSword', "wieldsSword's real name is swingsSword");
    </script>
</body>
</html>

作用域和函数

在 JavaScript 中,作用域是由 function 进行声明的,而不是代码块。声明的作用域创建于代码块,但不是终结于代码块。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>作用域断言测试</title>
    <style>
        li.pass{color: green;}
        li.fail{color: red;}
    </style>
</head>
<body>
    <ul id="results"></ul>

    <script>
        // 定义assert方法
        function assert(value,desc){
            var li = document.createElement('li');
            li.className = value ? "pass" : "fail";
            li.appendChild(document.createTextNode(desc));
            document.getElementById("results").appendChild(li);
        }

        assert(true,"|------BEFORE OUTER ------|")
        assert(typeof outer == 'function', "outer() is in scope");
        assert(typeof inner === 'function', "inner() is scope");
        assert(typeof a === 'number', "a is in scope");
        assert(typeof b === 'number', "b is in scope");
        assert(typeof c === 'number', "c is in scope");
        assert(typeof d === 'number', "d is not in scope");
        assert(typeof e === 'number', "e is not in scope");

        function outer(){
            assert(true,"|------ INSIDE OUTER,BEFORE a ------|")
            assert(typeof outer == 'function', "outer() is in scope");
            assert(typeof inner === 'function', "inner() is scope");
            assert(typeof a === 'number', "a is in scope");
            assert(typeof b === 'number', "b is in scope");
            assert(typeof c === 'number', "c is in scope");
            assert(typeof d === 'number', "d is not in scope");
            assert(typeof e === 'number', "e is not in scope");

            var a = 1;

            assert(true,"|------ INSIDE OUTER,AFTER a ------|")
            assert(typeof outer == 'function', "outer() is in scope");
            assert(typeof inner === 'function', "inner() is scope");
            assert(typeof a === 'number', "a is in scope");
            assert(typeof b === 'number', "b is in scope");
            assert(typeof c === 'number', "c is in scope");
            assert(typeof d === 'number', "d is not in scope");
            assert(typeof e === 'number', "e is not in scope");

            function inner(){/******/}
            var b = 2;

            assert(true,"|------ INSIDE OUTER,AFTER inner() AND b ------|")
            assert(typeof outer == 'function', "outer() is in scope");
            assert(typeof inner === 'function', "inner() is scope");
            assert(typeof a === 'number', "a is in scope");
            assert(typeof b === 'number', "b is in scope");
            assert(typeof c === 'number', "c is in scope");
            assert(typeof d === 'number', "d is not in scope");
            assert(typeof e === 'number', "e is not in scope");

            if(a == 1){
                assert(true,"|------ INSIDE OUTER,INSIDE if AND INSIDE if ------|")
                assert(typeof outer == 'function', "outer() is in scope");
                assert(typeof inner === 'function', "inner() is scope");
                assert(typeof a === 'number', "a is in scope");
                assert(typeof b === 'number', "b is in scope");
                assert(typeof c === 'number', "c is in scope"); // 这里c 被初始化为undefined,所有断言会失败
                var c = 3;
                // 不能再let const 声明之前调用 assert 会报错
                let d=4; // 添加 ES6 的let
                const e=5; // 添加 ES6 的const

                assert(true,"|------ INSIDE OUTER,INSIDE if AND AFTER let const ------|")
                assert(typeof outer == 'function', "outer() is in scope");
                assert(typeof inner === 'function', "inner() is scope");
                assert(typeof a === 'number', "a is in scope");
                assert(typeof b === 'number', "b is in scope");
                assert(typeof c === 'number', "c is in scope");
                assert(typeof d === 'number', "d is not in scope");
                assert(typeof e === 'number', "e is not in scope");
            }

            assert(true,"|------ INSIDE OUTER,OUTSIDE if ------|")
            assert(typeof outer == 'function', "outer() is in scope");
            assert(typeof inner === 'function', "inner() is scope");
            assert(typeof a === 'number', "a is in scope");
            assert(typeof b === 'number', "b is in scope");
            assert(typeof c === 'number', "c is in scope");
            assert(typeof d === 'number', "d is not in scope");
            assert(typeof e === 'number', "e is not in scope");
        }
        outer();
        assert(true,"|------ INSIDE OUTER,OUTSIDE if ------|")
        assert(typeof outer == 'function', "outer() is in scope");
        assert(typeof inner === 'function', "inner() is scope");
        assert(typeof a === 'number', "a is in scope");
        assert(typeof b === 'number', "b is in scope");
        assert(typeof c === 'number', "c is in scope");
        assert(typeof d === 'number', "d is not in scope");
        assert(typeof e === 'number', "e is not in scope");
    </script>
</body>
</html>

函数调用

有四种不同的方式进行函数调用,每种方式都有细微的差别(主要区别在于如何定义每种调用类型的this):

从参数到函数形参

所有的函数调用都会传递两个隐式参数:argumentsthis

下面的代码中展示了作为函数调用和作为方法调用的区别:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>函数调用和方法调用的区别</title>
    <style>
        /*定义结果样式*/
        li.pass{color: green;}
        li.fail{color: red;}
    </style>
</head>
<body>
    <!--显示测试结果-->
    <ul id="results"></ul>

    <script>
        // 定义assert方法
        function assert(value,desc){
            var li = document.createElement('li');
            li.className = value ? "pass" : "fail";
            li.appendChild(document.createTextNode(desc));
            document.getElementById("results").appendChild(li);
        }
        
        function creep(){return this;}
        assert(creep() === window, "Creeping in the window");

        var sneak = creep; // 创建变量引用creep
        assert(sneak() === window, "Sneaking in the window");

        var ninja1 = {
            skulk: creep // 创建属性引用creep
        };
        assert(ninja1.skulk() === ninja1, "The 1st ninja is skulking");

        var ninja2 = {
            skulk: creep
        };
        assert(ninja2.skulk() === ninja2, "The 2nd ninja is skulking");
    </script>
</body>
</html>

将函数作为构造器(constructor)进行调用,要在函数调用前使用 new 关键字。构造器调用时,会发生如下特殊行为:

构造器的目的是创建一个新对象并对其进行设置,然后将其作为构造器的返回值进行返回。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>使用构造器设置通用对象</title>
    <style>
        /*定义结果样式*/
        li.pass{color: green;}
        li.fail{color: red;}
    </style>
</head>
<body>
    <!--显示测试结果-->
    <ul id="results"></ul>

    <script>
        // 定义assert方法
        function assert(value,desc){
            var li = document.createElement('li');
            li.className = value ? "pass" : "fail";
            li.appendChild(document.createTextNode(desc));
            document.getElementById("results").appendChild(li);
        }

        // 声明一个构造器,在函数上下文对象上创建一个skulk属性。该属性方法又返回了上下文自身
        function Ninja(){
            this.skulk = function(){return this;};
        }

        var ninja1 = new Ninja();
        var ninja2 = new Ninja();

        assert(ninja1.skulk() === ninja1, "The 1st ninja is skulking");
        assert(ninja2.skulk() === ninja2, "The 1st ninja is skulking");
    </script>
</body>
</html>

函数和方法的命名通常以动词开头,来描述它们所做的事情,并以小写字母开头。而构造器的命名通常是由一个描述所构造对象的名词来命名,并以大写字母开头。

JavaScript 的每个函数都有 apply()call()方法,使用任何一个方法,都可以显式指定任何一个对象作为其函数上下文。

函数作为第一型对象,可以向其它任何类型的对象一样,拥有属性和方法。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>使用apply()和call()指定函数上下文</title>
    <style>
        /*定义结果样式*/
        li.pass{color: green;}
        li.fail{color: red;}
    </style>
</head>
<body>
    <!--显示测试结果-->
    <ul id="results"></ul>

    <script>
        // 定义assert方法
        function assert(value,desc){
            var li = document.createElement('li');
            li.className = value ? "pass" : "fail";
            li.appendChild(document.createTextNode(desc));
            document.getElementById("results").appendChild(li);
        }
        
      function juggle(){
          var result = 0;
          for(var n=0; n<arguments.length; n++){
              result += arguments[n];
          }
          this.result = result; // 在上下文保存结果
      }

          var ninja1 = {};
          var ninja2 = {};
          
          juggle.apply(ninja1,[1,2,3,4]);
          juggle.call(ninja2,5,6,7,8);
          
          assert(ninja1.result === 10, "juggled via apply");
          assert(ninja2.result === 26, "juggled via call");
    </script>
</body>
</html>

函数式编程和命令式编程的区别在于思维层面:函数式程序的构建块而不是命令式语句。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>构建for-each函数演示函数上下文功能</title>
    <style>
        /*定义结果样式*/
        li.pass{color: green;}
        li.fail{color: red;}
    </style>
</head>
<body>
    <!--显示测试结果-->
    <ul id="results"></ul>

    <script>
        // 定义assert方法
        function assert(value,desc){
            var li = document.createElement('li');
            li.className = value ? "pass" : "fail";
            li.appendChild(document.createTextNode(desc));
            document.getElementById("results").appendChild(li);
        }
        
      function forEach(list,callback){
          for(var n=0; n<list.length; n++){
              callback.call(list[n],n);
          }
      }

      // 创建测试对象
      var weapons = ['shuriken','katana','nunchuncks'];

      forEach(weapons,function(index){
          assert(this == weapons[index], "Got the expected value of " + weapons[index]);
      });
    </script>
</body>
</html>
上一篇下一篇

猜你喜欢

热点阅读