函数
一、函数简介
JavaScript中的函数是一段代码,被定义一次,但是可以调用或执行多次。函数定义的时候会包含一个函数形参的标识符列表,这些参数在函数内部像局部变量一样工作。函数调用时会为形参提供实参的值。函数调用时还有一个值,就是本次调用的上下文,就是this关键字指向的值。
函数是对象,我们可以把它赋值给变量或者作为函数的参数。此外,我们还可以给他设置属性和方法。
二、函数定义
1、函数声明语句
1 function x(a,b){
2 return a+b;
3 }
2、函数定义表达式
1 var x = function (a,b) {
2 return a + b;
3 }
4 //自調用函數
5 var y = (function (a, b) {
6 return a + b;
7 }(1, 2));
8 console.log(y);//3
没有return语句的函数的返回值时undefined;同时函数可以嵌套在另个函数里面,内部函数可以读取外部函数的局部变量。
三、函数调用的方法
1、作为函数
1 //函数调用 2 function X(){ 3 return 0; 4 } 5 var x=X();//0
函数调用的this指向全局对象,严格模式下指向undefined,如果该函数有嵌套函数,嵌套函数的this也指向全局对象(undefined)。
2、方法调用
1 function X(){
2 var self=this;
3 function myfun(){
4 console.log(self===o);//true
5 console.log(this===global);//true
6 }
7 myfun();
8 return 0;
9 }
10 var o={};
11 o.m=X;
12 //方法调用
13 var x=o.m();//x
方法调用的this指向调用方法的对象。内部函数要访问外部函数的this,需要将this保存在一个对象中。
3、构造函数调用
调用构造函数通常使用new关键字,构造函数的调用对象(this)就是这个新创建的。使用new 关键字调用构造函数时,先创建一个新的空对象,对象的原型指向构造函数的prototype属性,然后初始化这个对象并返回。
如果构造函数没有return或者有return但是返回的是一个原始值或者没有指定返回值,则表达式的值是这个新建的对象,如果return返回的是一个对象,则表达式的值是这个对象。
4、间接调用
使用Function.call()和Function.apply()间接调用函数,第一个参数指定调用方法的上下文(this),后面的参数指定调用方法需要的参数,call()中参数作为方法的实参,apply的参数以数组的形式传给方法的实参。
1 function x(x){
2 console.log(x);
3 }
4 x.call(null,'hello');//hello
5 x.apply(this,['world']);//world
四、函数的形参和实参
函数定义的时候传入的参数是形参,函数调用的时候传入的参数是实参,形参的引用指向实参。
JavaScript中函数调用时如果实参多余形参或自动省略,实参少于形参时用undefined填补。
利用不够的形参将使用undefined,我们可以实现一个参数可选的函数,可选的参数应该放在最后,当我们不给他传入值时应该使用默认值。
1 //可选参数的函数
2 function x(a,b){
3 if(a===undefined) a=10;
4 b=b||1;
5 return a+b;
6 }
7 console.log(x()); //11
arguments是指向实参对象的引用,实参对象是一个类数组对象。可以同下标的形式访问实参(第一个实参用argumens[0],...),也可以实现可变长形参的函数。
比较任意个数的中的最大值;
1 //可选参数的函数
2 function x(/*...*/){
3 var max=arguments[0];
4 for(var i=0;i<arguments.length;i++){
5 if(max<arguments[i])
6 max=arguments[i];
7 }
8 return max;
9 }
10 console.log(x(1,2,3)); //3
五、实参对象callee和length属性
callee指向当前正在执行的函数,length代表了当前调用函数的实参数量。
1 function X(){
2 console.log(arguments.length);//0
3 function Y(){
4 console.log(arguments.caller);//Y
5 }
6 Y();
7 }
8 X();
六、函数的属性
1、caller
如果一个函数f
是在全局作用域内被调用的,则f.caller为
null
,相反,如果一个函数是在另外一个函数作用域内被调用的,则f.caller指向调用它的那个函数.
该属性的常用形式arguments.callee.caller
替代了被废弃的 arguments.caller.
1 function X(){
2 console.log(X.caller);//null
3 function Y(){
4 console.log(Y.caller);//X
5 }
6 Y();
7 }
8 X();
2.length属性指明函数的形参个数。
1 function X(a,b){
2 console.log(X.length);//2
3 }
4 X();
3、prototype属性
每个函数都有prototype属性,该属性指向一个对象,这个对象成为原型对象。当时用该函数创建一个对象,对象会从原型对象继承属性。
4、call()和apply()
函数的这两个方法使的函数可以作为其他对象的方法调用。即使对象没有这个方法。
call()和apply()的一个参数是函数调用的上下文对象,可以是null和undefined,当时null和undefined的时候,会用全局对象代替,当时原始值的时候,会转化为对应的包装对象。
第二个参数是函数的调用的参数是要传入的参数。apply()把函数要传入的参数以数组的形式传入。
5、bind()
bind()用来将一个函数f绑定到一个对象o,并返回一个新的函数,这个新的函数会把f作为o的方法调用。
bind()可以接受额外的参数,额外的实参会作为函数的参数传入;
1 function add(x,y){return x+y};
2 var x=add.bind(null,1,2);//把函数绑定到null,x绑定1;y绑定2
3 console.log(x());//3
当绑定完的返回的新函数length属性为原函数的length减去绑定的实参个数。所以上面的函数x的length为0;
当绑定的函数作为构造函数时,函数无prototype属性,函数的this会忽略绑定的对象。创建的对象的原型指向原来函数的prototype,使用instanceof验证时,绑定函数和原函数没有区别。
1 function X(a,b){
2 this.a=a;
3 this.b=b;
4 }
5 var o=Object.create({x:1});
6 var x=X.bind(o,1,2);//把函数绑定到o,a=1;b=2
7 var z=new x();
8 console.log(z instanceof x);//true
9 console.log(z instanceof X);//true
6、toString()
大多数函数的toString()返回源码,内置函数的toString()返回[native code].
七、函数的使用
JavaScript中的函数是一个值,可以赋值给变量、对象的属性、数组的元素,还可以作为函数的参数。数组的sort()方法的参数就是一个函数,允许他设置数组的元素的排序规则。
函数是一个对象,我们可以给函数定义属性。
1 //用函数的属性保存一个值,这样可以减少全局变量的使用
2 XX.n=0;
3 function XX(){
4 return XX.n++;
5 }
作为命名空间的函数:函数内部定义的变量只在函数内部有效,而不会污染全局命名空间。模块的定义常用到这个方法,因为模块会在不同的程序中运行,我们不能保证模块中定义的变量是否在程序中已经存在,我们可以把变量定义在一个函数内部,这样变量的作用域就不是全局的。
1 (function(){
2 //moudle中的代码
3 }())
八、闭包
闭包:函数的变量可以隐藏于作用域链之内,因此看起来像函数把变量包裹起来了一样。
JavaScript采用词法作用域,函数的执行依赖变量的作用域,这个作用域实在函数定义的时决定的,而不是在函数调用时决定的。为了实现这种词法作用域,JavaScript的函数不仅包含代码的逻辑部分,还要引用当前的作用域链。
1 var scope="global scope";
2 function f(){
3 var scope="local scope";
4 function fun(){
5 return scope;
6 }
7 return fun;
8 }
9 var x=f()();
10 console.log(x);//"local scope"
函数定义的时候会保存一个作用域链,当函数调用的时候会创建一个保存函数局部变量的对象,把对象添加到作用域链上。当函数返回时会从作用域链删除该对象。当函数内部存在嵌套函数,嵌套函数的作用域链指向该对象,如果嵌套函数在外部函数中保存下来,外部函数返回的时候变量绑定对象会被回收,但是如果嵌套函数被返回或者存储在某处的属性里,则会有一个外部引用指向该函数,他的作用域链上的绑定对象则不会被回收。当我们调用函数时会从函数的作用域上查找变量,所以上面我们访问到的还是局部变量。
我们可以利用闭包来实现变量的私有。
1 function fun(){
2 var n=10;
3 return {
4 add:function f(){
5 return n++;
6 },
7 set: function g(){
8 n=0;
9 }
10 }
11 }
12 var o=fun();
13 o.add();//10
14 o.set
15 o.add();//0
当fun()方法调用返回后,只能通过函数的嵌套函数才能访问到局部变量n,这个变量就变成了一个私有变量。两个嵌套函数(闭包)共享变量n.
很多时候我们不希望私有变量共享。
1 function fun(){
2 var arr=[],i=0;
3 for(i;i<10;i++){
4 arr[i]=function(){
5 return i;
6 }
7 }
8 return arr;
9 }
10 var a=fun();
11 a.forEach((ele)=>{
12 console.log(ele());//结果全是10;
13 });
上面的代码中我们创建了10个闭包。10个闭包共享变量i;
1 function fun(){
2 var arr=[],i=0;
3 for(i;i<10;i++){
4 arr[i]=(function(x){
5 function f(){
6 return x;
7 }
8 return f;
9 }(i));
10 }
11 return arr;
12 }
13 var a=fun();
14 a.forEach((ele)=>{
15 console.log(ele());//结果全是0....9;
16 });
这个例子中我们把i作为参数传给不同的函数的形参x,然后让闭包中的x不共享。
当使用闭包是无法访问到外部函数的this和arguments。只能将其赋值给一个变量才能访问到。
九、Function构造函数
1 var z=new Function('x','y',"return x+y");
上面的函数等价于var z=function(x,y){return x+y};