你不知道的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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

posted @ 2019-02-18 11:25  聂小恶  阅读(198)  评论(0编辑  收藏  举报