重学javaScript (十)| 来说说 'this' 吧

172 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

前言

经常会被问到this的指向问题,我们今天就来一探究竟吧

为什么要有this关键字呢?

  • 在java等一些编程语言中,this的关键字一般指向类,且只能在类中使用
  • js中,this的使用则更加灵活 我们先来看个栗子,看下有没有this有什么不同
var obj = {
    name:'juejin',
    eat:function(){
        console.log(this.name+'在吃饭')
    }
    
}

obj.eat() // juejin在吃饭

上述代码执行会打印一个juejin在吃饭,那我们如果把代码稍微做一个修改,把this改为obj

var obj = {
    name:'juejin',
    eat:function(){
        console.log(obj.name+'在吃饭')
    }
    
}

obj.eat() // juejin在吃饭

这个时候我们会发现,依然会打印juejin在吃饭,这就说明即使没有this,我们所需要的需求依旧可以实现。 那么为什么还需要this关键字呢,那是因为如果没有this关键字我们的实现就会复杂难读了许多

那么this到底指向什么呢?

this在全局作用域里的指向

我们先来看个简单的,this在全局作用域下的指向是什么呢

// 1.在全局下的指向
console.log(this)

这个其实可以分为浏览器环境和nodejs环境来看

  • 浏览器下,this指向window对象

image.png

  • 而在nodejs下则会指向一个空对象({})

image.png

this在函数中的指向

然而我们的this很少在全局中被使用,更多的是被使用在函数中

this的生成

  • 所有的函数在被调用的时候都会生成一个执行上下文
  • 这个执行上下文记录着函数的调用栈,AO对象
  • this就是其中的一条记录

this的绑定规则

我们先来看一个小小的栗子

// 定义一个函数
function foo () {
    console.log(this)
}

// 1.在window中调用
foo()

// 2.将foo放入一个对象的变量中进行调用
var obj = {
    fn:foo
}
obj.fn()

// 3.通过call和apply调用

foo.call('111')

来看下它的执行结果

image.png

我们会发现,不同的调用方式,会有不同的结果。

由上述的栗子我们可以知道

  • 在函数被调用时,js会默认给this绑定一个值
  • this的绑定和它编写的位置没有关系
  • this的绑定和函数的调用方式以及函数的调用位置有关
  • this是在函数运行时被绑定的

那么函数的绑定规则到底是怎样的呢,我们一起来学习下吧

它的调用规则大概分为这么四种,接下来我们一一的来对其进行解释

  • 默认绑定
  • 隐式绑定
  • 显式绑定
  • new关键字绑定

默认绑定

定义

函数调用时无任何调用前缀的情景,也就是函数直接自己执行

在默认绑定下的this默认指向全局对象

栗子

举个栗子

// 定义一个函数
function foo () {
    console.log(this)
}

//  默认绑定
foo()

它的执行结果

image.png

几个题目

  1. 题目1
// 定义一个函数
function foo () {
    console.log(this)
}

//  默认绑定
// foo()
// 默认绑定栗子1
var obj = {
    fn:foo
}
var foo1 = obj.fn
foo1()

foo1的打印结果就是全局变量,因为它是独立执行的

image.png

  1. 题目2
// 定义一个函数
function foo () {
    console.log(this)
}

function bar(){
    foo()
}

bar()

来看下执行结果依然是全局对象

image.png

隐式绑定

定义

隐式绑定是通过函数的调用来进行绑定的,也就是说如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上

栗子

// 定义一个函数
function foo () {
    console.log(this)
}

var obj = {
    name:'kaka',
    fn:foo
}

obj.fn()

根据定义它应该指向的是obj对象,也就是谁直接调用它,this就指向谁,来看下它的执行结果

image.png

再来看个栗子,当有多次调用时的情况

// 定义一个函数
function foo () {
    console.log(this)
}

var obj = {
    fn:foo
}

var obj2 = {
    name: 'messi',
    sonobj: obj
}

obj2.sonobj.fn()

因为fn由sonobj调用的,而sonobj又是obj对象,所以this指向obj

image.png

显示绑定

定义

因为隐式绑定有一个前提条件,必须在函数内部有一个对函数的引用,比如一个对象,然后通过这个引用间接的this绑定到这个对象上,如果没有这个引用的话,调用函数就会报找不到该函数的错误

因此,隐式绑定会有一些弊端

如果我们想让自己指定函数的this指向,就应该用到显示绑定来自己指定this的指向,通常会用到下面三个方法

  • call
  • apply
  • bind

这个三个方法是js内置的方法,可以帮我们改变this的指向

栗子

// 定义一个函数
function sum (a,b) {
    console.log(a+b)
    console.log(this)
}
var obj = {
    name:'kaka'
}
sum.call(obj, 100,200)

这个栗子的执行结果

image.png

call方法改变了this的指向,将其从原本的window对象指向了obj对象,call的第一个参数就是你要指定的this的指向,后续的参数就是原函数的变量,挨个传入

三个方法的区别

上面我们说过,call,apply和bind都是改变this的指向的,它们区别是什么呢

  • call和apply的区别就是后边参数的传入方式

    • call是直接挨个传入 javascript sum.call(obj,100,200)
    • apply是把函数的参数当数组传入第二个参数
    sum.apply(obj,[100,200])
    

    来看下执行结果

    image.png

  • bind函数的返回值是个函数,所以需要执行一下才能改变指向

  • bind的函数传参方式和apply相同

sum.bind(obj, 100,200)()

来看下运行结果

image.png

new关键字绑定

定义

  • javascript中的函数可以当作一个类的构造函数来使用,也就是使用new关键字
  • 使用函数的new关键字来进行调用的时候会执行如下操作
    1. 创建一个全新的对象
    2. 这个新对象会被执行prototype连接
    3. 这个新对象会被绑定到函数调用的this上(this绑定在这个时候完成)
    4. 如果函数没有返回其他对象,函数会返回这个新对象

栗子

// 定义一个函数
function person (name,age) {
    this.name = name
    this.age = age
    this.fn = function () {
        console.log(this)
    }
}

const kaka =  new person('kaka', 22)
kaka.fn()

来看下运行结果

image.png