Vue3 面试常见八股文 200 题速记版

Vue3 面试常见八股文 200 题速记版

一、基础认知(1-15)

1. 什么是 Vue3?

  • Vue3 是新一代渐进式前端框架。
  • 核心升级是响应式系统、组合式 API、性能和 TS 支持。

2. Vue3 相比 Vue2 最大变化是什么?

  • 响应式从 defineProperty 改成 Proxy
  • 新增 Composition API
  • 更好支持 TS
  • 编译优化更强

3. Vue3 的核心优势是什么?

  • 性能更好
  • 逻辑复用更强
  • 类型支持更友好
  • Tree Shaking 更彻底

4. Vue3 还是 MVVM 吗?

  • 是。
  • 依然是数据驱动视图,本质思想没变。

5. Vue3 为什么更适合大型项目?

  • Composition API 更利于逻辑拆分
  • TS 支持更好
  • 可维护性更高

6. 什么是渐进式框架?

  • 可以按需接入能力,不强制一开始全用。

7. Vue3 适合哪些项目?

  • 中后台
  • 管理系统
  • 组件库
  • 中大型 SPA
  • 新项目优先

8. Vue3 和 Vue2 能共存吗?

  • 可以在不同项目中共存。
  • 同一项目迁移通常需要逐步改造。

9. Vue3 为什么性能更好?

  • 响应式更高效
  • 编译优化更多
  • diff 更精细
  • 运行时体积更小

10. Vue3 的生态有哪些?

  • Vue Router
  • Pinia
  • Vite
  • Vue Devtools

11. Vue3 默认推荐什么状态管理?

  • Pinia。
  • 更轻、更直观、TS 更友好。

12. Vue3 默认推荐什么构建工具?

  • Vite。
  • 启动快,热更新快。

13. Vue3 和 React 区别?

  • Vue 偏模板
  • React 偏 JSX
  • Vue 提供更完整官方方案

14. Vue3 为什么更适合 TS?

  • API 设计更利于类型推导。
  • 组合式写法也更适合抽类型。

15. Vue3 的学习主线是什么?

  • 响应式
  • setup
  • ref/reactive
  • 生命周期
  • 组件通信
  • 路由
  • Pinia

二、响应式原理(16-35)

16. Vue3 响应式原理是什么?

  • 基于 Proxy 劫持对象。
  • 通过 track 收集依赖,trigger 触发更新。

17. Vue3 为什么用 Proxy?

  • 能拦截更多操作
  • 能直接监听新增/删除属性
  • 对数组支持更自然

18. Proxy 比 defineProperty 好在哪?

  • 不用递归一上来遍历全部属性
  • 可监听新增、删除、数组下标等变化

19. Vue3 的依赖收集是怎么做的?

  • 读取数据时 track
  • 修改数据时 trigger
  • 副作用函数重新执行

20. 什么是 effect?

  • 响应式副作用函数。
  • 依赖变化后会重新执行。

21. 什么是 track?

  • 依赖收集。
  • 谁读取了响应式数据,就记录谁。

22. 什么是 trigger?

  • 派发更新。
  • 数据变化后通知依赖重新执行。

23. Vue3 能监听对象新增属性吗?

  • 能。
  • 因为 Proxy 可以拦截 set。

24. Vue3 能监听数组下标变化吗?

  • 能。
  • 这是相对 Vue2 的明显改进。

25. Vue3 响应式一定是深层的吗?

  • reactive 默认是深层响应式。
  • 但也有 shallowReactive 这种浅层版本。

26. shallowReactive 是什么?

  • 只代理第一层。
  • 深层对象不继续做响应式。

27. shallowRef 是什么?

  • 只跟踪 .value 本身变化。
  • 不深度代理内部对象。

28. readonly 是什么?

  • 创建只读响应式对象。
  • 不能直接修改。

29. shallowReadonly 是什么?

  • 第一层只读。
  • 深层对象不做深只读处理。

30. markRaw 是什么?

  • 标记对象永远不要转成响应式。
  • 常用于第三方实例、性能敏感对象。

31. toRaw 是什么?

  • 获取响应式对象对应的原始对象。

32. reactive 和普通对象区别?

  • reactive 返回的是代理对象,不是原对象本身。

33. 为什么 Vue3 响应式更适合大对象?

  • 不需要像 Vue2 那样初始化时深度遍历所有属性做劫持。

34. 响应式丢失常见原因有哪些?

  • 解构 reactive
  • 直接赋值覆盖响应式对象
  • 脱离代理访问

35. 怎么避免响应式丢失?

  • 优先用 ref
  • 解构时配合 toRefs/toRef
  • 不直接替换整个 reactive 对象引用

三、Composition API(36-70)

36. 什么是 Composition API?

  • 组合式 API。
  • 按功能组织代码,而不是按 options 分类。

37. Composition API 为什么更强?

  • 逻辑更集中
  • 复用更方便
  • 大组件更好维护

38. setup 是什么?

  • Composition API 的入口函数。
  • 组件创建时最早执行。

39. setup 里能拿到 this 吗?

  • 不能。
  • setup 中 this 是 undefined。

40. setup 执行时机是什么?

  • 在 beforeCreate 前后这个初始化阶段。
  • 比 data、methods 更早。

41. setup 返回值有什么用?

  • 返回给模板使用。
  • 也可以返回给 render 函数使用。

42. 为什么 setup 不要写太大?

  • 会导致逻辑堆积。
  • 应拆成 composable。

43. 什么是 composable?

  • 可复用的组合式逻辑函数。
  • 一般以 useXxx 命名。

44. composable 和 mixin 区别?

  • composable 来源清晰
  • 不容易命名冲突
  • 逻辑复用更显式

45. Vue3 为什么弱化 mixin?

  • mixin 来源不清楚,冲突难排查。
  • composable 更好维护。

