this 总结(下篇)

159 阅读4分钟

绑定规则


默认绑定

如下代码

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

foo执行时,this.a 被解析成了全局对象a,函数调用时应用了this的默认绑定,因此this指向全局对象。

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

function foo(){
    'use strict'
    console.log(this.a)
}
var a = 2;
foo(); //TypeError: this is undefined

其次,在严格模式下调用,默认绑定也会生效

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


隐式绑定

函数的调用位置是否含有上下文对象,或者说是否被某个对象拥有或者包含,隐式绑定规则会把函数中的this绑定到这个上下文对象。

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

需要注意的是,对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。

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

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

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

虽然bar 是obj.foo的一个引用,但实际上,它引用的是foo函数本身,因此,此时的bar是一个不带任何修饰的函数调用,因此应用了默认绑定。同理,传入回调函数是也是如此,如下代码

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

将函数传入内置函数也是同理,

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

显式绑定

与隐式绑定不同的是,显示绑定无需在一个对象内部 包含一个指向函数的属性,并通过这个属性间接引用函数,而是通过使用函数的call和aplly方法。此两者的第一给参数是一个对象,在调用函数时将其绑定到this。如下代码

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

  需要注意的是,显式绑定依然无法解决丢失this绑定的问题,尽管call中的this指向了obj,但是,参数fn依然是函数别名,依然是没有任何修饰的函数调用。如下代码

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

但是我们可以使用显式绑定的变种来解决这个问题

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

需要注意到是,硬绑定的函数不可能再修改它的this,因为我们在该函数的内部手动调用来foo.call(obj),因此强制把foo的this绑定到了obj。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。

创建一个可以复用的辅助函数

function bind(fn,obj) {
    return function(){
        return fn.apply(obj,arguments)
    }
}

foo(something){
    console.log(this.a,something)
    return this.a + something
}

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(someting){
    console.log(this.a,something)
    return this.a+something
}
foo.bind()

API调用的上下文

第三方库的许多函数,以及JavaScript语言和宿主环境中许多新的内置函数,都提供了一些可选参数,通常被称作上下文,其作用和bind()一样,确保你的回调函数使用指定的this,举例

function foo(el){
    console.log(el,this.a)
}
var a='global'
var obj={
    a:2
}

[1,2,3].forEack(foo,obj)

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

绑定例外

  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
    var bar = foo.bind(null,2)
    bar(3) //a:2,b:3


  2. 间接引用

this词法

以上规则都无法作用于箭头函数,此函数根据外层作用域来决定this

function foo(){
    return ()=>{
        //this继承自foo
        console.log(this.a)
    }
}
var obj1={
    a:2
}
var obj2={
    a:3
}
var bar=foo.call(obj1)
bar.call(obj2)//2

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