一、调用位置

在理解this的绑定过程之前,首先要理解调用位置:调用位置就是函数在代码中被调用的位置(而不是申明的位置)。只有仔细分析调用位置才能回答这个问题:这个this到底引用的是什么?

function foo() {console.log('foo')// 当前调用栈是: bar// 因此,当前调用位置在bar中
}function bar() {console.log('bar')// foo当前调用位置foo()// 当前调用栈是: bar// 因此,当前调用位置在bar中
}function baz() {console.log('baz')// bar当前调用位置bar()// 当前调用栈是: baz// 因此,当前调用位置是全局作用域
}// baz当前调用位置
// 全局作用域
baz()

二、绑定规则

1、默认绑定(非严格模式):独立函数调用

const a = 2
function foo() {console.log(this.a)
}
foo()  // 2
// foo() 在这里直接食用不带任何修饰的函数引用进行调用,因此只能使用默认绑定;调用位置为全局作用域,因此this默认绑定到全局作用域。

2、隐式绑定:调用位置是否有上下文,或者说是否被某个对象拥有或者包含

  • 当函数引用有上下文对象时,隐式绑定规则会把函数调用中this绑定到这个上下文对象上。
function foo() {console.log(this.a)
}
const obj = {a: 2,foo: foo
}
obj.foo()  // 2// 注意: 首先需要注意的是foo()的声明方式,及其之后是如何北方做引用属性添加到obj中的。但是无论是直接在obj中定义还是先定义在添加为引用属性,这个函数严格来说都不属于obj对象。
// 然而,调用位置会使用obj上下文来引用函数,因此你可以说函数被调用时obj对象‘拥有’或‘包含’它。// 当函数引用有上下文对象时,隐式绑定规则会把函数调用中this绑定到这个上下文对象上。

对象属性引用链中只有 最顶层 或者说 最后一层 会影响调用位置

function foo() {console.log(this.a)
}
const obj1 = {a: 2,foo: obj2
}
const obj2 = {a: 42,foo: foo
}
obj1.obj2.foo()  // 42

隐式丢失:一个最常见的this绑定问题就是 被隐式绑定的函数 会丢失绑定对象,也就是说它会引用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式。

function foo() {console.log(this.a)
}
const obj = {a: 2,foo: foo
}
const bar = obj.foo // 函数别名!
const a = '我是全局对象'
bar() // -> 我是全局对象
// 虽然bar是obj.foo的一个引用,但是实际上,他引用的是foo函数本身,因此此时的 bar()  相当于  foo(), 是一个不带任何修饰的函数调用,因此应用了默认绑定
function foo() {console.log(this.a)
}
function doFoo(fn) {// fn其实引用的是foofn()  // <--调用位置!
}
const obj = {a: 2,foo: foo
}
const a = '我是全局对象'
doFoo(foo) // -> 我是全局对象
// 参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样
// 如果把函数传入语言内置的函数而不是自定义的函数,会怎么样呐?结果是一样的,没有区别
function foo() {console.log(this.a)
}
const obj = {a: 2,foo: foo
}
const a = '我是全局对象'
setTimeout(obj.foo, 100)  // -> 我是全局对象// JavaScript环境中内置的setTimeout()函数实现和下面的代码类似:
function setTimeout(fn, delay) {// 等待delay毫秒fn() // <--调用位置!
}

3、显式绑定

function foo() {console.log(this.a)
}
const obj = {a: 2,
}
foo.call(obj) // -> 2
// 通过foo.call(...)可以在调用foo时强制把它绑定到obj上// 思考下面代码输出什么
const b = '我是全局对象'
function foo() {console.log(this.b)
}
const obj = {b: 2,foo: foo
}
const bar = function(fn) {fn()
}
bar.call(obj, foo)  // -> 我是全局对象// 显然,通过这种方式还是无法解决绑定丢失问题

3.1、硬绑定

function foo() {console.log(this.b)
}
const obj = {b: 2,
}
const bar = function() {foo.call(obj)
}
bar()  // -> 2
// 硬绑定的bar不能再修改它的this
bar.call(window) // -> 2

