本文对JavaScript中的this关键字进行全方位的解析,看完本篇文章,希望读者们能够完全理解this的绑定问题。
开篇:对于那些没有投入时间去学习this机制的JavaScript开发者来说,this的绑定是一件令人困惑的事。(包括曾经的自己)。
误区:学习this的第一步是明白this既不指向函数本身也不指向函数的词法作用域,你是否被类似这样的解释所误导?但其实这种说法都是错误的。
概括:this实际是在函数被调用时发生的绑定,它所指向的位置完全取决于函数被调用的位置。
在理解this的绑定过程之前,首先要理解调用位置:调用位置就是函数在代码中被调用的位置(而不是声明的位置)。
所以说,寻找调用位置就是寻找“函数被调用的位置”,这里最重要的点是要分析调用栈(存放当前正在执行的函数的位置)。
什么是调用栈和调用位置?
关系:调用位置就在当前正在执行的函数(调用栈)的前一个位置。
function?func1()?{
??//?当前调用栈:func1
??//?当前调用位置是全局作用域(调用栈的前一个位置)
??console.log('func1')
??func2()?//?这里是:func2的调用位置
}
function?func2()?{
??//?当前调用栈:func1?->?func2
??//?当前调用位置是在func1(调用栈的前一个位置)
??console.log('func2')
??func3()?//?这里是:func3的调用位置
}
function?func3()?{
??//?当前调用栈:func1?->?func2?->?func3
??//?当前调用位置是在func2(调用栈的前一个位置)
??console.log('func3')
}
func1()?//?这里是:func1的调用位置
关注点:我们是如何从调用栈中分析出真正的调用位置的,因为这决定了this的绑定。
最常用的函数调用类型:独立函数调用
function?getName()?{
??console.log(this.name)
}
var?name?=?'kyrie'
getName()?//?'kyrie'
当调用getName()时,this.name拿到了全局对象的name。因为getName()是直接调用的,不带任何修饰符,使用的是默认绑定,因此this指向全局对象(非严格模式)。
如果使用严格模式('strict mode')呢?
function?getName()?{
??'use?strict';
??console.log(this.name)
}
var?name?=?'kyrie'
getName()?//?'TypeError:?this?is?undefined'
那么全局对象无法使用默认绑定,因此this会绑定到undefined。
调用位置是否有上下文对象
function?getName()?{
??console.log(this.name)
}
var?person?=?{
??name:?'kyrie',
??getName:?getName
}
person.getName()?//?'kyrie'
当getName()被调用时,它的落脚点指向person对象,当函数引用有上下文对象时,隐式绑定会把函数调用中的this绑定到这个上下文对象,因此调用getName()时this被绑定到person,因此this.name跟person.name是一样的
常见问题:隐式丢失?
function?getName()?{
??console.log(this.name)
}
var?person?=?{
??name:?'kyrie',
??getName:?getName
}
var?getName2?=?person.getName()?//?函数别名
var?name?=?'wen'?//?name是全局对象的属性
getName2()?//?'wen'?这里拿到的是全局对象的name
解释:虽然getName2是person.getName的一个函数引用,但它引用的getName函数的本身,因此getName2()调用时不带任何修饰符,使用的是默认绑定,因此this绑定了全局对象。
使用call() / apply() / bind() 指定this的绑定对象
function?getName()?{
??console.log(this.name)
}
var?person?=?{
??name:?'kyrie'
}
getName.call(person)?//?'kyrie'
getName.apply(person)?//?'kyrie'
通过getName.call()/ getName.apply() 调用强制把它的this绑定到person上。
所有函数都可以用new来调用,这种函数调用称为构造函数调用。
重点:实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
function?setName(name)?{
??this.name?=?name
}
var?person?=?new?setName('kyrie')
console.log(person.name)?//?'kyrie'
使用new调用setName()时,会创建一个新对象并把这个新对象绑定到setName()调用的this上,并把这个对象返回。
毫无疑问,默认绑定的优先级是四条规则中最低的,所以暂不考虑它。
function?getName()?{
??console.log(this.name)
}
var?p1?=?{
??name:?'kyrie',
??getName:?getName
}
var?p2?=?{
??name:?'wen',
??getName:?getName
}
p1.getName()?//?'kyrie'
p2.getName()?//?'wen'
p1.getName.call(p2)?//?'wen'
p2.getName.call(p1)?//?'kyrie'
结果,显式绑定的优先级比隐式绑定高。
function?setName(name)?{
??this.name?=?name
}
var?p1?=?{
??setName:?setName
}
var?p2?=?{}
p1.setName('kyrie')
console.log(p1.name)?//?'kyrie'
p1.setName.call(p2,?'wen')
console.log(p2.name)?//?'wen'
var?p3?=?new?p1.setName('zbw')
console.log(p1.name)?//?'kyrie'
console.log(p3.name)?//?'zbw'
结果,new绑定的优先级比隐式绑定高
function?setName(name)?{
??this.name?=?name
}
var?p1?=?{}
//?bind会返回一个新的函数
var?setP1Name?=?setName.bind(p1)
setP1Name('kyrie')
console.log(p1.name)?//?'kyrie'
var?p2?=?new?setP1Name('wen')
console.log(p1.name)?//?'kyrie'
console.log(p2.name)?//?'wen'
结果,new绑定的优先级比显示绑定高
综上,优先级的正确排序:
从高到低: new > 显示 > 隐式 > 默认
现在我们可以根据优先级来判断函数在某个位置调用this的指向。
var?p1?=?new?Person()
var?p1?=?setName.call(p2)
var?p2?=?p1.setName()
var?p1?=?setName()
以上上提到判断this指向的四条规则包含所有正常的函数,除了ES6中的箭头函数。
概括:箭头函数不像普通函数那样使用function关键字定义,而是用 “胖箭头” => 定义 。而且箭头函数并不适用以上的四条规则,它的this绑定完全是根据 外层作用域(函数或者全局) 来决定的。
function?getName()?{
??//?箭头函数的this指向外层作用域
??return?(name)?=>?{
????console.log(this.name)
??}
}
var?p1?=?{
??name:?'kyrie'
}
var?p2?=?{
??name:?'wen'
}
var?func?=?getName.call(p1)
func.call(p2)?//?'kyrie'
getName()内部创建的箭头函数会捕获调用时外层作用域(getName)的this,由于getName的this通过显示绑定到p1上,所以getName里创建的箭头函数也会指向p1,最重要的一点:箭头函数的this无法被修改(即使是优先级最高的new绑定也不行)
要判断一个运行中的函数的this绑定,需要找到该函数的调用位置(结合调用栈),接着根据优先级得出的四条规则来判断this的绑定对象。
ES6的箭头函数不适用以上四条规则,而是根据当前的词法作用域来决定this绑定,也就是说,箭头函数会继承外层函数调用的this绑定(无论绑定到什么),而且箭头函数的this绑定无法被修改。
本文转载自微信公众号「SQL数据库」,作者丶平凡世界 。转载本文请联系开发公众...
大家好,我是狂聊君。 今天来聊一聊 Mysql 缓存池原理。 提纲附上,话不多说,直...
CKeditor,以前叫FCKeditor,已经使用过好多年了,功能自然没的说。最近升级到3....
idea官方推送了2020.2.4版本的更新,那么大家最关心的问题来了,之前激活idea202...
本文实例讲述了AJAX+Servlet实现的数据处理显示功能。分享给大家供大家参考,具...
来源:DeepenStudy 漏洞文件:js.asp % Dimoblog setoblog=newclass_sys oblog.a...
本文转载自微信公众号「SH的全栈笔记」,作者SH。转载本文请联系SH的全栈笔记公...
问题:我们在做flex的开发中,如果用到别人搭建好的框架,而别人的server名称往...
前言 项目开发中不管是前台还是后台都会遇到烦人的null,数据库表中字段允许空值...
在Flash Player 10.1及以上版本中,adobe新增了全局错误处理程序UncaughtErrorEv...