46. script setup 是什么?

  • setup 语法糖。
  • 写法更简洁,模板可直接用顶层变量。

47. script setup 有什么优点?

  • 代码少
  • 可读性好
  • 编译优化更好

48. defineProps 是什么?

  • script setup 中定义 props 的宏。

49. defineEmits 是什么?

  • script setup 中定义 emits 的宏。

50. defineExpose 是什么?

  • 显式暴露子组件内容给父组件 ref 访问。

51. defineOptions 是什么?

  • 在 script setup 中声明组件选项。

52. defineSlots 是什么?

  • 声明插槽类型信息,常用于 TS。

53. useAttrs 是什么?

  • 获取透传 attrs。

54. useSlots 是什么?

  • 获取当前组件 slots。

55. getCurrentInstance 有什么用?

  • 获取当前组件实例。
  • 一般只在少数底层封装场景用。

56. onMounted 在 setup 里怎么用?

  • 直接调用生命周期函数注册。

57. setup 能写异步吗?

  • 可以,但常结合 Suspense 或异步组件场景。

58. 为什么 Composition API 更适合逻辑拆分?

  • 同一功能的数据、方法、监听能放一起。

59. setup 和 data 谁先执行?

  • setup 更早。

60. setup 和 methods 能一起用吗?

  • 能。
  • Options API 和 Composition API 可以共存。

61. setup 里怎么访问 props?

  • setup(props) 第一个参数就是 props。

62. setup 里怎么触发 emit?

  • setup(props, { emit }) 中通过 emit 触发。

63. setup 第二个参数有哪些?

  • attrs
  • slots
  • emit
  • expose

64. 为什么说 script setup 更适合业务组件?

  • 模板和逻辑更紧凑,样板代码更少。

65. 什么时候仍然会用 Options API?

  • 老项目维护
  • 小组件
  • 团队习惯

66. setup 返回 reactive 和 ref 给模板时要 .value 吗?

  • 模板中通常自动解包 ref,不用手写 .value。

67. JS 中访问 ref 要不要 .value?

  • 要。
  • 除非经过模板自动解包或特定语法处理。

68. setup 为什么适合封装公共逻辑?

  • 不依赖 this,逻辑可直接抽函数复用。

69. composable 命名为什么常用 use 开头?

  • 社区约定俗成,看到就知道是复用逻辑。

70. Composition API 的核心心智是什么?

  • 按功能聚合代码,而不是按配置项分散代码。

四、ref / reactive(71-95)

71. ref 是什么?

  • 用于包装基本类型,也可包装对象。
  • 通过 .value 取值。

72. reactive 是什么?

  • 用于创建对象或数组的响应式代理。

73. ref 和 reactive 区别?

  • ref 更通用
  • reactive 更适合对象
  • ref 有 .value

74. 为什么推荐优先用 ref?

  • 一致性更强
  • 替换整体值更方便
  • TS 推导也常更稳

75. reactive 能直接接收基本类型吗?

  • 不能。
  • 只适合对象类型。

76. ref 包装对象时底层是什么?

  • 内部也会把对象转成 reactive 风格的响应式。

77. toRef 是什么?

  • 把对象某个属性转成 ref。

78. toRefs 是什么?

  • 把对象每个属性都转成 ref。
  • 常用于解构后仍保持响应式。

79. unref 是什么?

  • 如果是 ref 就返回 .value,否则原样返回。

80. isRef 有什么用?

  • 判断一个值是不是 ref。

81. isReactive 有什么用?

  • 判断一个对象是不是 reactive 创建的。

82. isReadonly 有什么用?

  • 判断对象是不是只读代理。

83. isProxy 有什么用?

  • 判断对象是不是由响应式代理创建。

84. 为什么 reactive 解构后会丢响应式?

  • 解构拿到的是普通值,不再经过代理访问。

85. ref 在模板中为什么不用 .value?

  • 模板会自动解包。

86. reactive 为什么不能整体替换?

  • 直接替换变量引用会丢失原来的代理关联。

87. ref 为什么适合整体替换值?

  • 改的是 .value,本身引用关系不变。

88. toRefs 常见使用场景?

  • setup 中返回 reactive 的各个字段给模板或外部使用。

89. 为什么很多团队不用 reactive 管整个表单?

  • 大对象解构和替换时更容易出坑。

90. shallowRef 常用场景?

  • 大对象
  • 第三方实例
  • 只关心整体替换,不关心深层变化

91. customRef 是什么?

  • 自定义 ref 的依赖追踪和触发逻辑。

92. customRef 常见场景?

  • 防抖输入框
  • 自定义触发时机

93. triggerRef 是什么?

  • 手动触发 shallowRef 更新。

94. ref 嵌套 ref 会怎样?

  • 一般不推荐,容易增加理解成本。

95. ref/reactive 怎么选?

  • 简单记:基本类型用 ref,对象通常 ref 或 reactive 都行,但团队统一更重要。

五、计算属性与侦听(96-115)

96. computed 是什么?

  • 计算属性。
  • 基于依赖派生新值。

97. computed 为什么有缓存?

  • 依赖不变就复用上次结果。

98. computed 和 methods 区别?

  • computed 有缓存
  • methods 每次执行都会重新算

99. computed 和 watch 区别?

  • computed 产出值
  • watch 监听变化做副作用

100. watch 是什么?

  • 监听数据变化执行回调。

101. watch 能监听哪些源?

  • ref
  • reactive 对象
  • getter 函数
  • 多个源数组

102. watch 默认是深度监听吗?

  • 不是。
  • 但直接监听 reactive 对象时会递归追踪。

103. watch 的 immediate 有什么用?

  • 立即执行一次回调。

104. watch 的 deep 有什么用?

  • 深度监听对象内部变化。

