函数

函数就是最基本的一种代码抽象的方式

函数的声明与调用

JavaScript中声明函数的方法有两种

  • 声明一个有名称的函数,使用类似C语言的function name(arguments) {}
  • 声明一个匿名函数,再将该函数赋值给一个变量,使用var i = function (arguments) {};
    函数的调用方法与C和Python都很相似,使用name(arguments)的方式调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function add(a,b) {
    return a + b
    }
    console.log(add(4,8)) //12

    var add = function(a,b) {
    return a + b
    }
    console.log(add(4,9)) //13

    函数的参数

    JavaScript中的参数传递方式很灵活,可以使用argumentsrest关键字获取动态数量的参数
  • arguments关键字指向所有传入的参数arguments是一个对象,不能使用for of循环,且arguments的每个元素都是string类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function sum(a) {
    var s = 0
    for (var i in arguments) {
    s += i
    console.log(typeof(i))
    }
    return s
    }
    data = sum(1,2,3,4)
    console.log(data)
    /*
    string
    string
    string
    string
    00123
    */
  • rest关键字指向剩余的参数,在已经申明的参数后添加...rest,在函数中可已使用rest参数获取被声明参数以外的参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function test_rest(a,b,...rest) {
    console.log("a =",a)
    console.log("b =",b)
    console.log(rest)
    }
    test_rest(1,2,3,4,5)
    /*
    a = 1
    b = 2
    [ 3, 4, 5 ]
    */
    test_rest()
    /*
    a = undefined
    b = undefined
    []
    */

    函数与变量作用域

    函数内的变量作用域是函数的{}内,但在使用其他的带{}的语句中的var声明的变量作用域并不是在{}内而是函数内,使用let可声明语法块级{}作用域的变量。同时,const声明的常量作用也是语法块级的{}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function print_list(a) {
    for (var i in a){ //变量声明在for循环内,但是由于使用var声明,作用域为函数print_list
    var c = i
    console.log(i)
    /*
    0
    1
    2
    3
    */
    }
    console.log(i) //3
    console.log(c) //3
    }
    print_list([1,2,3,4])
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    function print_list2(a) {
    for (let i in a){ //使用let声明的变量作用域为当前的for循环
    var c = i
    console.log(i)
    /*
    0
    1
    2
    3
    */
    }
    console.log(c) //3
    console.log(i) //ReferenceError: i is not defined
    }
    // print_list2([1,2,3,4])

    function print_list3(a) {
    for (let i of a) {
    const c = 0 //const声明的常量作用域为当前for循环
    console.log(i)
    /*
    1
    2
    */
    }
    console.log(c) //ReferenceError: c is not defined
    }
    print_list3([1,2])
    函数中搜索变量的方式是先从当前作用域中搜索,若未找到,则向更高层的作用域搜索,因此,当函数内的变量名和全局变量冲突时,优先使用局部变量。全局变量绑定在window上,可以使用window.变量名直接访问。
    1
    2
    3
    4
    5
    6
    data = 10
    function test_window() {
    var data = 5
    console.log(data) //5
    }
    test_window()
    另外,JavaScript还用变量定义前提的机制
    JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量

方法

被绑定在对象里的函数称为方法,在该函数中可以使用this关键字访问本对象的属性和方法,调用的使用使用对象名.方法名()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var test = {
name:"a",
age:10,
count:0,
ageadd:function () {
this.age += 1
return this.age
},
changename:function (new_name) {
this.name += new_name
return this.name
},
test_d:function () {
this.count += 1
return this.ageadd.apply(test)
}
}
console.log(test.name,test.age) //a 10
test.ageadd()
console.log(test.name,test.age) //a 11

需要说明的this关键字,该关键字仅在使用对象名.方法名()的方式调用的时候是指向该对象的,若不是使用这种方法调用,可以使用.apply(object,[arguments]).call(object,arguments)的方式使this指向对象
1
2
3
4
5
6
7
8
func_test = test.ageadd
console.log(func_test()) //NaN
console.log(func_test.apply(test)) //12

changename_test = test.changename
console.log(changename_test("x")) //undefinedx
console.log(changename_test.apply(test,["x"])) //ax
console.log(changename_test.call(test,"x")) //axx

装饰器

装饰器是一种动态修改函数功能的方法,该函数中的test_d()函数就是一个装饰器,动态修改了ageadd()功能,增加了计数的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var test = {
name:"a",
age:10,
count:0,
ageadd:function () {
this.age += 1
return this.age
},
changename:function (new_name) {
this.name += new_name
return this.name
},
test_d:function () {
this.count += 1
return this.ageadd.apply(test)
}
}
console.log(test.count,test.age) //0 12
c = test.test_d()
console.log(test.count,test.age) //1 13

高阶函数

高阶函数就是传入函数作为参数的函数,类似于C语言中传入函数指针。如以下的例子test_adfunc(),就是传入了一个平方的函数作为参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function test_adfunc(f,...rest) {
for (let i in rest) {
console.log(f(i))
}
}

test_adfunc(function (x) {
return x * x
},1,2,3,4)
/*
0
1
4
9
*/

