JavaScript this指向问题详解

112 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

javascript this详解

什么是this

在javascript中,this表示对象的引用,this指向的对象并不是固定的,会根据调用的上下文(即执行时的环境)的改变而改变

this指向哪里

  • 全局调用时this指向window
  • 谁调用指向谁,即指向当前调用的对象
  • 独立函数调用时,指向window
  • 构造函数的this指向
  • 箭头函数的this指向
  • prototype中的this指向

1、全局调用函数时,this指向window

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

point(). // window

2、当函数属于某对象时


var obj = {
  count : 0
  func: function() {
    console.log(this.count)
  }

}

// 调用

obj.func() //  0

var fun = obj.func()
fun() // undefined


obj.func() 调用时func内部的this指向obj对象

3、fun调用时是独立于obj对象之外的,是独立函数调用,this指向window

4、构造函数中的this指向

构造函数和普通函数区别不大,主要在于调用方式,当使用new关键字调用构造函数时,往往会返回一个对象,而this就会指向这个对象,如果不返回对象,则指向构造函数

 var name = 'frank';

// 当构造函数没有返回值时
let myClass = function() {
   this.name = 'lily';
   this.sayName = function() {
      console.log('class内部的this',this.name)
   }
}

// 调用实例的方法,this指向构造函数
let myObj = new myClass()
myObj.sayName() // lily


// 把实例中的function作为独立函数调用,独立函数中的this指向window
let outSay = myObj.sayName
outSay()

console.log('外部的this',myObj.name) // lily
// 当构造函数有返回值时

var name = "waite";
let myClass = function() {
   this.name = "lucy",
   this.sayHellow = function() {
     console.log('hellow',this.name)
   }
   return {
     name: 'sucy'
   }

}
 let myObj = new myClass()
// 调用实例的方法
myObj.sayHellow()   // 报错,因为myObj时myClass的返回值,{name: 'sucy'},没有sayHellow方法

// 把实例方法作为独立函数调用
let func = myObj.sayHellow;
func() // 报错,同上

// 直接访问实例的this
console.log("直接访问实例的this",myObj.name)  //  sucy

5.箭头函数中的this指向 箭头函数不会创建自己的上下文,而是将this指向外部函数已经创建好的上下文

v

var fun = () => {
  console.log(this)
}

fun()  // window


let obj = {
  name: "object",
  print:() => {
    console.log(this)
  },
  log: function() {
    setTimeout(() => {
      console.log(this)
    },500)
  }
}

 // 当使用箭头函数作为对象的方法时,this指向window
obj.print() // window
obj.log()  // obj本身



6.prototype中的this 在对象的原型上定义方法时的this和在对象中定义的方法差不多,主要看调用形式

var name = "window"
var myClass = function() {
  this.name = 'prototype',
  this.insetFun = function() {
    console.log('inset',this.name)
  }
}

myClass.prototype.protoFun = function() {
  console.log('proto',this.name)
}

var myObj = new myClass()

// 实例调用
myObj.insetFun()  // prototype
myObj.protoFun()  // prototype

// 独立函数调用

var fun1 = myObj.insetFun;
var fun2 = myObj.protoFun;

fun1()  // window
fun2()  // window

this的绑定方式

1、隐式绑定 2、显式绑定 3、new绑定

1、隐式绑定

this的隐式绑定是指谁调用就指向谁,自动搜寻上下文 最外层的this指向window对象 孤立函数中的this指向的是window

2、显式绑定

显式绑定的优先级大于隐式绑定,常用来强行改变this指向 常用的显示绑定方法有bind,call,apply

call,apply,bind在使用上的区别

var year = '2022';
function getDate(month, day) {
  console.log(this.year +'-' + month + "-" + day)
}

var obj = {
  year: "2021",
}

getDate.call(null, 3, 8)  // 2022-3-8
getDate.call(obj, 3, 8)   // 2021-3-8
getDate.apply(obj,[6, 8]) // 2021-6-8
getDate.bind(obj)(9, 8)   // 2021-6-8

1、bind

bind使用时返回一个新的函数,bind的参数是this将要指向的对象,调用bind返回的函数时传递原函数的参数

bind传参问题

let newThis = {
  name: "newThis",
}

function sayHellow (greet1, greet2){
   console.log(this.name + "," + greet1 +"、" + greet2)
}

let greet = sayHellow.bind(newThis, "morning",'afternoon')
greet("evening")  // 输出 newThis,morning,afternoon

调用bind时传入的参数会拼接在bind返回函数的参数之前一起传给返回函数,多余的参数不起作用

那么怎么手写实现一个bind呢

Function.prototype.mtbind = function (context = window) {
    let fn = this   //this指代调用bind的函数
   // 保存参数,调用bind时返回的参数
   let args = arguments.slice(1)

   let bind = function (){
      // bind的返回函数
      // 存储调用返回函数时的参数
      let bindargs = [...arguments]
      return fn.apply(context,[...args,...bindargs])
   }
   
  return bind
}
2、call

call使用时是参数形式是第一个参数是this将要指向的对象,之后函数参数逐个添加

怎么实现一个call呢

Function.prototype.mycall = function (context = window) {
  context.fn = this. // this指代当前调用mycall的函数
  let args = [...arguments].slice(1). // 取出第一个参数之后的所有参数
  
  let res = context.fn(...args)   / 调用函数并传递参数得到执行结果
   delete context.fn      // 删除context上的fn函数
   return res    // 返回执行结果
   
}
3、apply

apply函数只有两个参数,第一个参数是this将要指向的对象,第二个参数是数组,原函数的所有参数添加到该数组中

Function.prototype.myapply = function (context = window) {
   context.fn = this 
   // 此处需要判断是否有第二参数
   let res
   if(arguments.length > 1){
      let args = arguments[1]
      res = context.fn(...args)
   }else {
      res = context.fn()
   }
   
   delete context.fn
   return res
}

3、new

首先我们明确一点,使用new操作符时发生了什么

存在以下两种情况 当构造函数没有明确返回对象时,进行以下几步 1)、创建一个新的对象 2)、把构造函数的作用域赋给当前对象(this即指向当前对象) 3)、执行构造函数中的代码,把构造函数的属性赋给当前对象 4)、返回当前对象

当构造函数有return 一个对象时,发生以下几步

1)、把构造函数的this指向返回值Object 2)、返回该Object

// 当构造函数没有返回值时
let myClass = function(name,age,family){
  this.name = name;
  this.age = age;
  this.family = family;
  
}

let pasonA = new myClass("axion",18,"上海")

console.log(pasonA)
// 输出  {
  "name": "axion",
  "age": 18,
  "family": "上海"
}

// 当构造函数有返回值时
   this.name = name;
  this.age = age;
  this.family = family;
  return {
    color: "yellow"
  }
}

let pasonA = new myClass("axion",18,"上海")

console.log(pasonA)
// 输出 { color: "yellow"}