105. watchEffect 是什么?

  • 自动收集依赖的副作用监听。

106. watch 和 watchEffect 区别?

  • watch 依赖明确
  • watchEffect 自动收集依赖

107. watchEffect 什么时候用?

  • 依赖很多且不想手动一个个声明时。

108. watch 为什么更适合精确控制?

  • 可以拿到新值旧值,可控制 immediate、deep、flush。

109. flush 有什么用?

  • 控制 watch 回调执行时机。

110. flush 常见值有哪些?

  • pre
  • post
  • sync

111. watch 什么时候停止?

  • 组件卸载时自动停止。
  • 手动创建也可手动 stop。

112. computed 能写 setter 吗?

  • 能。
  • 可写成 get/set 形式。

113. 为什么 computed 不适合异步?

  • 它更适合返回同步计算结果。

114. 侦听路由变化怎么做?

  • watch route
  • 或用路由守卫

115. onWatcherCleanup 是做什么的?

  • 侦听副作用清理。
  • 常用于取消请求、清除上次副作用。

六、生命周期(116-130)

116. Vue3 生命周期有哪些?

  • onBeforeMount
  • onMounted
  • onBeforeUpdate
  • onUpdated
  • onBeforeUnmount
  • onUnmounted
  • 还有 onActivated、onDeactivated 等

117. Vue3 生命周期和 Vue2 对应关系?

  • beforeDestroy -> beforeUnmount
  • destroyed -> unmounted

118. onMounted 什么时候用?

  • DOM 挂载完成后。
  • 常用于请求、DOM 操作、第三方库初始化。

119. onUpdated 什么时候用?

  • 组件更新完成后。
  • 但不要在里面乱改响应式数据。

120. onUnmounted 什么时候用?

  • 组件卸载完成后。
  • 常用于清理资源。

121. onBeforeUnmount 和 onUnmounted 区别?

  • 一个是卸载前
  • 一个是卸载后

122. keep-alive 相关生命周期有哪些?

  • onActivated
  • onDeactivated

123. onErrorCaptured 是什么?

  • 捕获后代组件错误。

124. onRenderTracked 是什么?

  • 调试渲染时依赖收集。

125. onRenderTriggered 是什么?

  • 调试什么依赖触发了重新渲染。

126. onServerPrefetch 是什么?

  • SSR 场景服务端预取数据。

127. setup 和 onMounted 的顺序?

  • setup 先执行,再到 mounted 阶段。

128. 生命周期钩子一定要同步注册吗?

  • 是。
  • 要在 setup 同步阶段注册。

129. 为什么不能在异步回调里随便注册生命周期?

  • 因为那时当前组件实例上下文可能已经丢了。

130. 生命周期的使用原则是什么?

  • 创建期做初始化
  • 挂载期做 DOM/请求
  • 卸载期做清理

七、组件通信(131-145)

131. Vue3 组件通信方式有哪些?

  • props
  • emit
  • v-model
  • ref + defineExpose
  • provide/inject
  • Pinia

132. 父传子怎么做?

  • props。

133. 子传父怎么做?

  • emit。

134. 为什么 props 不能直接改?

  • 单向数据流。
  • 子组件直接改会破坏数据来源。

135. Vue3 的 v-model 和 Vue2 有什么区别?

  • Vue3 默认是 modelValue + update:modelValue。

136. 多个 v-model 怎么做?

  • 用 v-model:xxx 语法。

137. defineEmits 有什么好处?

  • 事件定义更明确。
  • TS 下类型更清晰。

138. 父组件怎么调用子组件方法?

  • ref + defineExpose。

139. provide/inject 常见场景?

  • 跨层级传值
  • 组件库封装
  • 祖孙通信

140. provide/inject 是响应式的吗?

  • 传 ref/reactive 时可保持响应式。
  • 传普通值则不是自动响应式。

141. emits 选项有什么用?

  • 声明组件允许触发哪些事件。

142. attrs 和 props 区别?

  • props 是显式声明的输入
  • attrs 是未声明的透传属性

143. 什么是透传 attrs?

  • 父组件传进来但子组件没声明成 props 的属性。

144. inheritAttrs 有什么用?

  • 控制 attrs 是否自动挂到根节点。

145. 为什么 Vue3 通信更规范?

  • script setup、defineProps、defineEmits 让组件接口更清晰。

八、模板、指令与渲染(146-165)

146. Vue3 模板语法和 Vue2 差很多吗?

  • 不大。
  • 大部分基础指令一致。

147. v-if 和 v-show 区别?

  • v-if 控制渲染
  • v-show 控制 display

148. v-for 为什么一定要 key?

  • 帮助 diff 正确识别节点身份。

149. key 为什么别用 index?

  • 容易导致错误复用。

150. Vue3 的 template ref 怎么用?

  • DOM 或组件上写 ref,setup 中通过同名 ref 获取。

151. 什么是 Fragment?

  • 组件可以有多个根节点,不必强制单根。

152. 什么是 Teleport?

  • 把组件内容渲染到指定 DOM 节点。
  • 常用于弹窗。

153. 什么是 Suspense?

  • 处理异步依赖加载中的占位渲染。

154. Teleport 常见场景?

  • Modal
  • Drawer
  • 全局浮层

155. Suspense 常见场景?

  • 异步组件加载
  • setup 异步依赖等待

156. slot 在 Vue3 有什么变化?

  • 统一成函数式插槽。

157. 为什么函数式插槽更好?

  • 依赖收集更准确。
  • 性能更可控。

158. 动态组件怎么写?

  • component + is。

159. keep-alive 有什么用?

  • 缓存组件实例,避免重复创建。

160. Transition 是什么?

  • 过渡动画组件。

161. TransitionGroup 是什么?

  • 列表过渡动画组件。

