zoukankan      html  css  js  c++  java
  • 函数

    原文地址:https://wangdoc.com/javascript/

    概述

    函数的声明

    function命令
    function命令声明的代码区域就是一个函数。

    function print(s) {
        console.log(s);
    }
    

    函数表达式

    var print = funtion(s) {
        console.log(s);
    };
    

    采用函数表达式声明函数时,function命令不带有函数名。如果加上函数名,该函数名只在函数内部有效,在函数外部无效。

    var print = function x() {
        console.log(typeof x);
    };
    
    x // ReferenceError: x is not defined
    
    print() // function
    

    以上写法有两个用途:

    • 可以在函数内部调用自身
    • 方便除错,除错工具显示函数调用栈时,将显示函数名,而不在显示这里是个匿名函数
      因此下面形式的函数声明也很常见
    var f = function f() {};
    

    Function构造函数

    var foo = new Function("args...", "function body");
    

    new可以省略,效果一样。但是这种方式几乎无人使用。

    函数的重复声明

    function f() {
        console.log(1);
    }
    
    f(); // 2
    
    function f() {
        console.log(2);
    }
    
    f(); // 2
    

    上面代码表明,后一次的函数声明会覆盖前一次。由于函数名的提升,前一次的声明在任何时候都是无效的

    return语句和递归

    return不是必须的,如果没有的话,不返回任何值,或者说返回undefined

    function fibonacci(num) {
        if (num === 0)  // 出口
            return 0;
        if (num === 1) // 出口
            return 1;
        return fibonacci(num -2) + fibonacci(num -1);
    }
    
    fibonacci(6); // 8
    

    以上就是一个简单的递归。

    第一等公民

    JavaScript语音将函数看作一种值,其它值(数值、字符串、布尔值等等)地位相同。凡事可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当做参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,并无特殊之处。
    JavaScript引擎将函数名视同变量名,所以采用function命令声明函数时,整改函数会想变量声明一样,被提升到代码头部。

    f();
    function f() {} // 整个是一段声明
    

    但是,如果采用赋值语句定义函数,JavaScript会报错。

    f();
    var f = function() {}; // TypeError: undefined is not a function
    

    上面等同于

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

    f只是被声明了,但是没有被赋值。因此如果同时采用function命令和赋值语句同时声明一个函数,总是采用赋值语句的定义。

    var f = function() {
        console.log("1");
    }
    
    function f() {
        console.log(2);
    }
    
    f() // 1
    

    函数的属性和方法

    name属性

    函数的name属性返回函数的名字,如果是通过变量赋值定义的函数,那么name返回变量名。

    function f1() {}
    f1.name // f1
    var f2 = function() {};
    f2.name // f2
    

    name属性的一个用处就是获取参数函数的名字。

    var myFunc = function() {};
    function test(func) {
        console.log(func.name);
    }
    test(myFunc);
    

    length属性

    函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数。
    length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的方法重载(overload)。

    toString()

    函数的toString返回一个字符串,内容是函数的源码,注释也一并返回,利用这一点,可以变相实现多行字符串。

    函数作用域

    定义

    作用域是指变量存在的范围。在ES5规范中,JavaScript只有两种作用域:全局作用域和函数作用域。ES6新增了块级作用域。

    • 函数外部声明的变量就是全局作用域(global variable),它可以在函数内部读取
    • 函数内部定义的变量,外部无法读取,称为局部变量(local variable)
      函数内部定义的变量,会在该作用域内覆盖同名全局变量。
    var v = 1;
    function f() {
        var v = 2;
        console.log(v);
    }
    
    f() // 2
    v // 1
    

    注意局部变量只能在函数内部声明,在其他区块中声明的,一律都是全局变量

    if (true) {
        var x = 5;
    }
    console.log(x); // 5
    

    函数内部的变量提升

    var命令声明的局部变量,不管在什么位置,变量声明都会被提升到函数体的头部。

    function foo(x) {
        if (x > 100) {
            var tmp = x - 100;
        }
    }
    // 等同于
    function foo(x) {
        var tmp;
        if (x > 100) {
            tmp = x - 100;
        }
    }
    

    函数本身的作用域

    函数本身也是一个值,也有自己的作用域。它的作用域就是其声明时所在的作用域,与其运行时所在的作用域无关。

    var a = 1;
    var x = function () {
        console.log(a);
    }
    function f() {
        var a = 2;
        x();
    }
    
    f() // 1
    

    上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以是1不是2。
    以下是一种常犯的错误

    var x = function() {
        console.log(a);
    }
    
    function y(f) {
        var a = 2;
        f();
    }
    
    y(x) // ReferenceError: a is not defined
    

    同样的,函数体内部声明的函数,作用域绑定函数体内部。

    function foo() {
        var x = 1;
        function bar() {
            console.log(x);
        }
        return bar;
    }
    
    var x = 2;
    var f = foo();
    foo() // 1
    

    参数

    参数的省略

    function f(a, b) {
        return a;
    }
    f(1, 2, 3) // 1
    f(1) // 1
    f() // undefined
    f.length // 2
    

    但是,没办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式的传入undefined

    传递方式

    • 原始类型(数值、字符串、布尔值),传递方式是按值传递。
    • 复合类型(数组、对象、其他函数),传递方式是引用传递。
      注意:如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值
    var obj = [1, 2, 3];
    function f(o) {
        o = [2, 3, 4];
    }
    f(obj);
    obj // [1, 2, 3]
    

    上面代码中,在函数f内部,参数对象obj被整个替换成另一个值,这时不会影响到原始值。这是因为,形参o实际上是对obj的地址的引用。重新对o赋值导致o引用了另一块地址,保存在原地址上的值不受影响。

    同名参数

    如果有同名参数,取最后出现的那个值。

    function(a, a) {
        console.log(a);
    }
    f(1, 2) // 2
    f(1) // undefined
    

    调用函数f时,没有提供第二个参数,a的取值就变成了undefined。这时需要获取第一个参数a的值,可以使用arguments对象。

    function f(a, a) {
        console.log(arguments[0]);
    }
    f(1) // 1
    

    arguments对象

    arguments提供了一种在函数内部读取所有参数的机制。
    正常模式下,arguments对象可以在运行时修改。

    var f = function(a, b) {
        arguments[0] = 3;
        arguments[1] = 2;
        return a + b;
    }
    
    function(1, 1) // 5
    

    严格模式下,arguments对象是一个只读对象,修改它是无效的,但是不会报错。注"use strict",开启严格模式
    通过argumentslength属性,可以判断函数调用时到底带几个参数。

    function f() {
        return arguments.length;
    }
    
    f(1, 2, 3)  // 3
    f() // 0
    

    需要注意的是,虽然arguments很像数组,但是它是一个对象。数组专有的方法(比如sliceforEach),不能在arguments对象上直接使用。
    如果要让arguments对象使用数组方法,需要将arguments转成数组。利用slice方法或者逐一填入新数组。

    var args = Array.prototype.slice.call(arguments);
    // or
    var args = [];
    for (var i = 0; i < arguments.length; i++) {
        args.push(arguments[i]);
    }
    

    arguments对象带有一个callee属性,返回它所对应的原函数。

    var f = function() {
        console.log(arguments.callee === f);
    }
    
    f() // true
    

    通过arguments.callee,可以达到调用函数自身的目的。这个属性在严格模式下是禁用的,所有不建议使用。

    函数的其它知识点

    闭包

    理解闭包,首先必须理解变量的作用域。前面提到,JavaScript有两种作用域:全局作用域和函数作用域。

    var n = 999;
    
    function f1() {
        console.log(n);
    }
    
    f1() // 999
    

    上面看出,函数f1可以读取全局变量n
    但是,函数外部无法访问函数内部声明的变量。如果出于种种原因,需要得到函数内的局部变量。正常情况下是办不到的,只有通过变通方法才能实现。那就是在函数内部再定义一个函数。

    function f1() {
        var n = 999;
        function f2() {
            console.log(n);
        }
    }
    

    上面代码中,函数f2在函数f1内部,所以f1内部的所有局部变量,对f2都是可见的。但是反过来,f2的局部变量对f1就是不可见的。这就是JavaScript语言特有的链式作用域结构。
    既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了么。

    function f1() {
        var n = 999;
        function f2() {
            console.log(n);
        }
        return f2;
    }
    
    var result = f1();
    result(); // 999 
    

    闭包就是函数f2,即能够读取其它函数内部变量的函数。由于JavaScript语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成定义在一个函数内部的函数。闭包最大的特点就是它记住了诞生的环境,比如f2记住了诞生的环境f1,所以f2可以得到f1的内部变量。在本质上讲,闭包就是函数内部和函数外部连接的一座桥梁。
    闭包最大用处有两个:

    • 读取函数内部的变量
    • 让这些变量始终保持在内存中,即闭包可以使它诞生的环境一直存在。
    function createIncrementor(start) {
        return function() {
            start++;
        };
    } 
    var inc = createIncrementor(5);
    inc() // 5
    inc() // 6
    inc() // 7
    

    闭包的另一个用处,是封装对象的私有属性和私有方法。

    function Person(name) {
        var _age;
        function setAge(n) {
            _age = n;
        }
    
        function getAge() {
            return _age;
        }
    
        return {
            name: name,
            getAge: getAge,
            setAge: setAge
        };
    }
    

    上面代码中,函数Person的内部变量_age,通过闭包getAgesetAge,变成了返回对象p1的私有变量。Person只能通过get和set方法访问。
    注意:外层函数都会返回新的函数,即新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大,因此不能滥用闭包,否则会造成网页性能问题

    立即调用的函数表达式

    在JavaScript中,圆括号是一种运算符,跟在函数名之后,表示调用该函数。有时我们需要在函数定义之后立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会导致语法错误。产生错误的原因是,function关键字即可以当成语句,也可以当成表达式。为了避免歧义,JavaScript引擎规定,如果function关键字出现在行首,一律解释成语句。因此JavaScript因此看到行首是function关键字,认为这一段是函数的定义,不应该以圆括号结尾,所有报错。
    解决方案:

    (function() {} ());
    (function() {})();
    

    通常情况下,只对匿名函数使用立即执行的函数表达式。它的目的有:

    • 不必为函数命名,避免了污染全局变量
    • IIFE内部形成了单独的作用域,可以封装一些外部无法读取的私有变量
    // 写法一
    var tmp = newData;
    processData(tmp);
    storeData(tmp);
    
    // 写法二
    (function () {
        var tmp = newData;
        processData(tmp);
        storeData(tmp);
    }());
    

    上面代码,写法二完全避免了污染全局变量。

    eval命令

    基本用法

    eval命令接受一个字符串作为参数,并将这个字符串当做语句执行。

    eval("var a = 1;");
    a // 1
    

    上面代码将字符串当做语句运行,生成了变量a
    如果参数字符串无法运行,就会报错。
    如果参数不是字符串,那么会原样返回。
    eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题。为了防止这种风险,JavaScript规定,在严格模式下,eval内部声明的变量,不会影响到外部作用域,但是依然会读写当前作用域的变量。

    (function () {
        "use strict";
        var foo = 1;
        eval("foo = 2");
        console.log(foo); // 2
    })();
    

    可见严格模式下,eval内部还是改写了外部变量,可见安全风险依然存在。

    eval的别名调用

    eval不利于引擎优化执行速度,更麻烦的是,下面这种情况,引擎在静态代码分析阶段,根本无法分辨执行的是eval

    var m = eval;
    m("var x = 1");
    x // 1
    

    上面代码中,变量meval的别名。静态代码分析阶段,引擎分辨不出m("var x = 1")执行的eval命令。为了保证不影响代码优化,JavaScript的标准规定,凡是使用别名执行evaleval内部一律是全局作用域
    eval的别名调用五花八门,只要不是直接调用都属于别名调用,因为引擎只能分辨eval()一种形式是直接调用。

    eval.call(null, "...")
    window.eval("...")
    (1, eval)("...")
    (eval, eval)("...")
    

    上面都属于eval别名调用,作用域都是全局作用域。

  • 相关阅读:
    -bash: fork: Cannot allocate memory 问题的处理
    Docker top 命令
    docker常见问题修复方法
    The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
    What's the difference between encoding and charset?
    hexcode of é î Latin-1 Supplement
    炉石Advanced rulebook
    炉石bug反馈
    Sidecar pattern
    SQL JOIN
  • 原文地址:https://www.cnblogs.com/chris-jichen/p/9944001.html
Copyright © 2011-2022 走看看