2022必会的前端手写面试题
作者:互联网
面试题视频讲解(高效学习):进入学习
二、题目
1. 防抖节流
这也是一个经典题目了,首先要知道什么是防抖,什么是节流。
-
防抖: 在一段时间内,事件只会最后触发一次。
-
节流: 事件,按照一段时间的间隔来进行触发。
实在不懂的话,可以去这个大佬的Demo地址玩玩防抖节流DEMO
// 防抖
function debounce(fn) {
let timeout = null;
return function () {
// 如果事件再次触发就清除定时器,重新计时
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, 500);
};
}
// 节流
function throttle(fn) {
let flag = null; // 通过闭包保存一个标记
return function () {
if (flag) return; // 当定时器没有执行的时候标记永远是null
flag = setTimeout(() => {
fn.apply(this, arguments);
// 最后在setTimeout执行完毕后再把标记设置为null(关键)
// 表示可以执行下一次循环了。
flag = null;
}, 500);
};
}
复制代码
这道题主要还是考查对 防抖 节流 的理解吧,千万别记反了!
2.一个正则题
要求写出 区号+8位数字,或者区号+特殊号码: 10010/110,中间用短横线隔开的正则验证。 区号就是三位数字开头。
例如 010-12345678
let reg = /^\d{3}-(\d{8}|10010|110)/g
复制代码
这个比较简单,熟悉正则的基本用法就可以做出来了。
3. 不使用a标签,如何实现a标签的功能
// 通过 window.open 和 location.href 方法其实就可以实现。
// 分别对应了a标签的 blank 和 self 属性
复制代码
4. 不使用循环API 来删除数组中指定位置的元素(如:删除第三位) 写越多越好
这个题的意思就是,不能循环的API(如 for filter之类的)。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 方法一 : splice 操作数组 会改变原数组
arr.splice(2, 1)
// 方法二 : slice 截取选中元素 返回新数组 不改变原数组
arr.slice(0, 2).concat(arr.slice(3,))
// 方法三 delete数组中的元素 再把这个元素给剔除掉
delete arr[2]
arr.join("").replace("empty", "").split("")
复制代码
5. 深拷贝
深拷贝和浅拷贝的区别就在于
- 浅拷贝: 对于复杂数据类型,浅拷贝只是把引用地址赋值给了新的对象,改变这个新对象的值,原对象的值也会一起改变。
- 深拷贝: 对于复杂数据类型,拷贝后地址引用都是新的,改变拷贝后新对象的值,不会影响原对象的值。
所以关键点就在于对复杂数据类型的处理,这里我写了两种写法,第二中比第一种有部分性能提升
const isObj = (val) => typeof val === "object" && val !== null;
// 写法1
function deepClone(obj) {
// 通过 instanceof 去判断你要拷贝的变量它是否是数组(如果不是数组则对象)。
// 1. 准备你想返回的变量(新地址)。
const newObj = obj instanceof Array ? [] : {}; // 核心代码。
// 2. 做拷贝;简单数据类型只需要赋值,如果遇到复杂数据类型就再次进入进行深拷贝,直到所找到的数据为简单数据类型为止。
for (const key in obj) {
const item = obj[key];
newObj[key] = isObj(item) ? deepClone(item) : item;
}
// 3. 返回拷贝的变量。
return newObj;
}
// 写法2 利用es6新特性 WeakMap弱引用 性能更好 并且支持 Symbol
function deepClone2(obj, wMap = new WeakMap()) {
if (isObj(obj)) {
// 判断是对象还是数组
let target = Array.isArray(obj) ? [] : {};
// 如果存在这个就直接返回
if (wMap.has(obj)) {
return wMap.get(obj);
}
wMap.set(obj, target);
// 遍历对象
Reflect.ownKeys(obj).forEach((item) => {
// 拿到数据后判断是复杂数据还是简单数据 如果是复杂数据类型就继续递归调用
target[item] = isObj(obj[item]) ? deepClone2(obj[item], wMap) : obj[item];
});
return target;
} else {
return obj;
}
}
复制代码
这道题主要是的方案就是,递归加数据类型的判断。
如是复杂数据类型,就递归的再次调用你这个拷贝方法 直到是简单数据类型后可以进行直接赋值
6. 手写call bind apply
call bind apply的作用都是可以进行修改this指向
- call 和 apply的区别在于参数传递的不同
- bind 区别在于最后会返回一个函数。
// call
Function.prototype.MyCall = function (context) {
if (typeof this !== "function") {
throw new Error('type error')
}
if (context === null || context === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window
} else {
// 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
context = Object(context)
}
// 使用Symbol 来确定唯一
const fnSym = Symbol()
//模拟对象的this指向
context[fnSym] = this
// 获取参数
const args = [...arguments].slice(1)
//绑定参数 并执行函数
const result = context[fnSym](...args)
//清除定义的this
delete context[fnSym]
// 返回结果
return result
}
// call 如果能明白的话 apply其实就是改一下参数的问题
// apply
Function.prototype.MyApply = function (context) {
if (typeof this !== "function") {
throw new Error('type error')
}
if (context === null || context === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window
} else {
// 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
context = Object(context)
}
// 使用Symbol 来确定唯一
const fnSym = Symbol()
//模拟对象的this指向
context[fnSym] = this
// 获取参数
const args = [...arguments][1]
//绑定参数 并执行函数 由于apply 传入的是一个数组 所以需要解构
const result = arguments.length > 1 ? context[fnSym](...args) : context[fnSym]()
//清除定义的this
delete context[fnSym]
// 返回结果 //清除定义的this
return result
}
// bind
Function.prototype.MyBind = function (context) {
if (typeof this !== "function") {
throw new Error('type error')
}
if (context === null || context === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window
} else {
// 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
context = Object(context)
}
//模拟对象的this指向
const self = this
// 获取参数
const args = [...arguments].slice(1)
// 最后返回一个函数 并绑定 this 要考虑到使用new去调用,并且bind是可以传参的
return function Fn(...newFnArgs) {
if (this instanceof Fn) {
return new self(...args, ...newFnArgs)
}
return self.apply(context, [...args, ...newFnArgs])
}
}
复制代码
7. 手写实现继承
这里我就只实现两种方法了,ES6之前的寄生组合式继承 和 ES6之后的class继承方式。
/**
* es6之前 寄生组合继承
*/
{
function Parent(name) {
this.name = name
this.arr = [1, 2, 3]
}
Parent.prototype.say = () => {
console.log('Hi');
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
// 核心代码 通过Object.create创建新对象 子类 和 父类就会隔离
// Object.create:创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
}
/**
* es6继承 使用关键字class
*/
{
class Parent {
constructor(name) {
this.name = name
this.arr = [1, 2, 3]
}
}
class Child extends Parent {
constructor(name, age) {
super(name)
this.age = age
}
}
}
复制代码
补充一个小知识, ES6的Class继承在通过 Babel 进行转换成ES5代码的时候 使用的就是 寄生组合式继承。
继承的方法有很多,记住上面这两种基本就可以了!
8. 手写 new 操作符
首先我们要知道 new一个对象的时候他发生了什么。
其实就是在内部生成了一个对象,然后把你的属性这些附加到这个对象上,最后再返回这个对象。
function myNew(fn, ...args) {
// 基于原型链 创建一个新对象
let newObj = Object.create(fn.prototype)
// 添加属性到新对象上 并获取obj函数的结果
let res = fn.call(newObj, ...args)
// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
return res && typeof res === 'object' ? res : newObj;
}
复制代码
9. js执行机制 说出结果并说出why
这道题考察的是,js的任务执行流程,对宏任务和微任务的理解
console.log("start");
setTimeout(() => {
console.log("setTimeout1");
}, 0);
(async function foo() {
console.log("async 1");
await asyncFunction();
console.log("async2");
})().then(console.log("foo.then"));
async function asyncFunction() {
console.log("asyncFunction");
setTimeout(() => {
console.log("setTimeout2");
}, 0);
new Promise((res) => {
console.log("promise1");
res("promise2");
}).then(console.log);
}
console.log("end");
复制代码
提示:
- script标签算一个宏任务所以最开始就执行了
- async await 在await之后的代码都会被放到微任务队列中去
开始执行:
- 最开始碰到 console.log("start"); 直接执行并打印出
start
- 往下走,遇到一个 setTimeout1 就放到
宏任务队列
- 碰到立即执行函数 foo, 打印出
async 1
- 遇到 await 堵塞队列,先
执行await的函数
- 执行 asyncFunction 函数, 打印出
asyncFunction
- 遇到第二个 setTimeout2,
放到宏任务队列
- new Promise 立即执行,打印出
promise1
- 执行到 res("promise2") 函数调用,就是Promise.then。
放到微任务队列
- asyncFunction函数就执行完毕, 把后面的打印 async2 会放到
微任务队列
- 然后打印出立即执行函数的then方法
foo.then
- 最后执行打印
end
- 开始执行微任务的队列 打印出第一个
promise2
- 然后打印第二个
async2
- 微任务执行完毕,执行宏任务 打印第一个
setTimeout1
- 执行第二个宏任务 打印
setTimeout2
、 - 就此,函数执行完毕
画工不好,能理解到意思就行
标签:function,面试题,return,数组,getName,2022,new,手写,Foo 来源: https://www.cnblogs.com/zhang-a222/p/16212704.html