162. 自定义指令在 Vue3 生命周期名字有什么变化?

  • 更贴近组件生命周期,如 mounted、updated、unmounted。

163. v-model 修饰符在组件上怎么处理?

  • 通过 modelModifiers 等方式接收修饰信息。

164. 模板中 ref 自动解包规则是什么?

  • 顶层 ref 在模板中通常自动解包。

165. 渲染函数在 Vue3 里还重要吗?

  • 重要。
  • 底层封装、组件库、复杂渲染场景仍常用。

九、组件化与复用(166-180)

166. 什么是组件化?

  • 把页面拆成独立可复用模块。

167. Vue3 逻辑复用为什么更好?

  • 因为 composable 比 mixin 更清晰。

168. composable 常见场景有哪些?

  • 请求封装
  • 表单逻辑
  • 滚动监听
  • 权限逻辑

169. 什么是高阶组件思路?

  • 通过包装组件增强能力。
  • Vue 中不如 composable 常见。

170. 什么是插槽复用?

  • 组件提供结构,父组件提供内容。

171. 什么是受控组件思路?

  • 数据由父组件控制,子组件只负责展示和触发事件。

172. defineExpose 为什么重要?

  • script setup 默认不自动暴露全部内容。

173. 组件库开发为什么喜欢 Vue3?

  • 逻辑复用强、TS 好、编译能力强。

174. Vue3 异步组件怎么写?

  • defineAsyncComponent。

175. defineAsyncComponent 有什么用?

  • 按需加载组件,优化首屏。

176. keep-alive 的 include/exclude 有什么用?

  • 控制哪些组件缓存、哪些不缓存。

177. 组件命名为什么重要?

  • 影响调试、缓存匹配、团队协作。

178. 什么是递归组件?

  • 组件在内部调用自己。
  • 常用于树结构。

179. 递归组件要注意什么?

  • 必须有终止条件。
  • 否则会无限递归。

180. Vue3 组件设计核心是什么?

  • 接口清晰
  • 输入输出明确
  • 低耦合

十、路由(181-190)

181. Vue Router 4 常和 Vue3 搭配吗?

  • 是。
  • Vue3 通常配 Vue Router 4。

182. createRouter 是什么?

  • 创建路由实例。

183. createWebHistory 和 createWebHashHistory 区别?

  • 一个是 history 模式
  • 一个是 hash 模式

184. Vue3 路由懒加载怎么做?

  • 路由组件写动态 import。

185. 路由守卫有哪些?

  • 全局守卫
  • 路由独享守卫
  • 组件内守卫

186. useRoute 是什么?

  • 在 setup 中获取当前路由信息。

187. useRouter 是什么?

  • 在 setup 中获取路由实例。

188. setup 中怎么监听路由变化?

  • watch(useRoute()) 或 watch 某个 route 字段。

189. beforeRouteLeave 常见场景?

  • 离开确认
  • 未保存表单提示

190. 路由 meta 常用来做什么?

  • 权限
  • 标题
  • 缓存标记

十一、Pinia(191-200)

191. Pinia 是什么?

  • Vue3 官方推荐状态管理库。

192. Pinia 相比 Vuex 好在哪?

  • API 更简单
  • TS 更友好
  • 没有 mutations

193. defineStore 是什么?

  • 定义 store 的函数。

194. Pinia 的核心概念有哪些?

  • state
  • getters
  • actions

195. Pinia 为什么没有 mutations?

  • 直接修改 state 或在 actions 中处理即可。

196. Pinia 支持模块化吗?

  • 支持。
  • 一个 store 就是一个天然模块。

197. Pinia 怎么做持久化?

  • 配合本地存储或插件。

198. setup store 和 option store 区别?

  • 一个写法更像 setup
  • 一个更像传统选项式

199. storeToRefs 有什么用?

  • 把 store 状态转成 ref,避免解构丢响应式。

200. Vue3 状态管理面试主线是什么?

  • 为什么推荐 Pinia
  • Pinia 和 Vuex 区别
  • state/getters/actions
  • 持久化和模块化

高频补充

  • 核心主线:Proxy -> ref/reactive -> setup -> 生命周期 -> 通信 -> 路由 -> Pinia
  • 高频对比:ref/reactive、watch/watchEffect、Vue2/Vue3、Pinia/Vuex
  • 高频场景:Teleport、Suspense、defineExpose、storeToRefs、shallowRef

前端手写题 15 题速记版

前端手写题 15 题速记版

1. 手写 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.value = undefined
this.reason = undefined
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []

const resolve = (value) => {
if (this.state !== 'pending') return
this.state = 'fulfilled'
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn())
}

const reject = (reason) => {
if (this.state !== 'pending') return
this.state = 'rejected'
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}

try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}

then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}

if (this.state === 'rejected') {
queueMicrotask(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}

if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
})

this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
})
}
})

return promise2
}

catch(onRejected) {
return this.then(null, onRejected)
}

static resolve(value) {
return new MyPromise(resolve => resolve(value))
}

static reject(reason) {
return new MyPromise((_, reject) => reject(reason))
}
}

function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'))
}

if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}

2. 手写深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (map.has(obj)) return map.get(obj)

const clone = Array.isArray(obj) ? [] : {}
map.set(obj, clone)

Reflect.ownKeys(obj).forEach(key => {
clone[key] = deepClone(obj[key], map)
})

return clone
}

3. 手写防抖

1
2
3
4
5
6
7
8
9
function debounce(fn, delay) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}

4. 手写节流

1
2
3
4
5
6
7
8
9
10
function throttle(fn, delay) {
let lastTime = 0
return function (...args) {
const now = Date.now()
if (now - lastTime >= delay) {
lastTime = now
fn.apply(this, args)
}
}
}

5. 手写 call

