函数是这样的一段JavaScript代码,它只定义一次,可是可能被执行或调用任意次。你可能已经从诸如子例程或者过程这些名字里对函数的概念有所了解。<!-- more --> JavaScript函数时参数化的:函数的定义会包括一个称为形参的表示符列表,这些参数在函数体中像局部变量同样工做。函数调用会为形参提供实参的值,函数使用它们实参的值来计算返回值,成为该函数调用表达式的值。除了实参外,每次调用还会拥有另外一个值 —— 本次调用的上下文 —— 这就是this关键字的值。web
参数有形参(parameter)和实参(argument)的区别,形参至关于函数中定义的变量,实参是运行时的函数调用时传入的参数。json
通常咱们有三种方法去建立函数:数组
函数声明:function foo(){}
浏览器
函数表达式:var foo = function(){}
闭包
函数构造法:var foo = new Function('a','b',"return a+b")
app
1.什么是函数声明,什么是函数表达式函数
ECMA规范说:函数声明必须带有标示符(Identifier)(就是你们常说的函数名称),而函数表达式则能够省略这个标示符:this
// 函数声明 function 函数名(可选参数){} // 函数表达式 function 可选函数名(可选参数){}
咱们能够很清楚的看出,若是没有标识符(函数名),该函数就必定是函数表达式,若是有标识符咱们该怎么区分函数声明和函数表达式呢。prototype
函数声明咱们不用太多的考虑,很容易看出来,咱们来看看函数表达式都有哪些天然就知道哪些是函数声明了。code
第一种函数表达式:var
声明的函数咱们很常见
// 没有标识符必定是表达式 var fun = function(){} //咱们还能够这样写 var fun = function foo(){}
第二种咱们带有标识符,但将函数赋值给变量后,该函数就成了函数表达式。在这里foo
标识符在该函数体外访问时并不起做用
var fun = function foo(a){ if(a){ console.log("外部调用了"); foo(false); //这会执行函数fun。 }else{ console.log("内部调用了"); } } fun(true); //外部调用了 ,内部调用了 foo(); // 报错 ReferenceError: foo is not defined
咱们能够看出,函数表达式中foo
标识符只能在函数内部使用,去访问该函数,在函数外部访问不到任何东西。上面咱们加判断是为了防止函数一直递归。
第二种函数表达式:()
括号包住的函数
// 这是函数表达式 (function foo(){})
括号包住的函数是函数表达式是由于,()
是一个分组操做符,分组操做符内只能是表达式。
例如:
//Unexpected token var (var a = 0)
上面报错,这是由于var是一个语句,而分组操做符内只能是表达式。还有咱们在使用evel()
时这样写 var ev = eval("(" + json + ")")
,这里是强制将{}
解析为表达式而不是代码块。
2.函数声明和函数表达式的特色
函数声明在咱们的代码执行前,会被整个提高,也就是说咱们在函数声明以前就能够调用该函数,而函数表达式不可以被提高。
a() // 结果 我是函数a" function a(){ console.log("我是函数a"); } fun() //结果 fun is not a function var fun = function b(){ console.log("我是函数b"); }
为何会出现这种状况咱们在之后的文章中会详细的讨论。同时,咱们应该养成一种习惯,尽可能不要在函数声明以前使用函数。这只是一种良好的代码书写习惯。
其次咱们须要注意的是在if判断语句中咱们不该该出现函数声明,即使是没有报错,也是不推荐这样去写的。
1. 当即执行函数(表达式)
咱们来看一下:
(function (){ console.log("one"); })(); (function (){ console.log("two"); }());
上边两个函数均可以本身去执行,而值有个共同的特色就是括号左边的函数必须是一个函数表达式,而不是函数声明。
// 若是括号左边是函数声明,报错 function fun(){ console.log() }() // 括号左边是函数时表达式,(函数表达式均可以自执行) var F = function(){ return 10; }(); true && function (){}(); 0 , function (){}(); //你甚至看还能够这样 !function () { /* code */ } (); ~function () { /* code */ } (); -function () { /* code */ } (); +function () { /* code */ } (); new function(){}();
其实上边的些自执行函数表达式咱们为了方便咱们的阅读咱们通常会用到第一个和第二个,固然若是你非要搞个性化,使用其余的也是能够。只不过你的代码并不美观,后期也不容易维护。
2. 什么是自执行函数,像这样
function fun1(){ //code } fun1(); function fun2(){ fun2() // 递归 } // 这是一个自执行匿名函数 var foo = function () { arguments.callee(); };
看了这些例子,咱们应该清楚了什么是自执行,什么是当即调用。
Function()
构造函数并不须要经过传入实参以指定函数名,就像函数直接量同样,Function()
构造函数建立一个匿名函数。
关于Function()
构造函数有几点须要特别注意
Function()
构造函数容许JavaScript在运行时动态的建立并编译函数。
Function()
构造函数每次执行时都会解析函数主体,并建立一个新的函数对象,因此当在一个循环或频繁执行的函数中调用Function()
构造函数效率是很是低的。而函数字面量(函数表达式)却不是每次遇到都会从新编译的,用Function()
构造函数建立一个函数时并不遵循典型的做用域,它一直把它看成是顶级函数来执行
它所建立的函数并非使用词法做用域,相反,函数体代码的编译老是会在顶层函数执行,
用函数构造器建立的函数不会在上下文中建立闭包,它们老是被建立在全局做用域中,当执行被建立的函数时,它们只能使用本身的局部变量或者全局变量。例以下面:
var scop = 'global'; function fun(){ var scop = 'fun'; return new Function('return scope') // global }
1. length属性
函数体中,arguments.length
表示传入函数的实参个数。而函数自己的length
表示该函数的形参。
function fun(x,y,z){ console.log(arguments.length);// 2 console.log(arguments.callee.length); //3 } fun(1,2);
咱们这里提一下,在函数中的arguments
,函数中arguments
包含所用实参,它并非一个真正的数组,而是一个实参的对象。另外他还有两个特别的属性callee
和caller
属性。
callee
属性只带党庆正在执行的函数。caller
属性时非标准的,但大多数浏览器都实现了这个属性它指带调用当前正在执行的函数的函数。
经过caller
属性能够访问调用栈。callee
属性在某些时候会很是的有用,好比在匿名函数中经过callee
来递归调用自身,上面咱们已经提到过。
2. prototype属性
每个函数都包含一个prototype
属性,这个属性是指向一个对象的引用,这个对象称做"原型对象"。每个函数都包含不一样的原型对象,当将函数用做构造函数的时候,新建立的对象会从原型对象上继承属性。
3. call()方法和apply()方法
call 和 apply 是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向。由于 JavaScript 的函数存在「定义时上下文」和「运行时上下文」以及「上下文是能够改变的」这样的概念。
它们的用法也是很简单的。例如:
var obj = { msg:"我是obj对象", fun:function(){ console.log(this.msg); }, } var objTwo = { msg:"我是objTwo对象" }; // 这里咱们想让经过objTwo 对象调用 obj对象的fun方法, // 此时obj的fun方法中的this已经改变了指向。 obj.fun.call(objTwo) // 我是objTwo对象
call 和 apply 的区别在于他们传参的形式不一样:
var obj = { msg:"我是obj对象", fun:function(one,two){ console.log(this.msg); console.log("我是参数:"+ one + ',' +two); }, } var objTwo = { msg:"我是objTwo对象" }; // 这里咱们想让经过objTwo 对象调用 obj对象的fun方法, // 此时obj的fun方法中的this已经改变了指向。 obj.fun.call(objTwo,1,2) // 我是objTwo对象 ,我是参数:1,2 obj.fun.apply(objTwo,[1,2]) // 我是objTwo对象 ,我是参数:1,2
所谓高阶函数就是操做函数的函数,它接收一个或多个函数做为参数,并返回一个新函数。咱们来看一个例子:
这个例子的主要做用是,fun
函数接收f()
和g()
两个函数,并返回一个新的函数用以计算f(g())
function fun(f,g){ return function(){ return f.call(this,g.apply(this,arguments)); } } var square = function(x){ return x * x } var sum = function(x,y){ return x + y; } var suqareOfSum = compose(square,sum) squareOfSum(2,3) // 25
在这里函数fun()
就是高阶函数。
不彻底函数是一种函数变换技巧,即把一次完整的函数调用折成屡次函数调用,每次传入的实参都是完整实参的一部分,每一个拆分开的函数叫作不彻底函数,每次函数调用叫作不彻底调用,这种函数变换的特色是每次调用都反一个函数,知道获得最终运行结果为止,例如:将函数f(1,2,3,4,5)
的调用修改成等价的f(1,2)(3,4)(5)
后者包含三次调用,和每次调用相关的函数就是 不彻底函数”。
参考文献: 《JavaScript权威指南》