箭头函数~你的this呢?(精辟细致)

前言

终于写完了,中途被绕晕了好几次不想写了,然后又作死的往不太清楚的地方编题,但被绕晕的原因终究是没弄懂箭头函数的指向,所以硬着头皮写下来了,算是我创作中耗费时间最久的一篇。

如果你此时也对this指向不是很清楚或者想练练this指向的题,请耐心读下去,希望对你有帮助。

this指向问题

关于 this 的指向问题,在笔试或者面试中常考的方面是有关箭头函数和普通函数之间的 this 指向,因此必须得记住下面三点

(1) 箭头函数是没有自己的 this ,而是继承外层最近的普通函数的上下文 this(如果外层没有普通函数,最终就会指向全局的上下文,在浏览器中即 window )。

(2) 箭头函数在被定义(或被创建)的时候就确认了 this 的指向(即包含箭头函数的上下文也就是箭头函数外层的 this),不管被谁调用的。

(3) 这个 this 的指向是静态的,改变不了。
普通函数则是最终谁调用的,this 就指向谁。如果最终没有谁调用,那就是由 window 调用的。

一阶------普通函数和箭头函数this的指向

js 复制代码
// 普通函数
function func1() {
    console.log(this, '1');
};
// 箭头函数
const func2 =  () => {
  console.log(this, '2');
}

func1() // window
func2() // window

普通函数是由谁调用,this 就指向谁,而箭头函数根据一开始的说法,定义的时候就确认了 this(即上下文)

普通函数 func1 由于是 window 调用的(即window.func1()),所以指向 window

func2 是箭头函数,根据箭头函数是没有自己的 this ,而是继承外层最近的普通函数的上下文 this(如果外层没有普通函数,最终就会指向全局的上下文,在浏览器中即 window ) ,因此它的 this 指向的是 window

二阶------在对象中判断普通函数和箭头函数的this指向(构造函数呢?)

函数在对象里创建

js 复制代码
let obj = {
    sex: "1",
    func1: function() {
        console.log(this, this.sex)
    },
    func2: () => {
        console.log(this, this.sex)
    },
}
obj.func1() // obj, 1
obj.func2() // window, undefined

普通函数经典的谁调用,this 就指向谁,obj 调用的普通函数 func1 ,所以指向 obj

由于箭头函数在被定义(或被创建)的时候就确认了 this 的指向 ,因此我们不管谁调用的,直接分析 obj 定义里的箭头函数,而箭头函数在 obj 中创建时,相当于在 obj 对象中创建了箭头函数 func2 ,而不是在普通函数 func2 中创建的(别钻进了牛角尖 ),func2 只是一个声明的 key 值,类似下面的伪代码。

js 复制代码
let obj = {
    () => {console.log(this)} // 但由于箭头函数是匿名函数,所以得声明一个func2
}

而又由于箭头函数没有自己的 this ,而是继承最近的普通函数的上下文 this ,所以它就往外找啊,先找到了 obj,但由于 obj 不是普通函数,所以 this 继续往外找,因此最后 func2 里的 this 指向 window

思考:在 window 里定义的箭头函数和在 obj 里定义的箭头函数,this 指向为什么是一样的?

js 复制代码
const func2 = () => {
    console.log(this);
}

const obj = {
    func2: () => {
        console.log(this);
    }
}
func2() // window
obj.func2() // window

第一个 func2 ,根据箭头函数是没有自己的 this ,而是继承外层最近的普通函数的上下文 this ,判断出 this 指向的是 window 这个上下文。

第二个 func2 同理,只是包了一层对象 obj 而已,但没有影响,obj 连函数都不算,所以 this 最终指向 window

同理,就算 obj 外面在包一层对象,箭头函数一样是指向 window 的。

js 复制代码
const parent = {
    sex: "1",
    obj: {
        sex: "2",
        func1: function() {
            console.log(this, this.sex)
        },
        func2: () => {
            console.log(this, this.sex);
        }
    }
}

