一文把 JavaScript 中的 this 聊得明明白白


在这里插入图片描述

1.this 是什么?

this 是一个代表指向性的关键词,他所指向的是当前整个代码执行上下文中它所属的对象

this会拥有不同的值,具体取决于它所使用的位置:

  • 在方法中,this 指的是所有者对象。
  • 单独的情况下,this 指的是全局对象。
  • 在函数中,this 指的是全局对象。
  • 在函数中,严格模式下,this 是 undefined
  • 在事件中,this 指的是接收事件的元素

相比于其他语言,在 JavaScript 中 函数的 this 关键字的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。

在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)。this 不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同。ES5 引入了 bind 方法来设置函数的 this 值,而不用考虑函数如何被调用的。ES6 引入了箭头函数,箭头函数不提供自身的 this 绑定(this 的值将保持为闭合词法上下文的值)。


2.this的指向

当前执行上下文(global、function 或 eval)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。

2.1 全局上下文的 this 指向

全局上下文环境中(在任何函数体外部),直接单独执行,不论开不开启严格模式this的指向都是 全局 window 对象。

<script>
   "use strict";   //开启严格模式
    console.log(this);    //window 对象
</script>

2.2 函数(普通函数)上下文中的 this 指向

在函数内部,this的值取决于函数被调用的方式。

如下的代码在未开启严格模式下,且 this 的值不是由该调用设置的,所以 this 的值默认指向也是全局对象 window

<script>
    function fn() {
        console.log(this);   //全局window 对象
    }
    fn()
</script>

即使函数经过 深层次嵌套,this 依然指向 全局 window 对象

<script>
    function fn() {
        console.log(this);   //全局window 对象
        function user() {
            console.log(this);  //全局window 对象
            function type() {
                console.log(this);   //全局window 对象
            }
            type()
        }
        user()
    }
    fn()
</script>

然而,在开启严格模式下,如果进入执行环境时没有设置 this 的值,并且 JavaScript 严格模式下不允许默认绑定。因此在函数中使用时,this 是未定义的,指向 undefined

<script>
    "use strict";   //开启严格模式
    function fn() {
        console.log(this);   //  undefined
    }
    fn()
</script>

2.3 事件处理程序中的 this 指向

在 HTML 事件处理程序中,this 指向的是接收此事件的 HTML Dom 元素

<button onclick="fn(this)">
    按钮点击
</button>

<script>
    function fn(val) {
        console.log(val);     // <button οnclick="fn(this)">按钮点击</button>
    }
</script>

在这里插入图片描述


2.4 以对象的方式调用时 this 的指向

如下案例中,person 属于 fullName 函数的拥有者,所以当函数作为对象里的方法被调用时,this 指向为调用该函数的对象

let person = {
  firstName: "Bill",
  lastName : "Gates",
  id     : 678,
  fullName : function() {
      console.log(this);
      return this
  }
};
console.log(person.fullName());

在这里插入图片描述
调用返回值是 person 整个对象。


2.5 构造函数中的 this 指向

在这里插入图片描述

当一个函数用作构造函数时(使用new关键字),它的 this 被绑定到正在构造的新对象

function Person(name) {
    this.name = name;
    console.log(this);	// {name:"实例参数"}
}
new Person("实例参数")

在这里插入图片描述
注意

虽然构造函数返回的默认值是 this 所指的那个对象,但它仍可以手动返回其它的对象(如果返回值不是一个对象,则返回 this 对象)。

如例:

function Person(name) {
    this.name = name;
    return { a: 666 }
}
let type = new Person("实例参数")
console.log(type.a);  //666

如上例,在调用构造函数的过程中,手动的设置了返回对象,与this绑定的默认对象被丢弃了。(这基本上使得语句“this.name = name;” 成了“僵尸”代码,但实际上这并不是真正的“僵尸”,这条语句实际还是执行了,但是对于外部没有任何影响,因此完全可以忽略它)。


2.6 在 上下文中 this 的指向。

this 在 类 中的表现与在函数中类似,因为类本质上也是函数(构造函数),但也有一些区别和注意事项。

在类的构造函数中,this 是一个常规对象。类中所有非静态的方法都会被添加到 this 的原型中
在这里插入图片描述

class Example {
    constructor() {
        const proto = Object.getPrototypeOf(this);
        console.log(proto);
        console.log(Object.getOwnPropertyNames(proto));  // ['constructor', 'first', 'second']
    }
    first() { }
    second() { }
    static third() { }
}
new Example()

在这里插入图片描述
注明

静态方法不是 this 的属性,它们只是类自身的属性。


2.7 派生类中的 this 指向

不像基类的构造函数,派生类的构造函数没有初始的 this 绑定。在构造函数中调用 super() 会生成一个 this 绑定。

在这里插入图片描述
派生类不能在调用 super() 之前返回,除非其构造函数返回的是一个对象,或者根本没有构造函数。

class Base { }   //Base 为基类:
class Bad extends Base {
    constructor() { 
        console.log(this);    //此处输出会报错
        super()
        console.log(this);    // 此处输出不会报错
    }
}
new Bad(); 

在这里插入图片描述


2.8 原型链 中的 this 指向

对于在对象原型链上某处定义的方法,同样的概念也适用。如果该方法存在于一个对象的原型链上,那么 this 指向的是调用这个方法的对象,就像该方法就在这个对象上一样。

