由浅入深的this用法

206 阅读5分钟

前言

上篇文章我们讲到new的原理时,我们提及到了this的绑定,而上文并没有详细介绍,今天这篇文章我们就来详细聊聊js中关键字this的用法,打开this世界的大门,熟练运用this能让你对js代码的脉络能加清晰

new的原理传送门:一篇文章带你了解JS中的原型

1.为什么要有this

function identify(context){
    return context.name.toUpperCase()
} 
function speak(context){ 
    var gretting = "hello, i am "+identify(context)
    console.log(gretting); 
} 
var me = {
    name:"Tom" 
} 
speak(me); 

image.png 这段代码不难理解,就是调用speak函数然后把对象me作为实参传入,运行过程中又调用了函数identify,此时又将函数speak中的实参me作为函数identify的实参,我们这么一看这么这实参形参关系这么乱嘞,能不能搞点大家伙都好理解的,那么 this 的作用就凸显出来了

function identify() {
  return this.name.toUpperCase()
}
function speak() {
  var gretting = 'Hello i am' + ' ' + identify.call(this);
  console.log(gretting)
}
var me = {
  name: 'tom'
}
speak.call(me)

image.png 看这段代码,我们得到了同样的结果,但是函数形参实参都因为this的出现消失了,this一定程度上代替了形参实参的作用,代码也看着相对简洁明了了,我们大致能知道this的作用:

this 让函数可以自动引用合适的上下文对象

2.认识this

在英语里面大家对this应该都不陌生吧,this是一个代词,我可以指着手边的苹果说this apple ,这个this就像我的手指一样,指向了这个苹果。而js里面的this也是一个代词,在js中永远代指某一个域,且this只存在于域中才有意义

image.png 我们先来点开我们的浏览器的控制台试试,我们看到它指向的是window(全局)
而在node里面,它指向的是global,也是全局作用域。

3.this的指向

现在我们要来聊点干货了,this的指向,也可以用绑定这个说法

1.默认绑定(指向):函数独立调用,this指向window

2.隐式绑定:当函数的引用有上下文对象时(当函数被某一个对象所拥有时且调用),this指向该上下文对象

3.隐式丢失:当函数的引用有一连串(多个)上下文对象,this指向最近的那个对象(就近原则)

4.显示绑定:call apply bind 显示的将函数的this绑定到一个对象上

我们先来聊聊函数的独立调用和非独立调用吧,

函数独立调用 直接调用函数 例如foo()

非独立调用 函数被某一个对象所拥有时且调用 例如obj.foo()

现在我们可以来看看

默认绑定

function bar() {
  let person = {
    name: 'lisi',
    age: 19
  }
  foo()
}
bar()
function foo() {
  console.log(this)
}

image.png 我们可以看到我们是直接调用了函数foo,因此这里输出的this指向的还是全局

隐式绑定

function foo() {
  console.log(this)
}

const obj = {
  a: 1,
  foo: foo
  }
obj.foo()

image.png 这段代码我们可以看到是函数foo的非独立调用,对象obj中左边的foo是自定义的key,值为foo,没有括号就是引用,相当于foo:function foo() { console.log(this) },因此这时候this指向的是对象obj

隐式丢失

function foo() {
  console.log(this)
}

const obj = {
  a: 1,
  foo: foo//左边的foo的自定义的key,值为foo,没有括号就是引用,相当于foo:function foo() { console.log(this) }
  //foo: foo() 下面的obj.foo()指向window
}
// obj.foo()//非独立调用

const obj2 = {
  a: 2,
  obj: obj
}

obj2.obj.foo()

image.png 我们看到这里对foo的控制权还是在obj身上,也就是说函数的隐式绑定遵循就近原则

显示绑定

我们可以人为的用call apply bind 显示的将函数的this绑定到一个对象上,我们这里就只拿call来举例,想要了解apply和bind的伙伴们请自行去查阅用法

function foo() {
  console.log(this.a)
}
var obj = {
  a: 1
}
foo() //undefined 这时候this指向的时window window里面没有a属性
foo.call(obj)  //  call方法的参数是this的指向

image.png 我们看到第七行调用foo函数得到的是undefined,这是因为这里的foo是直接调用的,也就是此时它的this指向全局,而全局中我们并不能找到属性a,因此就输出了undefined,但是第八行,我们人为的用call方法将foo的this绑定到了obj身上,此时它的this指向对象obj,这时属性a能找到值为1

function foo(x, y) {
  console.log(this.a, x + y)
}
var obj = {
  a: 1
}
foo.call(obj, 2, 3) 

image.png 如果我们的函数要显示绑定的同时也要穿参数我们也可以用call方法帮我们把参数给函数

小知识:

call方法在Function.prototype.call(),也就是构造函数身上显示原型

call方法调用时就直接调用了函数,能帮函数接收参数

4、箭头函数

(在不用this的情况下,箭头函数和普通函数没有区别)

1.箭头函数没有this,写在了箭头函数中的this那也是它外层的非箭头函数的

function foo() {
  let bar = function () {
    let baz = () => { // 箭头函数里的this指向外层的非箭头函数
      console.log(this)
    }
    baz()
  }
  bar()
}
foo()

因为this在箭头函数不存在,所以this是外层的bar()函数,而bar又被独立调用,所以this指向全局global

2.箭头函数不能作为构造函数使用

let Foo = () => {
  this.name = 'zhangsan'
}
let foo = new Foo()
console.log(foo)

image.png 这里的箭头函数是不能作为构造函数的,因为new的过程中,需要使用this,而箭头函数中没有this,详细的可以了解我上一篇文章 传送门

进阶 手写call方法

function foo(x) {
  console.log(this.a, x)
}

const obj = {
  a: 1
}
Function.prototype.myCall = function (...args) {
  //1.将foo引用到obj上
  //2.让obj调用foo
  //3.移除obj上的foo
  const context = args[0]
  const arg = args.slice(1) //从第二个一路切
  context.fn = this
  const res = context.fn(...arg)//解构,传参
  delete context.fn
  return res
}
foo.myCall(obj, 2)

image.png