1
2
3
4
5
6
7
8
Function.prototype.myCall = function (context, ...args) {
context = context == null ? globalThis : Object(context)
const key = Symbol('fn')
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}

6. 手写 apply

1
2
3
4
5
6
7
8
Function.prototype.myApply = function (context, args = []) {
context = context == null ? globalThis : Object(context)
const key = Symbol('fn')
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}

7. 手写 bind

1
2
3
4
5
6
7
8
9
Function.prototype.myBind = function (context, ...args1) {
const fn = this
function boundFn(...args2) {
const isNew = this instanceof boundFn
return fn.apply(isNew ? this : context, [...args1, ...args2])
}
boundFn.prototype = Object.create(fn.prototype)
return boundFn
}

8. 手写 new

1
2
3
4
5
function myNew(fn, ...args) {
const obj = Object.create(fn.prototype)
const result = fn.apply(obj, args)
return (result !== null && (typeof result === 'object' || typeof result === 'function')) ? result : obj
}

9. 手写 instanceof

1
2
3
4
5
6
7
8
9
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left)
const prototype = right.prototype
while (proto) {
if (proto === prototype) return true
proto = Object.getPrototypeOf(proto)
}
return false
}

10. 手写数组扁平化

1
2
3
4
5
function flatten(arr) {
return arr.reduce((prev, cur) => {
return prev.concat(Array.isArray(cur) ? flatten(cur) : cur)
}, [])
}

11. 手写柯里化

1
2
3
4
5
6
7
8
9
function curry(fn, ...args) {
return function (...rest) {
const allArgs = [...args, ...rest]
if (allArgs.length >= fn.length) {
return fn(...allArgs)
}
return curry(fn, ...allArgs)
}
}

12. 手写 Promise.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const result = []
let count = 0

if (promises.length === 0) {
resolve([])
return
}

promises.forEach((item, index) => {
Promise.resolve(item).then(value => {
result[index] = value
count++
if (count === promises.length) {
resolve(result)
}
}, reject)
})
})
}

13. 手写 Promise.race

1
2
3
4
5
6
7
function promiseRace(promises) {
return new Promise((resolve, reject) => {
promises.forEach(item => {
Promise.resolve(item).then(resolve, reject)
})
})
}

14. 手写发布订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class EventEmitter {
constructor() {
this.events = {}
}

on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = []
}
this.events[eventName].push(callback)
}

emit(eventName, ...args) {
if (!this.events[eventName]) return
this.events[eventName].forEach(fn => fn(...args))
}

off(eventName, callback) {
if (!this.events[eventName]) return
this.events[eventName] = this.events[eventName].filter(fn => fn !== callback)
}

once(eventName, callback) {
const wrapper = (...args) => {
callback(...args)
this.off(eventName, wrapper)
}
this.on(eventName, wrapper)
}
}

15. 手写 Proxy 响应式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
console.log('更新了', key, value)
return result
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('删除了', key)
return result
}
})
}

高频补充

  • Promise:核心是状态、then 链式调用、异常捕获
  • 深拷贝:循环引用必考,WeakMap 加分
  • 防抖节流:区别 + 场景必问
  • call/apply/bind:核心都是显式绑定 this
  • new:核心是原型链接 + 返回对象规则
  • instanceof:顺着原型链找
  • Proxy:Vue3 响应式基础

前端面经速记

前端面经速记

一、JavaScript 基础

数组方法

  • 增:push/pop(尾)、unshift/shift(头)、splice(任意位置)
  • 删:spliceslice(返回新数组)、filter
  • 改:splicefill
  • 查:indexOffindfindIndexincludes
  • 遍历:forEach(无返回)、map(返回新数组)、filterreducesomeevery
  • 其他:concatjoinreversesortflat

数组深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 方法1:JSON(不能拷贝函数、undefined、循环引用)
JSON.parse(JSON.stringify(arr))

// 方法2:结构化克隆(推荐)
structuredClone(arr)

// 方法3:递归实现
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
const clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
clone[key] = deepClone(obj[key]);
}
return clone;
}

forEach vs map

forEach map
返回值 undefined 新数组
用途 纯遍历/副作用 数据转换
链式调用
性能 略快 略慢

find

  • 作用:返回第一个符合条件的元素
  • 无匹配:返回 undefined
  • 对比:findIndex 返回索引,无匹配返回 -1

二、Vue2 核心

组件通信方式

  1. 父子props / $emit
  2. 父→子ref / $children / $parent
  3. 兄弟/跨级EventBus$on/$emit
  4. 全局Vuex / provide/inject
  5. v-model.sync 修饰符

computed vs watch

computed watch
性质 计算属性(缓存) 监听器
用途 派生数据 响应数据变化执行逻辑
依赖 自动追踪 手动声明
异步
立即执行 immediate: true

子组件调父组件属性

1
2
3
4
5
// 方法1:$parent(不推荐,耦合高)
this.$parent.xxx

// 方法2:props + $emit(推荐)
// 方法3:provide/inject(跨级)

Vue2 指令

  • v-bind(:):绑定属性
  • v-on(@):绑定事件
  • v-model:双向绑定
  • v-if/v-else/v-show:条件渲染
  • v-for:列表渲染(key必加)
  • v-html:渲染HTML(XSS风险)
  • v-text:文本插值
  • v-pre:跳过编译
  • v-once:只渲染一次
  • v-cloak:解决闪烁

v-if vs v-show

v-if v-show
原理 条件渲染(DOM增删) CSS切换 display:none
切换开销
初始渲染 条件为false时不渲染 始终渲染
适用场景 很少切换 频繁切换

data 为什么是函数不是对象

1
2
3
4
5
// 对象:所有实例共享同一引用,数据互相影响 ❌
// 函数:每次返回新对象,实例数据独立 ✅
data() {
return { count: 0 }
}

