文章目录
- 一、this 是什么?
- 二、四条铁律:this 绑定规则
- 1. 默认绑定 —— 独立函数调用
- 2. 隐式绑定 —— 作为对象方法调用
- 3. 显式绑定 —— call、apply、bind
- 4. new 绑定 —— 构造函数调用
- 三、箭头函数的 this
- 四、this 绑定优先级
- 五、常见场景实战
- 1. 事件处理函数中的 this
- 2. 类(Class)中的 this
- 3. 回调函数中的 this
- 六、经典面试题分析
- 七、最佳实践总结
一、this 是什么?
简单说,this 是函数执行时的上下文对象。在绝大多数情况下,函数的调用方式决定了 this 的值。它不能在函数定义时确定,只有在函数被调用时才会绑定。
理解这一点至关重要:同一个函数,不同方式调用,this 截然不同。
二、四条铁律:this 绑定规则
1. 默认绑定 —— 独立函数调用
当函数直接调用(非严格模式)时,this 指向全局对象(浏览器中是 window,Node.js 中是 global)。
functionshowThis(){console.log(this);}showThis();// window(或 global)如果是严格模式(‘use strict’),this 为 undefined。
functionstrictShow(){'use strict';console.log(this);}strictShow();// undefined2. 隐式绑定 —— 作为对象方法调用
函数作为对象的属性被调用时,this 指向调用该方法的对象(即点号前面的对象)。
const obj={name:'Alice',sayName(){console.log(this.name);}};obj.sayName();// Alice,this 指向 obj常见陷阱:隐式丢失
将方法赋值给变量或作为回调传递时,会丢失原对象,回退为默认绑定。
const fn=obj.sayName;fn();// undefined(非严格模式下 window.name 通常为''或 undefined)在 setTimeout 中也一样:
setTimeout(obj.sayName,100);// this 指向 window3. 显式绑定 —— call、apply、bind
这三大方法可以强制指定 this。
call(thisArg, arg1, arg2, …):立即执行函数,参数逐个传递。
apply(thisArg, [args]):立即执行函数,参数以数组形式传递。
bind(thisArg, …):返回一个绑定了 this 的新函数,不立即执行。
functiongreet(greeting){console.log(`${greeting}, ${this.name}`);}const user={name:'Bob'};greet.call(user,'Hi');// Hi, Bob greet.apply(user,['Hello']);// Hello, Bob const boundGreet=greet.bind(user,'Hey');boundGreet();// Hey, Bob注意:一旦使用 bind 绑定,后续再使用 call/apply 也无法改变 this(箭头函数同样无法改变,见下文)。
4. new 绑定 —— 构造函数调用
使用 new 调用函数时,会创建一个全新的对象,并将 this 绑定到这个新对象上。
functionPerson(name){this.name=name;}const p=new Person('Carol');console.log(p.name);// Carolnew 做了四件事:
1、创建一个空对象。
2、该对象的proto指向构造函数的 prototype。
3、将 this 绑定到该对象上,执行构造函数。
4、如果构造函数没有返回对象,则返回 this。
三、箭头函数的 this
箭头函数没有自己的 this。它会捕获定义时外层作用域的 this 作为自己的 this,且之后无法被改变(call、apply、bind 均无效)。
const obj={name:'Dave',regularFunc(){setTimeout(function(){console.log('regular:', this.name);// undefined(或 window.name)},100);},arrowFunc(){setTimeout(()=>{console.log('arrow:', this.name);// Dave,捕获到 arrowFunc 的 this 即 obj},100);}};obj.regularFunc();obj.arrowFunc();这解决了回调函数中 this 丢失的经典难题。但也要注意:箭头函数不能用作构造函数(new 会报错)。
四、this 绑定优先级
当多个规则同时出现时,优先级为:
new 绑定 > 显式绑定(call/apply/bind) > 隐式绑定 > 默认绑定
验证一下:
functionfoo(){console.log(this.a);}const obj1={a:1, foo};const obj2={a:2};obj1.foo.call(obj2);//2(显式 vs 隐式 → 显式赢)const bound=foo.bind(obj1);new bound();// undefined,new 会覆盖 bind,this 指向新对象五、常见场景实战
1. 事件处理函数中的 this
DOM 事件中,this 默认指向绑定事件的元素(除 IE attachEvent 外)。
button.addEventListener('click',function(){console.log(this);// 指向 button 元素});若在回调中使用箭头函数:
button.addEventListener('click',()=>{console.log(this);// 捕获外层 this,可能是 window});2. 类(Class)中的 this
ES6 类的方法默认启用严格模式,且 this 容易在赋值传递时丢失。
class Counter{constructor(){this.count=0;}increment(){this.count++;}}const c=new Counter();const inc=c.increment;inc();// TypeError: Cannotreadproperty'count'of undefined解决方案:
在构造函数中使用 bind:this.increment = this.increment.bind(this);
使用类字段(箭头函数语法):increment = () => { this.count++; }
3. 回调函数中的 this
在 map、filter、forEach 等数组方法中可传入 thisArg 作为第二个参数,或直接使用箭头函数。
const obj={multiplier:2};[1,2,3].map(function(x){returnx * this.multiplier;}, obj);//[2,4,6]// 或者[1,2,3].map(x=>x * obj.multiplier);六、经典面试题分析
var length=10;functionfn(){console.log(this.length);}var obj={length:5, method: function(fn){fn();// ① arguments[0]();// ②}};obj.method(fn,1);答案:① 10(或 undefined严格模式),② 2。
解析:
①:fn() 独立调用,this 指向 window,window.length = 10(或严格模式 undefined)。
②:arguments0 相当于 arguments.0(),arguments 对象调用了 fn,隐式绑定 this 为 arguments,而 arguments.length 是实参个数(2 个:fn 和 1),所以输出 2。
这道题极好地体现了隐式绑定与独立调用的差异。
七、最佳实践总结
1、优先使用箭头函数 来处理回调,避免 this 丢失问题,但需清楚它没有自己的 this。
2、在类中,若方法需作为回调传递,在构造函数内 bind 或使用箭头函数类字段。
3、普通函数用 call/apply/bind 显式绑定,尤其在工具函数中。
4、避免混用 var 和 this 在对象方法内,建议统一用 this 访问实例属性。
5、开启严格模式 是个好习惯,能让默认绑定的 undefined 尽早暴露错误。
6、理解优先级,遇到复杂 this 问题时按 new → 显式 → 隐式 → 默认 的顺序推理。