持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情
前言
整理了26道2022年面试常遇到的,面试官要求手写的js基本题,如一些方法的实现等
手写深拷贝
const deepCopy = (obj) => {
if (typeof obj !== 'object' && obj !== null) {
return obj
}
// 创建新对象
const cloneObj = Array.isArray(obj) ? [] : {};
// symbol拷贝
const symArr = Object.getOwnPropertySymbols(obj);
symArr.forEach(key => {
if (obj[key] && typeof obj[key] === 'object') {
cloneObj[key] = deepCopy(obj[key]);
} else cloneObj[key] = obj[key];
})
// 对象的key拷贝
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === 'object') {
cloneObj[key] = deepCopy(obj[key])
} else cloneObj[key] = obj[key]
}
}
return cloneObj
}
手写防抖函数
// 一定时间内,重复触发同一个事件,会先清除掉上一次的执行,然后开启一个新的执行事件,并等待一定时间执行 多次重复触发同一个事件,只执最后一次
function debounce (fn, delay) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(fn, delay)
}
}
手写节流函数
// 持续触发事件时,一定时间内只能触发一次。而不是清除掉
// 定时器方式
function throttle (fn, delay) {
let timer = null
return function() {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this,arguments)
timer = null
}, delay)
}
}
手写多维数组扁平化
function arrFlat (arr) {
if (!arr.some(a => Array.isArray(a))) {
return arr
}
return arrFlat(Array.prototype.concat.apply([], arr))
}
手写翻转二叉树
function invertTree (root) {
if(root != null){
let temp = root.left
root.left = root.right
root.right = temp
invertTree(root.left)
invertTree(root.right)
}
return root
}
归并排序
// 思路:
// 分:把数组劈成两半,再递归地对子数组进行“分”操作,直到分成一个个单独的数组
// 合:把两个数组合并为有序数组,再对有序数组进行合并,直到全部子数组合并为一个完整数组。
function mergeSort(arr){
if (arr.length === 1) {
return arr
}
const mid = Math.floor(arr.length / 2)
const left = arr.slice(0, mid)
const right = arr.slice(mid, arr.length)
const orderLeft = mergeSort(left)
const orderRight = mergeSort(right)
const res = []
while (orderLeft.length || orderRight.length) {
if(orderLeft.length && orderRight.length){
res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift())
} else if (orderLeft.length) {
res.push(orderLeft.shift())
} else if (orderRight.length) {
res.push(orderRight.shift())
}
}
return res
}
闭包实现加法器
function add(){
let count = 0;
return function closure(){
count++;
console.log(count);
}
}
var counter = add();
counter() // 1
counter() // 2
counter() // 3
数组去重
// 1. 双循环
function unique(arr){
let res = [arr[0]]
for (let i = 1; i < arr.length; i++) {
let flag = true
for (let j = 0; j < res.length; j++) {
if (arr[i] === res[j]) {
flag = false
break
}
}
if (flag) {
res.push(arr[i])
}
}
return res
}
// 2. indexOf
function unique(arr){
let res = []
for (let i = 0; i < arr.length; i++) {
if (res.indexOf(arr[i]) === -1) {
res.push(arr[i])
}
}
return res
}
// 3. indexOf
function unique(arr){
return arr.filter(function(item, index){
return arr.indexOf(item) === index;
})
}
// 4. 相邻元素去重
function unique(arr){
arr = arr.sort()
let res = []
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
res.push(arr[i])
}
}
return res
}
// 5. 利用对象属性去重
function unique(arr){
let res = [], obj = {}
for (let i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
res.push(arr[i])
obj[arr[i]] = 1
} else {
obj[arr[i]]++
}
}
return res
}
// 6. Set与解构赋值去重
function unique(arr){
return [...new Set(arr)]
}
// 7. Array.from 与 Set去重
function unique(arr){
return Array.from(new Set(arr))
}
手写 apply
Function.prototype.myApply = function(context, args){
context = context || window
args = args || []
const symbol = Symbol()
context[symbol] = this
const result = context[symbol](...args)
delete context[symbol]
return result
}
手写 call
Function.prototype.myCall = function(context, ...args){
context = context || window
args = args || []
const symbol = Symbol()
context[symbol] = this
const result = context[symbol](...args)
delete context[symbol]
return result
}
手写 bind
Function.prototype.myBind = function(context, ...args){
context = context || window
args = args || []
const fn = this
return function newFn(...newArgs){
// 考虑new的情况
if (this instanceof newFn) {
return new fn(...args, ...newArgs)
}
return fn.apply(context, [...args, ...newArgs])
}
}
实现 add(1)(2)(3) 累加
function add(...args) {
if (args.length < 7) { // 7为参数个数
return function (...newArgs) {
return add(...args, ...newArgs)
}
}
return args.reduce((sum, cur) => sum + cur, 0)
}
// 如:
console.log(add(1)(2)(3)(4,5)(6)(7));
实现 new 方法
function _new(constructor, ...args){
let obj = {}
obj.__proto__ = constructor.prototype
constructor.apply(obj, args)
return obj
}
// 示例:
function A(name) {
this.name = name
}
A.prototype.say = () => {
console.log('say')
}
let a1 = new A('123')
let a2 = _new(A, '456')
console.log(a1.name); // 123
console.log(a2.name); // 456
a1.say() // say
a2.say() // say
js 实现函数重载
function addMethod(object, name, fn) {
var old = object[name]; // 把前一次添加的方法存在一个临时变量old里面
object[name] = function () { // 重写了object[name]的方法
if (fn.length === arguments.length) { // 调用object[name]方法时,如过传入的参数个数跟预期的一致,则直接调用
return fn.apply(this, arguments);
} else if (typeof old === "function") { // 否则,判断old是否是函数,如果是,就调用old
return old.apply(this, arguments);
}
}
}
// 示例:
let obj = {}
addMethod(obj, 'fn', (name) => console.log(`我是${name}`))
addMethod(obj, 'fn', (name, age) => console.log(`我是${name}, 今年${age}岁`))
addMethod(obj, 'fn', (name, age, sport) => console.log(`我是${name}, 今年${age}岁, 喜欢${sport}`))
obj.fn(1)
obj.fn(1,2)
obj.fn(1,2,3)
Promise 方法实现6个
只执行一次函数实现
// once
function once(fn){
let done = true
return function(){
if (done) {
done = false
fn.apply(this, arguments)
}
}
}
// 示例
function pay (money) {
console.log(`支付${money}元,调起api`)
}
let oncePay = once(pay)
oncePay(12) // 支付12元,调起api
oncePay(12)
oncePay(12)
模拟数组方法
// 1. 实现 forEach
function forEach(arr, fn){
for(let i = 0; i < arr.length; i++){
fn(arr[i])
}
}
// 2. 实现 filter
function filter(arr, fn){
let results = []
for (let i = 0; i < arr.length; i++) {
if (fn(arr[i])) {
results.push(arr[i])
}
}
return results
}
// 3. 实现 map
function map(arr, fn){
let results = []
for (let value of arr) {
results.push(fn(value))
}
return results
}
// 4. 实现 every
function every(arr, fn){
for (let value of arr) {
if(!fn(value)){
return false
}
}
return true
}
// 5. 实现 some
function some(arr, fn){
for (let value of arr) {
if(fn(value)){
return true
}
}
return false
}
发布订阅模式实现
class EventEmitter{
// 构造函数创建信息中心
constructor(){
this.subs = Object.create(null)
}
// 注册事件 判断如果有该信息事件,就继续追加,反之加入信息事件
$on(eventType, handler){
this.subs[eventType] = this.subs[eventType] || []
this.subs[eventType].push(handler)
}
// 触发事件 触发时执行每一个信息事件里存储的函数
$emit(eventType){
if(this.subs[eventType]){
this.subs[eventType].forEach(f => f())
}
}
}
// 示例:
var em = new EventEmitter()
// 订阅者注册信息事件
em.$on('click',() => console.log('click1'))
em.$on('click',() => console.log('click2'))
// 发布者执行信息事件
em.$emit('click')
观察者模式实现
// 发布器
class Dep{
constructor(){
this.subs = [] // 记录所有的观察者
}
// 添加观察者
addSub(sub){
// 判断存在并且必须有update方法
if(sub && sub.update){
this.subs.push(sub)
}
}
// 发布通知 挨个执行每个观察者的update方法
notify(){
this.subs.forEach(sub => sub.update())
}
}
// 观察者
class Watcher{
update(){
console.log('update')
}
}
// 示例:
var dep = new Dep()
var watcher = new Watcher()
dep.addSub(watcher)
dep.notify()
js实现 trim 函数
funciton trim(str){
return str.replace(/^\s*|\s*$/g,"");
}
reducer实现map
Array.prototype._map = function(fn, callbackThis) {
let res = [] // 最终返回的新数组
// 定义回调函数的执行环境
// call第一个参数传入null,则this指向全局对象,同map的规则
let CBThis = callbackThis || null
this.reduce((brfore, after, idx, arr) => {
// 传入map回调函数拥有的参数
// 把每一项的执行结果push进res中
res.push(fn.call(CBThis, after, idx, arr))
}, null)
return res
}
总结
大家如果有面试到新的手写方法之类的,欢迎补充,我更新文章
2022年就业行情萎靡的一年,祝大家都能找到自己满意的工作
整理不易,如果有帮到你,给个赞吧!👍
往期精彩文章
🌟 hash路由与history路由的区别
🌟 轻松理解前端模块化
🌟 ECMAScript新特性汇总,面试别在答错
🌟 Promise 面试常考手写方法汇总
🌟 Promise 常见误区