zoukankan      html  css  js  c++  java
  • javascript函数

    假设函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。

    定义一个函数

    函数声明

    function print(){
        // ...
    }
    

    函数表达式

    var print = function (){
        // ...
    };
    

    使用Function构造函数

    var add = new Function("x","y","return (x+y)");
    // 相当于定义了例如以下函数
    // function add(x, y) {
    //   return (x+y);
    // }
    

    除了最后一个參数是add函数的“函数体”,其它參数都是add函数的參数。

    假设仅仅有一个參数,该參数就是函数体。

    Function构造函数能够不使用new命令。返回结果全然一样。


    函数名的提升

    JavaScript引擎将函数名视同变量名,所以採用function命令声明函数时。整个函数会被提升到代码头部。所以。以下的代码不会报错。

    f();
    function f(){}
    

    假设是採用赋值语句定义函数。JavaScript就会报错。

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

    当调用f的时候。f仅仅是被声明,还没有被赋值,等于undefined。所以会报错。

    假设同一时候採用function命令和赋值语句声明同一个函数。最后总是採用赋值语句的定义。

    这与变量声明提前有关。

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

    不能在条件语句中声明函数

    依据ECMAScript的规范,不得在非函数的代码块中声明函数。最常见的情况就是if和try语句。

    if (foo) {
      function x() { return; }
    }
    
    try {
      function x() {return; }
    } catch(e) {
      console.log(e);
    }
    

    上面代码分别在if代码块和try代码块中声明了两个函数,依照语言规范,这是不合法的。

    可是,实际情况是各家浏览器往往并不报错,能够执行。

    可是因为存在函数名的提升,所以在条件语句中声明函数是无效的。这是很easy出错的地方。

    if (false){
      function f(){}
    }
    
    f()
    // 不报错
    

    因为函数f的声明被提升到了if语句的前面,导致if语句无效。所以上面的代码不会报错。要达到在条件语句中定义函数的目的。仅仅有使用函数表达式。

    if (false){
      var f = function (){};
    }
    
    f()
    // undefined
    

    函数的属性和方法

    name

    name属性返回紧跟在functionkeyword之后的那个函数名

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

    length

    length属性返回函数定义中參数的个数

    function f(a,b) {}
    
    f.length
    // 2
    

    toString

    函数的toString方法返回函数的源代码


    函数作用域

    函数内部的变量提升

    与全局作用域一样。函数作用域内部也会产生“变量提升”现象。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是在函数y体外声明的,作用域绑定外层。因此找不到函数y的内部变量a。导致报错。

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

    參数

    不管提供多少个參数(或者不提供參数),JavaScript都不会报错。被省略的參数的值就变为undefined。

    须要注意的是,函数的length属性与实际传入的參数个数无关。仅仅反映定义时的參数个数。

    没有办法仅仅省略靠前的參数。而保留靠后的參数。假设一定要省略靠前的參数,仅仅有显式传入undefined。

    function f(a,b){
        return a;
    }
    
    f(,1) // error
    f(undefined,1) // undefined
    

    默认值

    通过以下的方法,能够为函数的參数设置默认值

    function f(a){
        a = a || 1;
        return a;
    }
    
    f('') // 1
    f(0) // 1
    

    这样的写法会对a进行一次布尔运算。仅仅有为true时,才会返回a。

    可是,除了undefined以外,0、空字符、null等的布尔值也是false。也就是说,在上面的函数中,不能让a等于0或空字符串,否则在明明有參数的情况下,也会返回默认值。

    function f(a){
        (a !== undefined && a != null)?

    (a = a):(a = 1); return a; } f('') // "" f(0) // 0

    传递方式

    JavaScript的函数參数传递方式仅仅有传值(passes by value),这意味着,在函数体内改动參数值,不会影响到函数外部。

    // 改动原始类型的參数值
    var p = 2; 
    
    function f(p){
        p = 3;
    }
    
    f(p);
    p // 2
    
    // 改动复合类型的參数值
    var o = [1,2,3];
    
    function f(o){
        o = [2,3,4];
    }
    
    f(o);
    o // [1, 2, 3]
    

    上面代码分成两段。分别改动原始类型的參数值和复合类型的參数值。两种情况下,函数内部改动參数值,都不会影响到函数外部。

    再接着看

    // 改动对象的属性值
    var o = { p:1 };
    
    function f(obj){
        obj.p = 2;
    }
    
    f(o);
    o.p // 2
    
    // 改动数组的属性值
    var a = [1,2,3];
    
    function f(a){
        a[0]=4;
    }
    
    f(a);
    a // [4,2,3]
    

    这是为什么呢?
    复合类型的变量存储的是地址。传给函数时。会有一个副本。在函数内部。当给变量赋值时o = [2,3,4];,原来o的地址就变成指向[2, 3, 4]了。当o[0]=4;时,此时o存储的地址并未改变。

    不仅仅是在函数传參这一方面。js就是这样的

    var arr1 = [1, 2];
    var arr2 = arr1;
    console.log(arr1); // [1, 2]
    console.log(arr2); // [1, 2]
    
    arr2[0] = 0;
    console.log(arr1); // [0, 2]
    console.log(arr2); // [0, 2]
    
    arr2 = [3, 4];
    console.log(arr1); // [1, 2]
    console.log(arr2); // [3, 4]
    

    假设须要对某个变量达到传址传递的效果,能够将它写成全局对象的属性

    var a = 1;
    
    function f(p){
        window[p]=2;
    }
    
    f('a');
    
    a // 2
    

    上面代码中,变量a本来是传值传递,可是写成window对象的属性,就达到了传址传递的效果。

    同名參数

    假设有同名的參数。则取最后出现的那个值

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

    即使后面的a没有值或被省略。也是以其为准

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

    调用函数f的时候。没有提供第二个參数,a的取值就变成了undefined。

    这时。假设要获得第一个a的值,能够使用arguments对象。

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

    arguments对象

    因为JavaScript同意函数有不定数目的參数,所以我们须要一种机制。能够在函数体内部读取全部參数。这就是arguments对象的由来。

    arguments对象包括了函数执行时的全部參数,arguments[0]就是第一个參数,arguments[1]就是第二个參数。依次类推。这个对象仅仅有在函数体内部,才干够使用。

    arguments对象除了能够读取參数,还能够为參数赋值(严格模式不同意这样的使用方法)

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

    能够通过arguments对象的length属性,推断函数调用时究竟带几个參数

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

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

    callee属性在某些时候会很实用,比方在匿名函数中通过callee来递归地调用自身

    var factorial = function (x) {
        if (x < 1) return 1;
        return x * arguments.callee(x - 1);
    }
    

    闭包

    闭包(closure)就是定义在函数体内部的函数。更理论性的表达是,闭包是函数与其生成时所在的作用域对象(scope object)的一种结合。

    闭包的特点在于,在函数外部能够读取函数的内部变量

    function f() {
        var v = 1;
    
        var c = function (){
            return v;
        };
    
        return c;
    }
    
    var o = f();
    
    o();
    // 1
    

    上面代码表示。原先在函数f外部,我们是没有办法读取内部变量v的。

    可是,借助闭包c。能够读到这个变量。

    闭包不仅能够读取函数内部变量。还能够使得内部变量记住上一次调用时的运算结果

    function createIncrementor(start) {
            return function () { 
                return start++;
            }
    }
    
    var inc = createIncrementor(5);
    
    inc() // 5
    inc() // 6
    inc() // 7
    

    马上调用的函数表达式(IIFE)

    有时。我们须要在定义函数之后,马上调用该函数。这时。你不能在函数的定义之后加上圆括号,这会产生语法错误。

    function(){ /* code */ }();
    // SyntaxError: Unexpected token (
    

    产生这个错误的原因是,Javascript引擎看到functionkeyword之后。觉得后面跟的是函数定义语句,不应该以圆括号结尾。

    解决方法就是让引擎知道,圆括号前面的部分不是函数定义语句,而是一个表达式,能够对此进行运算。

    你能够这样写:

    (function(){ /* code */ }()); 
    
    // 或者
    
    (function(){ /* code */ })();
    

    注意,上面的两种写法的结尾,都必须加上分号。

    推而广之,不论什么让解释器以表达式来处理函数定义的方法。都能产生相同的效果。比方以下三种写法。

    var i = function(){ return 10; }();
    
    true && function(){ /* code */ }();
    
    0, function(){ /* code */ }();
    

    甚至像这样写

    !function(){ /* code */ }();
    
    ~function(){ /* code */ }();
    
    -function(){ /* code */ }();
    
    +function(){ /* code */ }();
    

    newkeyword也能达到这个效果

    new function(){ /* code */ } // 注意不是Function
    
    new function(){ /* code */ }() // 仅仅有传递參数时。才须要最后那个圆括号。

    通常情况下。仅仅对匿名函数使用这样的“马上执行的函数表达式”。

    它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是IIFE内部形成了一个单独的作用域。能够封装一些外部无法读取的私有变量。


    eval

    eval命令的作用是,将字符串当作语句执行

    因为eval没有自己的作用域。都在当前作用域内执行。因此可能会改动其它外部变量的值。造成安全问题。

    此外,eval的命令字符串不会得到JavaScript引擎的优化,执行速度较慢,也是还有一个不应该使用它的理由。

    通常情况下,eval最常见的场合是解析JSON数据字符串,正确的做法是这时应该使用浏览器提供的JSON.parse方法。


    參考

    http://javascript.ruanyifeng.com/grammar/function.html


  • 相关阅读:
    RPC-Thrift(三)
    RPC-Thrift(二)
    RPC-Thrift(一)
    RPC-整体概念
    Java并发编程--ThreadPoolExecutor
    Java并发编程--Exchanger
    编译libjpeg库
    树莓派3B+ wifi 5G连接
    手动安装 pygame
    摘记 pyinstaller 使用自定义 spec
  • 原文地址:https://www.cnblogs.com/jhcelue/p/7058197.html
Copyright © 2011-2022 走看看