• 你不知道的JavaScript——第二章:this全面解析


    1调用位置

      调用栈:为了到达当前执行位置所调用的所有函数。    

    function baz(){
        //当前调用栈:baz
        //因此,当前调用位置是全局作用域
        console.log('baz');
        bar();    //bar的调用位置
    }
    
    function bar(){
        //当前调用栈:baz->bar
        //因此,当前调用位置在baz
        console.log('bar');
        foo();    //foo的调用位置
    }
    function foo(){
        //当前调用栈:baz->bar->foo
        //因此,当前调用位置在bar中
        console.log('foo');
    }
    baz();    //baz的调用位置

    2绑定规则:

      2.1默认绑定:

    function foo(){
        console.log(this.a);
    }
    var a=2;
    foo();//2

        函数调用时应用了this的默认绑定,因此this指定全局对象。

        如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此this会绑定到undefined。

    function foo(){ //严格模式下声明
        "use strict";
        console.log(this.a);
    }
    var a=2;
    foo();//TypeError:this is undefined

          !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1

        虽然this的绑定规则完全取决于调用的位置,但是只有foo()运行在非strict mode下时,默认绑定才能绑定到全局对象。

        在严格模式下调用foo()则不影响默认绑定。!!!!

    function foo(){
        console.log(this.a);
    }
    var a=2;
    (function(){  //严格模式下调用
        "use strict";
        foo();//2
    })();

      2.2隐式绑定

        需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,!!不过这种说法可能会造成一些误导。

    funtion foo(){
        console.log(this.a);
    }
    var obj={
        a:2,
        foo:foo
    }
    obj.foo();//2

        无论是直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。

        然而,调用位置会使用obj上下文来引用函数,因此可以说函数被调用时obj对象“拥有”或者“包含”函数引用。

        当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。

        !!!!!!!!!!!!!!!!

        对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。(函数直接关联层起作用)    

    function foo(){
        console.log(this.a);
    }
    var obj2={
        a:42,
        foo:foo
    }
    var obj1={
        a:2,
        obj2:obj2
    }
    obj1.obj2.foo();//42

        隐式丢失:

          被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否在严格模式。      

    function foo(){
        console.log(this.a);
    }
    var obj={
        a:2,
        foo:foo
    }
    var bar=obj.foo;
    var a='oops,global';
    bar();//'oops,global'

          虽然bar是obj.foo的一个引用,但实际上,它引用的是foo函数本身,因此此时bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

    function foo(){
        console.log(this.a);
    }
    function doFoo(fn){
        fn();
    }
    var obj={
        a:2,
        foo:foo
    }
    var a='oops,global';
    doFoo(obj.foo);//'oops,global';

    参数传递其实就是一种隐式赋值。

    function foo(){
        console.log(this.a);
    }
    var obj={
        a:2,
        foo:foo
    }
    var a="oops,global";
    setTimeout(obj.foo,100);//"oops, global";

    JavaScript环境中内置的setTimeout()函数实现和下面的伪代码类似:

    function setTimeout(fn,delay){
        //等待delay毫秒
        fn();//调用位置
    }

      2.3显式绑定  (call,apply,bind)

    function foo(){
        console.log(this.a);
    }
    var obj={
        a:2
    }
    foo.call(obj);//2

        通过foo.call(...),我们可以在调用foo时强制把它的this绑定到obj上。

        如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(…)、new Boolean(…)或者new Number(…))。这通常被称为“装箱”。

        1.硬绑定:   显式的强制绑定   

    function foo(){
        console.log(this.a);
    }
    var obj={
        a:2
    };
    var bar=function(){
        foo.call(obj);
    };
    bar();    //2
    setTimeout(bar,100); //2
    //硬绑定的bar不可能再修改它的this
    bar.call(windwo);//2

        硬绑定的典型应用场景就是创建一个包裹函数,负责接收参数并返回值。  

    function foo(something){
        console.log(this.a,something);
        return this.a+something;
    }
    var obj={
        a:2
    };
    var bar=function(){
        return foo.apply(obj,arguments);
    };
    var b=bar(3);    //2 3
    console.log(b);//5

        另一种使用方法是创建一个可以重复使用的辅助函数:

    function foo(something){
        console.log(this.a,something);
        return this.a+something;
    }
    //简单的辅助绑定函数
    function bind(fn,obj){
        return function(){
            fn.appl(obj,arguments);
        };
    }
    var obj={
        a:2
    };
    var bar=bind(foo,obj);
    var b=bar(3);//2 3
    console.log(b);//5

        ES5提供了内置的方法Function.prototype.bind

    function foo(something){
        console.log(this.a, something);
        return this.a+something;
    }
    var obj={
        a:2
    };
    var bar=foo.bind(obj);
    var b=bar(3);    //2 3
    console.log(b);    //5

           bind(…)会返回一个硬编码的新函数,它会把你指定的参数设置为this的上下文并调用原始函数。

        2.API调用的“上下文”

    function foo(el){
        console.log(el,    this.id);
    }
    var obj={
        id:"awesome"
    };
    //调用foo(…)时把this绑定到obj
    [1,2,3].forEach(foo,obj);//1 awesome   2 awesome    3 awesome

      2.4new绑定

         包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。注:实际上并不存在所谓的“”构造函数“”,只有对于函数的“”构造调用“”。

        使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

          1.创建(或者说构造)一个全新的对象。

          2.这个新对象会被执行 [[Prototype]]连接

          3.这个新对象会绑定到函数调用的this。

          4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

    function foo(a){
        this.a=a;
    }
    var bar=new foo(2);//函数没有返回其他对象,new表达式中的函数调用会自动返回新对象。
    console.log(bar.a)//2

         使用new来调用foo(…)时,我们会构造一个新对象并把它绑定到foo(…)调用中的this上。

    3优先级:

      判断this:

        1.函数是否在new调用(new绑定)?如果是的话this绑定的是新创建的对象。  var bar=new foo();

        2.函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。  var bar=foo.call(obj2);

        3.函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。  var bar=obj1.foo();

        4.如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。  varbar=foo();

        隐式绑定和显示绑定比较显示绑定的优先级更高。例:    

        function foo(){
                console.log(this.a);
            }
            var obj1={
                a:2,
                foo:foo
            }
            var obj2={
                a:3,
                foo:foo
            }
            obj1.foo();//2
            obj2.foo();//3
            obj1.foo.call(obj2);//3
            obj2.foo.call(obj1);//2

        new绑定比隐式绑定的优先级高。例:    

        function foo(something){
                this.a=something;
            }
            var obj1={
                foo:foo
            }
            var obj2={}
            obj1.foo(2);
            console.log(obj1.a);//2
    
            obj1.foo.call(obj2,3);
            console.log(obj2.a);//3
    
            var bar=new obj1.foo(4);
            console.log(obj1.a);//2
            console.log(bar.a);//4

        new和bind比较:

            function foo(something){
                this.a=something;
            }
            var obj1={};
            var bar=foo.bind(obj1);
            bar(2);
            console.log(obj1.a);//2
    
            var baz=new bar(3);
            console.log(obj1.a);//2
            console.log(baz.a);//3

    4绑定例外;

      1被忽略的this:

          如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

            function foo(){
                console.log(this.a);
            }
            var a=2;
            foo.call(null);//2

         应用: 

            apply(…)来“展开”一个数组,并当做参数传入一个函数。

         bind(…)可以对参数进行柯里化(预先设置一些参数)。     

            function foo(a,b){
                console.log("a: "+a+",b: "+b);
            }
            //把数组“展开”成参数
            foo.apply(null,[2,3]);//a:2,b:3
            //使用bind(…)进行柯里化
            var bar=foo.bind(null,2);
            bar(3);//a:2,b:3

        更安全的this:

        一种“更安全”的做法是传入一个特殊的对象,把this绑定到这个对象不会对你的程序产生任何副作用。

        在JavaScript中创建一个空对象最简单的方法就是Object.create(null)。Object.create(null)和{}很像,但是并不会创建Object.prototype这个委托,所以它比{}“更空”:      

        function foo(a,b){
                console.log("a:"+a+",b:"+b);
            }
            //我们的DMZ空对象
            var ♓=Object.create(null);
            //把数组展开成参数
            foo.apply(♓,[2,3]);
            //使用bind(…)进行柯里化
            var bar=foo.bind(♓,2);
            bar(3);//a:2,b:3

       2间接引用:

          function foo(){
                console.log(this.a);
            }
            var a=2;
            var o={a:3,foo:foo};
            var p={a:4};
            
            o.foo();//3
            (p.foo=o.foo)();//2

        赋值表达式p.foo=o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()。根据我们之前所说的,这里会应用默认绑定。

        注意:

          对于默认绑定来说,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this会被绑定到undefined,否则this会被绑定到全局对象。

      3软绑定

        可以给默认绑定指定一个全局对象和undefined以外的值。可以实现和硬绑定相同的效果,同时保留隐式绑定或者显示绑定修改this的能力。    

        if(!Function.prototype.softBind){
                Function.prototype.softBind=function(obj){
                    var fn=this;
                    //捕获所有的curried参数
                    var curried=[].slice.call(arguments,1);
                    var bound=function(){
                        return fn.apply(
                                (!this||this===(windwo || global))?obj:this,
                                curried.concat.apply(curried,arguments)
                            );
                    };
                    bound.prototype=Object.create(fn.prototype);
                    return bound;
                };
            }

        softBind实现软绑定功能:      

            function foo(){
                console.log("name: "+this.name);
            }
            var obj={name:"obj"},
                obj2={name:"obj2"},
                obj3={name:"obj3"};
            var fooOBJ=foo.softBind(obj);
            fooOBJ();//obj
            obj2.foo=foo.softBind(obj);
            obj2.foo();//obj2
            fooOBJ.call(obj3);//obj3
            setTimeout(obj2.foo,100);//obj

    5this词法:

      箭头函数

        箭头函数并不是使用function关键字定义的,而是使用被称为“胖箭头”的操作符=>定义的。箭头函数不使用this的四种规则,而是根据外层(函数或者全局)作用域来决定this(定义函数时)。    

            function foo(){
                //返回一个箭头函数
                return (a)=>{
                    //this继承自foo()
                    console.log(this.a);
                };
            }
            var obj1={
                a:2
            };
            var obj2={
                a:3
            };
            var bar=foo.call(obj1);
            bar.call(obj2);//2    不是3       绑定到创建箭头函数时的this上,绑定无法被修改

          foo()内部创建的箭头函数会捕获调用是foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new 也不行!)

          箭头函数最常用于回调函数中,例如事件处理器或者定时器。      

            function foo(){
                setTimeout(()=>{
                    //这里的this在词法上继承自foo()
                    console.log(this.a);
                },100);
            }
            var obj={
                a:2
            };
            foo.call(obj);//2

      

  • 相关阅读:
    洛谷 P3353【在你窗外闪耀的星星】
    SpirngMVC源码分析
    处理器映射器(HandlerMapping)及处理器适配器(HandlerAdapter)详解(二)
    关于 DispatcherServlet.properties 文件
    处理器映射器(HandlerMapping)及处理器适配器(HandlerAdapter)详解(一)
    SpringMVC的入门程序
    Spring工作原理:初识SpringMVC
    Spring的事务管理
    Spring配置连接池和 Dao 层使用 jdbcTemplate
    Spring 的 jdbcTemplate 操作
  • 原文地址:https://www.cnblogs.com/em2464/p/10385084.html
Copyright © 2020-2023  润新知