zoukankan      html  css  js  c++  java
  • javascript 函数和作用域(函数,this)(六)

    重点。

    一、函数

    1、函数介绍

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

    注意:

    返回Return

    return语句可以使函数提前返回。

    一个函数总会返回一个值,函数的返回值,依赖于return语句。

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

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

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

    2、函数的属性和方法【20170501】

    函数属性

    • length属性
    • prototype属性
    • 自定义属性

    函数方法

    • call()
    • apply()

    3、函数的相关内容

    函数内容较多,重点有:

    • 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 variable
    var add=function(a,b){
        //do sth
    }

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

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

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

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

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

    //first-class function
    return 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。

    注意:在《jquery按钮绑定特殊事件》

    中第一次点击按钮触发一个事件,第二次点击触发另一个事件。遇到类似的情况,

    //不要这样做
    
    if(condition){
        function sayHi(){
            alert("Hi!");
        }
    }else{
        function sayHi(){
            alert("Yo!");
        }
    }
    
    //可以这样做
    var sayHi;
    if(condition){
        sayHi=function(){
            alert("Hi!");
        }
    }else{
        sayHi=function(){
            alert("Yo!");
        }
    }

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

    函数名字可以被调试器和开发工具识别。

     经典bug

     

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

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

    命名函数表达式相关规范:

    规范规定:命名函数表达式名字(标识符)在函数体的作用域内有效,且不能被覆盖。

    b = function c() {
        a = 1, b = 2, c = 3;
        console.log(typeof c); // function
    };

    命名函数表达式应用

    调试

     

     递归调用自己

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

    5、函数构造器

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

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

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

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

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

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

    //CASE1
    Function('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。

    注意:语言设计上的不足【update 20170306】

    调用函数时,this被绑定到全局对象。如下内部函数调用时this也是绑定到全局对象window。

    function outer(){
        console.log(this===window);
        function inner(){
            console.log("内部函数:"+(this===window));
        }
        inner();
    }
    View Code

    倘若语言设计正确,当内部函数被调用时,this应该仍然绑定到外部函数的this变量。这个设计错误的后果就是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权。

    一个简单的解决方案如下:方法定义一个一个变量that并给它赋值为this,那么内部函数就可以通过that访问到this。

    <script>
    //函数字面量创建add
    var add=function(a,b){
        return a+b;
    }
    var myObject = {
        value: 0,
        increment: function(inc) {
            this.value += typeof inc === 'number' ? inc : 1;
        }
    }
    
    myObject.increment();
    document.writeln(myObject.value); //1
    
    myObject.increment(2);
    document.writeln(myObject.value); //3
    
    //给myObject增加一个double方法
    myObject.double=function(){
        var that=this;//解决方法
    
        var helper=function(){
            that.value=add(that.value,that.value);
        }
        helper();  //以函数的形式调用helper
    }
    
    //以方法的形式调用double
    myObject.double();
    document.writeln(myObject.value); //6
    </script>

     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

    在方法调用模式中this到对象的绑定发生在调用时,这个“超级”延迟绑定(very late binding)使得函数可以对this高度重用。【update20170306】

    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【重点】

    如果在一个函数前面带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时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*/

    应用【bind的经典例子】

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

    四、函数属性arguments

    foo.length拿到形参的个数。在函数内和函数外都有效。foo.length===arguments.callee.length【20170501】

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

    arguments.callee当前正在执行的函数

    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);//3
    console.log(foo.name);//"foo"

    好处:使得编写一个无须指定参数个数的函数成为可能。

    注意设计错误

    因为语言设计错误,arguments并不是一个真正的数组。它只是一个"类似数组(array-like)"的对象。arguments拥有一个length属性,但arguments没有任何数组的方法

    五、bind和函数柯里化

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

     1、柯里化

    function add(a,b,c)    {
        console.log(a+'|'+b+'|'+c);
        console.log(a+b+c);
    }
    
    //不需要改变this,所以传入一个undefined就可以了
    var func=add.bind(undefined,100); func(1,2); //100|1|2 //103 var func2=func.bind(undefined,200); func2(10); //100|200|10 //310

    100固定赋值给a参数。

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

    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 123
    defaultConfig("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的模拟实现。

    本文作者starof,因知识本身在变化,作者也在不断学习成长,文章内容也不定时更新,为避免误导读者,方便追根溯源,请诸位转载注明出处:http://www.cnblogs.com/starof/p/6396450.html有问题欢迎与我讨论,共同进步。

  • 相关阅读:
    括号匹配的检验
    学习过程中遇到的比较有价值的博客--整理
    Spring+SpringMVC+MyBatis的pom.xml依赖
    Hibernate内容详解
    Struts2的拦截器配置
    Maven构建Struts2项目
    Mybatis增删改查,Demo整合
    简单Java类 全网最详细讲解 !!!
    Javaoop 遇到的问题
    Bootstrap 前端框架 遇到的问题 解决方案
  • 原文地址:https://www.cnblogs.com/starof/p/6396450.html
Copyright © 2011-2022 走看看