在当今技术日新月异的时代,前端开发作为互联网产品的核心组成部分,其重要性不言而喻。随着大厂们对技术要求的不断提升,程序员们不仅需要掌握扎实的基础知识,更需要在实践中不断磨砺自己的技能。为了更好地应对笔面试的挑战,通过一系列基础知识问题自检自己的前端开发能力,无疑是一个高效且实用的方法。本文将围绕前端开发的基础知识,精选几个常见问题,旨在帮助大家巩固基础,提升竞争力。
一、前端开发考前有哪些必备基础问答?
1. 在页面上隐藏元素的方法有哪些?
1、display: none; 2、visibility: hidden; 3、 opacity: 0; 4、transform: scale(0);
2. 数组的 slice 和 splice 有哪些区别?
slice: 截取数组, 不会改变原来的数组, 返回一个新的数组, 语法:
slice(start, end) // 不包含 end 索引
splice:可以对数组进行增删改操作, 会修改原数组,第一个是数组的起始位置, 第二个代表要删除多少个元素,传 0 代表不删除,后面的参数代表要添加的元素, 返回值包含了删除的元素的数组 , 语法:
splice(start, deleteCount, item1, item2, ...)
3. Map, Set 和 weakMap 的区别
Map: 对像保存键值对, 可以接收任何类型的数据作为键和值, 键是唯一的,且键必须是 Object 或 继承至 Object, 如果添加相同的键会覆盖原来键所对应的值,内部通过 SameValueZero 判断, js 无法访问该方法,类似于 ===。
Set:对象允许你存储任何数据类型的唯一值(是 Map 的一种增强 ),内部通过 SameValueZero 判断,类似于 ===。
weakMap: 与 Map 相似, 其中的键必须是对象或非全局注册的符号, 他是不可迭代的,不具备遍历等方法, 因为他的键是弱引用, 随时可能被回收,weak 的弱是指他不阻止 javascript 的垃圾回收。
4. 什么是深拷贝和浅拷贝? 有哪些手段实现了深拷贝?
深浅拷贝一般用于对象
浅拷贝: 创建一个新的引用,指向原引用指向的地址, 只拷贝对象的顶层属性
深拷贝: 创建一个新的引用指向一块新的地址, 将原引用地址的数据拷贝,存储在这个新的地址中
实现深拷贝:
1、JSON.parse(JSON.stringfy(obj)) — 原理: JSON.stringify()
方法将一个 JavaScript 对象或值转换为 JSON 字符串, 字符串是基本数据类型,创建他时会创建一个新的引用指向存放字符串的地址(字符串的值存放在堆区)。JSON.parse()
方法用来解析 JSON 字符串,构造由字符串描述的 JavaScript 值或对象。 最终新的值指向这个新的地址, 使用这种方法会丢失 undefined 和 函数值。
2、lodash 库中的 _.cloneDeep(obj)
3、递归函数
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
if (oldObj[k] instanceof Array) { // typeof [] ---> Object
newObj[k] = [];
deepCopy(newObj[k], oldObj[k]);
} else if (oldObj[k] instanceof Object) {
newObj[k] = {};
deepCopy(newObj[k], oldObj[k]);
} else {
newObj[k] = oldObj[k];
}
}
}
5. Git 回退代码的指令是什么?
git revert commitId — 会用一个新的 commit 来记录这次的撤回
git reset –hard commitId — 回退所有, commit , index, workspacetree
git reset –mixed commitId — 回退 commit, index
git reset –soft commitId — 回退 commit
6. 箭头函数和普通函数的区别?
箭头函数比普通函数简洁
箭头函数没有自己的 this
箭头函数没有自己的参数 arguments
箭头函数没有 prototype
箭头函数继承的 this 方向永远不会改变
apply、call、bind不能改变箭头函数的this指向
箭头函数不能作为构造函数使用
7. Promise 有几种状态, Promise.all 和 Promise.reace 有什么区别
3 种:pendding、reject 、 fullfill
Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个 promise。当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),并返回一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。
Promise.reace() 返回最快响应的, 不管成功还是失败
8. 请给出以下代码输出结果, 如果有问题, 可以如何改写?
for (var i= 0; i< 5; i++) {
setTimeOut(() => {
console.log(i)
}, 0)
}
// 输出 5 个 5
// 将 var ----> let
for (let i= 0; i< 5; i++) {
setTimeOut(() => {
console.log(i)
}, 0)
}
执行原理:
二、 前端开发常见代码如何实现?
1. typeOf 和 instanceOf 有什么区别? 实现一个 instanceOf
1、typeOf 返回一个字符串, 表示操作数的类型。检测类型一般有七种: number、 string、 boolean、 function、 Object、 undefined、 symbol), 新加了一种 bigint。 但是这种方式对 null 的类型也识别为 object ,因为在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针,因此,null 的类型标签是 0,typeof null 也因此返回 “Object”
2、instanceOf : 沿着作用域链查找,判断值的__proto__ 属性是否出现在某个实例对象的原型链上。但是有缺陷,如果是多全局对象,那他不一定准确, 因为多窗口意味着他们内置的构造函数并不相同
3、实现一个 instanceOf
function instanceOf (obj, Constructor) {
let temp = obj.__proto__
while(temp) {
if (temp === Constuctor.prototype) {
return true
}
temp = temp.__proto__
}
return false
}
2. bind、 call、 apply 有什么区别? 如何实现一个 bind?
都是 Function 函数原型上的方法, 用来修改函数的 this 指向。
1、bind: 创建一个新的函数返回, 不会立即执行,第一参数是函数 this 的指向, 后面的参数以, 分割
2、call : 会以给定的 this 值和逐个提供的参数调用函数
3、apply: 会以给定的 this 值和给定的数组类型的参数调用函数
// 实现一个 bind
Function.prototype.myBind (context, ...bindData) {
if (typeof this !== 'function') {
throw new Error('type Error')
}
const self = this // 这个 this 指向的实例对象, 是一个函数
return function (...args) { // 返回的函数也可以接受参数
const paramsArr = bindData.concat(args)
return self.apply(context, paramsArr)
}
}
// 实现一个 call
Function.prototype.myCall (context, ...bindData) {
if (typeof this !== 'function') {
throw new Error('type Error')
}
const self = this // 指向掉用的函数实例, 函数本质也是一个对象, Function.__proto__ === Object.prototype
context = context === null || context === undefined ? globalThis : Object(context)
// 最终的目的是要将函数的 this 指向传入的对象
const key = Symbol()
context[key] = self // 将函数作为对象中的成员
const res = context[key](...bindData) // 需要执行这个函数,并将数据传入函数
delete context[key]
return res // 返回执行结果
}
// 实现一个 apply
Functiion.prototype.myApply(context, paramsArr) {
if (typeof this !== 'function') {
throw new Error('type Error')
}
const self = this
context = context = null || undefined ? globalThis : Object(context)
const key = Symbol()
context[key] = self
const res = context[key](...paramsArr)
delete context[key]
return res
}
// call 和 apply 实现基本相同
3. 写一个防抖或节流函数
// 防抖函数: 一般用于点击事件等,在规定时间内, 多次点击,只执行最后一次
function antiShake(fn, delay) {
let timer = null
return function (...args) {
if (timer) {
clearTimeout(timer) // 这个时候的定时器还没有执行
}
timer = setTimeOut(() => {
fn.apply(this, args)
}, delay)
}
}
// 节流函数, 一般用在滚动事件等, 在规定时间内, 只触发一次
// 1. 使用 setTimeout 实现
function throttle (fn, delay) {
let timer = null
return function (...args) {
if (timer) return
timer = setTimeout(() => {
fn.apply(this, args)
timer = null // 不要使用 clearTimeout 清除, 因为正在执行中
}, delay)
}
}
// 2. 使用时间差实现
function throttle1 (fn, delay) {
let prev = 0
return function (...args) {
let now = new Date()
if (now - prev >= delay) {
prev = now
fn.apply(this, args)
}
}
}
4. 使用 reduce 将二维数组扁平化
// reduce() 方法对数组中的每个元素按序执行一个提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。
function flatArr(arr) {
if (Array.isArray(arr)) {
throw new Error('type Error')
}
return arr.reduce((pre, cur) => {
if (Array.isArray(cur)) {
pre = pre.concat(cur)
} else {
pre.push(cur)
}
return pre
}, [])
}
5. 手写一个快速排序
// 快速排序
const arr = [5, 2, 7, 8, 34, 7, 39, 12, 56, 9, 1]
function quickSort(arr) {
// 4.结束递归(当ary小于等于一项,则不用处理)
if (arr.length <= 1) {
return arr
}
// 1. 找到数组的中间项,在原有的数组中把它移除
const middleIndex = Math.floor(arr.length / 2)
const middle = arr.splice(middleIndex, 1)[0]
// 2. 准备左右两个数组,循环剩下数组中的每一项,比当前项小的放到左边数组中,反之放到右边数组中
const leftArr = [], rightArr = []
for (let i = 0; i < arr.length; i++) {
const current = arr[i]
current < middle ? leftArr.push(current) : rightArr.push(current)
}
// 3. 递归方式让左右两边的数组持续这样处理,一直到左右两边都排好序为止。
//(最后让左边+中间+右边拼接成最后的结果)
return quickSort(leftArr).concat(middle, quickSort(rightArr))
}
console.log(quickSort(arr)) // [1, 2, 5, 7, 7, 8, 9, 12, 34, 39, 56]
6. 请使用 ES6 的 proxy 实现一个数据绑定给实例
const obj = {}
const instance = new Proxy(obj, {
set: function (target, prop, value) { // target 原对象
target[prop] = value
return true
}
})
7. 实现一个三列布局, 左右宽度固定, 中间自适应(两种方式以上)
// 1. 圣杯布局
<div class="container">
<div class="content">中间</div>
<div class="left">左边</div>
<div class="right">右边</div>
</div>
<style>
.container {
height: 200px;
padding: 0 200px;
}
.left, .right {
width: 200px;
height: 200px;
background-color: #6d9b2f;
}
.content {
width: 100%;
height: 200px;
background-color: #d8a7a7;
}
.container div {
float: left;
}
.left {
margin-left: -200px;
position: relative;
left: -100%;
}
.right {
margin-left: -200px;
position: relative;
right: -200px;
}
</style>
// 2. 双飞翼布局
<div class="page">
<div class="content">
<div class="inner">主体内容</div>
</div>
<div class="left">左侧广告位</div>
<div class="right">右侧广告位</div>
</div>
<style>
*{
margin: 0;
padding: 0;
}
.page{
height: 200px;
}
.left,.right{
height: 200px;
width: 200px;
background-color: #7aa74d;
}
.content{
width: 100%;
height: 200px;
background-color: #d5adad;
}
.page > div{
float: left;
}
.inner{
margin: 0 200px; /*上下0 左右两百 */
height: 100%;
background-color: #e21616;
}
.left{
margin-left: -100%;
}
.right{
margin-left: -200px;
}
</style>
// 3. flex 布局
<div class="container">
<div class="left">左侧内容</div>
<div class="center">中间内容</div>
<div class="right">右侧内容</div>
</div>
<style>
.container {
display: flex;
}
.left {
width: 200px; /* 左侧宽度固定 */background-color: lightblue;
}
.right {
width: 200px; /* 右侧宽度固定 */background-color: lightgreen;
}
.center {
flex-grow: 1; /* 中间自适应 */background-color: lightcoral;
}
</style>
三、如何结合代码写结果
async function async1() {
console.log('async1 start') // 2
await async2()
console.log('async1 end') // 6
}
async function async2() {
console.log('async2') // 3
}
console.log('script start') // 1
setTimeOut(function() {
console.log('setTimeout') // 8
})
async1()
new Promise(function (resolve) {
console.log('promise1') // 4
resolve()
}).then(function () {
console.log('promise2') // 7
})
console.log('script end') // 5
执行原理:
根据事件循环机制来执行, 最后顺序为:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
结语
通过以上问题的梳理与解答,我们不仅回顾了前端开发中的一些基础知识,还深入探讨了如数组操作、数据结构选择、深拷贝与浅拷贝、Git操作、函数差异、Promise状态与用法等关键知识点。这些知识点不仅是笔面试中的高频考点,也是日常开发中不可或缺的技能。
延展阅读:
数据标注师如何提升自己的能力?有哪些优秀的数据标注工具使用案例?
咨询方案 获取更多方案详情