首页 > 代码库 > javascript 函数和作用域(六)

javascript 函数和作用域(六)

重点。

一、函数

函数是一块JavaScript代码,被定义一次,但可执行和调用多次。JS中的函数也是对象,所以JS函数可以像其他对象那样操作和传递,所以我们也常叫JS中的函数为函数对象。

技术分享

注意:

函数的返回值,依赖于return语句。

一般的函数调用:如果没有return语句的话,默认会在所有代码执行完以后返回undefined

如果是作为构造器,外部使用new去调用的话,如果没有return语句,或者return的是基本类型的话,默认会将this作为返回。

反之,如果return的是对象,将这个对象作为new构造器的返回值。

函数内容较多,重点有:

  • this
  • arguments
  • 作用域
  • 不同调用方式
    • 直接调用foo()
    • 对象方法o.method()
    • 构造器new Foo()
    • call/apply/bind调用 func.call(o)
  • 不同创建方法

二、函数声明和表达式

1、函数声明

function add(a,b){    a=+a;    b=+b;    if(isNaN(a)||isNaN(b)){        return;    }    return a+b;}

一个完整的语句以function开头,也不加括号,也不加叹号,也不会把它括起来,也不会把它作为赋值语句的右值等待。这样定义的函数就叫函数声明。

2、函数表达式

1、 函数变量

函数表达式赋值给变量。

//函数变量 function variablevar add=function(a,b){    //do sth}

2、立即执行函数表达式(IEF)

把一个匿名函数用括号括起来,再去直接调用。

//立即执行函数表达式 IEF(Immediately Executed Function)(function(){})();

3、作为返回值的函数表达式

将函数对象作为返回值,函数也是对象。

//first-class functionreturn function(){    //do sth}

