Reference : JavaScript教程 - 廖雪峰的官方网站
JavaScript函数基础
定义函数
在JavaScript中,定义函数的方式如下:
function abs (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
定义函数abs(x):
-
function关键字指明这是函数定义 -
abs是函数名称 -
(x)括号内列出函数的参数,多个参数以,分隔 -
{...}之间的代码是函数体
没有return的函数返回undefined
由于JavaScript的函数也是一个对象,上述定义的abs(x)函数实际上是一个函数对象,而函数名abs可以视为指向该函数的变量。
因此,第二种定义函数的方式为:
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
在这种方式下,function(x) {...}是一个匿名函数,它没有函数名。但是,这个匿名函数被赋值给了变量abs,所以,通过变量abs即可调用该函数。
上述两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个;,表示赋值语句结束。
调用函数
JavaScript的函数调用不规定传入参数的数量,可以多,也可以少。如果调用abs(),则函数内参数x接收到undefined,计算结果为null。
为了避免参数为undefined,可以进行参数检查:
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
arguments关键字
JavaScript的函数内部可以使用arguments关键字,它指向当前函数的调用者传入的所有参数,包括多处的不需要的参数。
-
arguments.length获取参数个数 -
arguments[i]通过下标索引参数 -
arguments的操作类似Array,但实际不是Array
arguments最常见的用法是判断传入参数的个数,从而在函数体内进行适当的操作(如设定默认值)。例如以下求圆面积函数,当参数pi不传入时,默认为3.14。
function area_of_circle(r, pi) {
if (arguments.length == 0) {
return null;
} else if (arguments.length == 1) {
pi = 3.14;
}
return pi * r * r;
}
rest参数[ES6]
定义函数如下:
function foo (a, b, ...rest) {
// ...
}
则rest参数以数组的形式获取arguments从下标为2开始至结束的所有参数,此时arguments[0]被传给a,arguments[1]被传给b。
如果没有多余的参数,或者连指明的参数个数都不满足,则rest接收空数组。
rest参数可以用来定义参数长度可变的函数,例如下面的求和函数:
function sum(...rest) {
var i;
var res = 0;
for (i = 0; i < rest.length; i ++) {
res += rest[i];
}
return res;
}
深入学习:高阶函数 - 廖雪峰的官方网站
深入学习2:箭头函数 - 廖雪峰的官方网站
对象方法
在一个对象中绑定函数,称为这个对象的方法。例如:
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指向调用函数的对象(这是JavaScript的大坑,接下来将重点理解这一点)
this关键字
要点1: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()返回NaN,这是因为单独调用的语句等价于window.getAge(),此时this指向调用者window对象,因此结果是NaN。
进一步地,如果以下面的形式调用:
var fn = xiaoming.age; // 拿到xiaoming对象的age函数句柄
fn(); // NaN
结果依然是NaN。所以想要保证this指向正确,必须用obj.xxx()的形式调用!
这是一个巨大的设计错误,为了修复它,ESMA决定在strict模式下让函数的this指向undefined,因此,在strict模式下调用fn()会得到错误:
Uncaught TypeError: Cannot read property 'birth' of undefined
但这个办法治标不治本,this还是没有指向正确的位置。
更进一步,采用下面的方式定义方法:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - this.birth;
}
return getAgeFromBirth();
}
};
xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
这样this对象依然无法指向xiaoming对象。对于这种情况,解决的办法很简单:在方法的开头获取this,如报错在that变量中,此时that具有整个函数体的作用域,这样在方法内定义的函数中,就可以用that代替this,而不会产生任何错误了。
apply
apply是任何函数自带的一个方法,使用它,我们可以控制函数体内this的指向!我们可以靠它根本地解决上述的this指向问题。
apply有两个参数,第一个参数是this指向的对象,第二个参数是Array类的对象,表示函数本身的参数。
回到上面的例子,我们用apply来修复报错:
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, 参数为空
call
与apply类似的方法是call,唯一的区别是:
-
apply把参数打包成Array再传入; -
call把参数按顺序传入。
比如调用Math.max(3, 5, 4),分别用apply和call实现如下:
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
对于普通函数调用,我们通常让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










网友评论