美文网首页
廖雪峰的JS教程2.1-函数

廖雪峰的JS教程2.1-函数

作者: 星腾_范特西 | 来源:发表于2018-10-03 11:25 被阅读0次

    函数

    函数的定义和调用

    定义函数

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

    上述abs()函数的定义如下:

    • function指出这是一个函数定义;
    • abs是函数的名称;
    • (x)括号内列出函数的参数,多个参数以,分隔;
    • { ... }之间的代码是函数体,可以包含若干语句,甚至可以没有任何语句。

    如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined

    由于JavaScript的函数也是一个对象,上述定义的abs()函数实际上是一个函数对象,而函数名abs可以视为指向该函数的变量。

    因此,第二种定义函数的方式如下:

    var abs = function (x) {
        if (x >= 0) {
            return x;
        } else {
            return -x;
        }
    };
    

    在这种方式下,function (x) { ... }是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量abs,所以,通过变量abs就可以调用该函数。

    上述两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个;,表示赋值语句结束。

    调用函数

    调用函数时,按顺序传入参数即可:

    abs(10); //返回10
    abs(-9); //返回9
    

    传入的参数比定义的少也没有问题:

    abs(); // 返回NaN
    

    此时abs(x)函数的参数x将收到undefined,计算结果为NaN

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

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

    arguments

    JavaScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array

    利用arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值:

    function abs() {
        if (arguments.length === 0) {
            return 0;
        }
        var x = arguments[0];
        return x >= 0 ? x : -x;
    }
    
    abs(); // 0
    abs(10); // 10
    abs(-9); // 9
    

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

    rest参数

    首先看一组采用arguments的代码

    function foo(a, b) {
        var i, rest = []; //为了防止参数大于2,所以定义一个空数组
        if (arguments.length > 2) {
            for (i = 2; i<arguments.length; i++) {
                rest.push(arguments[i]);
            }
        }
        console.log('a = ' + a);
        console.log('b = ' + b);
        console.log(rest);
    }
    

    接下来,我们使用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 []
    

    变量作用域与解构赋值

    var申明的变量有作用域

    1. 如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量
    2. 如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。换句话说,不同函数内部的同名变量互相独立,互不影响
    3. 由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行
    4. 如果内部函数和外部函数的变量名重名,则内部函数的变量将“屏蔽”外部函数的变量。

    变量提升

    JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,所以我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量:

    function foo() {
        var
            x = 1, // x初始化为1
            y = x + 1, // y初始化为2
            z, i; // z和i为undefined
        // 其他语句:
        for (i=0; i<100; i++) {
            ...
        }
    }
    

    全局作用域

    不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性

    var course = 'Learn JavaScript';
    alert(course); // 'Learn JavaScript'
    alert(window.course); // 'Learn JavaScript'
    

    由于函数定义有两种方式,以变量方式var foo = function () {}定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象

    名字空间

    定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。

    减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。

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

    局部作用域

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

    function foo() {
        var sum = 0;
        for (let i=0; i<100; i++) {
            sum += i;
        }
        // SyntaxError:
        i += 1;
    }
    

    常量

    ES6标准引入了新的关键字const来定义常量,constlet都具有块级作用域:

    const PI = 3.14;
    

    解构赋值

    传统方法把一个数组的元素分别赋值给几个变量:

    var array = ['hello', 'JavaScript', 'ES6'];
    var x = array[0];
    var y = array[1];
    var z = array[2];
    

    ES6中的解构赋值,直接对多个变量同时赋值:

    var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
    

    数组有嵌套时,注意嵌套层次和位置要保持一致:

    let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
    

    如果需要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性:

    var person = {
        name: '小明',     
        age: 20,     
        gender: 'male',    
        passport: 'G-12345678',     
        school: 'No.4 middle school' 
        address: {
            city: 'Beijing',
            street: 'No.1 Road',
            zipcode: '100001'
        }
    };
    var {name, address: {city, zip}} = person;
    name; // '小明'
    city; // 'Beijing'
    zip; // undefined, 因为属性名是zipcode而不是zip
    // 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
    address; // Uncaught ReferenceError: address is not defined
    
    //如果要使用的变量名和属性名不一致时
    // 把passport属性赋值给变量id:
    let {name, passport:id} = person;
    name; // '小明'
    id; // 'G-12345678'
    // 注意: passport不是变量,而是为了让变量id获得passport属性:
    passport; // Uncaught ReferenceError: passport is not defined
    

    如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误:

    // 声明变量:
    var x, y;
    // 解构赋值:
    {x, y} = { name: '小明', x: 100, y: 200};
    // 语法错误: Uncaught SyntaxError: Unexpected token =
    

    这是因为JavaScript引擎把{开头的语句当作了块处理,于是=不再合法。解决方法是用小括号括起来:

    ({x, y} = { name: '小明', x: 100, y: 200});
    

    使用场景

    解构赋值交换变量

    var x = 1, y = 2;
    [x, y] = [y, x]
    

    解构赋值获取当前页面的域名和路径

    var { hostname:domain, pathname:path } = location;
    

    如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。例如,下面的函数可以快速创建一个Date对象:

    function buildDate({year, month, day, hour=0, minute=0, second=0}) {
        return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
    }
    //赋值
    buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });
    // Sun Jan 01 2017 20:15:00 GMT+0800 (CST)
    

    方法

    在一个对象中绑定函数,称为这个对象的方法。

    对象的定义:

    var xiaoming = {
        name: '小明',
        birth: 1990
    };
    

    如果我们给xiaoming绑定一个函数,就可以做更多的事情。比如,写个age()方法,返回xiaoming的年龄:

    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关键字。

    在上面的方法内部,this是一个特殊变量,它始终指向当前对象,也就是xiaoming这个变量。所以,this.birth可以拿到xiaomingbirth属性。

    但是有时候,this的指向对象并不是非常明确。

    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

    修复办法,用一个that变量首先捕获this

    'use strict';
    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: function () {
            var that = this;//在方法内部一开始就捕获this
            function getAgeFromBirth() {
                var y = new Date().getFullYear();
                return y - taht.birth; //用that而不是this
            }
            return getAgeFullBirth();
        }
    };
    xiaoming.age(); //25
    

    var that = this;,你就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中。

    apply

    要指定函数的this指向哪个对象,可以用函数本身的apply方法,它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数Array,表示函数本身的参数。

    apply修复getAge()调用:

    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(3, 5, 4),分别用apply()call()实现如下:

    Math.max.apply(null, [3, 5, 4]);
    Math.max.call(null, 3, 5, 4);
    

    对普通函数调用,我们通常把this绑定为null

    装饰器

    利用apply(),我们还可以动态改变函数的行为。

    JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。现在假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的parseInt()

    'use strict';
    
    var count = 0;
    var oldParseInt = parseInt; // 保存原函数
    
    window.parseInt = function () {
        count += 1;
        return oldParseInt.apply(null, arguments); // 调用原函数
    };
    
    parseInt('10');
    parseInt('20');
    parseInt('30');
    console.log('count = ' + count); // 3
    

    相关文章

      网友评论

          本文标题:廖雪峰的JS教程2.1-函数

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