由于硬绑定是一种非常常用的模式。所以在es5中提供了内置的方法Funtion.prototype.bind
bind(…)会返回一个硬编码的新函数,它会吧参数设置为this的上下文并调用原始函数

function foo(something) {console.log(this.a, something)return this.a + something
}
const obj = {a: 2,
}
const bar = foo.bind(obj)
const b = bar(3)  // -> 2 3
console.log(b) // -> 5

4、new绑定
使用new来调用函数,或者说发生构造函数调用时,它会自动执行下面的操作:

  1. 创建(构造)一个全新的对象
  2. 这个新对象会被执行[[原型]]链接(将这个对象的_proto_指向其构造函数的原型)
  3. 这个新对象会绑定到函数调用(构造函数)的this
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
function foo(a) {this.a = a
}
const bar = new foo(2)
console.log(bar.a)  // -> 2

三、优先级

如果某个调用位置同时使用四条规则,那谁的优先级更高呢?
1、隐式绑定与显式绑定谁的优先级更高?

function foo() {console.log(this.a)
}
const obj1 = {a: 2,foo: foo
}
const obj2 = {a: 3,foo: foo
}
obj1.foo()  // -> 2
obj2.foo()  // -> 3obj1.foo().call(obj2)  // -> 3
obj2.foo().call(obj1)  // -> 2//  可以看到,显式绑定优先级高于隐式绑定

2、隐式绑定与new绑定谁的优先级更高?

function foo(something) {this.a = something
}
const obj1 = {foo: foo
}
const obj2 = {}obj1.foo(2)
console.log(obj1.a)  // -> 2obj1.foo.call(obj2, 3)
console.log(obj2.a)  // -> 3const bar = new obj1.foo(4)
console.log(obj1.a)  // -> 2
console.log(bar.a)  // -> 4// 可以看到,new绑定的优先级高于隐式绑定

3、new绑定与显式绑定谁的优先级更高?
new和call/apply无法一起使用,因此无法通过 new foo.call(obj1)来直接进行测试,但是我们可以使用硬绑定来进行测试

function foo(something) {this.a = something
}
const obj1 = {}
const bar = foo.bind(obj1)
bar(2)
console.log(obj1.a)  // -> 2const baz = new bar(3)
console.log(obj1.a)  // -> 2
console.log(baz.a) // -> 3// 可以看到,new绑定的优先级高于显式绑定

四、判断this步骤

现在我们可以根据优先级来判断函数在某个调用位置引用的式哪条规则:

  1. 函数是否在new中调用(new)绑定?如果是的话this绑定的是新创刊的对象。
    const bar = new foo()
  2. 函数是否通过call,apply(显示绑定)或者bind(硬绑定)调用?如果是的话,this绑定的式指定对象。
    const bar = foo.call(obj2)
  3. 函数是否在某个上下文对象中调用(隐士绑定)?如果是的话,this绑定的是那个上下文对象
    const bar = obj1.foo()
  4. 如果都不是的话,使用默认绑定。如果在严格式模式下,就绑定到undefined,否则绑定到全局对象
    const bar = foo()

