探究this的具体指向:教你轻松拿捏this

167 阅读4分钟

JavaScript 中,this 是一个关键字,它根据函数执行的上下文(context),指向函数调用对象。this 的值并不是在函数定义时确定的,而是在函数被调用时根据调用方式动态绑定的。这意味着 this 的具体指向取决于函数是如何被调用的。下面是this 的几种绑定规则:

默认绑定 和 严格模式

当函数作为普通函数被调用时,this 指向全局对象。在浏览器环境中,这个全局对象是 window

var x = 2;
var obj = {
    x: 1,
    foo: function () {
        console.log(this);
        console.log(this.x);
    }
}
// 函数体 
var foo = obj.foo
// 作为普通函数调用  this指向全局
foo()  // 2

如果启用了严格模式 ("use strict"),普通函数调用时, this 将会是 undefined

"use strict";
var x = 2;
var obj = {
    x: 1,
    foo: function () {
        console.log(this);
        console.log(this.x);
    }
}
var foo = obj.foo
// 严格模式下 普通函数调用, this 指向 undefined
foo()  

那么为什么在严格模式下普通函数的this就为undefined呢?

严格模式的目的是通过引入更严格的解析和执行规则来帮助开发者编写更安全、更清晰的代码,有四点好处:

  1. 避免意外的全局变量访问:在非严格模式下,this 指向全局对象意味着任何对 this 属性的访问都可能无意中修改全局状态。这可能导致难以调试的问题。
  2. 鼓励使用显式的上下文绑定:严格模式促使开发者更清楚地定义他们期望的 this 值,比如通过 .call(), .apply(), 或 .bind() 方法,或者确保函数总是作为方法调用。
  3. 提高代码可移植性:由于不同环境(如浏览器与 Node.js)的全局对象不同,依赖于隐式全局对象的行为可能会导致跨环境兼容性问题。严格模式下的 thisundefined 可以减少这类问题。
  4. 促进更好的编码习惯:它提醒开发者注意 this 的绑定,有助于写出更健壮和不易出错的代码。

隐式绑定

当函数作为对象的方法被调用时,this 指向调用该方法的对象。

var x = 2;
var obj = {
    x: 1,
    foo: function () {
        console.log(this);
        console.log(this.x);
    }
}
// 作为对象的方法调用时  this指向调用该方法的对象
obj.foo()  // 1     this 指向 obj

显式绑定

在这里我们来看一段代码:

var name = "李四"
var a = {
    name: "张三",
    func1: function () {
        console.log(this.name);
    },
    func2: function () {
        setTimeout(function () {
            this.func1()
        }, 1000)
    },
}
a.func2()  

可以猜一猜结果输出的应该是谁的名字?

结果显示为TypeError: this.func1 is not a function

在这里我们使用了setTimeout()函数,它是一个普通函数,this指向全局,但是全局中并没有func1()这个函数,它是在a对象中的,所以报错了。可见,this在有些时候并不是一件好事,他在普通函数中意义并不大,这也难怪在严格模式下要把普通函数调用中的this禁掉。

因此,为了防止这种需要思考的事情发生,设计者干脆添加了几种显示绑定来使程序员可以规定this指向的的对象是什么。

在这里,使用 call(), apply(), 或 bind() 方法可以显式地设置函数内部的 this 值。这些方法允许你指定 this 应该引用哪个对象,并且可以立即调用函数(对于 call()apply())或者创建一个新函数,其 this 被永久绑定到给定的对象(对于 bind())。

我们来修改一下上面的代码,使用call()this指向a

var name = "李四"
var a = {
    name: "张三",
    func1: function () {
        console.log(this.name);
    },
    func2: function () {
        setTimeout(function () {
            // this.func1()    // setTimeout是普通函数 this指向全局  全局没有func1()  报错
            // this 被指定了
            this.func1()
        }.call(a), 1000)   // 改变this指向 a
    },
}
a.func2()    // 张三

构造函数的 this

当函数通过 new 关键字作为构造函数被调用时,this 指向新创建的实例对象

function Person(name) {
    this.name = name;
}
const zhang = new Person('张三');
console.log(zhang.name); // 张三   this 指向 zhang 这个实例

箭头函数中的 this

箭头函数没有自己的 this 绑定;它们从最近的非箭头函数父级作用域继承 this。这使得箭头函数非常适合用作回调函数或需要访问外部 this 的地方。

const obj = {
    name: '张三',
    foo: () => {
        console.log(this.name); // 使用了箭头函数 this 指向上一层 全局
    }
};
obj.foo(); // undefined 在全局中没有foo()

总结

this 在 JavaScript 中是一个非常灵活的概念,它的值由函数的调用方式决定。理解 this 的不同绑定规则对于编写正确和高效的代码至关重要。掌握 this 的工作原理可以帮助开发者避免一些常见的陷阱,比如意外地操作了全局对象或无法正确访问对象属性。此外,了解如何使用 call(), apply(), bind() 以及箭头函数可以让你更好地控制 this 的行为。