8月更文挑战 | JS中的this详解

352 阅读5分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

你真的懂this吗?这是一个很真实的question对于前端玩家来说this其实说陌生每天都在接触,有的说不陌生但是可能真正说的很清楚确很少, 尤其是到了现在Es6和框架时代像React最新开发方式hooks和箭头函数甚至可以用不到this,那以后可能会越来越陌生,但是这不要紧今天来总结一下this在我们日程中使用的情况

前言

对于this有个经典的问题,this是声明的时候绑定的?还是调用的时候绑定的?有没有绑定优先级?好家伙上来三连问,但是不要紧今天看完这篇文章之后就都能了解,今天我会以demo的形式来讲述this在各种场景下的情况和绑定的优先级之分

介绍this

先对this进行个大概的介绍,首先不要把它想的很复杂其实有些时候有些事往往是自己钻了牛角尖跳出来就好了,this本身就是个指针,并且本身也并不指向谁,而是在最后调用的时候才能决定它最后的指向情况this的绑定方式有一下几种

  • 默认绑定
  • 隐式绑定
  • 显示绑定
  • new绑定

开始

可能你没见过上述说的几种绑定方式,但是不重要现在知道就行,为啥要把这几种规则写在前面首先是在自己的脑子里留个印象有这么几种绑定方式,然后在下面我们写demo的时候就可以对号入座,对我们理解this也会更方便些,话不多说我们用代码来讲

注意:本次都是在浏览器的环境下

默认绑定

1.我们举个🌰来讲,看起来其实很简单在调用fn的时候将this绑定到了window上面,所以日志可以看到太玄经

var name = '太玄经'function fn(){
  //太玄经
  console.log(this.name)
}
​
fn()

2.在看个🌰这个调用this.name会报错因为此时this是undefined,可以看到use strict参数我们把fn函数内部改成了严格模式,所以才会这样子

var name = '狗杂种'function fn(){
  'use strict'
  //Cannot read property 'num' of undefined
  console.log(this.name)
}
​
fn()

隐式绑定

1.看下面这个🌰在调用obj.fn的时候,自动将this绑定到了obj上,所以在调用this.a的时候和调用obj.a是一样的

var name = '太玄经'let obj = {
  name: '侠客岛',
  fn: function () {
    //侠客岛
    console.log(this.name)
  }
}
​
console.log(obj.fn())

2.在看个稍微长一点的🌰这里发现隐式绑定已经丢失了,因为此刻res执行的环境是在window下面,相当于window.res()所以日志是太玄经

var name = '太玄经'var obj = {
  name: '侠客岛',
  fn: function () {
    //太玄经
    console.log(this.name)
  }
}
​
var res = obj.fnres()

3.在来一个稍长的🌰可以看出来最后打印的是侠客岛,这就得出一个结果如果是隐式调用那么会根据最后一层来决定this的指向

var name = '太玄经'var obj = {
  name: '侠客岛',
  fn: function () {
    //侠客岛
    console.log(this.name)
  }
}
​
var obj1 = {
  name: '白万剑',
  fn1: obj
}
​
obj1.fn1.fn()
​

显示绑定

1.显示绑定主要使用call、apply、bind等关键字来实现我们来看个🌰,可以看到这个和第一个默认绑定的方式很相似,但是关键字call将我们的this指向了obj所以最后日志是侠客岛(顺便说一下如果调用call不传参数或者传null是指向的当前环境)

var name = '太玄经'var obj = {
  name: '侠客岛',
  fn: function () {
    //侠客岛
    console.log(this.name)
  }
}
​
obj.fn.call(obj)

2.在看第二个🌰打印了太玄经按理说已经用call了应该不会丢失但是还是丢了,这是因为我们在调call的时候传入了fn,相当于将obj.fn赋值给了fn形参,所以隐式绑定已经丢失了

var name = '太玄经'function fn() {
  //太玄经
  console.log(this.name)
}
​
var obj = {
  name: '侠客岛',
  fn: fn
}
​
var fn1 = function (fn) {
  fn();
}
​
fn1.call(obj, obj.fn)

3.可以在回调函数中绑定call来解决这个问题

var name = '太玄经'function fn() {
  console.log(this.name)
}
​
var obj = {
  name: '侠客岛',
  fn: fn
}
​
var fn1 = function (fn) {
  //绑定this
  fn.call(obj);
}
​
fn1.call(obj, obj.fn)
​

new绑定

new的原理

function _new() {
  //定义对象
  let obj = {};
  //取出参数列表的第一个参数(构造函数)
  let Con = [].shift.call(arguments);
  //指向构造函数原型
  obj.__proto__ = Con.prototype;
  //调用Con,改变this传入剩余参数
  let result = Con.apply(obj, arguments);
  //判断result是否有返回值
  return result instanceof Object ? result : obj
}
​
  • 创建一个空对象
  • 新对象的原型指向传进来的函数原型
  • 在函数内部this指向这个空对象
  • 最后如果构造函数内部没有返回其他对象,那么默认返回创建的新对象,否则就是返回构造函数中的其他对象

1.看看使用new绑定的🌰我们把参数传进来了并且绑定到了当前this上也返回了当前对象

var name = '白万剑'function fn(name) {
  this.name = name
}
​
let fn1 = new fn(name)
​
//白万剑
console.log(fn1.name)
​

结论

所以最后我们得出的结果,优先级是 new绑定 > 显示绑定 > 隐式绑定 > 默认绑定,但是也有些例外情况比如在箭头函数中因为箭头函数的特性,在里面使用this实际指向的是外层父作用域的this,在下面会介绍一下箭头函数的具体情况

箭头函数

  • 箭头函数this为父作用域的this,不是调用时的this
  • 箭头函数不能作为构造函数,不能使用new
  • 箭头函数没有arguments
  • 箭头函数通过call和apply调用,不会改变this指向,只会传入参数
  • 箭头函数没有原型属性prototype

总结

本次总结到这里就结束了,如果哪里有问题可以在评论区指出,最后祝大家周末愉快☕️