4、命名式函数表达式(不常用

同样是赋值给一个变量,但这个函数不是匿名函数,而是有一个名字的函数,

//NFE (Named Function Expression)var add=function(a,b){    //do sth}

3、函数声明和函数表达式的区别

最主要的区别是函数声明会被前置。

函数声明,在声明前调用也可以,因为会前置。

函数表达式在声明前调用会报错:undefined is not a function。

技术分享

函数表达式中

var add=function(a,b){//do sth};

变量的声明会被提前,var add会提前,add被提前后它的值是undefined。

当把一个undefined的变量尝试像函数那样去调用的时候,就会报异常:undefined is not a function。

 4、命名函数表达式(NFE)

 经典bug

技术分享 

命名函数表达式里的名字nfe在 函数对象创建所在的作用域中 正常情况下是访问不到的。所以会报错:nfe is not defined

老的IE浏览器(IE6~8)仍然可以访问得到,但是nfe和func又不是同一个对象。

命名函数表达式应用

调试

技术分享 

 递归调用自己

//递归调用var func=function nfe(){/** do sth. **/ nfe();}

5、函数构造器

除了函数声明和函数表达式,还有一种不常见的构造函数的方式—使用函数构造器。

Function()中参数可以有多个,前面的参数表示函数对象里面的形参,最后一个参数表示函数体里面的代码。

函数体里的代码也是字符串,这也是为什么函数构造器不常用的原因。

var func=new Function(‘a‘,‘b‘,‘console.log(a+b);‘);//创建一个对象,有a,b2个形参,函数体里面是输出a+bfunc(1,2);//3//不管用new还是不用new最后得到的结果都是一样的。var func=Function(‘a‘,‘b‘,‘console.log(a+b);‘);func(1,2);//3

Function构造器作用域和其他处理与一般函数声明和函数表达式的差异

1、在Function构造器里面创建的变量仍然是局部变量,

//CASE1Function(‘var localVal="local";console.log(localVal);‘)();//立即执行console.log(typeof localVal);     //undefined

2、Function函数声明能访问到全局变量,却访问不到它的外函数中定义的变量。

技术分享 

 local不可访问,全局global可以访问。

技术分享

三、this

1、全局的this(浏览器)

全局作用域下的this一般指向全局对象,浏览器汇总的全局对象就是window。

技术分享

 2、一般函数的this(浏览器)

全局作用域下直接调用f1(),this就仍然指向全局对象,浏览器中就是window,在node.js里面就是global对象。

 技术分享

严格模式下直接调用f2(),this执行是undefined。

技术分享

 3、作为对象方法的 函数的this

只要将函数作为对象的方法o.f,this就会指向这个对象o。

var o={    prop:37,    f:function(){        return this.prop;    }}console.log(o.f()); //37

或者

var o={prop:37};function independent(){    return this.prop;}o.f=independent;console.log(o.f()); //37

4、对象原型链上的this

对象o有个属性f。

p是个空对象,并且p的原型指向o。给p添加2个属性a和b,再调用p.f()。

调用p.f()的时候调用的是p的原型o上面的这样一个属性f。所以对象原型链上的this调用时指向的还是对象。

var o={f:function(){return this.a+this.b}};var p=Object.create(o);p.a=1;p.b=4;console.log(p.f()); //5

5、get/set方法与this

 get set方法中的this一般也是指向get,set方法所在的对象。

function modulus(){    return Math.sqrt(this.re*this.re+this.im*this.im);}var o={    re:1,    im:-1,    get phase(){        return Math.atan2(this.im,this.re);    }}Object.defineProperty(o,‘modulus‘,{    get:modulus,    enumerable:true,    configurable:true})console.log(o.phase,o.modulus); //-0.7853981633974483 1.4142135623730951

6、构造器中的this

 将MyClass作为了构造器来用。

function MyClass(){    this.a=37;}var o=new MyClass();/*this指向空对象,并且这个空对象的原型指向MyClass.prototype,this作为返回值,因为没有return所以对象o就会有属性a为37*/console.log(o.a);//37

注意:

return语句返回的是对象的话,将该对象作为返回值,所以下面a就是38。

function C2(){    this.a=37;    return {a:38};}o=new C2();console.log(o.a);//38

7、call/apply方法与this

function add(c,d){    console.log(this.a+this.b+c+d);}var o={a:1,b:3};//call调用add.call(o,5,7);//16   //1+3+5+7=16//apply调用add.apply(o,[10,20]);//34   //1+3+10+20=34

应用

function bar(){    console.log(Object.prototype.toString.call(this));}bar.call(7); //[object Number]

技术分享

call和apply如果this传入null或者undefined的话,this会指向全局对象,在浏览器里就是window。

如果是严格模式的话:

传入this为null和undefined,那this就是null和undefined。

技术分享

8、bind与this[ES5提供,IE9+才有]

想把某一个对象作为this的时候,就传进去。

function f(){    return this.a;}var g=f.bind({a:"test"});console.log(g());//test/*绑定一次,多次调用,仍然实现这样一个绑定,比apply和call更高效*/var o={a:37,f:f,g:g};/*f属性赋值为直接的f方法,g赋值为刚才绑定之后的方法*/console.log(o.f(),o.g());  //37 "test"/*o.f()通过对象的属性的方式调用的,返回37*//*比较特殊的一点,使用bind方法绑定了之后,即使把新绑定之后的方法作为对象的属性去调用,仍然会按照之前的绑定去走,所以仍然返回test*/

应用

this.x=9; //相当于window.x=9var module={    x:81,    getX:function(){return this.x;}};module.getX();//81 作为对象方法调用var getX=module.getX();//把对象的方法赋值给一个变量getX();//9  this指向window,调用的是window的xvar boundGetx=getX.bind(module);boundGetx();//81  通过bind修改运行时的this

四、函数属性arguments

foo.length拿到形参的个数。

arguments.length拿到实际传参的个数。

foo.name拿到函数名。

:尝试通过arguments[2]=100修改未传入的z的值,z还是undefined。

就是说:参数如果没传进来的话,arguments和参数没有改下修改这样的绑定关系。

function foo(x,y,z){    console.log(arguments.length);  //2    console.log(arguments[0]); //1    arguments[0]=10;    console.log(x);   //有绑定关系,形参x被修改为10    arguments[2]=100;//z未传入    console.log(z);//没有绑定关系,z仍然是undefined    console.log(arguments.callee===foo);//true,严格模式禁止使用}foo(1,2);console.log(foo.length);//3console.log(foo.name);//"foo"

五、bind和函数柯里化

 函数柯里化就是把一个函数拆成多个单元。

 1、柯里化

function add(a,b,c)    {    console.log(a+‘|‘+b+‘|‘+c);    console.log(a+b+c);}var func=add.bind(undefined,100);func(1,2);//100|1|2//103var func2=func.bind(undefined,200);func2(10);//100|200|10//310

100固定赋值给a参数。

再柯里化一次,200固定赋值给a参数。

2、实际例子

/*getConfig获取一套配置在不同的页面中配置可能是不一样的,但是在同一个模块下,可能前2个参数,或者某些参数是一样的*/        function getConfig(colors,size,otherOptions){    console.log(colors,size,otherOptions);}/*this无所谓,写个null或者undefined都可以,可能在某个模块下color都是#CC0000,size都是1024*768*/var defaultConfig=getConfig.bind(null,"#CC0000","1024*768");/*拿到defaultConfig这样一个模块级别的通用配置以后,只要传入最后一个参数,可能是每个页面下的单独配置*/defaultConfig("123");  //#CC0000 1024*768 123defaultConfig("456");  //#CC0000 1024*768 456

六、bind和new

用new去调用,在this这个层面上.bind()的作用会被忽略。

用new的时候,即使绑定了bind,也会被忽略。

技术分享

func()直接调用,this会指向bind参数{a:1},return this.a就会返回1.

执行了this.b=100其实是给{a:1}加了个b属性,最后是{a: 1, b: 100}只是不会作为返回值,因为指定了返回值。

 

new的话,return除非是对象,不是对象的话会把this作为返回值,并且this会被初始化为默认的一个空对象,这个对象的原型是foo.prototye。

所以这里new func()调用的时候,即使我们指定了bind方法,this仍然会指向没有bind时所指向的空对象,空对象的原型指向foo.prototype,这个空对象的b属性被设置为100,整个对象会作为一个返回值返回,会忽略return this.a。所以用new func()调用后会返回对象字面量{b:100}。

七、bind方法模拟

在老的浏览器里怎样实现bind方法?模拟实现。

bind方法实现2个功能,绑定this柯里化

MDN的模拟实现。

javascript 函数和作用域(六)