函数声明
1、function命令
function print(s) {
}
使用print()这种形式,就可以调用相应的代码
2、函数表达式
var f = function f() {};
f()
f只在函数体内部可用,指代函数表达式本身,其他地方都不可用。
3、Function构造函数
var add = new Function(
'x',
'y',
'return x + y'
);
Function构造函数接受三个参数,除了最后一个参数是add函数的“函数体”,其他参数都是add函数的参数。
可以传递任意数量的参数给Function构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体。
Function构造函数可以不使用new命令,返回结果完全一样。
第一等公民
JavaScript语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。
函数名的提升
avaScript引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。
如果采用赋值语句定义函数,JavaScript就会报错。
如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。
var f = function() {
console.log('1');
}
function f() {
console.log('2');
}
f() // 1
不能在条件语句中声明函数
根据ECMAScript的规范,不得在非函数的代码块中声明函数,最常见的情况就是if和try语句。
if (false) {
function f() {}
}
f() // 不报错
要达到在条件语句中定义函数的目的,只有使用函数表达式。
if (false) {
var f = function () {};
}
f() // undefined
函数的属性和方法
1、name属性
function f1() {}
f1.name // 'f1'
var f2 = function () {};
f2.name // ''
匿名函数的name属性总是为空字符串;
var f3 = function myName() {};
f3.name // 'myName'
返回函数表达式的名字(真正的函数名还是f3,myName这个名字只在函数体内部可用)。
2、length属性
length属性返回函数预期传入的参数个数,即函数定义之中的参数个数。
function f(a, b) {}
f.length // 2
3、toString()
返回函数的源码,函数内部的注释也可以返回。
var multiline = function (fn) {
var arr = fn.toString().split('\n');
return arr.slice(1, arr.length - 1).join('\n');
};
function f() {/*
这是一个
多行注释
*/}
multiline(f);
// " 这是一个
// 多行注释"
变相实现多行字符串
函数作用域
在函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取。
在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)。
函数内部定义的变量,会在该作用域内覆盖同名全局变量。
对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。
if (true) {
var x = 5;
}
console.log(x); // 5
变量x在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。
函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
函数的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
如果函数A调用函数B,函数B不会引用函数A的内部变量。
传递方式
函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
var obj = [1, 2, 3];
function f(o){
o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
某些情况下,如果需要对某个原始类型的变量,获取传址传递的效果,可以将它写成全局对象的属性。
var a = 1;
function f(p) {
window[p] = 2;
}
f('a');
a // 2
同名参数
如果有同名的参数,则取最后出现的那个值。
function f(a, a) {
console.log(arguments[0]);
}
f(1) // 1
要获得第一个a的值,可以使用arguments对象。
arguments对象
arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。
正常模式下,arguments对象可以在运行时修改。
严格模式下,arguments对象是一个只读对象,修改它是无效的,但不会报错。
可以通过arguments对象的length属性,判断函数调用时到底带几个参数。
虽然arguments很像数组,但它是一个对象。但是,可以通过apply方法,把arguments作为参数传进去,这样就可以让arguments使用数组方法了。
可以通过arguments.callee,达到调用函数自身的目的。
闭包
闭包就是能够读取其他函数内部变量的函数。
闭包使得内部变量记住上一次调用时的运算结果。
闭包的另一个用处,是封装对象的私有属性和私有方法。
立即调用的函数表达式
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
!function(){ /* code */ }();
new function(){ /* code */ }
eval命令
eval命令的作用是,将字符串当作语句执行。
eval('var a = 1;');
a // 1
eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值
调用eval(expression),这叫做“直接使用”,这种情况下eval的作用域就是当前作用域。除此之外的调用方法,都叫“间接调用”,此时eval的作用域总是全局作用域
利用Function函数生成一个函数,然后调用该函数,也能将字符串当作命令执行。
var jsonp = 'foo({"id": 42})';
var f = new Function( "foo", jsonp );
// 相当于定义了如下函数
// function f(foo) {
// foo({"id":42});
// }
new Function()的写法也可以读写全局作用域