this指向恐惧消除器

52 阅读5分钟

前言

this是JavaScript中最令人困惑的概念之一,但也是最重要的概念之一。本文将通过30个题目帮助你消除对this指向问题的恐惧心理,废话不多说了,直接发车🫏🫏🫏

基础篇

题目1:全局环境中的this

console.log(this);

答案:在浏览器中指向window对象,在Node.js中指向global对象

题目2:函数中的this(非严格模式)

function showThis() {
  console.log(this);
}
showThis();

答案:在非严格模式下指向window(浏览器)或global(Node.js)

题目3:函数中的this(严格模式)

'use strict';
function showThis() {
  console.log(this);
}
showThis();

答案undefined

题目4:对象方法中的this

const obj = {
  name: 'jjq',
  greet() {
    console.log(this.name);
  }
};
obj.greet();

答案'jjq'(指向调用方法的对象obj)

题目5:嵌套对象中的this

const obj = {
  name: 'jjq',
  inner: {
    name: 'jiangjianqing',
    greet() {
      console.log(this.name);
    }
  }
};
obj.inner.greet();

答案'jiangjianqing'(指向直接调用方法的对象inner)

进阶篇

题目6:解构方法后的this

const obj = {
  name: 'jjq',
  greet() {
    console.log(this.name);
  }
};
const { greet } = obj;
greet();

答案undefined(解构后相当于普通函数调用)

题目7:回调函数中的this

const obj = {
  name: 'jjq',
  greet() {
    setTimeout(function() {
      console.log(this.name);
    }, 100);
  }
};
obj.greet();

答案undefined(回调函数的this默认指向全局对象)

题目8:箭头函数中的this

const obj = {
  name: 'jjq',
  greet() {
    setTimeout(() => {
      console.log(this.name);
    }, 100);
  }
};
obj.greet();

答案'jjq'(箭头函数继承外层作用域的this)

题目9:构造函数中的this

function Person(name) {
  this.name = name;
}
const jjq = new Person('jjq');
console.log(jjq.name);

答案'jjq'(构造函数中的this指向新创建的实例)

题目10:构造函数return对象时的this

function Person(name) {
  this.name = name;
  return { name: 'jiangjianqing' };
}
const jjq = new Person('jjq');
console.log(jjq.name);

答案'jiangjianqing'(构造函数返回对象时,this指向被覆盖)

改变this指向篇

题目11:call方法改变this

const obj1 = { name: 'jjq' };
const obj2 = { name: 'jiangjianqing' };

function greet() {
  console.log(this.name);
}

greet.call(obj1);
greet.call(obj2);

答案'jjq' 然后 'jiangjianqing'(call立即调用函数并改变this)

题目12:apply方法改变this

const obj = { name: 'jjq' };

function greet(greeting) {
  console.log(`${greeting}, ${this.name}`);
}

greet.apply(obj, ['Hello']);

答案'Hello, jjq'(apply与call类似,但参数是数组)

题目13:bind方法改变this

const obj = { name: 'jjq' };

function greet() {
  console.log(this.name);
}

const boundGreet = greet.bind(obj);
boundGreet();

答案'jjq'(bind返回一个绑定this的新函数)

题目14:多次bind的this

const obj1 = { name: 'jjq' };
const obj2 = { name: 'jiangjianqing' };

function greet() {
  console.log(this.name);
}

const boundGreet = greet.bind(obj1).bind(obj2);
boundGreet();

答案'jjq'(bind只能绑定一次,后续bind无效)

题目15:箭头函数与bind

const obj = { name: 'jjq' };

const greet = () => {
  console.log(this.name);
};

const boundGreet = greet.bind(obj);
boundGreet();

答案undefined(箭头函数的this无法被bind改变)

特殊场景篇

题目16:DOM事件处理函数中的this

<button id="btn">Click me</button>
<script>
  document.getElementById('btn').addEventListener('click', function() {
    console.log(this);
  });
</script>

答案:指向触发事件的DOM元素(button元素)

题目17:箭头函数作为DOM事件处理函数

<button id="btn">Click me</button>
<script>
  document.getElementById('btn').addEventListener('click', () => {
    console.log(this);
  });
</script>

答案:指向外层作用域的this(通常是window)

题目18:setTimeout中的this

const obj = {
  name: 'jjq',
  greet() {
    setTimeout(function() {
      console.log(this.name);
    }, 100);
  }
};
obj.greet();

