this指向原则透析

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战markdown

smalld9fcb449fa428b1cc001b40527b990761622906649.jpg

1.为何要用this?

  • this提供了一种更优雅的方式来隐式传递 一个对象引用。
function identify() {
    return this.name.toUpperCase();
}
function speak() {
    var greeting = "Hello, I'm " + identify.call( this );
    console.log( greeting );
}
var me = {
    name: "Kyle"
};
var you = {
    name: "Reader"
};
identify.call( me ); // KYLE
identify.call( you ); // READER
speak.call( me ); // Hello, 我是 KYLE
speak.call( you ); // Hello, 我是 READE
复制代码

这段代码能够在不一样的上下文对象(me 和 you)中重复使用函数 identify() 和 speak(), 不用针对每一个对象编写不一样版本的函数。app

若是不使用 this,那就须要给 identify() 和 speak() 显式传入一个上下文对象。ide

function identify(context) {
    return context.name.toUpperCase();
}
function speak(context) {
    var greeting = "Hello, I'm " + identify( context );
    console.log( greeting );
}
identify( you ); // READER
speak( me ); //hello, 我是 KYLE
复制代码

2. this四条绑定规则

this 关键字, 被自动定义在全部函数的做用域中。函数

注意:this的指向在函数建立时肯定不了,被调用时才能肯定。post

咱们在找this指向时,要先找到调用位置,而后判断符合哪一条绑定规则,同时注意绑定规则的优先级。ui

2.1 默认绑定

在找不到函数的调用对象时,this

  • 非严格模式:函数中this会指向全局对象window。
  • 严格模式下,函数中this会指向 undefined。

注意: 对于默认绑定来讲,决定 this 绑定对象的并非调用位置是否处于严格模式,而是函数体是否处于严格模式编码

例1:函数调用前面没有对象spa

function sayHi(){
    console.log('Hello,', this.name);
}
    var name = 'YvetteLau';
    sayHi();
    
// 'Hello,' YvetteLau
复制代码

2.2 隐式绑定

经过对象调用函数,那么函数中this会指向最后调用它的对象。code

隐式丢失

1.将obj.fn()赋值给某个变量

虽然 bar 是 obj.foo 的一个引用,可是实际上,它引用的是 foo 函数自己,所以此时的 bar() 实际上是一个不带任何修饰的函数调用,所以应用了默认绑定。

以下示例, o.foothis 隐式绑定在了 o 对象上, 而 bar 引用了 o.foo 函数自己, 因此此时的 bar() 实际上是一个不带任何修饰的函数调用, 所以使用了 默认绑定 规则:

var o = {
    a: 1,
    foo() {
        console.log(this.a)
    }
}
​
var bar = o.foo
​
o.foo() // 1
bar()   // undefined
复制代码

2.将obj.fn()赋值给函数的形参

参数传递其实就是一种隐式赋值,所以咱们传入函数时也会被隐式赋值。示例中, bar(o.foo) 实际上采用了隐式赋值: callback = o.foo, 事实上跟上面的例子同样, 都是直接引用了 o.foo 函数自己, 因此形成了 隐式丢失:

function bar(callback) {
    callback()
}
​
var o = {
    a: 1,
    foo() {
        console.log(this.a)
    }
}
​
bar(o.foo) // undefined
复制代码

3.setTimeOut函数中的this

例2:setTimeOut中包含的函数,在后面执行时,并无对象去调用这个函数,因此也是默认绑定。

btn.onclick = function(){
    setTimeout(function(){
        console.log(this);
    },0)
}
btn.onclick();

// window
复制代码

虽然onclick函数是btn对象调用的,但setTimeout函数中的内容并没有在btn对象调用onclick函数时当即执行,等到setTimeout函数执行时,setTimeout函数找不到调用对象,这时变为默认绑定,非严格模式下,setTimeout函数内的this指向window

(setTimeOut任务是宏任务,会在下一轮事件循环执行。具体参见:juejin.cn/post/693532…)

2.3 显示绑定

咱们有时想要强制为某个函数绑定this,能够经过使用 call()apply() 方法来实现;

函数.call(要绑定this的对象, 参数1, 参数2, ...)

函数.apply(要绑定this的对象, [参数1, 参数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
​
复制代码

咱们建立了函数 bar(),并在它的内部手动调用 了 foo.call(obj) ,所以强制把 foo 的 this 绑定到了 obj。不管以后如何调用函数 bar,它 总会手动在 obj 上调用 foo。这种绑定是一种显式的强制绑定,所以咱们称之为硬绑定。

硬绑定ES5API

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

硬绑定时传入null/undefined,则会忽略本次硬绑定,采用默认绑定规则。

2.4 new绑定

当使用 new 操做符调用某个函数时,这个函数被构造调用

new 操做符在调用函数时作的工做:

  • 建立或构造了一个全新的对象
  • 这个新对象会被执行[[原型]]链接
  • 函数中的 this 会指向这个新对象
  • 若是被调用的函数没有返回(return), 则 new 表达式中的函数调用会自动返回这个新对象

3. 绑定规则的优先级

new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

判断this:

  1. 函数是否在 new 中调用(new 绑定)?若是是的话 this 绑定的是新建立的对象。
  2. 函数是否经过 call、apply(显式绑定)或者硬绑定调用?若是是的话,this 绑定的是 指定的对象。
  3. 函数是否在某个上下文对象中调用(隐式绑定)?若是是的话,this 绑定的是那个上 下文对象。
  4. 若是都不是的话,使用默认绑定。若是在严格模式下,就绑定到 undefined,不然绑定到全局对象。

4. 箭头函数

箭头函数没法使用上述规则。根据根据外层(函数或者全局)做用域来决定 this。

箭头函数的this指向它外层做用域调用时的this。

foo() 内部建立的箭头函数会捕获调用时 foo() 的 this。因为 foo() 的 this 绑定到 obj1, bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定没法被修改。(new 也不行!)

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 绑定(不管 this 绑定到什么)。

参考:

你不知道的JavaScript

相关文章
相关标签/搜索