JavaScript的this运行机制

this基本概念

在js中,this是一个关键字,它会被自动定义在js所有函数的作用域中,JavaScript高程在函数一章指出:定义一个函数会获得this和protype两个属性,而this很特殊在于this可以用做声明指向某个对象,this指向的值在运行时最后调用的那个函数确定,于是便有了谁最后调用this的函数,this就指向它

this到底是什么

this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

函数绑定方式

this的绑定规则和函数的调用方式有着重大联系

普通函数调用

function foo() { 
    console.log( this.a );
}

var a = 2;

foo(); // 2

new方式调用

function foo(a) { 
    this.a = a;
} 

var bar = new foo(2);

console.log( bar.a ); // 2

call,apply,bind的方式调用

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

通过apply

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

由于硬绑定是一种非常常用的模式,所以在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

箭头函数方式调用

es6新增的箭头函数声明方式,本身是不带this的,按照ECMA的标准来说,箭头函数的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!

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

作为对象的属性调用

function foo() { 
    console.log( this.a );
}

var obj = { 
    a: 2,
    foo: foo 
};

obj.foo(); // 2

对象属性引用链中只有最顶层或者说最后一层会影响调用位置。举例来说:

function foo() { 
    console.log( this.a );
}

var obj2 = { 
    a: 42,
    foo: foo 
};

var obj1 = { 
    a: 2,
    obj2: obj2 
};

obj1.obj2.foo(); // 42

this绑定优先级

隐式绑定和显式绑定哪个优先级更高?

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绑定比隐式绑定优先级高。但是new绑定和显式绑定谁的优先级更高呢

被忽略的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

间接引用

另一个需要注意的是,你有可能(有意或者无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则
间接引用最容易在赋值时发生:

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会被绑定到全局对象

小结

如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断 this的绑定对象。
1.由new调用?绑定到新创建的对象。
2.由call或者apply(或者bind)调用?绑定到指定的对象。
3.由上下文对象调用?绑定到那个调用上下文对象。
4.默认:在严格模式下绑定到undefined,否则绑定到全局对象。
一定要注意,有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略this绑定,你可以使用一个DMZ对象,比如a=Object.create(null),以保护全局对象。
ES6中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this绑定(无论this绑定到什么)。这其实和ES6之前代码中的self=this机制一样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值