JavaScript学习笔记2:函数

函数

函数定义和调用

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

// 第二种函数定义方式,末尾加;
// 定义一个匿名函数并赋值给变量
var abs2 = function (x) {
    // 对参数进行检查,避免收到undefined
    if (typeof x !== 'number') {
        throw 'Not a number';
    }
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
};

// 调用函数
abs1(10);
abs1(-9);
// 允许传入任意参数而不影响调用
abs1(10, 'blablabla'); // 返回10
abs1(-9, 'haha', 'hehe', null); // 返回9
// 不传参也没问题
// 只是参数x将收到undefined,return -x; 将返回NaN
abs1(); // 返回NaN
abs2(); // throw

// arguments只在函数内部起作用,指向函数传入的所有参数
// 类似Array但不是一个Array
// 常用于判断传入参数的个数,如foo(a[, b], c)函数,b为可选参数
// 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null:
function foo1(a, b, c) {
    if (arguments.length === 2) {
        // 实际拿到的参数是a和b,c为undefined
        c = b; // 把b赋给c
        b = null; // b变为默认值
    }
    // ...
}

foo1(1, 2, 3); // a=1 b=2 c=3
foo1(1, 3); // a=1 b=null c=3

// rest 在ES6引入,只能写在参数最后,前面用...标识
function foo2(a, b, ...rest) {
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(rest);
}

foo2(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]

foo2(1);
// 结果:
// a = 1
// b = undefined
// Array []

变量作用域与解构赋值

// var申明的变量是有作用域的

// 一个变量在函数体内部申明,则该变量的作用域为整个函数体
function foo() {
    var x = 1;
    x = x + 1;
}

// 在函数体外不可引用该变量
// x = x + 2; // ReferenceError! 无法在函数体外引用变量x

// 不同函数内部的同名变量互相独立,互不影响
function bar() {
    var x = 'A';
    x = x + 'B';
}

// 内部函数可以访问外部函数定义的变量,反之不行
// 内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量
function foo1() {
    var x = 1;
    var fooX = 1;

    function bar1() {
        var x = 2; // bar1将屏蔽外部函数的变量x
        var barY = x + fooX; // bar1可以访问foo1的变量fooX
    }

    var fooY = barY + 1; // ReferenceError! foo1不可以访问bar1的变量barY
}

// 函数内定义变量会提升到函数顶部
function foo2() {
    var x = 'Hello, ' + y;
    console.log(x); // Hello, undefined
    var y = 'Bob';
}

// 在函数内部定义变量需要遵守"首先申明所有变量原则"
function foo3() {
    var
        x = 1, // x初始化为1
        y = x + 1, // y初始化为2
        z, i; // z和i为undefined
    // 其他语句:
    for (i = 0; i < 100; i++) {
    }
}

// 不在任何函数内定义的变量就具有全局作用域(全局对象window)
// 全局作用域的变量实际上被绑定到window中
function foo4() {
    console.log('foo');
}

foo4(); // 直接调用foo()
// window.foo4(); // 通过window.foo()调用

// 把自己的变量全部放入自定义的名字空间中,会大大减少全局变量冲突的可能
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
    return 'foo';
};

// 局部作用域
function foo5() {
    // 使用var关键字无法在for循环等语句块中定义局部作用域的变量
    for (var i1 = 0; i1 < 100; i1++) {
        //
    }
    i1 += 100; // 仍然可以引用变量i

    // ES6引入的let关键字可以申明一个块级作用域
    var sum = 0;
    for (let i2 = 0; i < 100; i++) {
        sum += i2;
    }
    // SyntaxError:
    i2 += 1;
}

// 常量
// 在ES6之前通常用全部大写的变量来表示“这是一个常量,不要修改它的值”
var PI1 = 3.14;
// ES6引入了新的关键字const来定义常量,const与let都具有块级作用域
const PI2 = 3.14;
// PI2 = 3; // 某些浏览器不报错,但是无效果!
PI2; // 3.14


// 解构赋值
// ES6引入了结构赋值,可以同时最一组变量进行赋值
// 对数组元素进行解构赋值时,多个变量要用[...]括起来
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// 对对象进行解构赋值时,多个变量要用{...}括起来
// passport: id 表示把passport属性赋值给变量id
// single = true 表示如果对象没有single属性则默认赋值true
var {name, age, passport: id, single = true} = {
    name: '小明',
    age: 20,
    passport: 'G-12345678',
};

// 解构赋值使用场景
// 交换变量
var x=1, y=2;
[x, y] = [y, x]
// 获取当前页面的域名和路径
var {hostname:domain, pathname:path} = location;
// 使用对象作为参数的函数,可以使用解构赋值
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 });

方法

// 绑定到对象上的函数称为方法
var xiaoming = {
    name: '小明',
    birth: 1990,
    age1: function () {
        var y = new Date().getFullYear();
        // this是一个特殊变量,它始终指向当前对象(xiaoming)
        return y - this.birth;
    },
    age2: getAge
};

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

xiaoming.age1; // function xiaoming.age()
xiaoming.age1(); // 今年调用是32,明年调用就变成33了
xiaoming.age2(); // 32

