前端开发者必须掌握的“this”知识点:从基础到进阶的全方位剖析

86 阅读5分钟

this 是 JavaScript 语言中的一个关键字,它引用的是函数被调用时的上下文对象。this 的具体指向取决于函数如何被调用,也就是说,它的值是在运行时动态绑定的(谁调用this就指向谁),而非在编写时确定(箭头函数除外)。

  • 作为普通函数去调用 (返回window)
  • 使用call apply bind 被调用(传入什么,就返回什么)
  • 作为对象方法被调用 (返回对象的本身)
  • 在class方法中被调用 (当前实例的本身)
  • 在箭头函数中被调用 (永远找它上级作用域的this取值)

1.this的场景

(1)全局上下文:

在全局作用域中,this 指向的是全局对象。在浏览器环境中,全局对象通常是 window。

console.log(this); // window

(2)函数作为对象方法调用:

当函数作为某个对象的方法被调用时,this 指向调用该方法的对象。

var obj = {  
  name: 'Alice',  
  sayHello: function() {  
    console.log('Hello, ' + this.name);  
  }  
};  
obj.sayHello(); // 输出 "Hello, Alice"

(3)构造函数:

当函数作为构造函数使用(即使用 new 关键字调用)时,this 指向新创建的对象实例。

function Person(name) {  
  this.name = name;   //相当于let obj=new Object();obj.name=name; return obj;
}  
var alice = new Person('Alice');  
console.log(alice.name); // 输出 "Alice"

(4)函数作为普通函数调用:

如果函数并非作为对象的方法或构造函数调用,而是直接调用,那么 this 通常会指向全局对象(在浏览器中是 window)。

(5)在类(class)中:

在类(class)中,this 关键字引用的是类的当前实例。在类的方法中,你可以使用 this 来访问类的属性和方 法。

class Person {  
  constructor(name, age) {  
    // 在构造函数中,this 指向新创建的 Person 实例  
    this.name = name; 
    this.age = age;  
  }  
   
  introduce() {  
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);  
  }  
  
  grow() {  
    this.age++;  
    this.introduce(); // 调用 introduce 方法  
  }  
}  
  
// 创建一个 Person 实例  
const alice = new Person('Alice', 25);  
  
// 调用实例方法  
alice.introduce(); // 输出 "Hello, my name is Alice and I am 25 years old."  
  
// 调用另一个实例方法  
alice.grow(); // 输出 "Hello, my name is Alice and I am 26 years old."

(6)箭头函数中的 this

箭头函数不绑定自己的 this,它会捕获其所在上下文代码块的 this 值,作为自己的 this 值。这意味着在箭头函数中,this 的值将始终保持不变,无论该函数如何被调用.

function outerFunction() {  
  this.value = 42;  //this是window
    
  var innerFunction = () => {  
    console.log(this.value);  //捕获其所在上下文的 this 值,作为自己的 this 值,所以this是window
  };  
    
  innerFunction();  
}  
  
outerFunction(); // 输出 42
var x=11
var obj={
  x:22,
  say:()=>{
    console.log(this.x) //捕获obj对象的this,obj对象的this是window
  }
}
window.obj.say() //输出11
var x=11
var obj={
  x:22,
  say:function(){
     
    const fn=()=>{
      console.log(this.x) //捕获function函数的this作为自己的this,function函数this是obj
    }
   fn()
   
  }
}
window.obj.say() //输出22

2.改变this(call apply bind 方法

call、apply和bind都是JavaScript中用于改变函数执行上下文(也就是this的值)的方法,但它们在使用方式和行为上有一些重要的区别。

call使用场景常见的有:改变this指向,复杂数据类型的判断Object.prototype.toString.call({a:1}),js继承,构造函数继承,es5把伪数组转成数组Array.prototype.slice.call(arguments)

(1)参数传递方式:

    • call方法接受一个对象作为this的值,然后是一个参数列表。例如:func.call(obj, arg1, arg2, ...)。
    • apply方法也接受一个对象作为this的值,但它的第二个参数是一个参数数组或类数组对象。例如:func.apply(obj, [arg1, arg2, ...])。
    • bind方法接受一个对象作为this的值,以及一个参数列表,但它返回一个新的函数,而不是立即执行。这个新函数在被调用时,会使用bind方法指定的this值和参数。例如:func.bind(obj, arg1, arg2, ...)。

(2)执行时机:

    • call和apply都是立即执行函数,调用它们会立即执行对应的函数。
    • bind方法不会立即执行函数,而是返回一个新的函数。这个新的函数在被调用时,才会执行原函数,并且this的值会被设置为bind方法指定的值。

(3)返回值:

    • call和apply调用原函数后,会返回原函数的返回值。
    • bind方法返回一个新的函数,这个新函数在被调用时,会使用bind方法指定的this值和参数来调用原函数,并返回原函数的返回值。
function greet(greeting, punctuation) {  
  console.log(greeting + ', ' + this.name + punctuation);  
}  
  
var obj = {name: 'Alice'};  
  
// 使用 call 方法  
greet.call(obj, 'Hello', '!'); // 输出 "Hello, Alice!"  
  
// 使用 apply 方法  
greet.apply(obj, ['Hello', '!']); // 输出 "Hello, Alice!"  
  
// 使用 bind 方法  
var greetAlice = greet.bind(obj, 'Hello', '!');  
greetAlice(); // 输出 "Hello, Alice!"

3.手写实现call apply bind 方法

(1)手写实现call:


var person={
  getName(a,b){
    console.log('getName:::this',this)
    return this.name
  }
}
const man={
  name:'奶茶不加冰七分甜'
}

Function.prototype.myCall=function(content){
  
  if(typeof this!=='function'){
    throw new Error('this is not a function')
  }
  
  const arr=[...arguments].slice(1)
  
  content.fn= this //该this是getName函数
  
  const res=content.fn(...arr) //该this是getName函数
  
  delete content.fn
  
  return res
}

const res=person.getName.myCall(man,'奶茶只喝常温','666')

(2)手写实现apply:


var person={
  getName(a,b){
    console.log('getName:::this',this)
    return this.name
  }
}
const man={
  name:'奶茶不加冰七分甜'
}

Function.prototype.myApply=function(content){
  
  if(typeof this!=='function'){
    throw new Error('this is not a function')
  }
  content.fn= this //该this是getName函数
  let res=null
  if(arguments[1]){
    res=content.fn(...arguments[1])
  }else{
    res=content.fn()
  }

  
  delete content.fn
  return res
}

const res=person.getName.myApply(man,['奶茶只喝常温','666'])

(3)手写实现bind:

var person={
  getName(a,b){
   
    console.log('getName:::this',this)
    return this.name
  }
}
const man={
  name:'奶茶不加冰七分甜'
}

Function.prototype.myBind=function(content){
  
  if(typeof this!=='function'){
    throw new Error('this is not a function')
  }
  const arr=[...arguments].slice(1)
  const fn= this //该this是getName函数
  
  return function(){
    return fn.call(content,...arr)
  }
}

let Fn=person.getName.myBind(man,'奶茶只喝常温','666')
const res=Fn()
console.log(res) //'奶茶不加冰七分甜'

4.this 大厂实战题:

var name=222
var a={
  name:111,
  say:function(){
    console.log(this.name)
    
  }
}
var fun=a.say
fun() //222 this指向window
a.say() //111 this指向a

var b={
  name:333,
  say:function(fun) {
    fun()
  }
}
b.say(a.say) //222 this指向window
b.say=a.say
b.say() //333 this指向b