1.函数的申明:三种方法:
- function命令
- 函数表达式:变量赋值
- Function构造函数
1 //method 1: function命令 2 function test(){ 3 console.log('hello function'); 4 } 5 6 //method 2:函数表达式,赋值给变量 7 var test1=function(){//这是个匿名函数 8 console.log('hello function1'); 9 };//注意这里有分号 10 11 //method 3:Function构造函数 12 var test2=new Function( 13 'return "hello function2"' 14 ); 15 16 test(); 17 test1(); 18 console.log(test2());
运行结果:
第二种方法:函数表达式,如果函数不是匿名函数,而是加上相应的函数名,则只在函数内部有效。
1 var test3=function x(){ 2 return typeof x;//此处有效 3 }; 4 console.log(test3()); 5 console.log(x());//报错 is not defined;外部访问无效
所以引申可以这样写:
1 //这样:内部和外部均能访问test4 2 var test4=function test4(){ 3 return typeof test4; 4 }; 5 console.log(test4());
好处:可以在函数体内部调用自身;方便排错(除错工具显示函数调用栈时,一层一层往上抛,将显示函数名,而不是匿名函数)
第三种方法:Function构造函数:可以不加new,返回结果一样;可以传递多个参数,只有最后的这个参数被当做函数体。
建议:不要使用该Function构造函数来申明函数!不直观。
1 var test5=new Function( 2 'return "hello Function construct"' 3 ); 4 //效果相当于 不加new 5 // var test5=Function( 6 // 'return "hello Function construct"' 7 // ); 8 var test6=new Function( 9 'a',//函数参数 10 'b',//函数参数 11 'return a<b ? a: b'//函数体 12 ); 13 console.log(test5());//hello Function construct 14 console.log(test6(10,100));//10
函数可以重复申明,但是后申明的函数会覆盖前面申明的函数。而且由于函数名的提升,前面的函数在任何时候均无效。
第一等公民:因为函数与其它数据类型的地位平等,因此称作第一等公民!
JavasScript将函数看作一种值,与数值、字符串、布尔值等等其它值地位相等。凡是可以使用值的地方,均能使用函数。如:将函数赋值给变量;将函数赋值给对象的属性(键);将函数作为参数传入其它函数;将函数作为另一个函数的返回结果!
1 console.log('函数是第一等公民'); 2 function print(s){ 3 console.log(s); 4 } 5 //将函数赋值给变量 6 var test7=function (){ 7 console.log('将函数赋值给变量'); 8 }; 9 //将函数赋值给对象的属性 10 var obj={ 11 x:1, 12 y:print 13 }; 14 //将函数作为参数传入另一个函数;将函数作为另一个函数结果返回 15 function test8(print){ 16 return print; 17 } 18 print('haha---'); 19 test7(); 20 obj.y('xixi---'); 21 test8(print)('nicai---');
运行结果:
函数名的提升:JavaScript引擎将函数名等同视为变量名,所以采用function命令申明函数时,函数会像变量提升一样,提升至代码头部。
1 test9();//相当于函数申明之后,然后调用 2 function test9(){ 3 console.log('test 9'); 4 }
运行结果:
上面相当于:
1 function test9(){ 2 console.log('test 9'); 3 } 4 test9();
但是如果采用函数表达式:
1 test10();//报错 2 var test10=function (){ 3 console.log('test 10'); 4 };
运行结果:
其实上面代码相当于:
1 var test10; 2 test10(); 3 test10=function(){ 4 console.log('test 10'); 5 };
不要在条件语句中使用申明函数:(ECMAScript规范);虽然浏览器中可能不报错!
1 var a=null; 2 if(a){ 3 function test11(){console.log('test')} 4 } 5 test11();
运行结果:
但是有些浏览器可能由于函数名提升,而导致运行结果正确。所以要达到这种效果,可以用函数表达式替代function命令申明函数。
1 var a=null; 2 if(a){ 3 var test11=function (){console.log('test')} 4 } 5 test11();
函数属性和方法:
name,length,toString()
name:返回function后面的函数名称
1 console.log('---function property name'); 2 function test12(){} 3 var test13=function (){} 4 var test14=function x(){} 5 console.log(test12.name,test13.name,test14.name);
运行结果:
length:返回函数预期传入的参数个数(注意:无论实际传入的参数个数是多少,length返回的是预期的参数个数)
1 // var test15=function (a,b,c){};//test15.length=3 2 function test15(a,b,c){} 3 console.log(test15.length); 4 test15(1); 5 console.log(test15.length); 6 test15(); 7 console.log(test15.length);
运行结果:
toString():返回函数源码;以字符串形式输出
1 function test16(){console.log('test 16')} 2 console.log(test16.toString());//将函数完整的以字符串形式输出 3 console.log(typeof test16.toString());//string
运行结果:
函数的参数:JavaScript参数不是必须的,允许参数省略;但是不能只传入靠后的参数,不传入靠前的参数(如果要达到这种效果,传入undefined)
1 console.log('---paras'); 2 function test17(a,b){ 3 console.log(a<b?a:b); 4 } 5 test17(1,10);//正常传入参数 6 test17(1);//只传入一个参数 7 test17(1,2,3);//传入多个参数 8 // test17(,1);//报错 9 test17(undefined,100);//替代上面这行代码 10 console.log(test17.length);//获取预期参数个数:2
运行结果:
如果参数传入的是原始类型的值(数值,字符,布尔值),传递方式是传值传递。即函数内部修改变量并不会影响外部。
如果函数参数传入的是复合类型的值(数组,对象,函数),传递方式是传址传递。即函数内部修改变量将会影响外部。
1 var op=10; 2 function test18(op){//传值传递,不影响外部 3 op=100; 4 return op; 5 } 6 console.log(test18(op));//100 7 console.log(op);//10 8 var op={x:1,y:2}; 9 function test19(op){//修改复合类型里面的单个属性,会影响外部 10 return op.x=10;//传址引用 11 } 12 console.log(test19(op));//10 13 console.log(op.x);//10 14 15 function test20(op){ 16 return op={x:100,y:1000};//完全替换复合属性,不会影响外部 17 } 18 console.log(test20(op));//{x:100,y:1000} 19 console.log(op);//{x:10,y:2}
arguments对象:函数允许不定数目参数,所以arguments对象包含函数运行时所有参数。arguments[0]表示第一个参数,依此类题。同时需要注意:1.arguments只在函数内部使用有效。2.尽管arguments看起来很像一个数组,但它是对象。函数特有的slice,forEach方法,arguments都没有。
1 function test21(a,b){ 2 console.log(arguments[0],arguments[1]); 3 console.log(arguments.length); 4 } 5 test21(10,100); 6 test21(10,100,100);
运行结果:
所以我们可以通过arguments.length达到查看函数实际运行中带有的参数个数。这是test21.length只能查看预期参数的个数所不具备的!
函数作用域(scope):全局变量(global variable);局部变量(local variable)
1 var a1=1000; 2 function test22(){ 3 console.log(a1);//函数内部能够读取函数外部的变量 4 } 5 console.log('---scope'); 6 test22();//1000 7 function test2_2(a2){ 8 var a2=100;//var申明,其实是局部变量,不能delete掉 9 return a2; 10 } 11 // console.log(a2);//报错;因为函数外部不能读取函数内部的变量(局部变量) 12 13 function test23(){ 14 a3=1;//不加var 申明,其实是一个全局变量,可以用delete删除掉 15 } 16 test23(); 17 console.log(a3);//因为a3是全局变量,所以能够访问 18 19 var a4=4; 20 function test24(a4){ 21 a4=100; 22 console.log(a4);//函数内部的局部变量可以覆盖函数外部全局变量 23 } 24 test24(a4);
即:函数内部可以访问全局变量和局部变量;局部变量会覆盖全局变量;函数外部通常不能访问函数内部局部变量。
注意:var命令申明的局部变量,在函数内部也存在变量提升的现象。
同时注意:函数执行时所在的作用域,是定义时的作用域,不是调用时的作用域!
1 var x=1; 2 function f1(){ 3 console.log(x); 4 } 5 function f2(){ 6 var x=10; 7 f1(); 8 } 9 f2();
f1()调用时使用的是定义时的作用域。返回结果是1。
注意:
如果“箭头”前面这里没有var,那么其实是定义了全局变量。返回结果得到是10。所有建议所有变量申明的地方均使用var命令!
注意下面这种情况:
1 var f3=function(){ 2 console.log(a); 3 }; 4 function f4(f3){ 5 var a=10; 6 f3(); 7 } 8 //报错,a is not defined 9 f4(f3);//f3()调用时使用的定义时作用域,所以无法访问a.
函数本身作用域:函数作为第一等公民,是一个值,也有自己的作用域。它的作用域与变量一样,就是其申明时所在的定义域。与其运行时所在作用域无关!
由此也就产生了闭包!
闭包(closure):可以简单理解为“定义在函数内部的函数”。本质上闭包就是将函数内部与函数外部相连接的一个桥梁!
1 console.log('---closure'); 2 function g(){ 3 var value=10; 4 function g1(){ 5 console.log(value); 6 } 7 return g1; 8 } 9 //正常来说我们不能直接在外面访问函数内部局部变量value,但是借助闭包函数g1,我们可以访问value. 10 var v=g(); 11 v();//获取到了value
闭包的用处:1.读取函数内部变量;2.让这些变量始终保存在内存中!
故:外层函数每次运行,都会产生一个新的闭包。而这个闭包又会保存外层函数的内部变量,内存消耗很大。所以不能随意滥用闭包,否则容易影响网页性能!
1 function clickNum(i){ 2 return function () { return i++}; 3 } 4 var cli=clickNum(10);//cli始终保存在内存中 5 console.log(cli());//10 6 console.log(cli());//11 7 console.log(cli());//12
闭包的另外一个作用是可以封装对象的私有属性和方法。
1 function Bird(){ 2 var _weight=10;//私有属性加_表示,类似python 3 function getWeight(){ 4 return _weight; 5 } 6 function setWeight(weight){ 7 _weight=weight; 8 } 9 return {//返回对象 10 getWeight:getWeight, 11 setWeight:setWeight 12 } 13 14 } 15 16 var b=Bird(); 17 console.log(b.getWeight());//10 18 b.setWeight(100); 19 console.log(b.getWeight());//100
立即调用的函数表达式(IIFE,Immediately-Invoked Function Expression):
分清语句与表达式:(程序由语句组成,语句由表达式组成!)
1 //这是语句 2 function fn1(){} 3 //这是表达式:函数表达式 4 var f2=function (){};
将function fn1(){}放入()中,那么就变成了表达式,可以立即进行调用!这就是“立即调用的函数表达式”。
1 (function (){console.log('hi!')})();
也可以这样写:
1 (function(){console.log('orange')}());
所以扩展开来,JavaScript以表达式来处理的函数定义的方法,均能产生这样的效果。
var g=function (){console.log('white')}(); true&&function(){console.log('blue')}(); !function(){console.log('gold')}(); +function(){console.log('silver')}(); new function(){console.log('red')}();
通常:只对匿名函数使用这种“立即执行的函数表达式”。目的:1.不需为函数命名,2.IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
eval函数:将字符串当做语句执行!
1 console.log(eval('3+5'));//8 2 console.log(eval('var a=3+5'));//undefined 3 4 var a=10; 5 eval('a=100');//如果这里是外部人员输入的,那么内部数据a被修改,产生安全问题 6 console.log(a);//100,
为了规避上面eval函数所带来的风险,严格模式规定,eval内部申明的变量,不会对外部变量造成影响!
但是严格模式下依然可以修改外部变量,安全问题依然存在!
在代码第一行加上'use strict';
1 var a=10; 2 eval('a=100');//如果这里是外部人员输入的,那么内部数据a被修改,产生安全问题 3 console.log(a);//100, 4 eval('var ab=1000;'); 5 console.log(ab);// ab is not defined
运行结果:
此外,eval函数中的字符串不会得到JavaScript引擎的优化,运行速度较慢!所有,建议尽量不要使用eval.
经常可以见到eval解析JSON数据字符串,不过正确的写法是使用JSON.parse方法。
eval还有“直接调用”和“间接调用”之分。
“直接调用”的作用域是当前作用域;“间接调用”的作用域是全局作用域。
1 //直接调用 2 var b1=10; 3 function testb(){ 4 var b1=1; 5 eval('console.log(b1)');//当前作用域 6 } 7 testb();//1 8 console.log(b1);//10 9 10 //间接调用 11 var b2=10; 12 function testc(){ 13 var e=eval; 14 var b2=1; 15 e('console.log(b2)')//e是eval的引用,作用域是全局作用域 16 } 17 testc();//10 18 console.log(b2);//10