前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

再来40道this面试题酸爽继续|博客系列

# 前期准备

在正式阅读之前,你需要知道this的5种绑定方式:

  • 默认绑定(非严格模式下this指向全局对象, 严格模式下this会绑定到undefined)
  • 隐式绑定(当函数引用有上下文对象时, 如 obj.foo()的调用方式, foo内的this指向obj)
  • 显示绑定(通过call()或者apply()方法直接指定this的绑定对象, 如foo.call(obj))
  • new绑定
  • 箭头函数绑定(this的指向由外层作用域决定的)

# 1. 默认绑定

先介绍一种最简单的绑定方式吧:默认绑定。

也就是我们常说的:在非严格模式下this指向的是全局对象window,而在严格模式下会绑定到undefined。

# 1.1 题目一

老规矩,来看个最基本的案例:

var a = 10;
function foo () {
  console.log(this.a)
}
foo();
@前端进阶之旅: 代码已经复制到剪贴板

我们知道在使用var创建变量的时候(不在函数里),会把创建的变量绑定到window上,所以此时a是window下的属性。

而函数foo也是window下的属性。

因此上面的代码其实就相当于是这样:

window.a = 10;
function foo() {
  console.log(this.a)
}
window.foo();
@前端进阶之旅: 代码已经复制到剪贴板

在这里,调用foo()函数的是window对象,且又是在非严格模式下,所以foo()中this的指向是window对象,因此this.a会输出10。

答案:

10
@前端进阶之旅: 代码已经复制到剪贴板

# 1.2 题目二

改造下题目一,看看在严格模式下。

(想要开启严格模式,只要在需要开启的地方写上"use strict")

"use strict";
var a = 10;
function foo () {
  console.log('this1', this)
  console.log(window.a)
  console.log(this.a)
}
console.log(window.foo)
console.log('this2', this)
foo();
@前端进阶之旅: 代码已经复制到剪贴板

需要注意的点:

  • 开启了严格模式,只是说使得函数内的this指向undefined,它并不会改变全局中this的指向。因此this1中打印的是undefined,而this2还是window对象。

  • 另外,它也不会阻止a被绑定到window对象上。

所以最后的执行结果:

f foo() {...}
'this2' Window{...}
'this1' undefined
10
Uncaught TypeError: Cannot read property 'a' of undefined
@前端进阶之旅: 代码已经复制到剪贴板

# 1.3 题目三

let a = 10
const b = 20

function foo () {
  console.log(this.a)
  console.log(this.b)
}
foo();
console.log(window.a)
@前端进阶之旅: 代码已经复制到剪贴板

如果把var改成了let 或者 const,变量是不会被绑定到window上的,所以此时会打印出三个undefined。

答案:

undefined
undefined
undefined
@前端进阶之旅: 代码已经复制到剪贴板

# 1.4 题目四

var a = 1
function foo () {
  var a = 2
  console.log(this)
  console.log(this.a)
}

foo()
@前端进阶之旅: 代码已经复制到剪贴板

这里我们很容易就知道,foo()函数内的this指向的是window,因为是window调用的foo。

但是打印出的this.a呢?注意,是this.a,不是a,因此是window下的a。

并且由于函数作用域的原因我们知道window下的a还是1。

因此答案为:

Window{...}
1
@前端进阶之旅: 代码已经复制到剪贴板

# 1.5 题目五

把题目1.4改造一下 😁。

var a = 1
function foo () {
  var a = 2
  function inner () { 
    console.log(this.a)
  }
  inner()
}

foo()
@前端进阶之旅: 代码已经复制到剪贴板

其实这里和1.4很像,不过一看到函数内的函数,就很容易让人联想到闭包 😂,然后… 然后就脱口而出,答案是2啊,这还不简单。

小伙伴们,审题可得仔细啊,这里问你的是this.a,而在inner中,this指向的还是window。

答案:

1
@前端进阶之旅: 代码已经复制到剪贴板

(我知道有的小伙伴不满这种题目,这吖的不就是和人玩文字游戏吗?有什么技术含量,但是现实就是这样,很多面试官会喜欢问这种细节题来考察你细心不细心。在没能力改变这种情况的前提下,你只能试着接受它…)

# 2. 隐式绑定

OK👌,介绍完了默认绑定之后,让我们来看看第二种隐式绑定。

其实大佬 sunshine小小倩 教了我们一个简单的规则,this 永远指向最后调用它的那个对象。

谁最后调用的函数,函数内的this指向的就是谁(不考虑箭头函数)。