三、Vue Router

路由传参方式

  1. params/user/:idthis.$route.params.id
  2. query/user?id=1this.$route.query.id
  3. propsprops: true 解耦

params vs query

params query
URL /user/1 /user?id=1
刷新丢失 ✅(需配动态路由)
可见性 较隐蔽 明文

四、异步处理

三组件请求都完成再执行

1
2
3
4
5
6
7
8
9
// Promise.all
Promise.all([req1(), req2(), req3()]).then(([r1, r2, r3]) => {
// 全部完成
});

// async/await
async function fetchAll() {
const [r1, r2, r3] = await Promise.all([req1(), req2(), req3()]);
}

Promise vs async/await

  • Promise:链式调用,处理并发用 Promise.all/race
  • async/await:同步写法,可读性更好,本质还是Promise

五、浏览器存储

sessionStorage vs localStorage

sessionStorage localStorage
生命周期 页面关闭清除 永久(手动清除)
作用域 同源同标签页 同源所有标签页
容量 ~5MB ~5-10MB
刷新后 保留 保留

刷新后清除的存储

  • 需求:页面刷新后数据清除
  • 方案:sessionStorage
  • 注意:关闭标签页/浏览器才清除,刷新保留
  • 特殊:要刷新也清除 → 用 beforeunload 手动清空

六、性能优化

防抖 vs 节流(表单提交防重复点击)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 防抖:停止操作后执行(搜索框)
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}

// 节流:固定间隔执行(按钮点击)
function throttle(fn, delay) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last > delay) {
last = now;
fn(...args);
}
};
}

// 表单提交用:loading状态 / 节流 / 防抖

PHP Composer常用命令详解

PHP Composer常用命令详解

前言

Composer是PHP的依赖管理工具,它允许开发者声明项目所依赖的库,并自动安装这些依赖。掌握Composer的常用命令对于PHP开发者来说至关重要。本文将详细介绍Composer的常用命令及其使用场景,帮助您更高效地管理PHP项目依赖。

一、Composer基础命令

1. 安装Composer

全局安装(推荐)

1
2
3
4
5
6
# Linux/macOS
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

# Windows
# 下载Composer-Setup.exe并安装

局部安装

1
2
curl -sS https://getcomposer.org/installer | php
php composer.phar [命令]

2. 检查Composer版本

1
2
composer --version
composer -V

3. 更新Composer

1
2
3
4
composer self-update
composer self-update --rollback # 回滚到上一个版本
composer self-update --preview # 更新到预览版
composer self-update --stable # 更新到稳定版

二、项目初始化与依赖管理

1. 创建新项目

1
2
3
4
5
6
# 基于现有包创建新项目
composer create-project laravel/laravel my-laravel-app
composer create-project symfony/skeleton my-symfony-app "6.3.*"

# 指定版本
composer create-project --prefer-dist laravel/laravel blog "8.*"

2. 初始化现有项目

1
2
3
4
5
# 在现有项目目录中创建composer.json
composer init

# 交互式创建composer.json
composer init --name="vendor/project" --description="项目描述" --author="作者 <email@example.com>"

3. 安装依赖

1
2
3
4
5
6
7
8
# 安装composer.json中定义的所有依赖
composer install

# 安装开发依赖
composer install --dev

# 优化自动加载(生产环境推荐)
composer install --no-dev --optimize-autoloader

4. 更新依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 更新所有依赖
composer update

# 更新指定包
composer update vendor/package

# 更新多个包
composer update vendor/package1 vendor/package2

# 只更新主要版本
composer update --prefer-stable

# 更新到最新版本(包括不稳定版本)
composer update --prefer-lowest

5. 添加新依赖

1
2
3
4
5
6
7
8
9
10
11
12
# 添加最新稳定版
composer require vendor/package

# 添加指定版本
composer require vendor/package:^1.0
composer require vendor/package:~1.2.3

# 添加开发依赖
composer require --dev vendor/package

# 添加多个包
composer require vendor/package1 vendor/package2

6. 移除依赖

1
2
3
4
5
6
7
8
# 移除包
composer remove vendor/package

# 移除开发依赖
composer remove --dev vendor/package

# 移除多个包
composer remove vendor/package1 vendor/package2

三、依赖查询与信息

1. 搜索包

1
2
3
4
5
# 搜索包
composer search monolog

# 只搜索包名
composer search --only-name monolog

2. 显示包信息

1
2
3
4
5
6
7
8
9
10
11
# 显示包详细信息
composer show monolog/monolog

# 显示已安装的包
composer show --installed

# 显示树形结构的依赖关系
composer show --tree

# 显示所有可用包
composer show --all

3. 检查过时依赖

1
2
3
4
5
6
7
8
# 检查所有过时的依赖
composer outdated

# 检查直接依赖
composer outdated --direct

# 检查开发依赖
composer outdated --dev

四、自动加载管理

1. 生成自动加载文件

1
2
3
4
5
6
7
8
9
10
11
# 生成自动加载文件
composer dump-autoload

# 优化自动加载(生产环境推荐)
composer dump-autoload --optimize

# 生成类映射(提高性能)
composer dump-autoload --classmap-authoritative

# 生成APCu缓存
composer dump-autoload --apcu

2. 自动加载配置

