一篇文章搞懂this

开篇说明

随着科技的发展和历史的变迁,我对 this 的理解也越来越深入了,还记得之前写的一篇关于 this 的文章,自己看起来都感觉“味同嚼蜡”😂,所以更新一篇,将功补过,哈哈~~,我们拿五种情况来说明 this 的指向问题,优先级从低到高,但是在这之前我们先说一下 js 的作用域链的问题。

作用域链

案例一
function outer() {
  var name = 'outer';
  function inner() {
    console.log( 'name: ', name );
  }
  inner();
}

outer();
  1. 大家想一下打印出来的 name 是啥呢?
  • 此时假装过大家思考了10秒~
  • 打印出来的是 outer
  • 原因是什么呢?
  • 明明 inner 函数中是没有 name 这个属性的,他怎么会打印出外层的 outer 函数中定义的 name 呢?
  • 因为 js 在执行函数的过程中,如果某个变量找不到,他会沿着「!!!:函数定义时」的作用域链去外层找是否有这个变量,如果外层没有,继续向外层的外层去找找找。。。最终找到全局对象上(一般情况下是window)如果还没有,那么就会报错;在我们这个案例中,查找的顺序为:inner ? -> outer ? -> window ?
  • 如果按照上面的作用域链最终到 window 对象上也没有找到这个变量,那么浏览器就会抛出错误Error: name is not defiend

五种情况的 this 指向

一、函数的自然执行

先解释下什么叫自然执行?

function func() {
  console.log( 'this: ', this );
};

let obj = {
  a: func
};

func(); // 自然执行
obj.a(); // 不是自然执行
func.call( obj ); // 不是自然执行

总结:自然执行:为函数直接执行,这样: func();

1.直接执行,不通过 '.' 操作符执行
2.不使用 new 操作符
3.不使用 call、apply、bind

总之:就是光突突地执行:func();

函数自然执行的情况下:

  1. 在非严格模式下的 js 中,指向window
  2. 在严格模式「“use strict”」下,指向 undefined
  3. 在 node 环境下,指向 node 的全局对象 global
二、被对象的访问操作符访问出来,并执行

什么是对象的访问操作符?其实就是访问对象中属性的两种方式,我们平时肯定也很常用,请看下面

function func() {
  console.log( 'this: ', this );
}

let obj = {
  // a 属性的值指向了 func 函数的引用,这里不清楚可以查阅下 js 中的基本数据类型和复杂数据类型的区别
  name: 'obj',
  a: func 
}

obj.a(); // 被 obj 通过「对象.键名」的方式访问出来并执行
obj[a](); // 被 obj 通过「对象[键名]」的方式访问出来执行

上面两种执行方式中,this 的指向被隐式(偷偷)绑定到 obj 上了

这种情况我们只需要记住一句话:函数不管被多少层访问符,层层访问到,他总是指向离他最近的调用对象上,下面举例说明:

function func() {
  console.log( 'this ', this );
}

let obj = {
  name: 'obj',
  father: {
    name: 'father',
    son: {
      a: func,
      name: 'son'
	}
  }
}

obj.father.son.a(); // this 指向 son ,因为是离他最近的调用者
三、通过 new 操作符执行函数

也就是调用构造函数的方式了,也就是我们用 javaScript 造出来的“类”的概念。

function func(name) {
  this.name = name;
  console.log( 'this: ', this );
}

let a = new func('a'); // this 指向 a 对象
let b = new func('b'); // this 指向 b 对象

只要通过 new 操作符调用函数,那么 this 会隐式(偷偷地)指向返回的实例对象。

下面出一道题,大家可以尝试思考一下,打印出的 this 是什么?

可以运行输出验证一下自己的答案哦!

function outer() {
  let name = 'outer';
  function inner() {
    let name = 'inner';
    console.log( 'this: ', this );
  }
  return inner;
}

let a = outer();
a();
四、call、apply、bind
先来说下 call 和 apply

call 和 apply 都是可以把其他函数借过来自己用一下,而且还不用在自己身上扩展这个方法,举例说明:

let a = {
  name: '我是a'
}

let b = {
  name: 'b',
  sayName: function() {
    console.log( 'name: ', this.name );
  }
}

b.call( a ); // name: '我是a'
b.apply( a ); // name: '我是a'

// call 和 apply 的区别就是传参方式的不同
// 这里不做过多说明
// b.call( a, 1, 2, 3 )  b.apply( a, [1, 2, 3] );

我们的两个对象 a 和 b,他们都有名为 name 的属性,但是 a 中没有 sayName 的方法,但是他可以借用 b 的 sayName 方法,但是在借用 b 的 sayName 方法的过程中,this 被隐式(偷偷地)指向了被借用的对象上,在这里就是 a

bind

只讲解最简单的使用方式,也是借用函数,并隐式改变 this 的指向,但跟 call 和 apply 不同的是:bind 返回一个新函数,举例说明:

let a = {
  name: 'a',
  log: function () {
    console.log( 'this: ', this );
  }
}

let b = {
  name: 'b',
}

let c = a.log.bind( b );
c(); // this 指向对象 b
// `c()`  相当于执行  `a.log.call( b )`
五、ES6 的箭头函数

我们平时写代码的时候经常听到一种说法:你的项目中要不就都用箭头函数,要不就都不用,别把 this 搞乱,对吧,但是我们搞清楚 this 在不同执行情景下的指向之后,哪种写法我们就可以自己斟酌了,或者混着用😏(不推荐)

我们先看下概念:箭头函数的 this 指向「定义时」离他最近的一个非箭头函数的 this 指向的对象,举例说明:

function outer() {
  let name = 'outer';
  let inner = () => {
    let name = 'inner';
    console.log( 'this: ', this );
  }
  inner();
}

outer(); // this: window
// 1.箭头函数的 this 指向「`定义时`」离他最近的一个非箭头函数的 this 指向的对象
// 2.那么我们从 inner 向外层看,外层 outer 就是离 inner 最近的非箭头函数,
//   那么他的 this 指向就是箭头函数 inner 的 this 指向
// 3.我们分析 outer,outer 是自然执行的(上面说过自然执行了),那么 outer 的 this 
//   指向为 window,所以 inner 的 this 执行也是 window
function outer() {
  let name = 'outer';
  let inner = () => {
    let name = 'inner';
    console.log( 'this: ', this );
  }
  inner();
}

outer.call( outer ); // this: ?
// 这个留给大家自己分析了,欢迎提问哦~
this 的优先级问题

ES6箭头函数 > call、apply、bind > 自然执行、对象访问操作符执行、new 调用
!:大于号中间的多种情况为并列关系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值