parent.obj.func1() // obj, 2
parent.obj.func2() // window, undefined

普通函数仍然是谁调用就指向谁 ,不必在意普通函数所在的上下文,因此只需弄清最后谁调用方法就能知道 this 指向谁。parent.obj.func1() 这个过程,是谁调用了 func1() ,一看就知道是 parent.obj ,是 parent 里的 obj ,因此 this 是指向 obj 的。其实只是通过 parent 拿到了 obj 这个对象,然后再通过 obj 这个对象调用了 func1 ,所以这个过程中,parent 其实只是单纯的取值,真正调用方法的其实是 obj

func2 中,箭头函数继承外层最近的普通函数的上下文 this 。在这个例子中,由于箭头函数 func2 是在对象字面量内部定义的,而该对象字面量外层没有非箭头函数,因此箭头函数中的 this 将指向全局对象即 window

函数在对象外创建

js 复制代码
const test1 = function() {
    console.log(this,this.sex)
}
const test2 = () => {
    console.log(this,this.sex)
}

const obj = {
    sex: 1,
    func1: test1,
    func2: test2
}

obj.func1(); // obj, 1
obj.func2(); // window, undefined

同上述一种情况类似,普通函数func1最终由谁调用,this 就指向谁,而箭头函数 func2 则是在创建时就确定了上下文 ,即 window继承最近的普通函数的上下文 this ),那么 this 也就指向了window

三阶------改变了this的指向

把对象里的函数赋值给一个新的变量

js 复制代码
const obj = {
  func1: function() {
    console.log(this, '1');
  },
  func2: () => {
    console.log(this, '2');
  }
};
// 第一组
obj.func1() // obj
obj.func2() // window

// 第二组
const fn1 = obj.func1;
const fn2 = obj.func2;
fn1(); // window
fn2(); // window

第一组不分析,就是二阶里的,放这里只是为了误导和对比,直接分析第二组二阶------改变指向

第一个,先是通过 obj.func1 拿到了普通函数(但是,它还没调用呢,this 还不急着确认),然后把 obj.func1 赋值给了变量fn1(也就是把 obj 里的 func1 的函数赋值给了变量 fn1),使 fn1 成为了一个函数,这时再调用 fn1 ,就相当于 window.fn1(),也就是 window 调用了这个函数,那么 this 就指向了最终调用的 window

fn2() ,它是 obj.func2 赋值给的 fn2 ,由于 obj 里的 func2 是箭头函数,它在定义的时候就确认了 this 指向,并且它是继承最近的普通函数的上下文 this ,而且这个 this 指向是静态的 ,不管谁调用 、怎么改变方向都没用,因此 this 指向一开始的定义时就确定好的 this 指向 window

把对象外的函数赋值给一个新的变量

js 复制代码
const test1 = function() {
    console.log(this, '1')
}
const test2 = () => {
    console.log(this, '2')
}
const obj = {
  func1: test1,
  func2: test2
};
// 第一组
obj.func1() // obj
obj.func2() // window

// 第二组
const fn1 = obj.func1;
const fn2 = obj.func2;
fn1(); // window
fn2(); // window

同上,第一组不分析,直接分析第二组。

先是在 window 中创建了普通函数 test1 ,然后把 test1 赋值给了对象 obj 中的 func1 ,接着再通过 obj 对象拿到普通函数 func1 ,并赋值给了 fn1 ,使 fn1 光荣的成为了一名普通函数,最后,不管普通函数 test1 , func1 还是 fn1 ,它们本质上就是个普通函数,因此 fn1() 最终是由 window 调用的(window.fn1()),所以 this 指向 window

test2 是在 window 下创建的,根据箭头函数在被定义(或被创建)的时候就确认了 this 的指向(即包含箭头函数的上下文也就是箭头函数外层的 this ),并且 this 是静态的 ,因此不管箭头函数怎么被赋值改变,this 仍然指向箭头函数 test2 被创建时的上下文 window

