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学习笔记 】