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