// 单独调用getAge()函数,this指向全局对象
getAge(); // NaN
// 使用函数本身的apply或call方法,指定函数的this指向哪个对象
// apply第一个参数是需要绑定的this变量,第二个参数是数组,表示调用函数时需要传递的参数
getAge.apply(xiaoming, [1, 2, 3]); // 32, this指向xiaoming, 参数为空
// call与array的区别
// apply把参数打包成Array再传入
// call把参数按顺序传入
getAge.call(xiaoming, 1, 2, 3);

// 装饰器
// 使用apply可以动态改变函数的行为
var count = 0;
var oldParseInt = parseInt; // 保存原函数
parseInt = function () { // 统计parseInt函数调用了多少次
    count += 1;
    return oldParseInt.apply(null, arguments); // 调用原函数
};
parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); // 3

高阶函数

// 高阶函数表示一个函数可以接收另一个函数作为参数
function add(x, y, f) {
    return f(x) + f(y);
}

// Math.abs(-5) + Math.abs(6)
var x = add(-5, 6, Math.abs); // 11
console.log(x);

// map 将自定义函数作用到数组中每个值上, 返回新数组
var arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var result1 = arr1.map(function (x) {
    return x * x;
});
result1; // [1, 4, 9, 16, 25, 36, 49, 64, 81]

// reduce 累计运算
var arr2 = [1, 3, 5, 7, 9];
var result2 = arr2.reduce(function (x, y) {
    return x + y;
});
result2; // 25

// filter 筛选出数组中符合条件的元素, 返回新数组
var arr3 = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
var result3 = arr3.filter(function (e, i, self) {
    // 保留当前索引与indexOf相等的元素, 后续重复元素不保留 (indexOf总是返回第一个元素位置)
    return self.indexOf(e) === i;
});
result3; // [ 'apple', 'strawberry', 'banana', 'pear', 'orange' ]

// sort 直接对原数组进行排序
var arr4 = [10, 20, 1, 2];
arr4.sort(function (x, y) {
    if (x < y) {
        return -1;
    }
    if (x > y) {
        return 1;
    }
    return 0;
});
arr4; // [1, 2, 10, 20]

// every 判断数组的所有元素是否满足测试条件
var arr = ['Apple', 'pear', 'orange'];
var result5 = arr.every(function (s) {
    return s.length > 0;
});
result5; // true

// find 查找符合条件的第一个元素,找到返回这个元素,否则返回undefined
var result6 = arr.find(function (s) {
    return s.length > 0;
});
result6; // Apple

// findIndex 与find类似, 找到返回索引, 否则返回-1
var result7 = arr.findIndex(function (s) {
    return s.length > 10;
});
result7; // -1

// forEach 常用于遍历数组
arr.forEach(function (e) {
    console.log(e);
});

闭包

// 闭包
function lazy_sum(arr) {
    // 在函数lazy_sum中定义函数sum
    var sum = function () {
        // 内部函数sum可以引用外部函数lazy_sum的参数和局部变量
        return arr.reduce(function (x, y) {
            return x + y;
        });
    }
    // 返回sum时,相关参数和变量都保存在返回的函数中
    return sum;
}

var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
f();

// 返回函数不要引用任何循环变量,或者后续会发生变化的变量
// 否则执行函数时,变量已经循环到最后一位
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];
f1(); // 16
f2(); // 16
f3(); // 16

// 一定要引用循环变量则再创建一个函数,用该函数的参数绑定循环变量当前的值
function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

// 通过闭包把多参数函数变成单参数的函数
function make_pow(n) {
    return function (x) {
        return Math.pow(x, n);
    }
}
// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

console.log(pow2(5)); // 25
console.log(pow3(7)); // 343

箭头函数

// ES6标准新增了一种新的函数:Arrow Function(箭头函数)
// 单表达式,可以省略return
x => x * x;
// 多表达式,不能省略{}和return
x => {
    if (x > 0) {
        return x * x;
    } else {
        return -x * x;
    }
};
// 参数不是一个就用括号括起来
// 多个参数
(x, y) => x * x + y * y;
// 无参数
() => 3.14;
// 单表达式返回对象要加花括号
x => ({ foo: x });

// 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this
var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        // 这里箭头函数中的this继承getAge: function ()内的this
        var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
        return fn();
    }
};
console.log(obj.getAge());; // 32
console.log()

generator

// generator(生成器)是ES6标准引入的新的数据类型
// 一个generator看上去像一个函数,但可以返回多次
function* foo(x) {
    yield x + 1;
    yield x + 2;
    return x + 3;
}

// 调用foo(0)仅仅是创建了一个generator对象
var f = foo(0);
// 使用next()方法执行generator的代码
// 返回的value就是实际的返回值,done表示generator是否已经结束
f.next(); // { value: 1, done: false }
f.next(); // { value: 2, done: false }
f.next(); // { value: 3, done: true }
f.next(); // { value: undefined, done: true }

// 使用for of循环迭代generator对象
// 当next方法返回的对象的done属性为true,循环就会终止
for (var x of foo(0)) {
    // 由于执行到return语句时,done属性就变为true,循环终止了
    // 因此不会输出return语句的值
    console.log(x); // 依次输出1, 2
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值