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

    函数定义和调用

    • 调用函数

    由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数个数和定义的参数个数不一致也没有问题,虽然函数内部并不需要这些参数:

    var abs = function (x) {
        if (x >= 0) {
            return x;
        } else {
            return -x;
        }
    };
    abs(10); // 返回10
    abs(-9); // 返回9
    abs(10, 'blablabla'); // 返回10
    abs(-9, 'haha', 'hehe', null); // 返回9
    abs(); // 返回NaN

    要避免收到undefined,可以对参数进行检查:

    function abs(x) {
        if (typeof x !== 'number') {
            throw 'Not a number';
        }
        if (x >= 0) {
            return x;
        } else {
            return -x;
        }
    }
    • arguments

     它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。

    实际上arguments最常用于判断传入参数的个数

    function foo(a, b, c) {
        if (arguments.length === 2) {
            console.log(2);
        }
        if(arguments.length ===3){
            console.log(3);
        } 
    }
    foo(1,2);
    foo(1,2,3);
    • rest

    ES6标准引入了rest参数,来获取额外的参数。

    rest参数只能写在最后,前面用...标识(扩展运算符将一个数组,变为参数序列),从运行结果可知,传入的参数先绑定ab,多余的参数以数组形式交给变量rest

    function foo(a, b, ...rest) {
        console.log('a = ' + a);
        console.log('b = ' + b);
        console.log(rest);
    }
    
    foo(1, 2, 3, 4, 5);
    // 结果:
    // a = 1
    // b = 2
    // Array [ 3, 4, 5 ]
    
    foo(1);
    // 结果:
    // a = 1
    // b = undefined
    // Array []
    • 变量提升

    'use strict';
    
    function foo() {
        var x = 'Hello, ' + y;
        console.log(x);
        var y = 'Bob';
    }
    
    foo();
    结果:Hello, undefined

    JavaScript引擎自动提升了变量y声明,但不会提升变量y的赋值。

    • 全局作用域

    window

    JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误。

    • 名字空间

    // 唯一的全局变量MYAPP:
    var MYAPP = {};
    
    // 其他变量:
    MYAPP.name = 'myapp';
    MYAPP.version = 1.0;
    
    // 其他函数:
    MYAPP.foo = function () {
        return 'foo';
    };

    把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。

    • 局部作用域

    function foo() {
        for (var i=0; i<100; i++) {
            //
        }
        i += 100; // 仍然可以引用变量i
    console.log(i);
    }
    foo();
    结果: 200

    为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量:

    function foo() {
        for (let i=0; i<100; i++) {
            //
        }
        i += 100; 
    console.log(i);
    }
    foo();
    结果:Uncaught ReferenceError: i is not defined
    • 解构赋值

    ES6解构赋值

    • 方法

    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: function () {
            var y = new Date().getFullYear();
            return y - this.birth;
        }
    };
    
    xiaoming.age; // function xiaoming.age()
    xiaoming.age(); // 今年调用是25,明年调用就变成26了

    以上代码,在一个方法内部,this是一个特殊变量,它始终指向当前对象,也就是xiaoming这个变量。

    function getAge() {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
    
    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: getAge
    };
    
    xiaoming.age(); // 25, 正常结果
    getAge(); // NaN

    以上方法,如果单独调用函数,比如getAge(),此时,该函数的this指向全局对象,也就是window。在strict模式下让函数的this指向undefined

    注意,要保证this指向正确,必须用obj.xxx()的形式调用!

    apply

    要指定函数的this指向哪个对象,可以用函数本身apply方法,

    它接收两个参数,第一个参数就是这个对象将代替Function类里this对象,第二个参数是Array,表示函数本身的参数。

    function getAge() {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
    
    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: getAge
    };
    
    xiaoming.age(); // 25
    getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空

    apply()类似的方法是call(),唯一区别是:参数列表不同

    apply()把参数打包成Array再传入;

    call()把参数按顺序传入。

    Math.max.apply(null, [3, 5, 4]); // 5
    Math.max.call(null, 3, 5, 4); // 5
    • 高阶函数

    高阶函数英文叫Higher-order function:一个函数就可以接收另一个函数作为参数

    function add(x, y, f) {
        return f(x) + f(y);
    }
    var x = add(-5, 6, Math.abs); // 11
    console.log(x);  11

    1.map/reduce

    由于map()方法定义在JavaScript的Array中,我们调用Arraymap()方法,传入我们自己的函数,就得到了一个新的Array作为结果:

    function pow(x) {
        return x * x;
    }
    var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]

    注意:map()传入的参数是pow,即函数对象本身。不是pow()。

    Array的reduce()把一个函数作用在这个Array[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算

    var arr = [1, 3, 5, 7, 9];
    arr.reduce(function (x, y) {
        return x + y;
    }); // 25
    第一步:(1,3)第二步:(4,5)第三步:(9,7)第四步:(16,9)

    要把[1, 3, 5, 7, 9]变换成整数13579,reduce()也能派上用场:

    var arr = [1, 3, 5, 7, 9];
    arr.reduce(function (x, y) {
        return x * 10 + y;
    }); // 13579filter

    2.filter

    filter也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素。

    Arrayfilter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。

    例如,在一个Array中,删掉偶数,只保留奇数,可以这么写:

    var arr = [1, 2, 4, 5, 6, 9, 10, 15];
    var r = arr.filter(function (x) {
        return x % 2 !== 0;
    });
    r; // [1, 5, 9, 15]

    3.sort

    // 看上去正常的结果:
    ['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];
    
    // apple排在了最后:
    ['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']
    
    // 无法理解的结果:
    [10, 20, 1, 2].sort(); // [1, 10, 2, 20]

    第二个排序把apple排在了最后,是因为字符串根据ASCII码进行排序,而小写字母a的ASCII码在大写字母之后。(A十进制表示65,a十进制表示97)

    第三个排序结果是什么鬼?简单的数字排序都能错?

    这是因为Arraysort()方法默认把所有元素先转换为String再排序,结果'10'排在了'2'的前面,因为字符'1'比字符'2'的ASCII码小。(0十进制表示48)

    要按数字大小排序,我们可以这么写:

    通常规定,对于两个元素xy,如果认为x < y,则返回-1,如果认为x == y,则返回0,如果认为x > y,则返回1

    var arr = [10, 20, 1, 2];
    arr.sort(function (x, y) {
        if (x < y) {
            return -1;
        }
        if (x > y) {
            return 1;
        }
        return 0;
    });
    console.log(arr); // [1, 2, 10, 20]

    最后友情提示,sort()方法会直接Array进行修改,它返回的结果仍是当前Array

    var a1 = ['B', 'A', 'C'];
    var a2 = a1.sort();
    a1; // ['A', 'B', 'C']
    a2; // ['A', 'B', 'C']
    a1 === a2; // true, a1和a2是同一对象
    • 闭包

    函数声明和函数表达式

    1、Function Declaration(函数声明)必须以“function”开头。

    ECMA 5(13.0)定义语法:

    function Identifier ( FormalParameterList[opt] ) { FunctionBody }

    函数名在自身作用域和父作用域内是可获取的

    function bar() {
     return 3;
    }
      
    bar() //3
    bar //function

    2、Function Expression(函数表达式)将函数定义为表达式语句(通常是变量赋值)的一部分。

    函数名(如果有的话)在作用域外是不可获取的

    //anonymous function expression
    var a = function() {
     return 3;
    }
      
    //named function expression
    var a = function bar() {
     return 3;
    }
      
    //self invoking function expression
    (function sayHello() {
     alert("hello!");
    })();

     小测试题目:

    function foo(){
     function bar() {
     return 3;
     }
     return bar();
     function bar() {
     return 8;
     }
    }
    alert(foo());//8
    //Function declaration和function variable(函数变量)通常会被 JavaScript 解释器移(‘hoisted')到当前作用域顶部
    
    =>
    
    function foo(){
     //define bar once
     function bar() {
     return 3;
     }
     //redefine it
     function bar() {
     return 8;
     }
     //return its invocation
     return bar(); //8
    }
    alert(foo());
    function foo(){
     var bar = function() {
     return 3;
     };
     return bar();
     var bar = function() {
     return 8;
     };
    }
    alert(foo());// 3
    //等号左边的Variable Declaration 会被提升,但是等号右边的 Assignment Expression(赋值表达式)不会
    
    =>
    
    //**Simulated processing sequence for Question 2**
    function foo(){
     //a declaration for each function expression
     var bar = undefined;
     var bar = undefined;
     //first Function Expression is executed
     bar = function() {
     return 3;
     };
     // Function created by first Function Expression is invoked
     return bar();
     // second Function Expression unreachable
    bar = function() {
     return 8;
     };
    }
    alert(foo()); //3
    alert(foo()); //3
    function foo(){
     var bar = function() {
     return 3;
     };
     return bar();
     var bar = function() {
     return 8;
     };
    }
    
    //函数声明foo()被提升了,函数表达式var bar = function() {return 8;};没有被提升
    =>
    function foo(){
     var bar = function() {
     return 3;
     };
     return bar();
     var bar = function() {
     return 8;
     };
    }
    alert(foo()); //3
    function foo(){
     return bar();
     var bar = function() {
     return 3;
     };
     var bar = function() {
     return 8;
     };
    }
    alert(foo());//Uncaught TypeError: bar is not a function
    //函数没有提升,变量有提升
    =>
    //**Simulated processing sequence for Question 4**
    function foo(){
     //a declaration for each function expression
     var bar = undefined;
     var bar = undefined;
     return bar(); //TypeError: "bar not defined"
     //neither Function Expression is reached
    }
    alert(foo());

    函数声明和函数表达式的区别:

    函数声明和函数表达式不同之处在于:

    一、Javascript引擎在解析javascript代码时会‘函数声明提升’(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,

    而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式;

    fnName();
    function fnName(){
        ...
    }
    //正常,因为‘提升'了函数声明,函数调用可在函数声明之前
    
    fnName();
    var fnName=function(){
        ...
    }
    //报错,变量fnName还未保存对函数的引用,函数调用必须在函数表达式之后

    二、函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fnName()形式调用 。

    (function(a){
        console.log(a);   
    })(123);
    //function外面加括号表示函数表达式

    函数作为返回值

    1.实现一个对Array的求和

    function sum(arr) {
        return arr.reduce(function (x, y) {
            return x + y;
        });
    }
    
    sum([1, 2, 3, 4, 5]); // 15

    2.如果不需要立刻求和,而是在后面的代码中。可以不返回求和的结果,而是返回求和的函数!

    function lazy_sum(arr) {
        var sum = function () {
            return arr.reduce(function (x, y) {
                return x + y;
            });
        }
        return sum;
    }

    3.当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

    var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
    //返回的函数并没有立刻执行,而是直到调用了f()才执行

    4.调用函数f时,才真正计算求和的结果:

    f(); // 15

    闭包的定义:在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum参数局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中。

    注意:

    当我们调用lazy_sum()时,即使传入相同的参数,每次调用都会返回一个的函数:

    f1()f2()的调用结果互不影响

    var f1 = lazy_sum([1, 2, 3, 4, 5]);
    var f2 = lazy_sum([1, 2, 3, 4, 5]);
    f1 === f2; // false

    闭包

    闭包最大用处有两个:

    1、是可以读取函数内部的变量

    //闭包概念:函数嵌套函数
    function t2() {
        var b=100;
        function t3() {
            console.log(b);
        }
        return t3();
    }
    t2(); //100
    
    在这段代码中,在函数t2内部声明的变量b本来是一个局部变量,为什么在调用时t3函数能打印出b变量的值呢:
      在上面的代码中,函数t3就被包括在函数t2内部,这时t2内部的所有局部变量,对t3都是可见的。
      但是反过来就不行,t3内部的局部变量,对t2就是不可见的。
      这就是Javascript语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。
      所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
      这就是闭包的其中一个作用,可以读取函数内部的一个变量。

    2、借助闭包,同样可以封装一个私有变量

    function create_counter(initial) {
        var x = initial || 0;
        return {
            inc: function () {
                x += 1;
                return x;
            }
        }
    }
    
    var c1 = create_counter();
    c1.inc(); // 1
    c1.inc(); // 2
    c1.inc(); // 3
    
    var c2 = create_counter(10);
    c2.inc(); // 11
    c2.inc(); // 12
    c2.inc(); // 13
    
    //在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。

    3、让这些变量的值始终保持在内存中

    返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。

    function count() {
        var arr = [];
        for (var i=1; i<=3; i++) {
            arr.push(function () {
                return i * i;
            });
        }
        return arr;
    }
    
    var results = count();
    var f1 = results[0];
    var f2 = results[1];
    var f3 = results[2];
    //结果不是1,4,9
    f1(); // 16
    f2(); // 16
    f3(); // 16
    //全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。

    返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量(以上,arr中不要引用i)。

     箭头函数

    x => x * x
    上面的箭头函数相当于:
    function (x) {
        return x * x;
    }

    箭头函数相当于匿名函数,并且简化了函数定义。

    箭头函数包含多条语句,这时候就不能省略{ ... }return

    x => {
        if (x > 0) {
            return x * x;
        }
        else {
            return - x * x;
        }
    }

    如果参数不是一个,就需要用括号()括起来:

    // 两个参数:
    (x, y) => x * x + y * y
    
    // 无参数:
    () => 3.14
    
    // 可变参数:
    (x, y, ...rest) => {
        var i, sum = x + y;
        for (i=0; i<rest.length; i++) {
            sum += rest[i];
        }
        return sum;
    }

    如果要返回一个对象,就要注意,如果是单表达式,这么写的话会报错:

    x => { foo: x }// SyntaxError:
    因为和函数体的{ ... }有语法冲突,所以要改为:
    x => ({ foo: x })// ok:

    箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。

    由于JavaScript函数对this绑定的错误处理,下面的例子无法得到预期结果:

    var obj = {
        birth: 1990,
        getAge: function () {
            var b = this.birth; // 1990
            var fn = function () {
                return new Date().getFullYear() - this.birth; // this指向window或undefined
            };
            return fn();
        }
    };
    
    obj.getAge(); //NaN
    
    //原因是this指针只在getAge方法的函数内指向obj。函数内部定义的函数,this又指向undefined了!(在非strict模式下,它重新指向全局对象window!)

    现在,箭头函数完全修复了this的指向,箭头函数中的this总是指向词法作用域,也就是外层调用者obj

    var obj = {
        birth: 1990,
        getAge: function () {
            var b = this.birth; // 1990
            var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
            return fn();
        }
    };
    obj.getAge(); // 25

    由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略:

    var obj = {
        birth: 1990,
        getAge: function (year) {
            var b = this.birth; // 1990
            var fn = (y) => y - this.birth; // this.birth仍是1990
            return fn.call({birth:2000}, year);//birth:2000被忽略
        }
    };
    obj.getAge(2015); // 25
  • 相关阅读:
    ideaj项目切换不同版本的jdk
    物理机(window)安装linux系统
    linux jar自启动
    swap扩容
    tomcat加载外部项目
    springboot2.3.2控制台输出@RequestMapping路径
    linux磁盘扩容
    springboot-easycode配置文件修改
    List
    Map HashMap跟HashTable
  • 原文地址:https://www.cnblogs.com/chrisghb8812/p/9537238.html
Copyright © 2011-2022 走看看