秒懂JS的this指向

1,278 阅读5分钟

小知识,大挑战!本文正在参与“[程序员必备小知识]

在开发中你有没有感觉this指向和自己想的不一样,这里让你不在感觉this指的方向没那么的神秘,让我们一起揭开this的神秘面纱。

前文

我之前在大学的时候学习c++、java、python面向对象的语言,当我从这些面向对象的思维中转到JS十分的难以理解这个这个JS的this,在我的思维里this是指向对象本身的,但是在JS中this可以随时改变指向,下面讲讲我们思维上的误区。

误区

指向自身

先来段代码:

var name = 'window'
function sbThis() {
  console.log(this.name); // window
}

sbThis.name = '我是sbThis'

sbThis()

这个就是在我们在我们的思维中认为this应该是自己的自身,但是这里的this指向window。

作用域

再来段代码:

var name = 'window'
function sbThis() {
  var name =  '我是sbThis'
  this.sbThisNo2()
}

function sbThisNo2() {
  console.log(this.name); // window
}

sbThis()

我们前面说sbThis是指向window,所以我们就可以调用了sbThisNo2函数。当调用sbThisNo2函数,在我们认知中又一种认为sbThisNo2的函数this指向sbThis,又错误了。但是,这种认知呢有时候是对的,又有时候是错误的,看这个的sbThis就是指向window,这个就是我们思维中的认知。所以我们要拿一套this的规则来学习它。

内容

开始学习内容篇章this真正的指向。记住一句话: 观察函数被使用的位置,进行一下判断。

四项法则

默认绑定

一段代码:

var name = 'window'
function sbThis() {
  console.log(this.name); // window
}

sbThis.name = '我是sbThis'

sbThis()

这个就是一种默认绑定,当我们在全局调用这个函数,因此this就是指向window

隐式绑定

看一段代码:

var name = 'window'
function sbThis() {
  console.log(this.name); // 我是OBJ
}

var obj = {
  name: '我是OBJ',
  fn: sbThis
} 

obj.fn()

这里我们是调用obj.fn。这里我们可以看是是通过obj调用的fn。下面再来一段代码:

var name = '我的window'
function sbThis() {
  console.log(this.name); // 我的window
}

var obj = {
  name: '我是OBJ',
  fn: sbThis
} 
var falseFn = obj.fn
falseFn()

这里是不是看的有点糊涂,我再看一段代码:

var name = '我的window'
function sbThis() {
  console.log(this.name); // 我的window
}

var obj = {
  name: '我是OBJ',
  fn: sbThis
} 
var falseFn = obj.fn
falseFn()
console.log(obj.fn === sbThis); // true
console.log(falseFn === sbThis); //true

这里就是相当于把sbThis的引用改成了falseFn,这里表示sbThis函数在运行。赋值引用this的指向是不会赋值过去,只是把this这个词赋值了而已。

有一点需要注意点 隐藏的赋值

var name = '我的window'
function sbThis() {
  console.log(this.name); // 我的window
}

function sbThisNo2(fn) {
  fn()
}

var obj = {
  name: '我是OBJ',
  fn: sbThis
} 

sbThisNo2(obj.fn)

函数作为参数也是一种赋值引用的操作。

硬绑定

这里指的就是call、apply、bind。强行的把this绑定上去。

这里我们经常会忘记它们的使用区别。

首先他们的第一个参数都是要绑定的this。我记忆方式是: call是打电话,所以我们需要一句一句的说,对应参数就是一个一个的传。apply是应用我想到了app,当我们填写个人信息就是把全部的信息一次性的填写上去,对应就是第二参数是一个数组。bind是绑定,跟一个人绑定起来,形成了一个新个体,第二个参数嘛,之前记住只有一个是数组其他的都是一个个传就可以了。这个是我的记忆方式,一般是不适用大家,你们大家看到这几个单词想到什么就使用这个场景进行记忆

var name = '我的window'
function sbThis() {
  console.log(this.name); // 我是OBJ * 3
}

var obj = {
  name: '我是OBJ',
  fn: sbThis
} 

sbThis.call(obj)
sbThis.apply(obj)
sbThis.bind(obj)()

New

在JavaScript中函数就可以当作一个对象,当使用new来使用一个函数,函数就会变成构造函数,产生出一个新的对象

function sbThis(name) {
  this.name = name
  console.log(this.name); // 我是OBJ
}

const obj = new sbThis('我是OBJ')

优先级

一句话记住:new > 硬绑定 > 隐式绑定 > 默认绑定 (详情看参考书籍)

规则例外

规则当中凡事都是有些例外的。你以为按照规则走的,其实是默认绑定。

被忽略的this

var name = '我是window'
function sbThis() {
  console.log(this.name); // 我是window
}

sbThis.call(null)

当我们使用了硬绑定第一个参数为null或者是undefined,硬绑定失效就是变成了默认绑定。

匿名函数的this

var name = '我是window'
function sbThis() {
  setTimeout(function () {
    console.log(this.name); // 我是window
  },1000)
}

var obj = {
  name: '我是obj',
  fn: sbThis
}

obj.fn()

匿名函数的this都是指向window的。

箭头函数

把上面的代码修改成箭头函数:

var name = '我是window'
function sbThis() {
  setTimeout(() => {
    console.log(this.name); // 我是obj
  },1000)
}

var obj = {
  name: '我是obj',
  fn: sbThis
}

obj.fn()

因为箭头函数没有this,所以指向上级作用域的this。

实践

var className = 'window'
function Page(callBack) {
  callBack('callBack') // window callBack
  this.className = 'Page'
  callBack('callBack') // Page callBack
  this.MessageCallBack = callBack // 
  this.MessageCallBack('MessageCallBack') // Page MessageCallBack
}

function PageA() {
  this.className = 'PageA'
  this.handleMessage = function (msg) {
    console.log(this.className, msg)
  }
 Page(this.handleMessage)
}

new PageA()
  1. 我们是先new了一个PageA对象
  2. 在PageA对象中使用Page(默认绑定)。
  3. 传值this.handleMessage (隐藏的赋值)赋值给callBack。
  4. 所以Page 和 callBack都是指向window

改变点代码:

var className = 'window'
function Page(callBack) {
  callBack('callBack') // window callBack
  this.className = 'Page'
  callBack('callBack') // window callBack
  this.MessageCallBack = callBack // 
  this.MessageCallBack('MessageCallBack') // Page MessageCallBack
}

function PageA() {
  this.className = 'PageA'
  this.handleMessage = function (msg) {
    console.log(this.className, msg)
  }
 new Page(this.handleMessage)
}

new PageA()

这里把Page调用改成了new Page。
逻辑还是之前那样但是使用了new Page所以Page里面的this就是指向Page。
然而callBack(隐式赋值)还是指向window。
所以this.className = 'Page'对后面 的打印是无效的。

结论

抛开以前的其他语言和自我的认知。

遵循四项规则。

多看。

参考

《你不知道的JavaScript》