常见的高阶函数包括:

  • map()函数:将一个列表(也可能是其他数据结构)中的所有属性使用传入的函数处理并返回处理完后的列表,原列表不变
    1
    2
    3
    4
    5
    var test_list = [1,2,3,4,5]
    console.log(test_list.map(function (x){
    return x * 2
    })) //[ 2, 4, 6, 8, 10 ]
    console.log(test_list) //[ 1, 2, 3, 4, 5 ]
  • reduce()函数:用于迭代处理,输入有两个值,分别是上一次该函数运行的结果和本次输入的属性,同样原列表不变
    1
    2
    3
    4
    5
    console.log(test_list) //[ 1, 2, 3, 4, 5 ]
    console.log(test_list.reduce(function (x,y) {
    return x + y
    })) //15
    console.log(test_list) //[ 1, 2, 3, 4, 5 ]
  • filter()函数:用于筛选,返回值为True时保留,返回False时删除,同样不改变原列表
    1
    2
    3
    4
    5
    console.log(test_list) //[ 1, 2, 3, 4, 5 ]
    console.log(test_list.filter(function (x) {
    return x % 2 == 0
    })) //[ 2, 4 ]
    console.log(test_list) //[ 1, 2, 3, 4, 5 ]
  • sort()函数:用于排序,默认都转换为string后按ASCII码排序,可传入一个函数说明大小关系
    1
    2
    3
    4
    5
    6
    7
    8
    9
    console.log(test_list) //[ 1, 2, 3, 4, 5 ]
    console.log(test_list.sort(function (x,y) {
    if(x > y) {
    return -1
    } else {
    return 1
    }
    })) //[ 5, 4, 3, 2, 1 ]
    console.log(test_list) //[ 5, 4, 3, 2, 1 ]

    闭包

    闭包对我个人来说是一个全新的概念,在学习Python的时候就放弃了这一部分的学习。闭包通俗的来说就是在函数内定义的函数可以访问到外面那个函数里的变量,个人感觉主要作用是基于一个模板(内部函数)定值函数和封装私有变量。闭包的使用很简单,就是在定义函数内的函数的时候直接使用外部函数的变量即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function counte(start) {
    init = start
    return function () {
    init ++
    return init
    }
    }

    a = counte(10)
    console.log(a()) //11
    console.log(a()) //12
    console.log(a()) //13
    这个例子中的init变量就类似于一个类的私有变量一样,不能由外部操作,仅能通过返回的函数操作。
    需要注意的时候,使用闭包的时候要避免使用循环的变量(值自己发生变化的变量),否则可能发生一些奇怪的事情
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function test_closure2() {
    var arr = [];
    for (var i=1; i<=3; i++) {
    arr.push(function () {
    return i * i;
    });
    }
    return arr;
    }

    var results = test_closure2();
    console.log(results[0]()); // 16
    console.log(results[1]()); // 16
    console.log(results[2]()); // 16
    这个例子调用了循环变量,产生这种结果的原因是在var results = test_closure2();中函数已经被完全执行,在后面调用的时候i已经为4了,要解决这个问题,第一种方法是再声明一个立即执行且输入i为参数的函数,返回一个函数,被返回的函数调用的是立即执行的函数的变量,(function ()) ()的语法可以声明一个立即执行的匿名函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function test_closure3() {
    var arr = []
    for (var i = 1; i <= 3; i++) {
    arr.push((function (n) {
    return function () {
    return n * n
    }
    })(i))
    }
    return arr
    }
    results = test_closure3()
    console.log(results[0]()) //1
    console.log(results[1]()) //4
    console.log(results[2]()) //9
    第二种方法比较奇怪,将var i改为let i即可,原因不明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function test_closure(start) {
    var arr = []
    for(let i = start;i < start + 3;i++) {
    arr.push(function () {
    return i * i
    });
    }
    return arr
    }
    var b = test_closure(2)
    console.log(b[0]()) //4
    console.log(b[1]()) //9
    console.log(b[2]()) //16

    箭头函数

    箭头函数是一种特殊的匿名函数,相对于普通的匿名函数最大的优势在于修复了this指针乱飞的问题,调用箭头函数的时候this指向调用它的对象
    只有一句的箭头函数可用(arguments) => (func)定义,不需要{}return,若是多语句的箭头函数,需要使用(arguments) => {}定义,最后需要return
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var test_arrow = (x) => (x*x)
    console.log(test_arrow(6)) //36

    var test_arrow2 = (x) => {
    if(x == 0) {
    return true
    } else {
    return false
    }
    }
    console.log(test_arrow2(2)) //false
    同时,多输入函数也可以使用箭头函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var test_arrow3 = (x,y) => {
    console.log("x =",x)
    console.log("y =",y)
    }
    test_arrow3(2,3)
    /*
    x = 2
    y = 3
    */
    箭头函数修复了this的问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    test_noarrow_ob = {
    data:10,
    handle:function() {
    y = function () {
    this.data ++
    return this.data
    }
    return y
    }
    }
    console.log(test_noarrow_ob.handle()()) //NaN

    test_arrow_ob = {
    data:10,
    handle:function() {
    y = () => {
    this.data ++
    return this.data
    }
    return y
    }
    }
    console.log(test_arrow_ob.handle()()) //11

    生成器

    这里的生成器与Python中的生成器完全相同,使用function* ()声明函数,使用yield返回值并继续迭代,使用return返回值并停止迭代
    1
    2
    3
    4
    5
    6
    function* test_gen(a) {
    for(let i = 0; i < a; i++){
    yield i
    }
    return a //to generate done:true
    }
    使用生成器时,不建议的方法是使用.next()方法,该方法返回一个Object,分别是值和是否继续迭代
    1
    2
    3
    4
    5
    6
    x = test_gen(3)
    console.log(typeof(x)) //object
    console.log(x.next()) //{ value: 0, done: false }
    console.log(x.next()) //{ value: 1, done: false }
    console.log(x.next()) //{ value: 2, done: false }
    console.log(x.next()) //{ value: 3, done: true }
    另一种方法是建议的,使用for of循环直接迭代,与python中的for相同
    1
    2
    3
    4
    5
    6
    7
    8
    for (let i of test_gen(3)) {
    console.log(i)
    /*
    0
    1
    2
    */
    }