自学的教程
阮一峰老师的JavaScript标准参考教程:https://wangdoc.com/javascript/
对于函数,有了不少应用,所以基本概念都理解,只是摘录一些比较容易出错或者之前使用较少的经验。
1. 函数的有趣定义
这个说法挺有意思。
JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。
2. 函数的属性
2.1. name属性
name属性的一个用处,就是获取参数函数的名字。
var myFunc = function () {};
function test(f) {
console.log(f.name);
}
test(myFunc) // myFunc
函数test内部通过name属性,就可以知道传入的参数是什么函数.
2.2. length属性
函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数.
length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的”方法重载“(overload)。
需要注意的是,函数的length属性与实际传入的参数个数无关,只反映函数预期传入的参数个数。
3. 函数的作用域
在 ES5 的规范中,Javascript 只有两种作用域:一种是全局作用域,一种是函数作用域。ES6中加了块级作用域,暂时不涉及。
注意,对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量
if (true) {
var x = 5;
}
console.log(x); // 5
上面代码中,变量x在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。
3.1函数内部的变量提升
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}
// 等同于
function foo(x) {
var tmp;
if (x > 100) {
tmp = x - 100;
};
}
3.2函数本身的作用域
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
函数体内部声明的函数,作用域绑定函数体内部:
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;
var f = foo();
f() // 1
上面代码中,函数foo内部声明了一个函数bar,bar的作用域绑定foo。当我们在foo外部取出bar执行时,变量x指向的是foo内部的x,而不是foo外部的x。正是这种机制,构成了下文要讲解的“闭包”现象。
【这里mark一下,一直没太搞明白闭包的概念,后面认真学!!敲黑板!】
4. 函数的参数
4.1 参数的省略
函数定义了多个参数,但是运行时无论提供多少个参数(或者不提供参数),JavaScript 都不会报错。省略的参数的值就变为undefined。但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined。
4.2 参数传递【敲黑板!!!经常出错】
1)函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
var p = 2;
function f(p) {
p = 3;
}
f(p);
p // 2
2)如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
3)如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值:
var obj = [1, 2, 3];
function f(o) {
o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
上面代码中,在函数f内部,参数对象obj被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(o)的值实际是参数obj的地址,重新对o赋值导致o指向另一个地址,保存在原地址上的值当然不受影响。
如果是下面这样,则原来的值会改变:
var obj = [1, 2, 3];
function f(o) {
o[0] = 8;
}
f(obj);
obj // [8, 2, 3]
因为是在传入的参数地址进行的修改,所以原值会改变。
4.3 arguments 对象
1)定义
由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来。
arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。
var f = function (one) {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
f(1, 2, 3)
// 1
// 2
// 3
正常模式下,arguments对象可以在运行时修改.
严格模式下,arguments对象是一个只读对象,修改它是无效的,但不会报错.
通过arguments对象的length属性,可以判断函数调用时到底带几个参数。
function f() {
return arguments.length;
}
f(1, 2, 3) // 3
f(1) // 1
f() // 0
2)与数组的关系
需要注意的是,虽然arguments很像数组,但它是一个对象。数组专有的方法(比如slice和forEach),不能在arguments对象上直接使用。
如果要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种常用的转换方法:slice方法和逐一填入新数组。
var args = Array.prototype.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}