let a = {
    f: function () {
        console.log(this);   //{a: 1, b: 4}
        return this.a + this.b;
    }
};
let body = Object.create(a);
body.a = 1;
body.b = 4;
console.log(body.f()); // 5

概述
上例中,对象 body 没有属于它自己的 f 属性,它的 f 属性继承自它的原型。虽然最终是在 a 中找到 f 属性的,这并没有关系;查找过程首先从 body .f 的引用开始,所以函数中的 this 指向 body 。也就是说,因为 f 是作为 body 的方法调用的,所以它的this指向了 body。概括说就是:在原型链中,函数都会把 this 绑定到设置或获取属性的对象身上。这是 JavaScript 的原型继承中的一个有趣的特性。


2.9 箭头函数的 this 指向

与常规函数相比,箭头函数对 this 的处理也有所不同,简而言之,使用箭头函数没有对 this 的绑定。在常规函数中,关键字 this 表示调用该函数的对象,可以是窗口、文档、按钮或其他任何东西。对于箭头函数,this 关键字始终表示定义箭头函数的对象

简而言之:

  • 箭头函数的 this 指向取决于当前箭头函数声明的环境(执行上下文)。
  • 执行上下文又分为:全局执行上下文、函数级别上下文、eval 执行上下文
  • 因为箭头函数没有自己的 arguments 和 this,箭头函数需要获取函数定义时所在的 context 的 this,箭头函数定义在哪个作用域中,它的 this 就继承当前作用域 this 的指向。

2.9.1定义在全局 作用域中

<script>
    let typefn = () => { console.log(this) }    // 指向 全局window 
</script>

2.9.2定义在对象中

由于箭头函数没有自己的 this,所以 它的 this 使用的是 父级作用域的 this。

var name = "关羽";
let obj = {
    user: "马超",
    nest: {
        talk: () => {
            console.log(this.name);	// 指向了 window,所以打印 关羽
        }
    }
}
obj.nest.talk();
   //  {} 不会生成作用域,即箭头函数声明在全局作用域中,this 指向全局
	//   通过 var 声明的变量会被认作是 window 的属性 即 var name = "关羽"; window.name == "关羽"

注意
如上案例中,由于 name 是通过 var 关键字声明的变量,属于ES5 的方法,具有变量提升的特性,会默认成为 window 对象的属性 而箭头函数中的 this 又直接指向全局 window 对象,所以,就可以直接在 this 身上 拿到 name的 值,但是如果:name 是通过 ES6的 let关键字 声明的 ,由于 let 不会变量提升,不会绑定到 window 对象身上,所以 在箭头函数中,输出 this.name 会找不到键值对。

2.9.3定义在普通函数中

如上 同理 箭头函数没有自己的 this,所以 它的 this 使用的是 父级作用域的 this。

function fn(){
  let num = ()=>{
    console.log(this);	// window,箭头函数没有自己的 this,它的 this 使用 fn 的 this
  }
  num();
}
fn();

3. 修改 this 的指向

JS 提供了3个方法 可以用来重定向 this 的指向

  • call()
  • apply()
  • bind()
let obj = { a: 1, b: 2 }
function foo() {
    console.log(this);   //默认指向全局 window 对象
}
foo.call(obj); 	//{a: 1, b: 2}    //改变指向,使其foo 的this 指向 obj
foo.apply(obj);	 //{a: 1, b: 2}   //改变指向,使其foo 的this 指向 obj
foo.bind(obj)();  //{a: 1, b: 2}   //改变指向,使其foo 的this 指向 obj

当有参数传递时候:

let obj = { a: 1, b: 2 }
function foo(val, num) {
    console.log(val,num);
}
 foo.call(obj, 3, 4);
foo.apply(obj, [5, 6]);  //第二个参数是一个数组,数组里面的元素会被展开传入foo,作为foo的参数
 foo.bind(obj)(7);

call、apply 和 bind 的区别?

相同点:

都是用来改变函数中this的指向的。

不同点:
(执行方式不同)

callapply 都能立即执行函数并且改变this的指向;

bind 则是将函数返回,供后续调用。bind不改变原函数的this指向,而是创建了一个新的函数。调用方式也不同,需要使用函数名加()来调用,如:func.bind(obj)(arg1, arg2)。

(参数传递方式不同)
callapply 传递参数的方式不同,call接受一个参数列表,如:func.call(obj, arg1, arg2);apply则接受一个数组数组作为参数,如:func.apply(obj, [arg1, arg2])。

bind 参数传递 和 call 是一样的

在函数中的形参,会把相应的参数,进行展开接收。


总结

以上就是本章节,给大家带来的 JavaScript 中 this 关键字的 作用以及使用方法,this 这个知识点,还是比较重要的,本章的知识点内容,略微有点多,但是知识点,博主整理的几乎涵盖了 this 的所有使用场景。所以本章节内容还是很值得大家阅读的。


🚵‍♂️ 博主座右铭:向阳而生,我还在路上!
——————————————————————————————
🚴博主想说:将持续性为社区输出自己的资源,同时也见证自己的进步!
——————————————————————————————
🤼‍♂️ 如果都看到这了,博主希望留下你的足迹!【📂收藏!👍点赞!✍️评论!】
——————————————————————————————

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旧梦星轨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值