this作为JavaScript语言的核心语法,我们在开发中是经常用到的。例如在对象方法中,通过this访问对象属性;在构造函数中,用this初始化实例对象。那么JavaScript为什么会有this呢?
JavaScript是一门函数优先(first-class function)的语言,即函数对象跟其它对象一样具有属性和方法,可以跟普通变量一样赋值给另一个变量。
那么,当一个函数被赋值给不同变量,在不同的执行上下文调用时,如何访问到当前执行上下文呢?
var msg = '全局信息'
function log(){
console.log(msg); // 打印消息
}
// 在全局上下文调用
log(); // 打印:'全局信息'
let checkObj={
msg:'校验信息',
log:log
}
checkObj.log(); // 打印:'校验信息' ???
let computeObj={
msg:'计算信息',
log:log
}
computeObj.log(); // 打印:'计算信息' ???
解读上面代码:
log()被赋值给不同变量,分别在全局上下文,checkObj对象方法和computeObj对象方法中被调用。这里,我们的预期是:
在全局上下文调用时,应该能访问全局变量msg = ‘全局信息’;
在checkObj.log()调用时,应该能访问到checkObj.msg = ‘校验信息’;
在computeObj.log()调用时,应该能访问到computeObj.msg = ‘计算信息’;
当然,上面代码的执行结果是不符预期的。因为根据词法作用域,log()中的msg就是全局变量msg = ‘全局信息’,跟log()的当前执行上下文无关。
所以,需要有一种机制,可以在函数内部获取到当前执行上下文,这就是this。
上面的代码用this重构如下:
var msg = '全局信息'
function log(){
console.log(this.msg); // 打印当前上下文的消息
}
// 在全局上下文调用
log(); // 打印:'全局信息'
let checkObj={
msg:'校验信息',
log:log
}
checkObj.log(); // 打印checkObj.msg:'校验信息'
let computeObj={
msg:'计算信息',
log:log
}
computeObj.log(); // computeObj.msg:'计算信息'
那么,JavaScript的this是怎么设计的呢?
容易想到的方法是:当函数被调用时,JavaScript引擎自动将this值设置为当前执行上下文。这样,在函数内部就能通过this访问到当前执行上下文了。
根据调用方式,函数的执行上下文可以分为两种。
作为对象方法调用时,函数中的this指向该对象。
var value="123"
function func(){
console.log(this.value)
}
let obj={
value:"abc",
getVal:func
}
obj.getVal(); // "abc"
如果把上面的func()换成下面这种写法:
var value="123"
function func(){
function foo(){
console.log(this.value)
}
foo();
}
let obj={
value:"abc",
getVal:func
}
obj.getVal(); // "123"
虽然func()作为obj对象方法被调用,但内部的foo()是在全局执行环境被调用的,所以foo()中的this指向顶层对象window/global。
在全局执行环境调用函数时,函数中的this指向顶层对象(window或global);
var value="123"
function func(){
console.log(this.value);
}
func(); // "123
注意:严格模式下,this为undefined。
var value="123"
function func(){
"use strict";
console.log(this.value);
}
func(); // TypeError: Cannot read properties of undefined (reading 'value')
在不同的执行上下文,this会自动绑定不同的值,但有时候我们想手动指定this值,这种情况又得怎么实现呢?
在es5中,可以通过call(),apply(),bind()手动绑定函数中的this值:
call()和apply()的区别就是传参不同,前者是列表参数,后者是数组参数。
call()和apply()是在每次函数调用时设置this指向;
bind()的入参是一个函数,将函数的this永久绑定一个值,然后返回新的函数。
应用场景:
call()和apply()常用于高阶函数中,将包裹函数的this传递到内部的函数。例如:限流函数和防抖函数。
当连续调用函数时,限制函数单位时间内只执行一次。适用于限制缩放/滚动/鼠标移动等事件处理函数的执行频率。
function throttle(fn, interval){
if(typeof interval !== "number" && !(interval instanceof Number)){
throw new TypeError("第二个参数是限流时间间隔(ms),请输入正整数")
}
let lastExec=0;
function wrapper(){
let self=this;
let args=arguments;
let pastTime=Date.now()-lastExec;
function exec(){
lastExec=Date.now();
fn.apply(self,args);
}
if(pastTime>interval){
exec()
}
}
return wrapper;
}
当连续调用函数时,保证函数只被执行一次。可以是在连续调用的第一次执行,也可以在最后一次执行。适用于按钮点击防抖,搜索框提交内容。
function debounce(fn, delay = 200, atBegin=true) {
let timeoutId = null;
return function wrapper() {
let self=this;
let args=arguments;
function exec(){
fn.apply(self,args);
}
function clear(){
timeoutId=undefined;
}
if(timeoutId){
clearTimeout(timeoutId);
}
if(atBegin&&!timeoutId){
exec();
}
timeoutId=setTimeout(atBegin?clear:exec,delay)
}
}
obj = {
prop:'1',
func:debounce(function(){
console.log(this.prop);
},3000)
}
obj.func(); // 3000ms后打印: 1
解读上面代码
bind()可以永久绑定函数中的this值。例如绑定回调函数的this。
// 实现一个Promise类
class MyPromise {
// 构造方法
constructor(executor) {
// 初始化值
this.initValue()
// 初始化this指向
this.initBind()
// 执行传进来的函数
executor(this.resolve, this.reject)
}
initBind() {
// 初始化this
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
initValue() {
// 初始化值
this.PromiseResult = null // 终值
this.PromiseState = 'pending' // 状态
}
resolve(value) {
// 如果执行resolve,状态变为fulfilled
this.PromiseState = 'fulfilled'
// 终值为传进来的值
this.PromiseResult = value
}
reject(reason) {
// 如果执行reject,状态变为rejected
this.PromiseState = 'rejected'
// 终值为传进来的reason
this.PromiseResult = reason
}
}
// 使用MyPromise处理异步操作
let pro = new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve('success');
},2000);
})
解读上面代码
// bind()的一种实现。
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
};
}
箭头函数本身没有this,而是属于外层词法作用域的。
var value="123"
function func(){
return ()=>this.value
}
let obj={
value:"kobe",
getValue:func
}
obj.getValue()(); // "kobe"
func()() // "123"
解读上面代码
箭头函数的词法作用域特性和this机制的动态特性是JavaScript中两种不同的编码风格。在箭头函数出现之前,我们常用self方法(别名方法)来捕获this:
function foo() {
var self = this; // lexical capture of this
setTimeout( function(){
console.log( self.a );
}, 100 );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
构造函数中的this指向正在创建的对象。
function Factory(){
this.name="kobe"
}
let obj=new Factory();
console.log(obj.name)
class Factory {
constructor(){
this.name="name"
}
}
let obj1=new Factory();
console.log(obj1.name)
一开始,JS函数内部的this是自动绑定到当前执行上下文的。在全局执行环境调用函数时,this指向顶层对象(window/global);作为对象方法被调用时,this指向调用对象。
后来,es5引入了call()、apply()、bind()等方法来支持手动绑定this值:
最后,es6引入了箭头函数,它本身没有this,而是属于外层词法作用域的。