this指向问题
❝一个堕落者的自我救赎之路
深夜赶文,如有误,望指教 。感恩!
临渊羡鱼,不如退而结网。愿我们都有一个美好的前程
❞
此系列博客文章进度
✅js篇
🔲html、css篇
🔲DOM、BOM篇
🔲轮子篇
🔲打包工具篇
🔲数据结构篇
🔲前端必刷小算法篇
🔲HTTP篇
🔲性能优化篇
🔲常见设计模式篇
从一段代码开始(缘起缘落开始便是结束)
小黑:this的指向问题还用说嘛,它不就指向调用它的那个对象吗
小白:黑哥莫急,话是这样说。咱还是先看几段代码
var name = 'gxb'
const obj1 = {
name: 'zs',
getName: function() {
const fn = obj2.getName
fn()
}
}
const obj2 = {
name: 'zs',
getName: function() {
console.log(this.name)
}
}
obj1.getName()
var name='gxb'
const obj1={
name:'zs',
obj2:{
name:'li',
getName:function(){
console.log(this.name)
}
}
}
const fn=obj1.obj2.getName
fn()
上面两段的最后输出都是gxb,黑哥你能一眼就能准确的判断出来吗?
小黑:你这不是故意刁难吗,哪有代码写成你这个样子的?
小白:嘿嘿,黑哥别生气。这里主要是看看黑哥this指向掌握的怎么样,其实只要抓住了this问题的命脉,这里只要瞅上一样就能看出答案来的
小黑:少废话,快点开始正题
小白:
首先来解释这一句话this就指向调用它的那个对象,不知道你有没有和小白开始一样的疑惑,this指向调用它?为什么是调用它呢?this又不是一个函数何谈调用呢?
这个问题困扰了小白好长一段时间,直到小白js的执行机制
即开始的时候js代码从宏任务队列进入执行栈,开始创建的全局上下文只有两个东西,即全局对象和this。这个this是指向全局对象的
当一个函数进栈的时候,又创建了一个函数上下文。函数上下文不会创建全局对象而是创建参数对象即 arguments 和this。这个this则是在执行期间才能确定
全局上下文中的this我们不用去处理,通常我们要搞清楚的就是这个函数上下文的this指向问题。正是由于这种函数与this的关系,所以你应该就能理解这句话了吧this就指向调用它的那个对象,这个它可以理解为就是指向这个this所在函数上下文的那个函数(额,有点绕口。哈哈)
再向下解释,this是在运行时才能确定的。也就是说和它所在的词法作用域没有半毛钱关系
怎么理解呢?还是看这段代码
var name='gxb'
const obj1={
name:'zs',
obj2:{
name:'li',
getName:function(){
console.log(this.name)
}
}
}
const fn=obj1.obj2.getName
fn()
按传统的作用链去分析,getName这个函数中没有找到name变量,按理说他会向上级去找,找到了name='li',再不济再向上找还能找到name='zs'。怎么也不能跨过中间直接就去拿全局的name吧
但是呢,this就是这样它不依赖于它所在的词法作用域。我们只关心是谁调用了它
是谁呢?通过观察上面代码,首先我们先把这个getName赋给了fn,fn直接在全局中调用(a.fn即a调用了fn,没有修饰符即window调用的 非严格模式下)
即拿到的是处于全局下的name
小黑:很就没有看到你使用var了,这里咋又用上了,怀旧呢?
小白:黑哥,这我就得说你两句了。我前两天刚总结了一下var、let、const的区别,你是不是没看。这三个只要var声明的变量能被直接挂到全局window上,我倒是想用let呢。。。
绑定规则
小黑:this还有绑定规则呢?原来没有注意过啊
小白:对啊黑哥,而且这里的绑定规则还不止一个呢。接下来让小弟一一给您解释一下吧
1 默认绑定
这个便是最简单的一种绑定规则了
举个栗子
window.name = 'gxb'
function getName() {
console.log(this.name)
}
getName()
这种函数前面没有任何修饰直接调用的,其使用的绑定规则就是默认绑定(非严格模式)
2 隐式绑定
这种也十分容易理解,即像这么栗子调用getName函数的对象具有上下文。故getName的this就指向这个obj的上下文了
举个栗子
const obj = {
name: 'gxb',
getName: getName
}
function getName() {
console.log(this.name)
}
obj.getName()
再来一个栗子,来体验一把隐式绑定和this就是指向调用它的那个对象这句话
const obj01 = {
name: 'gxb',
obj02: {
name: 'zs',
getName: function() {
console.log(this.name)
}
}
}
obj01.obj02.getName()
直接去看是谁调用了getName呢?哦,原来是obj01.obj02这个对象。故this就指向obj01.obj02这个对象的上下文了。这个上下文中找得name变量即zs了
「再来介绍一个名词——隐式丢失」
小黑:这个是啥玩意呢?
小白:哈哈。黑哥不要害怕。这个名词也就是一个名词而已,其实开始我们就接触过了
var name='gxb'
const obj1={
name:'zs',
obj2:{
name:'li',
getName:function(){
console.log(this.name)
}
}
}
const fn=obj1.obj2.getName
fn()
这个就算是隐式丢失,你本来好好的我可以用obj1.obj2.getName()去调用,直接可以让this绑定obj1.obj2对象的上下文。你非把它给赋值一个新的变量去调用。即此时的fn就是getName函数,你这样fn()不就相当于getName()前面没有修饰,不就恰好匹配到了默认绑定的规则,直接把this绑定到window对象的上下文中了吗(非严格模式)
还有一些隐式丢失的栗子,我就只放到下面不解释了
window.name = 'gxb'
const obj = {
name: 'zs',
foo1
}
function foo1() {
console.log(this.name)
}
function foo2(fn) {
fn()
}
foo2(obj.foo1)
window.name = 'gxb'
const obj = {
name: 'zs',
foo1
}
function foo1() {
console.log(this.name)
}
setTimeout(obj.foo1, 1000)
这俩最后都指向了全局对象的上下文
3 显示绑定
拿上面隐式绑定的第一个栗子开头
const obj = {
name: 'gxb',
getName: getName
}
function getName() {
console.log(this.name)
}
obj.getName()
在这个栗子中,我们想让函数getName的this指向对象obj的上下文,还需要在obj对象中搞一个方法属性用于引用这个getName函数
这样是不是有些麻烦呢?
这个时候就要引出三个我们非常熟悉,且非常重要的函数了。分别是call、apply、bind
用这三个函数可以明确指定this的指向,故称其为显示绑定
栗子:直接把getName函数中的this指向obj对象的上下文中
const obj = {
name: 'gxb'
}
function getName() {
console.log(this.name)
}
getName.call(obj)
小黑:这三个函数谁不会使喔,给我实现一下吧
小白:遵命,我亲爱的黑哥
手写一些call、apply、bind
其实它们的实现都是基于隐式绑定的,知道了上面的东西这里就十分简单了。
首先这三个函数为保证所有函数均能使用,我们直接将他写到函数构造函数的原型对象上吧
call
简单分析,call函数需要传入所要绑定对象和一些数据参数,返回的是调用call的那个函数的执行结果
Function.prototype.myCall = function(context = window) {
context.fn = this
let res = context.fn(...[...arguments].slice(1))
// 用完了fn,还要卸磨杀驴。我们不能给人家传进来的对象瞎加东西啊
delete context.fn
return res
}
apply
与call的区别在于,第二个参数是一个数组
Function.prototype.myApply = function(context = window, arr) {
context.fn = this
// 这里多了一步判断arr,要是arr没有我们就不传给this了,这个this指的谁呢?要绑定的那个函数啊,黑哥思考一下下面调用的时候为啥不这样this()?
//白小子快点写,隐式丢失刚小爷可没忘
let res
if (arr) {
res = context.fn(...arr)
} else {
res = context.fn()
}
delete context.fn
return res
}
bind
与上面两的区别在于不是立即执行调用bind的那个函数,而是把执行时机放到返回的那个函数调用执行时
用一下上面写好的没有myApply
Function.prototype.myBind = function(context = window) {
let that = this
let args = [...arguments].slice(1)
return function() {
return that.myApply(context, args)
}
}
4 new绑定
new绑定就不用多说了吧,咱上一期刚好有说过new
new做的事情
- 首先创建一个新的对象
- 链接到原型
- 绑定this
- 返回这个新的对象
箭头函数中的this问题
小黑:箭头函数的this有什么问题吗
小白:黑哥,这你就孤陋寡闻了吧。它可不和传统函数一样。可以这理解它本身是没有this的,但是却可以捕获它父级作用域的this来使用
接着看栗子
var name='gxb'
const obj={
name:'zs',
getName:function(){
console.log(this.name)
}
}
obj.getName()
这个我们一眼就能看出了,最后输出的是zs
这样呢?
var name='gxb'
const obj={
name:'zs',
getName:()=>{
console.log(this.name)
}
}
obj.getName()
哈哈,我们分析一下。上面说了箭头函数本身是没有this的,但是却可以捕获它父级作用域的this来使用
来根据代码来看首先箭头函数getName没有this,它的this是用的它父级的,即向上级作用域去找,它的上级作用域是一个块级作用域。块级的东西也不配有this,再向上找,到了全局,全局上下文可有this,且this指向的是全局变量window的上下文(浏览器环境)即最后输出gxb
缘灭也即缘起
以this的指向就是调用它的那个对象开始,同样以this的指向就是调用它的那个对象结束
写到最后
星光不问赶路人,时光不负有心人
期待着我们的下一次邂逅