前端进阶课程之this指向

223 阅读6分钟

目录:

  1. javascript中为什么会有this这样的语法
  2. 怎样判断this指向?
  3. settimeout或事件回调函数,箭头函数等特殊情况下this指向

一: 曾经学习this指向的惨痛经历

在学习本课程的this指向之前,想和大家分享一下学习this的悲惨经历,可能大家也都曾经看过很多很多关于this指向的文档,各种个样,琳琅满目,大部分说的都是常见那几种情况下,this是如何指向的,例如,new的情况下this指向实例,又或者直接调用指向window等等,当时看了好像有点豁然开朗了,但是做题的时候,又说有特殊情况,按之前理解的又走不通了,或者过段时间,又忘记啦。

其实说到底,还是没有真正理解this到底是如何运作的?接下来的文章希望可以带领大家去真正理解this到底怎么用?

二: javascript中为什么会用this这样的语法糖。

this的设计,完全是由于javascript中的数据(或者准确的说是函数)在内存中的存储方式所决定的。

具体来说:函数在内存中是占据一个独立的堆内存,那么其他地方调用这个函数其实是有一个地址指向该堆内存,因为,函数可能会有很多中调用方式,可能是直接调用,也可能是通过某个对象调用,不同的调用方式,那么在函数内部怎么区分呢?

有的人可能会说,采用call和apply啊,这样就可以手动的传入一个context,这样函数中就可以根据这个context去区分是哪种调用方式了,这位同学真聪明,js本身就是这样去处理的,但是这样有个什么问题呢?每次都需要我们手动的绑定一个context传入函数里,可以省略这一步吗?可以,怎么省略呢?

this啊,啦啦啦,this登场了,有了this,我们就不需要显示的绑定context,而是函数在调用的时候,会默认在函数内部生成一个this,它会隐式的帮我们指定一个对象。当然,这样就带来了一个问题,不同的调用情况,this指向的内容是不一样的(这也就衍生除了大家最头疼的问题,到底怎么判断this的指向?)

参考文档:www.ruanyifeng.com/blog/2018/0…

三:如何判断this的指向?

不同的函数调用方式,this指向都会不同

  1. 那么首先,我们来说一下函数的调用: 记住函数只有一种调用方式:fn.call() 或者fn.apply();(下文都以call为例解释) 其他方式都是基于该方式的语法糖
function fn(){
    
}
var obj = {
    fn: fn
}
我们大家最常见的调用方式:
fn();
obj.fn();

其实 转换一下:
fn() === fn.call() === fn.call(undefined) //此时传入的是undeifined,为什么说函数中this指向window呢?后面有解答
obj.fn() === fn.call(obj)

而call和apply的第一个参数不就是传入的context.
注意:
obj1.obj2.fn() <===> fn.call(obj1.obj2);
此时fn函数里this指向的是obj1.obj2,并不是obj1

所以,以后怎么判断this指向呢?将所有函数调用的方式统一转换成call方式去调用,看看第一个参数到底是什么就可以啦

参考文档:www.zhihu.com/question/19…

四:常见细节说明:

  1. fn.call() 在非严格模式下,传入unefined,或者null时,函数内部this指向window.但是在严格模式下,call方法传什么,this指向什么
function fn () {
    console.log(this); // 此时this === window
}
function gn () {
    "use strict";
    console.log(this); //此时 this === undefined
}
fn() <===> fn.call();
gn() <===> gn.call();

  1. 回调函数中的this指向什么? settimout和setinterval回调函数中的this永远指向window(前提是回调函数没有使用箭头函数,如果使用了箭头函数,则回调函数中的this指向与异步函数所在的词法作用域中的this保持一致)
//回调函数为普通函数
function fn(){
   setTimeout(function(){
       console.log(this.a);//此时this.a === window.a 为undefined
   },10)
}
var obj = {
   a: 3,
   f: fn
}
obj.f();

//回调函数为箭头函数
function fn(){
   setTimeout(() => {
       console.log(this.a); // 此时this.a === obj.a 为3
   },10)
}
var obj = {
   a: 3,
   f: fn
}
obj.f();
  1. 箭头函数中的this指向什么? 箭头函数比较特殊,它的this不是由函数的调用位置决定,而是由它的词法作用域中的this所决定。也就是说它的this不是在函数调用时确定,而是在函数编写时就已经决定,即它的词法作用域
var obj = {
    fn: function () {
        console.log(this); // 此时this === obj    
    },
    gn: () => {
        console.log(this); // 此时this === window
    }
}
obj.fn();
obj.gn();
  1. this什么时候可以省略?

这个可能是我自己在学习过程中产生的一个问题,就是有时候看代码好像this可以省略写,但是我们分析的时候,还是按有this的情况去分析,实际上是我可能混淆了,只要记住一句话:this什么时候也不可以省略,如果没写,就是没写,并不存在什么隐式的调用this的说法。

那么有的同学可能问了,在window中就可以省略啊。

例如如下代码:
var a = 10;
console.log(a === this.a); //true
这个时候,是因为在全局作用域下,window可以省略不写,
所以正常思路是a === window.a, 而同时在全局作用域下this === window, 所以 a === this.a;

那么如果是在window下调用函数:
function fn () {
    console.log(this);
}
fn() === fn.call()//此时函数内this === window //因为此时是由于call如果传入undefined,则函数内默认绑定window,
window.fn() === fn.call(window)//此时this === window,因为call方法显式的传入了window。

也就是说,在非严格模式下:fn() === fn.call() === window.fn() === fn.call(window);

但是在严格模式下
function gn() {
    'use strict';
    console.log(this);
}
gn() === gn.call()// 此时this指向undefined
window.gn() === gn.call(window); //此时this指向window 

也就是说,严格模式下,传入undefined,浏览器没有默认绑定window,但是不管是严格模式,还是非严格模式下,如果是call显示的传入一个对象,那么函数中this就一定指向该对象。

五:总结:

  1. 以后在遇到判断this指向的时候,统一全部转换成call方法去分析,同时结合上面说到的回调函数,箭头函数,去分析即可。

  2. this 任何时候都不可以省略,全局作用域下只是因为this === window,所以看上去可以不写this,

  3. 在全局作用域下,直接调用函数fn和手动通过window.fn()调用函数,在非严格模式下,可以认为是等价的,但是在严格模式下,函数内的this指向就不同了。

参考文档: developer.mozilla.org/zh-CN/docs/…