重拾 JavaScript 中 的 this

this 指向的是什么?相信这个问题大家都探究过,但是我们当中很多人选择了一种舒适的方式去回避了它。回头看 N 久之前,自己的项目里面是不是也存在这样的语句:let _this = this 。
让我们重新出发,认真的理解一下 JS 中 this 的机制吧。

在 JavaScript 中,this 是一个很特别的关键字,被自动定义在所有函数的作用域中。当一个函数被调用时,会创建一个执行上下文。这个上下文会包含函数在哪里被调用(调用栈)、函数的调用方法 、传入的参数等信息。this 就是上下文的其中一个属性,会在函数执行的过程中用到。

this 是在运行时进行绑定的,this 的绑定只取决于函数的调用方式。 即 this 是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

1、绑定规则

1.1 默认绑定
默认绑定就是 独立函数调用 :如果在严格模式下,此时 this 指向为 undifined ;如果在非严格模式下,此时 this 指向为 window。

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

var a = '123';

fn(); // 123

严格模式下:

"use strict";

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

var a = '123';

fn(); // Uncaught TypeError: Cannot read property 'a' of undefined

1.2 隐式绑定
隐式绑定是 函数调用位置是否被某个对象拥有或者包含

function fn() {
    console.log(this.a)
}

var obj = {
    a: '123',
    Fn: fn
}

obj.Fn() // 123

// obj.Fn() 调用时, this 被绑定到了 obj 上

对象属性引用链中只有最后一层会影响调用位置。

function fn() {
    console.log(this.a)
}

var obj1 = {
    a: '000',
    fn: fn
}

var obj2 = {
    a: '111',
    obj1
}

obj2.obj1.fn(); // 000

1.3 强制绑定
使用函数的 call()、apply() 或者 bind()。
call()、apply() 它们的第一个参数是一个对象,它们会把这个对象绑定到 this ,接着在调用函数时指定这个 this 。bind() 类似,只是创建一个新的函数,需要手动调用一下。

function fn() {
    console.log(this.a)
}

var obj = {
    a: '123'
}

fn.call(obj) // 123

call()、apply() 、bind()的区别

function callFn(p1, p2, p3){
    console.log(`${this.name} is learning ${p1}、${p2}、${p3}`)
}

function applyFn(arr){
    console.log(`${this.name} is learning ${arr.join('、')}`)
}

var person = {
    name: 'jens'
}

// call
callFn.call(person, 'koa', 'nodejs', 'redis'); 
// jens is learning koa、nodejs、redis

// apply
applyFn.call(person, ['koa', 'nodejs', 'redis']); 
// jens is learning koa、nodejs、redis

// bind
callFn.bind(person, 'cooking', 'eating', 'fatting')(); 
// jens is learning cooking、eating、fatting

1.4 new绑定
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

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

var bar = new foo('111');

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

2、如何使用规则

按照如下顺序应用规则:

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

  2. 函数是否通过 call、apply 或者 bind 调用?如果是的话,this 绑定的是传入的对象。
    var bar = foo.call(obj)

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

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

  5. 如果把 null 或者 undefined 作为 this 的绑定对象传入 call 、 apply 或者 bind , 实际应用的是默认绑定规则(同第4点)。

  6. 箭头函数会继承外层函数调用的 this 绑定,且绑定无法被修改(在声明时候绑定this)。


function foo() {
    return (a) => {
        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 , 
// 箭头函数的绑定无法被修改。

function foo() { 
    setTimeout(() => { 
        // 这里的 this 在此法上继承自 foo() 
        console.log( this.a ); 
    },100); 
}

var obj = { a:2 };

foo.call( obj ); // 2

相关试题

// test1
var Test ={
  foo:"test",
  func:function () {
    var self = this;
    console.log(this.foo);
    console.log(self.foo);
    (function () {
      console.log(this.foo);
      console.log(self.foo);
    })();
  }
};
Test.func();

运行结果:

test
test
undefined // 立即执行函数,适用 默认规则,非严格模式下 this 指向 window, window下没有foo属性, 故为 undefined
test

// test2
var length = 10;
function fn() {
    console.log(this.length);
}
 
var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};
 
obj.method(fn, 1);

运行结果:

10
2
method()内部执行的是fn()函数,
而fn()函数绑定的对象是window,即window.fn()

全局函数fn同时也属于arguments数组中的一员,
即当作为arguments成员之一调用的时候,
其作用域就绑定到了arguments上,
this也就是指向了arguments对象

// test3
var that = this;

setTimeout(function() {
    console.log(this === that); // true
});

(function(){
    console.log(that === this); // true
})();

// 在浏览器中setTimeout、setInterval和匿名函数执
// 行时的当前对象是全局对象window

posted @ 2019-11-19 13:42  jens1990  阅读(75)  评论(0编辑  收藏  举报