答案undefined(回调函数中的this默认指向全局对象)

题目19:setInterval中的this

const obj = {
  count: 0,
  start() {
    setInterval(function() {
      this.count++;
      console.log(this.count);
    }, 1000);
  }
};
obj.start();

答案NaN(this指向全局对象,全局count为undefined,undefined++为NaN)

题目20:class中的this

class Person {
  constructor(name) {
    this.name = name;
  }
  greet() {
    console.log(this.name);
  }
}
const jjq = new Person('jjq');
jjq.greet();

答案'jjq'(类方法中的this指向实例)

综合挑战篇

题目21:多层嵌套中的this

const obj = {
  name: 'jjq',
  outer() {
    function inner() {
      console.log(this.name);
    }
    inner();
  }
};
obj.outer();

答案undefined(inner作为普通函数调用,this指向全局)

题目22:方法赋值后的this

const obj1 = {
  name: 'jjq',
  greet() {
    console.log(this.name);
  }
};

const obj2 = {
  name: 'jiangjianqing'
};

obj2.greet = obj1.greet;
obj2.greet();

答案'jiangjianqing'(this指向调用方法的对象obj2)

题目23:立即执行函数中的this

const obj = {
  name: 'jjq',
  greet() {
    (function() {
      console.log(this.name);
    })();
  }
};
obj.greet();

答案undefined(IIFE中的this指向全局对象)

题目24:链式调用中的this

const calculator = {
  value: 0,
  add(num) {
    this.value += num;
    return this;
  },
  multiply(num) {
    this.value *= num;
    return this;
  },
  getValue() {
    return this.value;
  }
};

const result = calculator.add(5).multiply(2).getValue();
console.log(result);

答案10(每个方法都返回this,实现链式调用)

题目25:原型链中的this

function Person(name) {
  this.name = name;
}

Person.prototype.greet = function() {
  console.log(this.name);
};

const jjq = new Person('jjq');
jjq.greet();

答案'jjq'(原型方法中的this指向调用该方法的实例)

终极挑战篇

题目26:this与闭包结合

const obj = {
  name: 'jjq',
  tasks: ['task1', 'task2', 'task3'],
  showTasks() {
    this.tasks.forEach(function(task) {
      console.log(`${this.name} has ${task}`);
    });
  }
};
obj.showTasks();

答案:三次'undefined has taskX'(回调函数中的this指向全局)

题目27:解决题目26的问题

const obj = {
  name: 'jjq',
  tasks: ['task1', 'task2', 'task3'],
  showTasks() {
    this.tasks.forEach(function(task) {
      console.log(`${this.name} has ${task}`);
    }, this); // 使用forEach的第二个参数
  }
};
obj.showTasks();

答案:三次'jjq has taskX'(通过forEach的第二个参数绑定this)

题目28:箭头函数作为类字段

class Counter {
  count = 0;
  
  increment = () => {
    this.count++;
    console.log(this.count);
  };
}

const counter = new Counter();
const increment = counter.increment;
increment();

答案1(箭头函数类字段绑定实例this) 这时候可能有小伙伴要问了:为函数调用,this不指向全局?

注意!!!

在 JavaScript 中,this 的指向取决于函数的调用方式,而不是函数的定义方式

而在这一题中,this 不指向全局对象的原因是箭头函数的 this 是根据定义时所在上下文决定的,而不是根据调用方式决定的,可以按照下面的思路去理解:

  • 箭头函数的 this 是在定义时就确定的,它会捕获其定义时所在上下文的 this 值。
  • Counter 类中,increment 是一个箭头函数,它捕获了类的实例上下文(即 counter 实例)。

题目29:this与扩展运算符

const obj = {
  name: 'jjq',
  greet(...args) {
    console.log(this.name, args);
  }
};

const greet = obj.greet;
greet.call(obj, 1, 2, 3);

答案'jjq' [1, 2, 3](扩展运算符不影响this)

题目30:最复杂的this综合题

const obj1 = {
  name: 'jjq',
  greet() {
    return () => {
      console.log(this.name);
    };
  }
};

const obj2 = {
  name: 'jiangjianqing'
};

const greet = obj1.greet();
greet.call(obj2);

答案'jjq'(箭头函数的this在创建时确定,无法被call改变)

总结

好了 我先收摊