composer.json中配置自动加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"autoload": {
"psr-4": {
"App\\": "src/",
"MyProject\\": "lib/"
},
"psr-0": {
"LegacyNamespace_": "src/"
},
"classmap": [
"src/",
"lib/"
],
"files": [
"src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}

五、脚本与生命周期

1. 运行脚本

1
2
3
4
5
6
7
8
# 运行定义的脚本
composer run-script script-name

# 列出所有脚本
composer run-script --list

# 运行安装后脚本
composer run-script post-install-cmd

2. 定义脚本

composer.json中定义脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"scripts": {
"post-install-cmd": [
"php artisan clear-compiled",
"php artisan optimize"
],
"post-update-cmd": [
"php artisan clear-compiled"
],
"post-create-project-cmd": [
"php artisan key:generate"
],
"custom-script": [
"echo 'Running custom script'",
"php scripts/custom.php"
]
}
}

六、全局命令

1. 全局安装包

1
2
3
4
5
6
7
8
# 全局安装包
composer global require friendsofphp/php-cs-fixer

# 全局更新包
composer global update

# 全局移除包
composer global remove friendsofphp/php-cs-fixer

2. 全局包管理

1
2
3
4
5
6
7
8
# 列出全局安装的包
composer global show

# 显示全局包信息
composer global show friendsofphp/php-cs-fixer

# 更新全局Composer
composer global self-update

七、诊断与调试

1. 诊断命令

1
2
3
4
5
6
7
8
9
10
11
# 诊断Composer环境
composer diagnose

# 验证composer.json
composer validate

# 检查安全漏洞
composer audit

# 检查平台要求
composer check-platform-reqs

2. 调试选项

1
2
3
4
5
6
7
8
# 详细输出
composer install -vvv

# 显示内存使用情况
composer install --profile

# 显示执行时间
composer install --profile --timing

八、配置管理

1. 查看配置

1
2
3
4
5
6
7
8
# 显示所有配置
composer config --list

# 显示特定配置
composer config repositories

# 显示全局配置
composer config --global --list

2. 设置配置

1
2
3
4
5
6
7
8
# 设置配置
composer config process-timeout 600

# 设置全局配置
composer config --global github-oauth.github.com your-token

# 设置仓库
composer config repositories.example composer https://example.org/packages/

3. 常用配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"config": {
"process-timeout": 600,
"use-include-path": false,
"preferred-install": "dist",
"optimize-autoloader": true,
"sort-packages": true,
"platform": {
"php": "8.1.0"
},
"github-protocols": ["https", "ssh"],
"github-oauth": {
"github.com": "your-github-token"
}
}
}

九、仓库管理

1. 添加仓库

1
2
3
4
5
6
7
8
# 添加Composer仓库
composer config repositories.example composer https://example.org/packages/

# 添加VCS仓库
composer config repositories.example vcs https://github.com/example/repo.git

# 添加PEAR仓库
composer config repositories.example pear https://pear.example.org

2. 在composer.json中配置仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"repositories": [
{
"type": "composer",
"url": "https://example.org/packages/"
},
{
"type": "vcs",
"url": "https://github.com/example/repo.git"
},
{
"type": "pear",
"url": "https://pear.example.org"
},
{
"type": "package",
"package": {
"name": "vendor/custom-package",
"version": "1.0.0",
"dist": {
"url": "https://example.org/custom-package.zip",
"type": "zip"
}
}
}
]
}

十、实用技巧与最佳实践

1. 版本约束

1
2
3
4
5
6
7
8
9
10
11
12
{
"require": {
"vendor/package": "1.0.0", // 精确版本
"vendor/package": ">=1.0.0", // 大于等于1.0.0
"vendor/package": "~1.2.3", // ~1.2.3 >=1.2.3 <1.3.0
"vendor/package": "^1.2.3", // ^1.2.3 >=1.2.3 <2.0.0
"vendor/package": "1.0.*", // 通配符
"vendor/package": "~1.0", // ~1.0 >=1.0 <2.0
"vendor/package": "dev-master", // 开发分支
"vendor/package": "@stable" // 稳定版本
}
}

2. 生产环境优化

1
2
3
4
5
6
7
8
# 生产环境安装
composer install --no-dev --optimize-autoloader --no-interaction

# 清理缓存
composer clear-cache

# 清除composer.lock并重新安装
rm composer.lock && composer install

3. 常见问题解决

1
2
3
4
5
6
7
8
9
# 内存不足错误
php -d memory_limit=512M composer install

# SSL证书问题
composer config --global secure-http false

# 代理设置
composer config --global http-proxy http://proxy.example.com:8080
composer config --global https-proxy https://proxy.example.com:8443