通过 apply , call,bind 改变 this 的指向

以下以 apply 为例,call bind 同理。

js 复制代码
const obj = {
    sex: 1
}

// 普通函数
function func1() {
    console.log(this, '1');
};
// 箭头函数
const func2 =  () => {
  console.log(this, '2');
}
// 第一组
func1() // window
func2() // window

// 第二组
func1.apply(obj) // obj
func2.apply(obj) // window

func1.apply(obj).apply()
func2.apply(obj).apply()

同上,第一组不分析,它是一阶里的,直接分析第二组。

func1 虽然是普通函数,按照原本分析是由 window 调用的,但由于使用了 apply 强制改变了 func1this 指向,即 apply 函数的参数 obj ,因此最终指向被 apply 改变的 this 指向,即 obj

func2 是箭头函数,根据箭头函数在被定义(或被创建)的时候就确认了 this 的指向(即包含箭头函数的上下文也就是外层的 this ),并且 this 是静态的 ,不管你使用 apply 还是赋值,this 的指向仍旧指向 func2 被创建时的上下文 window

思考:如果有多个 apply 或者赋值, this 的指向又会是什么呢?

四阶------回调函数中的普通函数和箭头函数

回调函数在函数里定义

js 复制代码
setTimeout(function() {
    console.log(this, 1)
}, 1000); // window 1

setTimeout(() => {
    console.log(this, 2)
}, 1000); // window 2

乍一看有点懵逼,不知道怎么下手判断,但只需要把握了普通函数是谁调用的,箭头函数是在哪里创建的,即可判断出 this 的指向。

针对回调中的普通函数,得先清楚 setTimeout 这个延时的回调是谁调用的,根据 setTimeout 是宏任务,由宿主环境(即浏览器 window )调用,所以,最后的普通函数 this 指向的是调用了回调函数的 window

针对回调中的箭头函数,它的创建是在回调函数中的,但一开始回调函数还没有执行,继承外层最近的普通函数的上下文 this ,所以最后回调函数中的箭头函数的 this 是指向 window 的。

相当于

js 复制代码
setTimeout(fn, delay) {
    console.log(this) // window
    fn()
}
setTimeout(() => {
    console.log(this, 2)
}, 1000);

因为 setTimeout 是在 window 中创建的,setTimeout 的上下文就是 window ,也就是说 setTimeout {}里面的 this 是指向 window 的,而箭头函数最终会指向 setTimeout 的上下文,因此最后回调函数中的箭头函数的 this 是指向 window 的。

回调函数在函数外定义

js 复制代码
function func1() {
    console.log(this, 1)
}

const func2 = () => {
    console.log(this, 2)
}
setTimeout(func1, 1000); // window 1

setTimeout(func2, 1000); // window 2

同上述分析类似,这里就不再多言。

对象里的回调函数

js 复制代码
const obj = {
    sex: 1,
    setTimeout(function() {
        console.log(this,this.sex, '1')
    }, 1000), // window, undefined, '1'
    setTimeout(() => {
        console.log(this,this.sex, '2')
    }, 1000) // window, undefined, '2'
}

同上述分析类似,就是多了一层对象 obj ,但不是函数,因此没有影响。因此,第一个普通函数作为回调函数 this 指向 window ,由于是 window 调用的 setTimeout ;第二个箭头函数作为回调函数 this 指向 window ,虽然 setTimeout 是在 obj 里创建的,但因为继承外层最近的普通函数的上下文 this ,而 obj 不是函数只是对象,所以往外找到了 window

在对象里的普通函数(箭头函数)包含回调函数

js 复制代码
// 传统函数中的this绑定
const obj = {
    sex: 1,
    func1: function() {
        setTimeout(function() {
            console.log(this, this.sex, 1)
        }, 1000);
    },
    func2: function() {
        setTimeout(() => {
            console.log(this, this.sex, 2)
        }, 1000);
    },
    func3: () => {
         setTimeout(function() {
            console.log(this, this.sex, 3)
        }, 1000);
    },
    func4: () => {
        setTimeout(() => {
            console.log(this, this.sex, 4)
        }, 1000);
    }
};

