一、前端基础面试题
1. Ajax、Fetch、Axios 的区别
三者都用于网络请求,但是不同维度
Ajax (Asynchronous Javascript and XML),一种技术统称
- 延伸:用 XMLHttpRequest 实现 Ajaxjs
function ajax (url, successFn) { const xhr = new XMLHttpRequest(); xhr.open("GET", url, false) xhr.onreadystatechange = function () { // 这里的函数异步执行 if (xhr.readyState === 4) { if (xhr.status === 200) { successFn(xhr.responseText) } } } xhr.send(null) }
- 延伸:用 XMLHttpRequest 实现 Ajax
Fetch 就是一个具体的 API
- 浏览器原生 API,用于网络请求
- 和 XMLHttpRequest 一个级别
- Fetch 语法更加简洁、易用、支持 Promise
Axios, 第三方库
https://axios-http.com/
- 最常用的网络请求 lib
- 内部可用 XMLHttpRequest 和 Fetch 来实现
重点:lib 和 API 区别
- 第三库可以用API来实现
2. 节流 和 防抖
- 问题
- 两者有什么区别
- 分别用于什么场景
节流:
- 节流,顾名思义节省交互沟通。流,不一定指流量。"别急,一个一个来,按时间节奏来,插队者无效"
- 场景:drag 或 scroll 期间触发某个回调,要设置一个时间间隔。
- 代码演示:
js/** * 节流 */ function throttle(fn, delay = 100) { let timer = 0; return function () { if (timer) return timer = setTimeout(() => { fn.apply(this, arguments); timer = 0 }, delay) } }
防抖:
- 防抖,顾名思义防止抖动,"你先抖动着,啥时候停了,再执行下一步"
- 场景:一个搜索输入框,等输入停止之后,再触发搜索。
- 代码演示:
js/** * 防抖 */ function debounce(fn, delay = 200) { let timer = 0; return function () { if (timer) clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, arguments); timer = 0; }, delay) } }
总结:
- 节流:限制执行频率,有节奏的执行。
- 防抖:限制执行次数,多次密集的触发只执行一次。
- 节流关注"过程",防抖关注"结果"
- 实际工作可使用
https://loadash.com
3. px % em rem vw/vh 有什么区别
px 和 %
- px 基本单位。绝对单位(其他的都是相对单位)
- % 相对于父元素的宽度比例
em 和 rem
- em 相对于当前元素的 font-size
- rem 相对于根节点的 font-size
vw 和 vh
- vw 屏幕宽度的 1%
- vh 屏幕高度的 1%
- vmin 两者的最小值, vmax 两者的最大值
关注下:各自的使用场景
4. 箭头函数的缺点,哪里不能用箭头函数?
箭头函数的缺点:
- 没有 arguments
- 无法通过 apply call bind 改变 this, 箭头函数 this 指向的父作用域
箭头函数不适用场景:
对象方法
jsconst obj = { name: '张三', getName: () => { return this.name } } console.log(obj.getName())
原型方法
jsconst obj = { name: '张三', } obj.__proto__.getName = () => { return this.name; } console.log(obj.getName())
构造函数
jsconst Foo = (name, age) => { this.name = name; this.age = age; } const f = new Foo('张三', 20) // 报错 Foo is not a constructor
动态上下文中的回调函数
jsconst btn = document.getElementById('btn'); btn.addEventListener('click', () => { this.innerHTML = 'clicked' })
vue 生命周期和 method
vue<template> </template> <script> export default { data() { return { name: "张三" } }, methods: { getName: () => { // 报错 Cannot read properties of undefined (reading 'name') return this.name } // getName() { // return this.name // 正常 // } }, mounted: () => { // 报错 Cannot read properties of undefined (reading 'name') console.log('msg', this.name) }, // mounted() { // console.log('msg', this.name) // 正常 // } } } </script> <style scoped lang='less'> </style>
- 注意:Vue 组件本质上是一个 JS 对象, React 组件(非 Hooks) 它本质上是一个 ES6 classjs
class Foo { constructor(name, city) { this.name = name; this.city = city; } getName = () => { return this.name; } } const f = new Foo('张三', '上海'); console.log(f.getName())
- 注意:Vue 组件本质上是一个 JS 对象, React 组件(非 Hooks) 它本质上是一个 ES6 class
5. 请描述 TCP 三次握手 和 四次挥手
建立 TCP 连接
- 先建立连接(确保双方都有收发消息的能力)
- 再传输内容 (如发送给一个 get 请求)
- 网络连接是 TCP 协议,传输内容是 HTTP 协议
建立连接【三次握手】
- Client 发包,Server 接受。Server:有 Client 要找我。
- Server 发包,Client 接受。Client:Server 已经收到信息了。
- Client 发包,Server 接受。Server:Client 要准备发送了。
关闭连接【四次挥手】
- Client 发包,Server 接受。Server:Client 已请求结束。
- Server 发包,Client 接受。Client:Server 已收到,我等待它关闭。
- Server 发包,Client 接受。Client:Server 此时可以关闭连接了。
- Client 发包,Server 接受。Server:可以关闭了(然后关闭连接)。
重点
- 握手是建立连接,挥手是告别。
6. for in 和 for of 有什么区别
- key 和 value
- for in 遍历得到 key
- for of 遍历得到 value
- 适用于不同的数据类型
- 遍历对象:for in 可以,for of 不可以
- 遍历 Map Set:for of 可以, for in 不可以
- 遍历 generator: for of 可以, for in 不可以
- 可枚举 和 可迭代
- for in 用于可枚举数据,如对象、数组、字符串
- Object.getOwnPropertyDescriptors(obj)
- enumerable: true
- Object.getOwnPropertyDescriptors(obj)
- for of 用于可迭代数据,如数组、字符串、Map、Set
- arr[Symbol.iterator]
- for in 用于可枚举数据,如对象、数组、字符串
7. for await of 有什么作用
- for await of 用于遍历多个 Promise 也就是 Promise.all() 的代替品
8. offsetHeight scrollHeight clientHeight 区别
盒子模型
- width
- height
- padding
- border
- margin
- box-sizing
offsetHeight offsetWidth: border + padding + content
clientHeight clientWidth: padding + content
scrollHeight scrollWidth: padding + 实际内容尺寸(不一定是content,以实际为主)
scrollTop scrollLeft 超出部分的距离
9. HTMLCollection 和 NodeList 区别
Node 和 Element
- DOM 是一棵树,所有节点都是 Node
- Node 是 Element 的基类
- Element 是其他 HTML 元素的基类,如 HTMLDivElement
HTMLCollection 是 Element 的集合
NodeList 是 Node 集合
代码演示
jsclass Node {} // document class Document extends Node {} class DocumentFragment extends Node {} // 文本和注释 class CharacterData extends Node {} class Comment extends CharacterData {} class Text extends CharacterData {} // elem class Element extends Node {} class HTMLElement extends Element {} class HTMLDivElement extends HTMLElement {} class HTMLInputElement extends HTMLElement {}
重点
- 获取 Node 和 Element 的返回结果可能不一样
- 如 elem.childNodes 和 elem.children 不一样
- 前者会包含 Text 和 Comment 节点,后者不会
扩展
- HTMLCollection 和 NodeList 都不是数组,而是 "类数组"
- 将类数组转化成数组js
const arr1 = Array.from(list); const arr2 = Array.prototype.slice.call(list); const arr3 = [...list]
10. Vue 中 computed 和 watch 区别
- 两者用途不同
- computed 用于计算产生新的数据
- watch 用于监听现有数据
- 缓存
- computed 有缓存
- method 没有缓存
11. Vue 组件通讯方式有几种? 尽量说全面
通信方式
- props 和 $emit
- 自定义事件-事件总线
- 自定义事件 在mounted中 on 之后,一定需要在 beforeUnmount中 off 这个事件
- $attrs
- $parent
- $refs
- provide/inject
- Vuex
不同场景
- 父子组件
- 上下级组件(跨多级)通讯
- 全局组件
12. Vuex mutation action 区别
- mutation:原子操作,必须同步代码
- action: 可包含多个 mutation; 可包含异步代码;
13. JS 严格模式有什么特点
- JS 严格模式细节要求很多,只掌握重点即可
- 开启严格模式
- 特点
- 全局变量必须先声明js
'use strict' n = 100 // ReferenceError: n is not defined
- 禁止使用 withtext
'use strict' var obj = { x: 100 } with (obj) { // Uncaught SyntaxError: Strict mode code may not include a with statement console.log(x) }
- 创建 eval 作用域js
'use strict' var x = 10; eval('var x = 20; console.log(x)') console.log(x)
- 禁止 this 指向 windowjs
'use strict' function fn () { console.log('this', this) //undefined } fn()
- 函数参数不能重名text
'use strict' // Uncaught SyntaxError: Duplicate parameter name not allowed in this context function fn (x, x, y) { return }
- 全局变量必须先声明
14. HTTP 跨域请求时为何发送 options 请求
浏览器同源策略
同源策略一般限制Ajax 网络请求,不能跨域请求 server
不会限制
<link> <img> <script> <iframe>
加载第三方资源处理方式
JSONP
CORS
多余的 options 请求
- options 请求,是跨域请求之前的预检查
- 浏览器自行发起的,无需我们干预
- 不会影响实际的功能
15. JS 内存泄漏如何检测?场景有哪些?
什么是垃圾回收
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>gc</title> </head> <body> <p> Garbage collection </p> <script> function fn1() { const a = 'aa' console.log(a) const obj = { x: 100} console.log(obj) } // 执行完该函数,函数中的变量会被回收 fn1() function fn2() { const obj = { x: 100 } // 回收不了,这是用户期望的 window.obj = obj; } fn2() function getDataFns() { const data = {}; // 闭包的数据永远都是常驻内存的 return { get(key) { return data[key] }, set(key, value) { data[key] = value } } } const { get, set } = getDataFns(); set('x', 100); get('x'); // 对象被 a 引用 let a = { x: 100 }; let a1 = a; a = 10; a1 = null; // 引用计数缺陷:循环引用 function fn3() { const obj1 = {}; const obj2 = {}; obj1.a = obj2; obj2.a = obj1; } fn3() // JS 根 window 遍历属性 </script> </body> </html>
JS 垃圾回收的算法
- 引用计数(之前)js
// 对象被 a 引用 let a = { x: 100 }; let a1 = a; a = 10; a1 = null;
- 标记清除(现代)js
// JS 根 window 遍历属性
- 引用计数(之前)
闭包是内存泄漏吗?
- 闭包严格意义上不算内存泄漏
如何检测泄漏
- 使用 Chrome devTools 的 Performance 和 Memory 工具检测 js 内存
- 扩展:wangEditor 检测内存泄漏
- 内存泄漏的场景(Vue 为例)
被全局变量、函数引用,组件销毁时未清除
vue<template> </template> <script> export default { name: 'MemoryLeak', data() { return { arr: [10, 20, 30] } }, mounted() { window.arr = this.arr; window.printArr = () => { console.log(this.arr) } }, beforeUnmount() { window.arr = null; window.printArr = null }, methods: { } } </script> <style scoped lang='less'> </style>
被全局事件、定时器引用,组件销毁时未清除
vue<template> </template> <script> export default { name: 'MemoryLeak', data() { return { arr: [10, 20, 30], // 数组、对象 intervalId: 0 } }, mounted() { this.intervalId = setInterval(() => { console.log(this.arr) }, 100) }, beforeUnmount() { if (this.intervalId) { clearInterval(this.intervalId) } } } </script> <style scoped lang='less'> </style>
vue<template> </template> <script> export default { name: 'MemoryLeak', data() { return { arr: [10, 20, 30], // 数组、对象 } }, mounted() { window.addEventListener('resize', this.printArr) }, beforeUnmount() { window.removeEventListener('resize', this.printArr) }, methods: { printArr() { console.log(this.arr) } } } </script> <style scoped lang='less'> </style>
被自定义事件引用,组件销毁时未清除
- 使用 eventBus 的时候,on 绑定事件,需要在组件销毁的生命周期函数中使用 off 进行解绑。
- 扩展 WeakMap WeakSet
- WeakMap 的 key 只能是引用类型
16. 浏览器和 node.js 的事件循环有什么区别?
- 单线程和异步
- JS 是单线程的(无论在浏览器还是nodejs)。
- 浏览器中 JS 执行和 DOM 渲染公用一个线程。
- 异步
- 宏任务和微任务
- 宏任务:如 setTimeout setInterval 网络请求。
- 微任务:如 promise async/await。
- 微任务在下一轮 DOM 渲染之前执行,宏任务在之后执行。
- 宏任务和微任务
17. 伪类和伪元素的区别
伪类用于当已有元素处于的某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。比如说,当用户悬停在指定的元素时, 我们可以通过
:hover
来描述这个元素的状态。虽然它和普通的css
类相似,可以为已有的元素添加样式,但是它只有处于dom
树无法描 述的状态下才能为元素添加样式,所以将其称为伪类。伪元素用于创建一些不在文档树中的元素,并为其添加样式。比如说,我们可以通过
:before
来在一个元素前增加一些文本,并为这些文本 添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。
18. typeof能判断哪些类型
答案: 所有值类型、函数、引用类型
解析:
- 识别所有值类型
- 识别函数
- 判断是否是引用类型(不可再细分)js
// 判断所有值类型 let a; typeof a // 'undefined' const str = 'abc'; typeof str // 'string' const n = 100; typeof n // 'number' const b = true; typeof b // 'boolean' const s = Symbol('s'); typeof s // 'symbol' // 能判断函数 typeof console.log // 'function' typeof function () {} // 'function' // 能识别引用类型(不能再继续识别) typeof null; // 'object' typeof ['a', 'b'] // 'object' typeof { x: 100 } // 'object'
19. 变量计算-类型转换
- 字符串拼接js
const a = 100 + 10; // 110 const b = 100 + '10'; // '10010' const c = true + '10'; // 'true10'
==
js100 == '100' // true 0 == '' // true 0 == false // true false == '' // true null == undefined // true // 除了 == null 之外,其他都一律用 === 例如: const obj = { x: 100 }; if (obj.a == null) {} // 相当于 // if (obj.a === null || obj.a === undefined) {}
if
语句和逻辑运算truly
变量:!!a === true 的变量falsely
变量:!!a === false 的变量
js// 以下是 `falsely`变量。除此之外都是`truly`变量 !!0 === false !!NaN === false !!'' === false !!null === false !!undefined === false !!false === false
js// if 语句 // `truly`变量 const a = true; if (a) {} const b = 100; if (b) {} // falsely变量 const c = '' if (c){} const d = null if (d) {} let e if (e){}
js// 逻辑判断 console.log(10 && 0); console.log('' || 'acb') console.log(!window.abc)
20. instanceof
类型判断
jsxiaoming instanceof Student // true xiaoming instanceof People // true xiaoming instanceof Object // true console.log([] instanceof Array) // true console.log([] instanceof Object) // true console.log({} instanceof Object) // true
原型
js// class 实际上是函数,可见是语法糖 typeof People // 'function' typeof Student // 'function' // 隐式原型和显示原型 console.log( xiaoming.__proto__) console.log( Student.prototype) console.log( xiaoming.__proto__ === Student.prototype)
原型关系
- 每个
class
都有显示原型prototype
- 每个实例都有隐式原型
__proto__
- 实例的
__proto__
指向对应class
的prototype
- 每个
基于原型的执行规则
- 获取属性
xiaoming.name
或执行方法xiaoming.sayHi()
时 - 先在自身属性和方法寻找
- 如果找不到则自动去
__proto__
中查找
- 获取属性
原型链
jsconsole.log( Student.prototype.__proto__ ) console.log( People.prototype ) console.log( People.prototype === Student.prototype.__proto__)
instanceof 就是顺着原型链往上找,直到找到判别的真身,返回true 如果找不到需要判别的真身,则返回false
重要提示
class
是ES6
语法规范,由ECMA
委员发布ECMA
只规定语法规则,即代码的书写规范,不规定如何实现- 以上实现方式都是
V8
引擎的实现方式,也是主流的
如何准确判断一个变量是数组
jsa instanceof Array
21. class的原型本质,怎么理解
- 原型和原型链的图示
- 属性和方法的执行规则
22. 手写一个简易的jQuery
,考虑插件和拓展性
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
this.selector = selector
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
// 扩展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
// “造轮子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展自己的方法
addClass(className) {
}
style(data) {
}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))