最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • [深入JS]-this全面解析

    正文概述 掘金(JaylenL)   2021-01-12   628

    调用位置

    要找到函数的调用位置,最重要是找到函数的调用栈(就是为了到达当前执行位置所调用的所有函数),而函数的调用位置就是当前(栈顶)的前一个位置。

    举个栗子

    function baz() { 
     // 当前调用栈是:baz 
     // 因此,当前调用位置是全局作用域,浏览器下位window,node下为global
     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 的调用位置
    

    this绑定规则

    函数的this在js引擎执行时,会根据一些规则去进行绑定。

    默认绑定

    默认绑定应用在最常用的函数调用类型:独立函数调用上。可以把这条规则看作是无法应用其他规则时的默认规则。

    function foo() {
     //默认规则下,this指向全局对象,即顶层作用域
     console.log( this.a ); 
    }
    
    var a = 2;
    foo()//2
    

    怎么知道这里应用了默认绑定呢?可以通过分析调用位置来看看 foo()是如何调用的。在代码中,foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用 默认绑定,无法应用其他规则。

    这里有一个微妙但是非常重要的细节,虽然this的绑定规则完全取决于调用位置,但是只有 foo() 运行在非严格模式下时,默认绑定才能绑定到全局对象;在严格模式下调用 foo() 则不影响默认绑定:

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

    以上代码混合使用了严格模式和非严格模式,因此foothis不受严格模式影响,但混合使用严格模式是不提倡的,幸运的是es6默认是严格模式

    隐式绑定

    当一个函数的引用被一个对象持有时(作为该对象的方法),那么该函数的this就绑定在了这个对象上。通常这在声明一个对象,并将一个已声明的函数作为该对象属性时触发。

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

    obj对象声明时,foo作为obj的一个属性,因此其this被隐式绑定到了obj上,因为obj持有对foo的引用。

    举个栗子

    function foo() { 
     console.log( this.a ); 
    } 
    
    // obj2.foo引用了foo函数
    var obj2 = { 
     a: 42, 
     foo: foo 
    }; 
    
    //obj1.obj2 引用了obj1对象
    var obj1 = { 
     a: 2, 
     obj2: obj2 
    }; 
    
    //但是foo中的this永远指向直接持有它的引用的那个对象,即obj2
    obj1.obj2.foo(); // 42
    

    一个函数的引用被一个对象持有,而这个对象的引用又被另一个对象持有,另一个对象的引用再被另一个对象持有...,这就像一条项链,但是不管层次有多深,这个函数的this永远指向直接持有它的引用的那个对象。

    隐式丢失

    function foo() { 
     console.log( this.a ); 
    } 
    var obj = { 
     a: 2, 
     foo: foo 
    }; 
    var bar = obj.foo; // 函数别名!
     
    var a = "oops, global"; // a 是全局对象的属性
    bar(); // "oops, global"
    

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

    再看一个栗子,发生在传入回调函数时

    function foo() { 
     console.log( this.a ); 
    } 
    function doFoo(fn) { 
     // fn 其实引用的是 foo 
     fn(); // <-- 调用位置!,很明显,这是个默认绑定
    }
    var obj = { 
     a: 2, 
     foo: foo 
    }; 
    var a = "oops, global"; // a 是全局对象的属性
    doFoo( obj.foo ); // "oops, global"
    

    参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一 个例子一样。

    如果把函数传入语言内置的函数而不是传入你自己声明的函数,会发生什么呢?结果是一 样的,没有区别:

    function foo() { 
     console.log( this.a );
    }
    var obj = { 
     a: 2, 
     foo: foo 
    }; 
    var a = "oops, global"; // a 是全局对象的属性
    setTimeout( obj.foo, 100 ); // "oops, global"
    

    以上的栗子再次向我们证明了,函数this是在运行时绑定的,与声明位置无关。

    显式绑定

    显示绑定就是利用js提供的一些内置函数,将this绑定到指定的上下文中。

    function foo() { 
     console.log( this.a ); 
    } 
    var obj = { 
     a:2 
    }; 
    //执行时,foo的this就是obj了
    foo.call( obj ); // 2
    

    显式绑定仍然无法解决丢失绑定问题。

    硬绑定

    硬绑定是显式绑定的一个变种。

    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( window ); // 2
    

    很好理解,就是在函数运行时再把这个函数绑定到我们制定的this上。

    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() { 
     return fn.apply( 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,bind(..)会返回一个硬编码的新函数,它会把你指定的参数设置为this的上下文并调用原始函数。

    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
    

    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
    

    new 绑定

    使用new来调用函数时(函数也是对象),或者说发生构造函数调用时,会自动执行下面的操作:

    1. 创建(或者说构造)一个全新的对象。
    2. 这个新对象会被执行原型链[[Prototype]] 连接。
    3. 这个新对象会绑定到函数调用的 this
    4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
    function foo(a) { 
     this.a = a; 
    } 
    var bar = new foo(2); 
    console.log( bar.a ); // 2
    

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

    规则的优先级

    实际判断时,一个场景可能存在多个规则,因此判定时需要由高优先级往下判定。

    可以按照下面的顺序来进行判断:

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

    2. 函数是否通过callapply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象。

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

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

    规则例外

    1. 将null或undefined作为this进行显式绑定 使用bind(),apply()等显式绑定方法时,null或undefined作为要绑定的的this传入

    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()。根据我们之前说过的,这里会应用默认绑定。

    3.软绑定

    硬绑定很好地解决了隐式绑定可能会无意间将this绑定在顶级作用对象(严格模式下,为undefined)上的问题,但降低了其灵活性,我们要的结果是,保留其灵活性,既能绑定到指定的this上,但又不想让它默认绑定到全局对象上,解决方法就是软绑定。

    通俗的说,就是有一个默认值,指定了绑定对象的话就绑定到指定的对象上,否则就绑定到默认对象。

    //实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改 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 === (window || global)) ? 
     obj : this,
     curried.concat.apply( curried, arguments ) 
     ); 
     }; 
     bound.prototype = Object.create( fn.prototype ); 
     return bound; 
     }; 
    }
    

    总结

    如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。

    找到之后就可以顺序应用下面这四条规则来判断 this 的绑定对象。

    1. new 调用?绑定到新创建的对象。
    2. call 或者 apply(或者 bind)调用?绑定到指定的对象。
    3. 由上下文对象调用?绑定到那个上下文对象。
    4. 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。

    箭头函数不会以上四条标准的绑定规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论this绑定到什么)。这和我们创建一个变量来保存当前的this的效果是一样的。


    起源地下载网 » [深入JS]-this全面解析

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元