美文网首页
Fountion 对象

Fountion 对象

作者: 麓语 | 来源:发表于2018-08-01 14:50 被阅读0次

    上一篇:Object 对象
    下一篇:作⽤域链

    主要内容:

    • 函数也是对象
    • 函数的匿名表达式
    • 函数的参数
    • 函数名提升
    • evalFunction

    JavaScript 中, 函数是⼀等公民. 实际上所有的东⻄都需要依附于函数. 前⾯已经学习过函数的基本⽤法, 接下来详细讨论⼀下函数的更深层次的内容.

    函数也是⼀个对象

    JavaScript 中函数也是⼀种数据类型.

    function foo() {}
    console.log(typeof foo); // => function
    

    也就是说, 函数的地位与对象的地位是⼀样的, 与数字 123 , 字符串 "abc" ⼀样, 是有类型的⼀种数据. 因此也就有了函数表达式的概念 (后⾯介绍).

    Function 函数

    JavaScript 中, 有⼀个 Function 函数, 它是所有函数的类型, 每⼀个函数都是 Function 的实例. 定义⼀个函数, 除了使⽤ function 关键字定义, 同样可以使⽤ Function 来表⽰, 两种表⽰⽅法等价.

    定义 Function 的语法: var func = new Function(参数1, 参数2, ..., ⽅法体);
    在这个语法结构中, 构造函数 Function 的参数⾄少有⼀个, 参数都是字符串类型. 多个参数分别表⽰定义函数时的参数与⽅法体.

    例如, 定义⼀个加法的函数:

    function func (num1, num2) {
      return num1 + num2;
    }
    

    这个函数有两个参数, 分别是 num1 , 和 num2 . ⽅法体是 return num1 +num2; . 因此将其改写为 Function 的形式:
    var func = new Function("num1", "num2", "return num1 + num2";);
    调⽤函数直接使⽤ func 即可:
    console.log(func(1, 2)); // => 3
    可⻅使⽤⽅法与直接定义函数⼀样.

    改写下⾯函数为 Function 形式

    // 求最⼤值
    function max(num1, num2, num3) {
      var max;
      if (num1 >= num2) {
        if (num1 >= num3) {
        max = num1;
        } else {
        max = num3;
        }
      } else {
        if (num2 >= num3) {
          max = num2;
        } else {
          max = num3;
        }
      }
      return max;
    }
    

    ⾃定义函数是 Function 的对象

    可⻅ new Function(...) 后得到的是⼀个函数. 实际上, ⾃定义的每⼀个函数都是⼀个对象, 是 Function 的实例.
    JavaScript 中要判断⼀个对象是否为某个构造⽅法创建出来的, 可以使⽤instanceof 运算符, 其语法为:
    实例 instanceof 构造函数
    如果实例是被构造⽅法创建出来的, 该表达式返回 true ; 如果实例是继承⾃构造函数创建出来的对象的, 表达式返回 true . 否则返回 false

    function foo1() {}
    var o1 = new foo1();
    function foo2() {}
    function foo3() {};
    foo3.prototype = o1; // 设置原型
    var o2 = new foo3(); // o2 继承⾃ o1
    console.log(o2 instanceof foo1); // => true
    console.log(o2 instanceof foo2); // => false
    console.log(o2 instanceof foo3); // => true
    

    使⽤ instanceof 运算符, 可以验证⾃定义函数是否为 Function 的实例:

    function foo1() {}
    function foo2() {}
    console.log(foo1 instanceof Function); // => true
    console.log(foo1 instanceof Array); // => false
    console.log(foo1 instanceof foo2); // => false
    

    函数的字⾯量

    函数在使⽤的时候也可以动态的赋值, 使⽤的便是函数的字⾯量. 其语法为: function (参数, ...) { 函数体; }

    函数的字⾯量, 和数字字⾯量, 字符串字⾯量, 或对象字⾯量⼀样, 表⽰的就是函数本⾝. 使⽤的使⽤可以使⽤⼀个变量来接收, 再使⽤该变量来调⽤:

    var foo = function() {
      console.log("函数表达式");
    }; // 赋值表达式, 需要使⽤分号结束
    foo(); // 调⽤
    

    或者使⽤⾃调⽤函数:

    (function () {
      console.log("⾃调⽤函数");
    })();
    

    注意: 使⽤函数字⾯量的时候, 使⽤的是赋值语句, 因此需要使⽤分号结尾.

    Function 的原型

    类似于对象和 Object.prototype 的关系⼀样. ⾃定义函数是 Function 的实例. 因此⾃定义函数的⾮正式属性 __proto__Function.prototype 的引⽤相同:

    function foo1() {}
    var foo2 = function() {};
    console.log(foo1.__proto__ === Function.prototype); // => true
    console.log(foo2.__proto__ === Function.prototype); // => true
    

    因此 Function.prototype 就是任意函数的原型. 奇怪的是 Function 也是函数, 因此 Function.__proto__Function.prototype 引⽤也相等.
    console.log(Function.__proto__ === Function.prototype);
    也就是说任意函数都继承⾃ Function.prototype 对象.

    Function对象-图1.png
    因此如果需要给任意⼀个函数都添加⽅法, 可以使⽤下⾯语法:
    Function.prototype.showName = function() {
      console.log(/function\s(.+?)\(/.exec(this.toString())[1]);
    };
    function jkmethod() {}
    jkmethod.showName();
    

    Function.prototype 添加的任何成员, 都会被继承到任意函数中. 因此在DouglasJavaScript: The Good Parts 中有下⾯的经典代码 (后⾯解释):

    Function.prototype.method = function (name, func) {
      this.prototype[name] = func;
      return this;
    } // 任意⽅法都有 method ⽅法, 给当前⽅法的原型添加新⽅法
    

    函数参数

    函数在调⽤的时候, 为了更好的复⽤, 需要给其传递参数. 在本节中主要讨论三个话题: 形参与实参的概念, 没有函数重载, 以及 arguments 对象.

    形参与实参

    在函数的术语中有两个概念, ⼀个是形参⼀个是实参. 那么怎么理解呢? ⾸先看看形式计算的概念.
    所谓的形式计算, 就是符号计算, 不涉及具体的数据. 取⽽代之的是符号的规律与逻辑运算符. 例如中学阶段学习的代数就是典型的形式计算. 例如:
    $$ (a + b)^2 = a^2 + 2ab + b^2 \ \int_a^b f(x) \rm{d}x = \left.F(x)\right|_a^b$$
    那么将运算可以从具体的数据转到符号逻辑推导中. 将繁复的计算过程简化成⼏个简单的公式, 然后带⼊数据完成计算. 这便是形式计算.
    那么形参是什么呢? 定义⼀个函数, 例如打印⼀个数字

    function showNum(num) {
      console.log(num);
    }
    

    函数体就像数学符号表达式, 整个逻辑就是基于参数这个符号进⾏处理的⼀段逻辑. 打印的是参数 num . 也就是说整个函数体都是围绕参数这个符号来执⾏的逻辑, 不涉及到具体的参与数据 (函数体中定义的常量数据不算), 因此就是⼀个形式逻辑结构. 所以将函数定义中, 定义的参数称为形式参数.
    实际参数, 顾名思义就是实际的数据, 即具体参数运算的数据. 将函数定义中的参数称为形参, 将函数调⽤时传⼊的参数称为实际参数.

    function max(num1, num2) { // 参与函数逻辑的符号, num1, num2 是形式参数
    return num1 > num2 ? num1 : num2;
    }
    var res1 = max(12, 23); // 传⼊参与运算, 数字 12, 23 是实际参数
    var n1 = 34, n2 = 45;
    var res2 = max(n1, n2); // 传⼊参与运算, 变量 n1, n2 是实际参数
    

    没有函数重载

    C++ 中有函数重载的概念, 所谓的函数重载, 是指允许定义多个函数, 函数名相同, 但是要求参数不同. 那么在 JavaScript 中是不⽀持的.

    function foo() {
      console.log("⽆参数函数 foo");
    }
    function foo(n) {
      console.log("⼀个参数函数, 参数是 " + n);
    }
    function foo(n, m) {
      console.log("两个参数函数, 参数是 " + n + " 和 " + m);
    }
    foo();
    foo(1);
    foo(1,2);
    

    从词法分析的顺序看, 后⾯的函数会覆盖前⾯的函数.

    arguments 对象

    函数没有重载, 但是很显然, 前⾯介绍的 Function 在调⽤的时候可以有多个参数. 那么是如何实现的呢? 实际上每⼀个函数内部都有⼀个 arguments 对象. 它是⼀个像数组⼀样的对象, 描述了参数的信息与函数的信息.

    作为对象 arguments 有下⾯属性

    • length + 索引
    • callee

    arguments 是⼀个像数组的对象, 因此有 length 属性, 表⽰他⾥⾯有多少数据. 同时可以使⽤索引来访问每⼀个数据:

    function foo() {
      for (var i = 0; i < arguments.length; i++) {
        console.log(arguments[i]);
      }
    }
    foo(1,2,3);
    

    执⾏结果为

    Function对象-图2.png
    可⻅, arguments 中存储的就是传⼊的参数. 可以将其看成参数数组 (注意, 是看成数组).

    利⽤ arguments 对象, 可以判断传⼊的参数是什么, 以及传⼊的参数个数. 那么根据个数可以做出不同的判断, 完成不同的事情.
    将前⾯的 foo 函数进⾏改写, 既有

    function foo() {
      var len = arguments.length;
      switch (len) {
        case 0: 
          console.log("⽆参数函数 foo"); 
          break;
        case 1: 
          console.log("⼀个参数函数, 参数是 " + arguments[0]); 
          break
        case 2:
          console.log("两个参数函数, 参数是 " + arguments[0] + " 和 " + arguments[1]);
          break;
        default:
          console.log("参数依次是: " + [].join.call(arguments, ", "));
          break;
      }
    }
    foo();
    foo(1);
    foo(1, 2);
    foo(1, 2, 3, 4, 5, 6);
    

    例: 求多个数字的和:

    function getSum() {
      var sum = 0;
      for (var i = 0; i < arguments.length; i++) {
        sum += arguments[i];
      }
      return sum;
    }
    

    例: 简单模拟 jQuery 的 onload 与 id 选择

    function J() {
      var len = arguments.length,
      arg = arguments[0];
      if (len != 1) return;
      if (typeof (arg) === "function") {
        window.onload = arg;
      }
      if (typeof (arg) === "string") {
        return document.getElementById(arg);
      }
    }
    

    callee 是当前函数引⽤. 即在函数 foo 内部, arguments.callee 就是函数 foo 本⾝.

    function foo() {
      console.log(foo === arguments.callee);
    }
    foo(); // => true
    

    ⼀般来说⽤不着该属性, ⽽且在严格模式中, callee 是未定义. 但是使⽤它,可以⽀持匿名函数的递归.

    函数名提升

    函数有定义语法, 也有字⾯量语法. 虽然⼏乎是⼀样的, 但是还是有⼀点点的异同. 先来看⼀个案例:

    var num = 123;
    function foo() {
      console.log(num);
      var num = 456;
      console.log(num);
    }
    foo();
    

    原因是, 在 javascript 中, javascript 引擎在解释代码的时候, 会将所有的变量声明提升到最前⾯. 因此在函数 foo ⾥⾯, 代码就会变成:

    var num = 123;
    function foo() {
      var num;
      console.log(num);
      num = 456;
      console.log(num);
    }
    foo();
    

    打印未初始化的变量, 当然是 undefined . 这样虽然变量名定义在下⾯, 但是感觉好像将变量定义在前⾯⼀样. 这个的结论我们成为 "变量名提升". 同样我们说过: 函数也是⼀中数据类型, 和其他类型⼀样. 因此存储变量名提升, 也存在 函数名提升.

    var foo = function() {
      console.log("foo");
    }
    var func = function() {
      foo();
      var foo = function() {
        console.log("func.foo");
      }
    }
    func();
    

    运⾏结果是抛出异常. 原因很简单, 和变量名提升⼀样, 函数名也会提升, 修改⼀下代码:

    var foo = function() {
      console.log("foo");
    }
    var func = function() {
      var foo;
      foo();
      foo = function() {
        console.log("func.foo");
      }
    }
    func();
    

    很明显, fooundefined , ⾃然也就不能当做函数来调⽤. 所以出现错误.

    那么如果使⽤函数的声明语法呢? 那就会正常执⾏. 原因是在 javascript 引擎解析代码的时候. ⾸先会将函数的声明语法全部加载⼀遍, 因此在执⾏赋值语句的时候, 内存中早已有函数的声明了. 因此调⽤就不会出错啦.

    function foo() {
      console.log("foo");
    }
    function func() {
      foo();
      function foo() {
        console.log("func.foo");
      }
    }
    func();
    

    由于有了这些不同, 因此在实际开发的时候, 推荐将变量都写在开始的地⽅,也就是在函数的开头将变量就定义好, 类似于 C 语⾔的规定⼀样. 这个在 javascript 库中也是这么完成的, 如 jQuery 等.
    问题: 下⾯代码执⾏结果是什么

    // 1.
    var a = 123;
    function a() {
      console.log("a function");
    }
    console.log(a);
    // 2.
    b();
    function b() {
      console.log("b function");
    }
    // 3.
    c();
    var c = function () {
      console.log("c function");
    };
    

    eval 函数与 Function 函数

    javascript 有⼀个函数 eval ⾮常强⼤. 它可以动态的执⾏脚本. 将字符串作为参数传⼊, 然后函数执⾏的时候就会将这个字符串解析成脚本执⾏. 例如:
    console.log(num); // => 报错
    如果加上 eval 代码:
    eval("var num = 123;");
    console.log(num); // => 123;

    可⻅ eval 的功能⾮常强⼤. 当然也有⼈会认为这个写法完全是浪费表情. 当然实际开发中不会这么使⽤. 这⾥只是为了说明它的基本⽅法⽽已. 那么在实际开发中可以利⽤它实现⼀些较为灵活的功能. 例如绘制函数曲线. 可以根据⽤户的输⼊决定函数是什么, 然后再进⾏绘制. 再⽐如利⽤ ajax 获得数据. 那么实际开发中⼀般得到的是 json 格式的字符串, 那么完全可以使⽤该函数将字符串变成对象.

    var txt = '{name: "JK", course: "JavaScript ⾯向对象"}';
    var o = eval("(" + txt + ")");
    

    当然远远这么些, 凡是动态处理的东⻄都可以利⽤它来实现.

    但是 eval 函数也有它的弊端. 因为它会不经意的污染全局变量. 因此⼀般开发中推荐使⽤ Function 来代替 eval

    var txt = '{name: "JK", course: "JavaScript ⾯向对象"}';
    var o = (new Function("return " + txt))();
    

    上一篇:Object 对象
    下一篇:作⽤域链

    ⼩结

    • 函数也是对象, 是 Function 的实例
    • Function 既是函数, ⼜是对象
    • Function.prototype 是函数对象的原型
    • arguments 对象
    • 函数名提升
    • 使⽤ evalFunction 动态的执⾏脚本

    相关文章

      网友评论

          本文标题:Fountion 对象

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