obj.func1();
obj.func2();
obj.func3();
obj.func4();

obj.func1() ,首先 func1 是个普通函数,而 setTimeout 里的回调函数也是普通函数,这很nice,减少了我们的判断,根据普通函数最终是谁调用 this 就指向谁 ,而 setTimeout 根据上述的回答它是由宿主环境(即 window )调用的,所以它指向 window ((觉得不对的话可以试试在 func1 里添加一个console.log(this,this.sex, 'func1'),看看这个输出即可)

js 复制代码
func1: function() {
    console.log(this, this.sex, 'func1') // 也就是添加一行这个
    setTimeout(function() {
        console.log(this, this.sex, 1)
    }, 1000);
}

obj.func2() ,首先 func2 是个普通函数,但是,setTimeout 里的却是个箭头函数,根据箭头函数在被定义(或被创建)的时候就确认了 this 的指向 ,箭头函数在哪被创建的?是 setTimeout啊,但 setTimeout 又是在普通函数 func2 中声明的,根据箭头函数继承外层最近的普通函数的上下文 this ,此时 func2 是个普通函数,它的上下文是 obj(普通函数在 obj 中创建的),而 setTimeoutfunc2 包裹着,所以由里往外走,箭头函数 this 的指向是普通 func2 的上下文,即 obj。(如果感觉还是难以接受的话,可以在 func2 里添加一个 console.log(this) ,看看这个 this 的输出即可)

js 复制代码
 func2: function() {
        console.log(this, 'func2') // 也就是添加一行这个
        setTimeout(() => {
            console.log(this, this.sex, 2)
        }, 1000);
    },

从上述两个分析,应该隐隐约约有个结论了

分析 this 指向时,从内往外进行分析
箭头函数作为回调函数时,this 会和包裹着箭头函数的函数(setTimeout)上下文一致。

可以使用这个结论,试试分析接下来的两个嵌套

obj.func3() ,由于 setTimeout 的回调函数是普通函数,而 setTimeoutwindow 调用的,所以不管外面是箭头函数还是普通函数,this 指向 window

obj.func4() ,由于 setTimeout 的回调函数是箭头函数,根据箭头函数作为回调函数时,this 会和包裹着箭头函数的函数(setTimeout)上下文一致 ,所以 this 指向 func4 ,但由于 func4 是箭头函数,根据箭头函数继承外层最近的普通函数的上下文 this ,所以 obj.func4()this 指向 window

思考:为什么 func2 和 func4 同样是在 obj 中定义的,为什么 this 一个指向 obj ,一个指向 window。

js 复制代码
const obj = {
    sex: 1,
    func2: function() {
        setTimeout(() => {
            console.log(this, this.sex, 2)
        }, 1000);
    },
    func4: () => {
        setTimeout(() => {
            console.log(this, this.sex, 4)
        }, 1000);
    }
};

因为 func2 是普通函数,它有自己的上下文,里面的作为箭头函数的回调函数由于箭头函数继承外层最近的普通函数的上下文 this ,指向的是 func2 里的上下文,即 obj,而 func4 是箭头函数,里面的回调函数本来是指向 func4 里的上下文的,但 func4 不争气,它是个箭头函数,所以上下文继续往外指,即 obj 里的上下文,而 obj 是个对象,不是函数,this 只能继续往外找了,即 window 。因此,最后 obj.func2() 里的回调函数 this 指向 objobj.func4() 里的回调函数 this 指向 window

抽离一层

js 复制代码
function fn1() {
   console.log(this, this.sex, 1) 
}
const fn2 = () => {
    console.log(this, this.sex, 2)
}
const obj = {
    sex: 1,
    func1: function() {
        setTimeout(fn1, 1000);
    },
    func2: function() {
        setTimeout(fn2, 1000);
    },
    func3: () => {
         setTimeout(fn1, 1000);
    },
    func4: () => {
        setTimeout(fn2, 1000);
    }
};

obj.func1(); // window, undefined, 1
obj.func2(); // window, undefined, 2
obj.func3(); // window, undefined, 1
obj.func4(); // window, undefined, 2

这样看可能会更清晰一点,但结果和流程实则有点区别,普通函数当然没问题,即 func1func3 ,都是 window 调用 setTimouetthis 指向 window

obj.func2obj.func4 里的 setTimeout 回调函数都是 fn2 箭头函数,根据箭头函数在被定义(或被创建)的时候就确认了 this 的指向 ,箭头函数 fn2 创建时是在 window 下创建的,所以 this 指向 window

自定义的回调函数

js 复制代码
function someFunction(callback) {
    console.log(this, '1') // window
    callback();
}
// 传统回调函数
someFunction(function() {
  console.log(this, '2'); // window
});

// 箭头函数作为回调函数
someFunction(() => {
  console.log(this, '3'); // window
});

第一个回调函数是普通函数,由于作为普通函数的回调函数 this 是由调用方式决定的,所以可以看到调用 someFunction 的是 window(window.someFunction(...)),因此 somwFunction 里的 this 指向的 window,'1' 。而 callback() 呢,它被谁调用了,我们上下一看,发现它没有人调用,或者说,其实它是被 window 调用的(window.callback()),即普通函数则是最终谁调用的,this 就指向谁。如果最终没有谁调用,那就是由 window 调用的 ,因此 someFunction 里的回调参数,this 指向 window , '2'

第二个回调函数是箭头函数,就相当于把箭头函数放入了 someFunction 函数中调用了,所以作为箭头函数的回调函数 this 是指向 someFunction 内部的,而 someFunction 是在 window 中创建的,所以 someFunction 的上下文为 window,根据箭头函数继承外层最近的普通函数的上下文 this ,因此箭头函数里的 this最终是指向 window 的。

下面这个就不分析,和上面的setTimeou本质是一样的。

js 复制代码
function someFunction(callback) {
    console.log(this, '1')
    callback();
}
const obj12 = {
    sex: 1,
    // 传统回调函数
    func1: function () {
        someFunction(function () {
            console.log(this, this.sex, '2'); // 外层上下文的this
        })
    },
    // 箭头函数作为回调函数
    func2: function () {
        someFunction(() => {
            console.log(this, this.sex, '3'); // window
        })
    },
    func3: () => {
        someFunction(function () {
            console.log(this, this.sex, '2'); // 外层上下文的this
        })
    },
    func4: () => {
        someFunction(() => {
            console.log(this, this.sex, '3'); // window
        })
    }
}
obj12.func1()
obj12.func2()
obj12.func3()
obj12.func4()

五阶------闭包中函数和箭头函数

js 复制代码
let obj2 = {
    func1: function () {
        console.log(this, '1')
        function func2() {
            console.log(this, '2')
        }
    
        func2();
    }
}

obj2.func1()

obj2.func1()func1 是个普通函数,根据普通函数则是最终谁调用的,this 就指向谁 ,所以,第一个 this指向 obj2,而 func2 ,它没有被谁调用,相当于 window.func2() ,根据如果最终没有谁调用,那就是由 window 调用的 ,因此 func2 里的 this 指向 window

相关推荐
小白小白从不日白3 分钟前
react 组件通讯
前端·react.js
罗_三金13 分钟前
前端框架对比和选择?
javascript·前端框架·vue·react·angular
Redstone Monstrosity20 分钟前
字节二面
前端·面试
东方翱翔27 分钟前
CSS的三种基本选择器
前端·css
Fan_web1 小时前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
千穹凌帝1 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
叫我:松哥1 小时前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
Hellc0071 小时前
MacOS升级ruby版本
前端·macos·ruby