(了解上下文对象的小伙伴应该都知道,这与上下文对象以及作用域有关,想要了解的小伙伴可以看我的这篇文章 《JavaScript进阶-执行上下文栈和变量对象(一周一更)》

下面你可以用这个规则来做题啦 😁。

# 2.1 题目一

function foo () {
  console.log(this.a)
}
var obj = { a: 1, foo }
var a = 2
obj.foo()
@前端进阶之旅: 代码已经复制到剪贴板

(var obj = { foo }就相当于是var obj = { foo: foo },这个大家应该都知道吧)

在这道题中,函数foo()虽然是定义在window下,但是我在obj对象中引用了它,并将它重新赋值到obj.foo上。

且调用它的是obj对象,因此打印出来的this.a应该是obj中的a。

换个角度想,上面👆这段代码是不是就相当于是这样:

var obj = {
  a: 1,
  foo: function () {
    console.log(this.a)
  }
}
var a = 2
obj.foo()
@前端进阶之旅: 代码已经复制到剪贴板

在这里foo函数内的this指向的就是obj,和题目效果一样。

答案都是:

1
@前端进阶之旅: 代码已经复制到剪贴板

有小伙伴就会有有疑问了,obj.foo()不就相当于是window.obj.foo()吗?

那foo()内的this到底是算window呢,还是obj呢?

请注意我前面说的,是最后调用函数的对象,显然,obj要比window更后面一点。

# 3. 隐式绑定的隐式丢失问题

隐式绑定的基本概念大家应该都清楚了,不过其实有一个关于隐式绑定的常用考点,那就是隐式丢失问题。

隐式丢失其实就是被隐式绑定的函数在特定的情况下会丢失绑定对象。

有两种情况容易发生隐式丢失问题:

  • 使用另一个变量来给函数取别名
  • 将函数作为参数传递时会被隐式赋值,回调函数丢失this绑定

我们一种一种来看哈。

# 3.1 题目一

使用另一个变量来给函数取别名会发生隐式丢失。

function foo () {
  console.log(this.a)
};
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;

obj.foo();
foo2();
@前端进阶之旅: 代码已经复制到剪贴板

执行这段代码会打印出啥呢 🤔️?

在这里我们已经知道了,obj.foo()中this的指向是为obj的(可以看第二部分隐式绑定),所以obj.foo()执行的时候,打印出来的是obj对象中的a,也就是1。

但是foo2它不也是obj.foo吗?我只不过是用了一个变量foo2来盛放了它而已。所以你是不是认为它打印的也是1呢?

额 😅,其实这里不是的,它打印出的是window下的a。

答案:

1
2
@前端进阶之旅: 代码已经复制到剪贴板

这是因为虽然foo2指向的是obj.foo函数,不过调用它的却是window对象,所以它里面this的指向是为window。

其实也就相当于是window.foo2(),如果你不相信的话,可以看下面一题👇。

# 3.2 题目二

让我们在一个新的变量obj2中也定义一个foo2看看:

function foo () {
  console.log(this.a)
};
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;
var obj2 = { a: 3, foo2: obj.foo }

obj.foo();
foo2();
obj2.foo2();
@前端进阶之旅: 代码已经复制到剪贴板

这三种不同的foo()打印出来的分别是什么呢?

答案:

1
2
3
@前端进阶之旅: 代码已经复制到剪贴板
  • obj.foo()中的this指向调用者obj
  • foo2()发生了隐式丢失,调用者是window,使得foo()中的this指向window
  • foo3()发生了隐式丢失,调用者是obj2,使得foo()中的this指向obj2

# 3.3 题目三

再就是如果你把一个函数当成参数传递时,也会被隐式赋值,发生意想不到的问题。

来看看这道题目:

function foo () {
  console.log(this.a)
}
function doFoo (fn) {
  console.log(this)
  fn()
}
var obj = { a: 1, foo }
var a = 2
doFoo(obj.foo)
@前端进阶之旅: 代码已经复制到剪贴板

这里我们将obj.foo当成参数传递到doFoo函数中,在传递的过程中,obj.foo()函数内的this发生了改变,指向了window。

因此结果为:

Window{...}
2
@前端进阶之旅: 代码已经复制到剪贴板

注意,我这里说的是obj.foo()函数,而不是说doFoo()。doFoo()函数内的this本来就是指向window的,因为这里是window调用的它。

但是你不要以为是doFoo()函数内的this影响了obj.foo(),不信你看下一题。

# 3.4 题目四

现在我们不用window调用doFoo,而是放在对象obj2里,用obj2调用:

function foo () {
  console.log(this.a)
}
function doFoo (fn) {
  console.log(this)
  fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }

obj2.doFoo(obj.foo)
@前端进阶之旅: 代码已经复制到剪贴板

现在调用obj2.doFoo()函数,里面的this指向的应该是obj2,因为是obj2调用的它。

但是obj.foo()打印出来的a依然是2,也就是window下的。

执行结果为:

{ a:3, doFoo: f }
2
@前端进阶之旅: 代码已经复制到剪贴板

所以说,如果你把一个函数当成参数传递到另一个函数的时候,也会发生隐式丢失的问题,且与包裹着它的函数的this指向无关。在非严格模式下,会把该函数的this绑定到window上,严格模式下绑定到undefined。

一样的代码,试试严格模式下:

← Javascript之ES7详解这篇手写async函数及原理 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合