函数
参考:JavaScript 教程:函数 @wangdao.com
// 定义
function functionName(arg0, arg1,...argN) {
statments
}
// 示例代码
function sayHi(num1, num2) {
alert("Hello " + arguments[0] + "," + arguments[1]); // 用类似数组的方式访问传入的参数
alert(arguments.length); // 传入参数的个数
if(arguments.length == 1) {
alert(arguments[0] + 10);
}else if {
alert(arguments[0] + num2);
}
}
// 函数体内通过 arguments 对象访问参数数组。
// arguments 与 命名参数的内存空间是相互独立的;
// ***特性:单向影响***:修改 arguments 的值会自动映射同步到命名参数上,反之则不能;
// arguments 对象的长度由传入的参数个数决定;
ECMAScript 函数的重要特点:命名的参数只是提供便利,但不是必须的。
ECMAScript 中的参数在内部是用一个数组来表示的。
函数中
arguments.length
属性可以返回传入参数的个数,实际上,函数的。length
属性与实际传入的参数个数无关,只反映函数预期传入的参数个数<!–hexoPostRenderEscape:
function howManyArgs () {
console.log(arguments.length);
}
howManyArgs(“string”, 45); //2
:hexoPostRenderEscape–>
howManyArgs(); //0
howManyArgs(12); //1
arguments
对象的长度是由传入的参数个数决定的,不是由定义函数时的命名参数的个数决定的。参数:没有传递值的命名参数将自动被赋予 undefined 值。
由于不存在函数签名的特性,ECMAScript 函数不能重载。(就是函数名相同、参数的个数不同的函数)。
无须指定函数的返回值,因为任何 ECMAScript 函数都可以在任何时候返回任何值。 实际上,未指定返回值的函数返回的是一个特殊的 undefined 值。
函数的传值
函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
函数的作用域
函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
var a = 1;
// 函数 x 的作用域绑定在外层
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x(); // x 函数执行时,所处的是定义时的作用域。
}
f() // 1
立即调用的函数表达式(IIFE)
在定义函数之后,立即调用该函数:
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
函数表达式
定义函数的方式:
- 函数声明;
- 函数表达式;
- 构造函数;
函数声明语法
// 函数声明语法
function functionName(arg0, arg1, arg2) {
// 函数体
}
// Firefox、Safari、Chrome 和 Opera 都给函数定义了一个非标准的 name 属性,返回函数名
console.log(functionName.name); //"functionName"
函数声明提升
函数声明的重要特性:函数声明提升
在执行代码之前会先读取函数声明,意味着可以把函数声明放在调用它的语句后面:
sayHi();
function sayHi() {
console.log("Hi!");
}
函数表达式语法
该语法声明一个匿名函数,并将它赋值给变量。
// 函数表达式语法
// 匿名函数/拉姆达函数/函数表达式
var functionName = function (arg0, arg1, arg2) {
// 函数体
};
// 函数表达式与其他表达式一样,在使用前必须先赋值。
sayHi(); // 错误!!!
var sayHi = function sayHi() {
console.log("Hi!");
};
构造函数
var add = new Function(
'x',
'y',
'return x + y'
);
// 等同于
function add(x, y) {
return x + y;
}
函数声明 & 函数表达式的区别
- 函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫做匿名函数。
- 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂;
- 递归函数应该始终使用 arguments.callee 来递归地调用自身,不要使用函数名 —— 函数名可能会发生变化。
函数声明的特性:函数提升
// 不要这样做
if (condition) {
function sayHi() {
alert("Hi");
}
} else {
function sayHi() {
alert("Hi");
}
}
// 可以这样做
var sayHi;
if (condition) {
sayHi = function () {
alert("Hi");
}
} else {
sayHi = function () {
alert("Hi");
}
}
递归
递归:函数调用自身。
// 递归阶乘函数
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(4)); // 出错!!!
使用 arguments.callee 解决以上问题:
// arguments.callee 在严格模式下禁用
// arguments.callee 是一个指向正在执行的函数的指针
// 通过使用 arguments.callee 代替函数名,可以确保无论怎样调用函数都不会出问题。
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
但在严格模式下,不能通过脚本访问 arguments.callee,访问这个属性会导致错误。不过,可以使用命名函数表达式来达成相同的结果。
// 命名函数表达式
// 创建了一个名为 f() 的命名函数表达式,然后将它赋值给变量 factorial。
// 即便把函数赋值给了另一个变量,函数的名字 f 仍然有效,所以递归调用照样能正确完成。
var factorial = (function f(num) {
if (num <= 1) {
return 1;
} else {
return num *f(num - 1);
}
});
闭包
闭包是指有权访问 (另一个函数作用域中的变量) 的函数。
创建闭包的常见方式,就是在一个函数内部创建另一个函数。
示例一:
function createComparisonFunction(propertyName) {
return function(object1, object2) {
// 这两行代码访问了外部函数中的变量 propertyName。
// 即使这个内部函数被返回了,而且是在其他地方被调用了,但它仍然可以访问变量 propertyName。
// 因为内部函数的作用域链中包含 createComparisonFunction()的作用域。
var value1 = object[propertyName];
var value2 = object[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
// 在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。
// 因此,在 createComparisonFunction() 函数内部定义的匿名函数的作用域链中,实际上将会包含外部函数 createComparisonFunction() 的活动对象。
示例二:
// 默认在外部是无法读取函数 f1() 的内部属性的
function f1() {
var n = 999;
// 函数 f2() 就是闭包
function f2() {
// 在函数 f2() 内部,它是可以读取函数 f1() 的内部属性的
console.log(n);
}
return f2; // 返回函数 f2(),利用f2()读取 f1() 的属性
}
var result = f1();
result(); // 999
作用域链:当某个函数被调用时,会创建一个执行环境 (execution context) 及相应的作用域链。然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象 (activation object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,…… 直至作为作用域链终点的全局执行环境。
💡💡💡
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭包。虽然像 V8 等优化后的 JavaScript 引擎会尝试回收被闭包占用的内存,但请大家还是要慎重使用闭包。
闭包与变量
// 闭包只能取得包含函数中任何变量的最后一个值。
function createFunctions(){
var result = new Array();
for (var index = 0; index < 10; index++) {
result[index] = function() {
result index;
};
}
return result;
}
实际上,每个函数都返回 10。因为每个函数的作用域链中都保存着 createFunctions()
函数的活动对象,所以它们引用的都是同一个变量 index。当 createFunctions()
函数返回后,变量 index 的值是 10,此时每个函数都引用着保存变量 index 的同一个变量对象,所以在每个函数内部 index 的值都是 10。
// 通过创建另一个匿名函数强制让闭包的行为符合预期
function createFunctions(){
var result = new Array();
for (var index = 0; index < 10; index++) {
result[index] = function(num) {
return function(){
return num;
};
}(i);
}
return result;
}
闭包的作用:
- 可以读取函数内部的变量;
- 让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
- 封装对象的私有属性和私有方法;
this 对象
this 对象是在运行时基于函数的执行环境绑定的:
- 在全局函数中,this 等于 window,
- 而当函数被作为某个对象的方法调用时,this 等于那个对象。
- 匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。
模仿块级作用域
Tips:ES6 新增了块级作用域。
JavaScript 没有块级作用域的概念。
用作块级作用域 (通常称为私有作用域) 的匿名函数的语法:
// 定义并立即调用一个匿名函数
// 将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。
// 紧随其后的另一对圆括号会立即调用这个函数。
(function(){
// 这里是块级作用域
})();
私有变量
严格来讲,JavaScript 中没有私有成员的概念;所有对象属性都是公有的。不过,倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
function add(num1, num2) {
// 函数内部的私有变量:num1,num2,sum
var sum = num1 + num2;
return sum;
}
创建用于访问私有变量的公有方法:
- 在函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。
- 特权方法:有权访问私有变量和私有函数的公有方法。
// 1. 在构造函数中定义特权方法
function MyObject() {
// 私有变量和私有函数
var privateVariable = 10;
function privateFunction() {
return false;
}
// 特权方法
this.publicMethod = function() {
privateVariable ++;
return privateFunction();
}
}
静态私有变量
通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法。
(function(){
// 私有变量很私有函数
var privateVariable = 10;
function privateFunction() {
return false;
}
// 构造函数
MyObject = function(){
};
//共有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable ++;
return privateFunction();
};
})();
模块模式
模块模式:为单例创建私有变量和特权方法。所谓单例 (singleton),指的就是只有一个实例的对象。
JavaScript 是以对象字面量的方式来创建单例对象的。
增强的模块模式
未完待续…