JavaScript学习笔记(二) 函数

在 JavaScript 中函数是一种特殊的对象类型

1、函数定义

(1)认识函数字面量

函数字面量是定义函数的最简单、最直接的方式,它一般包含四个部分:

  • 关键字 function
  • 函数名称:函数名称用于标识函数,但是它也可以省略
  • 包含在圆括号中的一组参数:参数定义为函数中的变量,在函数调用时初始化为实际提供的值
  • 包含在花括号中的一组语句:语句是函数的主体,它们在函数被调用时执行

(2)定义函数

应用函数字面量,在 JavaScript 中提供两种方式定义函数:

  • 函数声明语句:函数声明语句是函数字面量的简单应用,实际上也是我们定义函数比较常用的方式
// 定义一个计算两数相加的函数
function add(a, b) {
    return a + b
}

// 定义一个计算阶乘的递归函数
function factorial(x) {
    return (x <= 1) ? 1 : x * factorial(x - 1)
}
  • 函数定义表达式:在 JavaScript 中,函数也是一个对象,所以我们可以把函数的定义赋值给一个变量
// 定义一个计算两数相加的函数,为了使得代码结构更加紧凑,这里的函数字面量一般省略函数名称
var add = function(a, b) {
    return a + b
}

// 定义一个计算阶乘的递归函数,为了方便函数调用自身,这里的函数字面量需要加上函数名称
var factorial = function fact(x) {
    return (x <= 1) ? 1 : x * fact(x - 1)
}

(3)对比两种定义函数的方式

两种定义函数的方式(函数声明语句和函数定义表达式)有什么不同呢?

  • 声明时机

    函数声明语句只能出现在全局代码或内嵌在其它函数中,不能出现在条件判断等语句中

    而函数定义表达式可以出现在代码中的任意地方

  • 调用时机

    以函数声明语句定义的函数,在编译时被提前到函数作用域的顶部,所以它可以在定义之前被调用

    对于函数定义表达式,虽然变量的声明会提前,但是变量的赋值不会提前,所以只能在函数定义后才能调用

2、函数调用

(1)隐式参数

在函数调用时,除了显式传入函数的实参,每个函数还会接受两个隐式参数:this 和 arguments

参数 this 的值取决于函数的调用模式;而参数 arguments 可以用于获取传入函数的所有实参

(2)函数调用方式与 this 取值

调用 JavaScript 函数常见的有三种方式,分别是函数调用、方法调用以及构造函数调用

  • 作为函数调用

最简单的一种情况,函数作为一个普通函数被调用,此时 this 被绑定到全局对象

> // 定义一个普通函数
> var add = function(a, b) {
    // 此时 this 被绑定到全局对象(在这里可以输出 this 观察一下)
    // this 的取值可能根据 JavaScript 运行环境的不同而不同
    return a + b
}
> // add 函数作为函数被调用,this 被绑定到全局对象
> // 函数调用方式:function(argument, ...)
> add(1, 1)

实际上,在函数作为普通函数被调用时,this 被绑定到全局对象是一个不太合理的设计

在《JavaScript语言精髓》一书中甚至称其为 “语言设计上的一个错误”,因为这样子容易造成全局对象的污染

  • 作为方法调用

当函数作为对象的一个属性存在时,它被称为方法,此时 this 被绑定到该对象

> // 定义一个 counter 对象
> var counter = {
    value: 0,
    // 定义 increment 函数作为方法(counter 对象的一个属性)
    increment: function() {
        // 当 increment 函数作为方法被调用时,会自动得到一个隐式参数 this,指向对象本身
        this.value += 1
    }
}
> // increment 函数作为方法被调用,this 绑定到 counter 对象
> // 方法调用方式:object.function(argument, ...)
> counter.increment()
> counter.value
// 1
  • 作为构造函数调用

构造函数的定义方法和普通函数完全一致,只是在命名上约定构造函数以大写字母开头

此时 this 被绑定到构造函数返回的新对象上

// 定义 Message 函数作为构造函数
> var Message = function(descrition) {
    // 当 Message 函数作为构造函数被调用时,会自动得到一个隐式参数 this,绑定到新对象
    this.detail = descrition
}
> // Message 函数作为构造函数被调用,this 绑定到 message 对象
> // 构造函数调用方式:new Function(argument, ...)
> var message = new Message('Hello')
> message.detail
// 'Hello'

这里存在一个严重的问题,如果说我们不小心把构造函数当成普通函数调用,想想会发生什么

此时 this 会绑定到全局对象,也就是说我们给新对象添加的属性都会添加到全局对象上,造成全局对象的污染

所以我们不得不要做额外的工作来保证构造函数被正常调用

> var Message = function(descrition) {
    // 先判断 this 是否指向当前对象
    // 如果是,则说明函数是通过构造函数的方式调用的,这时可以安心进行初始化对象的工作
    if (this instanceof Message) {
        this.detail = descrition
    }
    // 如果不是,那就说明函数可能是通过错误的方式调用的,需要以正确的方式重新调用构造函数
    else {
        return new Message(descrition)
    }
}

(3)函数参数与 arguments

在 JavaScript 中,没有要求传入的实参个数与函数定义时指定的形参个数一致,这样就会出现一些有趣的现象

  • 当实参个数小于形参个数

如果实参个数小于形参个数,多余的形参将会被设置为 undefined

这时,可以给传入函数的参数设置一个合适的默认值,以防止程序出现意想不到的错误

> function add(a ,b) {
    a = a || 0
    b = b || 0
    return a + b
}
> add(5, 7)
// 12
> add(9)
// 9
> add()
// 0
  • 当实参个数大于形参个数

如果实参个数大于形参个数,多余的实参也会正常传入函数,只是无法获取它们的引用罢了

这时,可以用隐式传入的参数 arguments 获取传入函数的所有实参

> function add() {
    var total = 0
    for (let curr = 0, len = arguments.length; curr < len; curr++) {
        total += arguments[curr]
    }
    return total
}
> add(1, 2, 3, 4, 5)
// 15

【 阅读更多 JavaScript 系列文章,请看 JavaScript学习笔记

猜你喜欢

转载自www.cnblogs.com/wsmrzx/p/11722321.html