this全面解析, 如何定位this指向,一文总结,再也不怕面试官追问啦相关推荐

  1. URL解析过程和浏览器渲染机制【面试必问、深入解析 下一次再也不怕面试官问这个问题】

    步骤拆分 网络-请求和响应 缓存 DNS解析 建立TCP连接(三次握手和四次挥手) 服务端验证请求 响应文件类型(Content-Type) 浏览器-解析与渲染 浏览器进程和线程 构建Dom树 lay ...

  2. ##连大学定位都找不准,还想虐面试官?(AKD)

    出道即是巅峰,天赋大于努力,成功是不可复制的.当你在为你考上不好的学校,觉得平台低的时候,不要不屑.不要言论.不然会后悔当初自己的行为举止.作为一个乐于助人的AKD,我会将大学四年的感悟告诉大家.为此 ...

  3. 面试官:注解@Component,@Service是如何被解析的?

    点击上方 好好学java ,选择 星标 公众号重磅资讯,干货,第一时间送达 今日推荐:推荐19个github超牛逼项目!个人原创100W +访问量博客:点击前往,查看更多 来源: https://my ...

  4. 面试官问:JS的this指向

    写于2018年12月25日,发布在掘金上阅读量近一万,现在发布到微信公众号申明原创. 前言 这是面试官问系列的第四篇,旨在帮助读者提升JS基础知识,包含new.call.apply.this.继承相关 ...

  5. “约见”面试官系列之常见面试题之第六十五篇之ajax如何解析jsonp(建议收藏)

    json是一种轻量级交互格式,本质上都是字符串,常用于前后端的数据交互,本质上就是字符串. 前端解析后端数据 前端在解析后端发来的数据,使用JSON.parse()方法把字符串转为json对象. 前端 ...

  6. “约见”面试官系列之常见面试题第四十四篇之webpack打包原理解析?(建议收藏)

    webpack打包是如何运行的 也可以称为,webpack是如何实现模块化的 CommonJS是同步加载模块,一般用于node.因为node应用程序运行在服务器上,程序通过文件系统可以直接读取到各个模 ...

  7. 用C++解析HTTP下载下来的HTML文档

    最近跟朋友一起写了一个 批量网站查询工具 BlueCatTools,其中,需要用C++解析HTTP下载下来的HTML文档. 懂的人不用我多说,不懂的我也没能力说道你懂,看代码吧. BlueCatToo ...

  8. 面试官系统精讲Java源码及大厂真题 - 11 HashSet、TreeSet 源码解析

    11 HashSet.TreeSet 源码解析 更新时间:2019-09-16 19:37:35 成功的奥秘在于目标的坚定. --迪斯雷利 引导语 HashSet.TreeSet 两个类是在 Map ...

  9. 面试官系统精讲Java源码及大厂真题 - 05 ArrayList 源码解析和设计思路

    05 ArrayList 源码解析和设计思路 耐心和恒心总会得到报酬的. --爱因斯坦 引导语 ArrayList 我们几乎每天都会使用到,但真正面试的时候,发现还是有不少人对源码细节说不清楚,给面试 ...

最新文章

  1. JQuery Ajax 使用FormData上传文件对象
  2. db2 sql 判断select是否为空_学会复杂一点的SQL语句:Oracle DDL和DML
  3. DeepMind和Unity合作,创建虚拟世界来训练AI
  4. 兮米安装包制作工具图文教程集锦电子书
  5. Linux操作系统中的路由表配置
  6. 橙瓜发布2018网络小说风云榜年度榜,顶级大神作家的无声角逐
  7. 微信云控的大触来一下
  8. Skiller V3
  9. C++多线程传参详解
  10. Angular 里使用 FormControl 的步骤
  11. java 实现站内信_群发站内信实现
  12. 微信小程序获取用户信息后的解密操作
  13. Why WPF's Text is Blurry?
  14. Android事件分发机制浅析
  15. JSP书香味道点餐系统课设
  16. 习题 11.10 将本章11.8节中的程序片段加以补充完善,成为一个完整的程序。在程序中使用继承和组合。在定义Professor类对象prof1时给出所有数据的初值,然后修改prof1的生日数据。。。
  17. 北大青鸟消防设备说明书_北大青鸟消防控制主机操作说明
  18. 定护你一世周全 《大圣归来》影评剧透慎点
  19. mongodb 导入数据inport,数据不能在C盘
  20. 手机解除移动宽带屏蔽_FANUC/三菱M70系统如何屏蔽伺服轴?

热门文章

  1. C字节对齐与C++类对象内存布局
  2. node.js路由控制
  3. Python-OpenCV设置摄像头分辨率
  4. 探索Java日志的奥秘:底层日志系统-log4j2
  5. 一致性协议浅析:从逻辑时钟到Raft
  6. 亚信安全发布2021年挖矿病毒专题报告,聚焦挖矿病毒进化与治理
  7. 从质疑到成为必选项,低代码技术发展及 2022 展望
  8. 震惊!程序员要放弃 Python 了!?发生了啥?
  9. 138 张图带你 MySQL 入门!
  10. 巧用 Trie 树,实现搜索引擎关键词提示功能