十一、Composer.json完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
{
"name": "vendor/project",
"description": "项目描述",
"type": "project",
"keywords": ["keyword1", "keyword2"],
"license": "MIT",
"authors": [
{
"name": "作者姓名",
"email": "author@example.com",
"homepage": "https://example.com",
"role": "Developer"
}
],
"require": {
"php": "^8.0",
"ext-json": "*",
"monolog/monolog": "^2.0",
"symfony/console": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"squizlabs/php_codesniffer": "^3.0"
},
"autoload": {
"psr-4": {
"App\\": "src/",
"Tests\\": "tests/"
},
"classmap": [
"database/seeds",
"database/factories"
],
"files": [
"src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-install-cmd": [
"@php artisan clear-compiled",
"@php artisan optimize"
],
"post-update-cmd": [
"@php artisan clear-compiled"
],
"post-create-project-cmd": [
"@php artisan key:generate"
],
"test": "phpunit",
"cs-check": "php-cs-fixer fix --dry-run --diff",
"cs-fix": "php-cs-fixer fix"
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"process-timeout": 600
},
"repositories": [
{
"type": "composer",
"url": "https://packages.example.com"
}
],
"minimum-stability": "stable",
"prefer-stable": true
}

JavaScript中this指向的速记方法

JavaScript中this指向的速记方法

前言

JavaScript中的this指向是许多开发者容易混淆的概念,但掌握它对于编写高质量的JavaScript代码至关重要。本文将提供一套简单易记的规则,帮助你快速判断this的指向,并通过实例加深理解。

一、this指向的基本规则

1. 全局上下文中的this

在全局执行上下文中(非严格模式下),this指向全局对象:

1
2
3
4
5
// 浏览器环境
console.log(this === window); // true

// Node.js环境
console.log(this === global); // true

速记口诀:全局上下文,this指向全局对象。

2. 函数调用中的this

2.1 普通函数调用

1
2
3
4
5
function fn() {
console.log(this);
}

fn(); // 非严格模式下指向window,严格模式下为undefined

速记口诀:普通函数调用,非严格模式指向全局,严格模式为undefined。

2.2 对象方法调用

1
2
3
4
5
6
7
8
const obj = {
name: '张三',
sayName() {
console.log(this.name);
}
};

obj.sayName(); // "张三"

速记口诀:对象方法调用,this指向调用该方法的对象。

2.3 构造函数调用

1
2
3
4
5
6
function Person(name) {
this.name = name;
}

const p = new Person('李四');
console.log(p.name); // "李四"

速记口诀:构造函数调用,this指向新创建的实例对象。

2.4 call/apply/bind调用

1
2
3
4
5
6
7
8
9
function fn() {
console.log(this.name);
}

const obj = { name: '王五' };
fn.call(obj); // "王五"
fn.apply(obj); // "王五"
const boundFn = fn.bind(obj);
boundFn(); // "王五"

速记口诀:call/apply/bind调用,this指向指定的第一个参数。

二、特殊情况与陷阱

1. 箭头函数中的this

箭头函数没有自己的this,它会捕获其所在上下文的this值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = {
name: '赵六',
regularFn() {
console.log(this.name); // "赵六"

const arrowFn = () => {
console.log(this.name); // "赵六" - 继承外层this
};

arrowFn();
}
};

obj.regularFn();

速记口诀:箭头函数没有自己的this,继承外层作用域的this

2. 事件处理函数中的this

1
2
3
4
5
6
7
8
9
const btn = document.getElementById('myBtn');

btn.addEventListener('click', function() {
console.log(this); // 指向触发事件的元素(btn)
});

btn.addEventListener('click', () => {
console.log(this); // 箭头函数,继承外层作用域的this
});

速记口诀:事件处理函数中,普通函数指向触发元素,箭头函数继承外层this

3. 定时器中的this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = {
name: '钱七',
regularFn() {
setTimeout(function() {
console.log(this.name); // undefined - this指向全局对象
}, 100);

setTimeout(() => {
console.log(this.name); // "钱七" - 继承外层this
}, 100);
}
};

obj.regularFn();

速记口诀:定时器回调中,普通函数指向全局,箭头函数继承外层this

三、this指向判断流程图

1
2
3
4
5
6
7
8
9
10
graph TD
A[开始判断this指向] --> B{是箭头函数吗?}
B -->|是| C[继承外层作用域的this]
B -->|否| D{是new调用吗?}
D -->|是| E[指向新创建的实例]
D -->|否| F{是call/apply/bind调用吗?}
F -->|是| G[指向指定的第一个参数]
F -->|否| H{是对象方法调用吗?}
H -->|是| I[指向调用该方法的对象]
H -->|否| J[指向全局对象或undefined]

四、实用技巧与解决方案

1. 保存this引用

在ES6之前,常用变量保存this引用:

1
2
3
4
5
6
7
8
9
const obj = {
name: '孙八',
regularFn() {
const self = this; // 保存this引用
setTimeout(function() {
console.log(self.name); // "孙八"
}, 100);
}
};

2. 使用箭头函数

ES6+推荐使用箭头函数:

1
2
3
4
5
6
7
8
const obj = {
name: '周九',
regularFn() {
setTimeout(() => {
console.log(this.name); // "周九"
}, 100);
}
};

3. 使用bind方法

1
2
3
4
5
6
7
8
const obj = {
name: '吴十',
regularFn() {
setTimeout(function() {
console.log(this.name); // "吴十"
}.bind(this), 100);
}
};

五、常见面试题解析

1. 对象方法中的this

1
2
3
4
5
6
7
8
9
10
const obj = {
name: '对象',
getName() {
return this.name;
},
getNameArrow: () => this.name
};

console.log(obj.getName()); // "对象"
console.log(obj.getNameArrow()); // undefined - 箭头函数继承全局this

2. 嵌套函数中的this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = {
name: '嵌套',
outer() {
console.log(this.name); // "嵌套"

function inner() {
console.log(this.name); // undefined - this指向全局
}

inner();
}
};

obj.outer();

3. 链式调用中的this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const calculator = {
value: 0,
add(num) {
this.value += num;
return this; // 返回this以支持链式调用
},
multiply(num) {
this.value *= num;
return this;
},
getResult() {
return this.value;
}
};

const result = calculator.add(5).multiply(2).add(3).getResult(); // 13

六、this指向速记表

调用方式 this指向 示例
全局上下文 全局对象 console.log(this)
普通函数调用 全局对象/undefined fn()
对象方法调用 调用该方法的对象 obj.method()
构造函数调用 新创建的实例 new Constructor()
call/apply/bind 指定的第一个参数 fn.call(obj)
箭头函数 继承外层作用域的this () => {}
DOM事件处理函数 触发事件的元素 element.onclick = function() {}
定时器回调 全局对象/继承外层this setTimeout(fn, 100)

七、总结

掌握JavaScript中this的指向规则是每个前端开发者的必备技能。通过本文提供的速记方法和规则,你可以快速判断各种场景下this的指向:

  1. 全局上下文this指向全局对象
  2. 普通函数调用:非严格模式指向全局,严格模式为undefined
  3. 对象方法调用this指向调用该方法的对象
  4. 构造函数调用this指向新创建的实例
  5. call/apply/bind调用this指向指定的第一个参数
  6. 箭头函数:没有自己的this,继承外层作用域的this