定义函数
函数声明
一个函数定义(也称为函数声明,或函数语句)由一系列的function关键字组成,依次为:
- 函数的名称。
- 函数参数列表,包围在括号中并由逗号分隔。
- 定义函数的 JavaScript 语句,用大括号{}括起来。
原始参数(比如一个具体的数字)被作为值传递给函数;值被传递给函数,如果被调用函数改变了这个参数的值,这样的改变不会影响到全局或调用函数。
如果传递一个对象(即一个非原始值,例如Array或用户自定义的对象)作为参数,而函数改变了这个对象的属性,这样的改变对函数外部是可见的,如下
function myFunc(theObject) {
theObject.make = "Toyota";
}
var mycar = {make: "Honda", model: "Accord", year: 1998};
var x, y;
x = mycar.make; // x获取的值为 "Honda"
myFunc(mycar);
y = mycar.make; // y获取的值为 "Toyota"
// (make属性被函数改变了)
函数表达式
函数也可以由函数表达式创建。这样的函数可以是匿名的;它不必有一个名称。
然而,函数表达式也可以提供函数名,并且可以用于在函数内部代指其本身,或者在调试器堆栈跟踪中识别该函数:
var factorial = function fac(n) {return n<2 ? 1 : n*fac(n-1)};
console.log(factorial(3));//阶乘
当将函数作为参数传递给另一个函数时,函数表达式很方便。下面的例子演示了一个叫map的函数如何被定义,而后使用一个表达式函数作为其第一个参数进行调用:
function map(f, a) {
var result = []; // 创建一个数组
var i; // 声明一个值,用来循环
for (i = 0; i != a.length; i++)
result[i] = f(a[i]);
return result;
}
var f = function(x) {
return x * x * x;
}
var numbers = [0,1, 2, 5,10];
var cube = map(f,numbers);
console.log(cube);// [0, 1, 8, 125, 1000]
在 JavaScript 中,可以根据条件来定义一个函数。
var myFunc;
if (num == 0){
myFunc = function(theObject) {
theObject.make = "Toyota"
}
}
调用函数
函数一定要处于调用它们的域中,但是函数的声明可以被提升(出现在调用语句之后)。
函数作用域
在函数内定义的变量不能在函数之外的任何地方访问,因为变量仅仅在该函数的域的内部有定义。相对应的,一个函数可以访问定义在其范围内的任何变量和函数。
作用域和函数堆栈
递归
一个函数可以指向并调用自身。有三种方法:
- 函数名
- arguments.callee
- 作用域下的一个指向该函数的变量名
var foo = function bar() {
// statements go here
/*在这个函数体内,以下语句是等价的:
bar()
arguments.callee()
foo()
*/
};
嵌套函数和闭包
一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的的表达式(通常是函数)。并且内部函数包含外部函数的作用域。
- 内部函数只可以在外部函数中访问。
- 内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。
/*内部函数形成了闭包,因此你可以调用外部函数并为外部函数和内部函数指定参数:*/
function outside(x) {
function inside(y) {
return x + y;
}
return inside;
}
fn_inside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it
result = fn_inside(5); // returns 8
result1 = outside(3)(5); // returns 8
保存变量
注意到上例中
inside
被返回时x
是怎么被保留下来的。一个闭包必须保存它可见作用域中所有参数和变量。因为每一次调用传入的参数都可能不同,每一次对外部函数的调用实际上重新创建了一遍这个闭包。只有当返回的inside
没有再被引用时,内存才会被释放。
多层嵌套函数
函数可以嵌套,那闭包就可以包含多个作用域,递归式包含函数作用域,称作用域链。
function A(x) {
function B(y) {
function C(z) {
console.log(x + y + z);
}
C(3);
}
B(2);
}
A(1); // logs 6 (1 + 2 + 3)
命名冲突
命名冲突,越近的作用域优先权越高,链的第一个元素就是最里面的作用域,最后一个元素就是最外层的作用域。
function outside() {
var x = 5;
function inside(x) {
return x * 2;
}
return inside;
}
/*这里的作用链域是{inside, outside, 全局对象}*/
outside()(10); // returns 20 instead of 10
闭包
此当内部函数生存周期大于外部函数时,外部函数中定义的变量和函数将的生存周期比内部函数执行时间长。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。
var pet = function(name) { //外部函数定义了一个变量"name"
var getName = function() {
//内部函数可以访问 外部函数定义的"name"
return name;
}
//返回这个内部函数,从而将其暴露在外部函数作用域
return getName;
};
myPet = pet("Vivie");
myPet(); // 返回结果 "Vivie"
外部函数的变量对内嵌函数可获取,但是内嵌函数的变量除了本身无法获取,相对安全。
注意!!!如果内嵌函数有和外部函数同名的变量,那外部函数域将再也无法指向该变量。
使用arguments对象
函数的实际参数会保存在一个类似数组的arguments对象中,
arguments[i]
使用arguments对象,可以处理比声明的更多的参数来调用函数。这在事先不知道会需要将多少参数传递给函数时十分有用。
function myConcat(separator) {
var result = ''; // 把值初始化成一个字符串,这样就可以用来保存字符串了!!
var i;
// iterate through arguments
for (i = 1; i < arguments.length; i++) {
result += arguments[i] + separator;
}
return result;
}
// returns "red, orange, blue, "
myConcat(", ", "red", "orange", "blue");
// returns "elephant; giraffe; lion; cheetah; "
myConcat("; ", "elephant", "giraffe", "lion", "cheetah");
// returns "sage. basil. oregano. pepper. parsley. "
myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");
函数参数
默认参数
函数默认参数是undefined,某些情况下需设置不同默认值。
在过去,用于设定默认的一般策略是在函数的主体测试参数值是否为undefined
function multiply(a, b) {
b = (typeof b !== 'undefined') ? b : 1;
return a*b;
}
multiply(5); // 5
使用默认参数,在函数体的检查就不再需要了。
function multiply(a, b = 1) {
return a*b;
}
multiply(5); // 5
剩余参数
剩余参数语法允许将不确定数量的参数表示为数组。
function multiply(multiplier, ...theArgs) {
return theArgs.map(x => multiplier * x);
}
var arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]
箭头函数
箭头函数表达式(也称胖箭头函数)相比函数表达式具有较短的语法并以词法的方式绑定 this。箭头函数总是匿名的。
更简洁的函数
var a = [
"Hydrogen",
"Helium",
"Lithium",
"Beryllium"
];
var a2 = a.map(function(s){ return s.length });
console.log(a2); // logs [ 8, 6, 7, 9 ]
var a3 = a.map( s => s.length );
console.log(a3); // logs [ 8, 6, 7, 9 ]
this的词法
在箭头函数出现之前,每一个新函数都重新定义了自己的 this 值(在严格模式下,一个新的对象在构造函数里是未定义的,以“对象方法”的方式调用的函数是上下文对象等)。
function Person() {
// The Person() constructor defines `this` as itself.
this.age = 0;
setInterval(function growUp() {
// In nonstrict mode, the growUp() function defines `this`
// as the global object, which is different from the `this`
// defined by the Person() constructor.
this.age++;
}, 1000);
}
var p = new Person();
在ECMAScript 3/5里,通过把this
的值赋值给一个变量可以修复这个问题。另外,创建一个约束函数可以使得 this值被正确传递给 growUp() 函数。
function Person() {
var self = this; // Some choose `that` instead of `self`.
// Choose one and be consistent.
self.age = 0;
setInterval(function growUp() {
// The callback refers to the `self` variable of which
// the value is the expected object.
self.age++;
}, 1000);
}
箭头函数捕捉闭包上下文的this
值,所以下面的代码工作正常。
function Person(){
this.age = 0;
setInterval(() => {
this.age++; // |this| properly refers to the person object
}, 1000);
}
var p = new Person();
预定义函数
- eval()函数会将传入的字符串当做 JavaScript 代码进行执行。
- uneval()函数会将传入的字符串当做 JavaScript 代码进行执行。
isFinite()
函数判断传入的值是否是有限的数值。 如果需要的话,其参数首先被转换为一个数值。- isNaN()函数判断一个值是否是NaN。注意:isNaN函数内部的强制转换规则十分有趣; 另一个可供选择的是ECMAScript 6 中定义Number.isNaN() , 或者使用 typeof来判断数值类型。
parseFloat()
函数解析字符串参数,并返回一个浮点数。parseInt()
函数解析字符串参数,并返回指定的基数(基础数学中的数制)的整数。- decodeURI() 函数对先前经过encodeURI函数或者其他类似方法编码过的字符串进行解码。
- decodeURIComponent()方法对先前经过encodeURIComponent函数或者其他类似方法编码过的字符串进行解码。
- encodeURI()方法通过用以一个,两个,三个或四个转义序列表示字符的UTF-8编码替换统一资源标识符(URI)的某些字符来进行编码(每个字符对应四个转义序列,这四个序列组了两个”替代“字符)。
- encodeURIComponent() 方法通过用以一个,两个,三个或四个转义序列表示字符的UTF-8编码替换统一资源标识符(URI)的每个字符来进行编码(每个字符对应四个转义序列,这四个序列组了两个”替代“字符)。