前端面试题总结-2020
记录了自己疫情期间复习总结的一些面试题+面试遇到的一些问题,文章将会持续更新。
JS
原型及继承
组合继承
function Person(name) {
this.name = name;
this.features = ['eyes'];
}
function Student(name, id) {
Person.call(this, name); //继承属性相当于super(this)
this.id = id;
}
Student.prototype = new Person();
Student.prototype.sayHello = function () {
console.log('hello');
};
const s1 = new Student('Alan', '001');
const s2 = new Student('Bob', '002');
console.log(s1 instanceof Person); // true
// 引用类型在各实例中不会相互影响
s1.features.push('hand');
console.log(s2.features); // ["eyes"]
更多继承方式及其优缺点可以查看红宝书第六章(讲得非常“干”)
深拷贝和浅拷贝
在 JS 中,变量分为了基本类型和引用类型。对基本类型进行赋值时是对值进行拷贝的,而对引用类型进行赋值则是对地址进行拷贝。
- 基本类型
- 引用类型
let a = 1;
let b = a;
console.log(a); // 1
a++;
console.log(a); // 2
console.log(b); // 1
const obj = {
name: 'Alan',
age: 22
};
const cloneObj = obj;
obj.age = 18;
console.log(cloneObj.age); // 18
通过上面的例子我们知道,cloneObj 和 obj 是指向同一个地址的,任何一方修改都会影响到对方,那如何创建一个独立的 cloneObj?,这就要使用深拷贝和浅拷贝了。
深拷贝和浅拷贝的区别:
根据拷贝的层级进行区分,浅拷贝只进行一层拷贝,深拷贝进行多层拷贝。
浅拷贝
- 方法1
- 方法2
const obj = { a: 1, b: { b1: 1, b2: 2 }, c: 0 };
function shallowClone(source) {
const result = {};
for (const key in source) {
if (source.hasOwnProperty(key)) {
result[key] = source[key];
}
}
return result;
}
const shallowObj = shallowClone(obj);
obj.a = 10;
console.log(shallowObj.a); // 1
obj.b.b1 = 6;
console.log(shallowObj.b.b1); // 6
const obj = { a: 1, b: { b1: 1, b2: 2 }, c: 0 };
function shallowClone1(source) {
return Object.assign({}, source);
}
const shallowObj = shallowClone1(obj);
obj.a = 10;
console.log(shallowObj.a); // 1
obj.b.b1 = 6;
console.log(shallowObj.b.b1); // 6
深拷贝
- 方法1
- 方法2
const obj = { a: 1, b: { b1: 1, b2: 2 }, c: 0 };
function deepClone(source) {
const result = {};
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object') {
result[key] = deepClone(source[key]);
} else {
result[key] = source[key];
}
}
}
return result;
}
const deepObj = deepClone(obj);
obj.a = 10;
console.log(deepObj.a); // 1
obj.b.b1 = 6;
console.log(deepObj.b.b1); // 1
const obj = { a: 1, b: { b1: 1, b2: 2 }, c: 0 };
function deepClone(source) {
return JSON.parse(JSON.stringify(source));
}
const deepObj = deepClone(obj);
obj.a = 10;
console.log(deepObj.a); // 1
obj.b.b1 = 6;
console.log(deepObj.b.b1); // 1
数组去重
- 方法1
- 方法2
- 方法3
// 笨方法
const arr = [1, 1, 2, 5, 2, 6, 8];
let newArr = [];
for (let i = 0; i < arr.length; i++) {
if (newArr.includes(arr[i])) {
continue;
}
newArr.push(arr[i]);
}
const arr = [1, 3, 45, 6, 3, 2, 0];
const newArr = arr.filter((item, index) => {
return arr.indexOf(item) === index;
});
console.log(arr); // [1, 3, 45, 6, 3, 2, 0]
console.log(newArr); // [1, 3, 45, 6, 2, 0]
const arr = [1, 3, 45, 6, 3, 2, 0];
const newArr = [...new Set(arr)];
console.log(arr); // [1, 3, 45, 6, 3, 2, 0]
console.log(newArr); // [1, 3, 45, 6, 2, 0]
数组填充
new Array(10).fill(1);
Array.from({ length: 10 }, item => 1);
for in 和 for of
- for in 会遍历数组中的可枚举属性,包括原型。可以遍历对象,遍历的是 key 值
- for of 只是遍历数组的元素或者可以迭代的对象,不包括原型。遍历的是 value 值。
Array.prototype.testMethod = function () {
console.log('testMethod');
};
const mArr = [1, 2, 3, 7];
const mObject = {
name: 'Alan',
age: 1
};
for (const key in mArr) {
console.log(mArr[key]);
// 1 2 3 7
/* ƒ () {
console.log('testMethod');
} */
}
// 解决方案
for (const key in mArr) {
if (mArr.hasOwnProperty(key)) {
console.log(mArr[key]);
// 1 2 3 7
}
}
for (const key in mObject) {
console.log(key);
// name
// age
}
try {
for (const iterator of mObject) {
console.log(iterator);
}
} catch (error) {
console.log(error);
//mObject is not iterable
}
事件委托
事件委托利用了事件冒泡,只指定了一个事件处理程序,就可以管理某一类型的所有事件。
例如,click 事件会一直冒泡到 document 层次。例如下面例子中我们无需对所有 li 元素添加 onclick 事件,只需使用事件冒泡的特性来实现事件委托。
<body>
<ul id="myList">
<li id="sayName">Name</li>
<li id="sayHello">Hello</li>
<li id="sayAge">Age</li>
</ul>
<script>
const myList = document.getElementById('myList');
myList.addEventListener('click', function (e) {
const target = e.target;
if (target.id === 'sayName') {
alert('Alan');
} else if (target.id === 'sayHello') {
alert('Hello');
} else {
alert('sayAge');
}
});
</script>
</body>
实现滑动动画
<div id="myDiv"></div>
<script>
const myDiv = document.getElementById('myDiv');
const time = Date.now(); //时间戳
const transition = setInterval(() => {
const timeLength = Date.now() - time;
const step = (5000 - timeLength) / 1000;
console.log(step);
if (timeLength > 5000) {
clearInterval(transition);
// 5s后结束
}
myDiv.style.left = myDiv.offsetLeft + step + 'px';
}, 50);
</script>
数组扁平化
- 方法1
- 方法2
- 方法3
- 方法4
// Array.prototype.flat([depth])
let arr = [1, 2, 7, [2, [2, 3], 6]];
console.log(arr.flat(Infinity));
// 使用for of 递归
let arr = [1, 2, 7, [2, [2, 3], 6]];
function flat(arr) {
let newArr = [];
for (const item of arr) {
if (Array.isArray(item)) {
newArr = newArr.concat(flat(item));
} else {
newArr.push(item);
}
}
return newArr;
}
console.log(flat(arr));
// 扩展运算符
const arr = [1, 2, 7, [2, [2, 3], 6]];
function flat(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(flat(arr));
// 骚操作
const arr = [1, 2, 7, [2, [2, 3], 6]];
function flat(arr) {
const result = arr
.toString()
.split(',')
.map(item => {
return +item;
});
return result;
}
console.log(flat(arr));
eventLoop
JS 是单线程的,那为什么不是多线程的呢,设想一个场景,一个线程修改了 body 的 background 为 red,另一个线程修改了 body 的 background 为 green。那最终浏览器就不知道 background 到底为什么。由此可以看到多线程会为浏览器的 DOM 操作带来很多同步问题。参考资料
JS 的任务可以分为同步任务和异步任务。
1.同步任务优先在主线程上执行,会形成一个执行栈。
2.异步任务会被放入任务队列中,当执行栈清空时会读取任务队列中的任务丢进执行栈中。
1、2 两步反复执行形成了 eventLoop。
如果将任务细分的话还可以分成宏任务和微任务:
- macro-task(宏任务):包括整体代码 script,setTimeout,setInterval
- micro-task(微任务):Promise.then(),process.nextTick
优先级:process.nextTick>Promise.then()
当执行栈中没有任务时,微任务总是优先于宏任务执行
详细查看文章这一次,彻底弄懂 JavaScript 执行机制
console.log('script start');
async function async1() {
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2 end');
return Promise.resolve().then(() => {
console.log('async2 end1');
});
}
async1();
setTimeout(function () {
console.log('setTimeout');
}, 0);
new Promise(resolve => {
console.log('Promise');
resolve();
})
.then(function () {
console.log('promise1');
})
.then(function () {
console.log('promise2');
});
console.log('script end');
// script start => async2 end => Promise => script end => async2 end1 => promise1 => promise2 => async1 end => setTimeout
this
- 指向调用它的那个对象,函数运行时获得的。
- 箭头函数的 this 指向取决于定义时最近一层的非箭头函数的 this 值。取决于外部的上下文。
特殊例子
var name = 'windowsName';
var a = {
name: null,
fn: function () {
console.log(this.name); // windowsName
}
};
var f = a.fn;
f();
闭包
关键词: 内存泄漏
- 当前函数的执行上下文中的内容被该上下文以外的内容占用,导致当前上下文无法释放。
- 闭包是指有权访问另一个函数作用域中的变量的函数。
- 创建闭包的常见方式,就是在一个函数内部创建另一个函数
求数组最大值
const arr = [1, 2, 1, 4, 2, 10];
console.log(Math.max.apply(null, arr));
const arr = [1, 2, 1, 4, 2, 10];
console.log(arr.sort((a, b) => a - b)[arr.length - 1]);
const arr = [1, 2, 1, 4, 2, 10];
console.log(Math.max(...arr));
Promise
// new Promise(executor),当new Promise被创建,executor自动执行
let promise = new Promise(function (resolve, reject) {
resolve('finished');
// reject(new Error);
});
// Promise.then(f1,f2) f1在resolve后运行(参数为resolve结果),f2在reject后运行(参数为reject错误)
promise.then(
result => console.log(result), //finished
error => console.log(error) //输出错误
);
- promise.all()同时执行多个 promise,只要有一个 promise 被 reject,那么将不再执行
- promise.allSettled()和 promise.all()类似,只是会等所有 promise 执行完。
- promise.race()返回最快执行完的 promise 结果。
- 通过 window.addEventListener('unhandledrejection', event => alert(event.reason)) 来捕获未处理的 rejection。
- thenable 对象(具有可调用的 then 方法的对象)
实现一个 sleep
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
(async () => {
console.log('2s后输出内容...');
await sleep(2000);
console.log('666');
})();
防抖和节流
节流
函数在一定时间内只执行一次,比如点击按钮后回去服务器获取数据,使用节流可以防止短时间内请求多次,减少服务器的压力
防抖
在一定时间后才执行(触发多次只会执行一次)。应用场景:input 搜索框在 wait 秒后再发送请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
height: 4000px;
width: 100px;
background: rgb(241, 165, 165);
}
</style>
</head>
<body>
<button id="fetchBtn">fetch data</button>
<span>searchBar</span><input id="inputBar" />
<script>
// 节流
function throttle(func, wait) {
let last = 0;
return function (...args) {
let now = new Date();
if (now - last > wait) {
last = now;
func.apply(this, args);
}
};
}
// 防抖
function debounce(func, wait) {
let timer = 0;
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
document.getElementById('fetchBtn').addEventListener(
'click',
throttle(function (numb) {
console.log('fetching');
}, 1000)
);
document.getElementById('inputBar').addEventListener(
'input',
debounce(function (numb) {
console.log('searching');
}, 1000)
);
</script>
</body>
</html>
XMLHttpRequest
常用方法:
- open(method, url, [async, user, password])初始化,async(false/true)控制同步/异步
- send([body])发送请求
- abort()中止请求
- setRequestHeader(name, value)设置请求头
常用属性:
status: 404/200...
statusText: Not Found/OK...
responseType: 响应格式
readyState: 状态
withCredentials: 跨域设置
UNSENT = 0; // 初始状态
OPENED = 1; // open 被调用
HEADERS_RECEIVED = 2; // 接收到 response header
LOADING = 3; // 响应正在被加载(接收到一个数据包)
DONE = 4; // 请求完成
常用监听事件:
- onload
- onerror
- onprogress
JS 题目
const a = ['1', '2', '3'].map(parseInt);
// 数组a中的'1'转化为10进制。
console.log(a); // [1, NaN, NaN]
// map的三个参数(item,index,array)
/* parseInt(string, radix)
当radix等于0或者undefined或者没有指定时,如果string以'0x'或者''0X'开头,则radix=16
以'0'开头,根据实际情况radix=10/8。 */
const users = [
{
name: 'Alan',
age: 19
},
{
name: 'Bob',
age: 25
}
];
const userList = users.sort((a, b) => b.age - a.age);
// 根据年龄进行排序,注意:sort会改变原来的数组
function isSameLetter(a, b) {
a = a.toString().toLowerCase();
b = b.toString().toLowerCase();
return a.split('').sort().join('') === b.split('').sort().join('');
}
console.log(isSameLetter('176as', 'a17s6'));
//判断两者是否是由相同的字母组成,顺序可以不一样
前端框架相关
React
- React 生命周期
- useRef
- 操作 DOM
- 保存不需要在 JSX 中渲染的变量
- 保存示例
- 使用
forwardRef
进行 ref 透传 - 使用
useImperativeHandle
约束向外暴露的东西,且可以解决函数组件 ref 无法获取示例的问题。 - 当
ref
要使用最新的state
时,使用 flushSync
cloneElement
可以劫持children element
混入props
- HOC
- 注解
- 复用逻辑
- 正向属性代理(强化 props)
- 反向属性代理(通过 extends 组件可以获取组件内部状态)
单页应用优缺点
优点:
- 基于 ajax 加载数据,无需刷新页面,用户体验好
- 前后端分离,后端代码可以应用到多端
- 减轻服务器压力
缺点:
- 不利于 SEO,可以使用 SSR 解决
- 首屏加载速度慢
- 不支持浏览器前进后退功能