刷刷前端手写题

闭包用途

闭包

闭包让你可以在一个内层函数中访问到其外层函数的作用域

防抖

描述

        前面所有触发都被取消,最后一次执行,在规定时间之后才会触发,也就是说如果连续快速的触发,用户操作频繁,但只会执行一次

        常用场景:输入框输入

代码实现

1、lodash的debounce函数

2、 当用户点击按钮时,debounce 包装的 getBtnValue 函数会延迟 3000 毫秒执行。如果在这 3000 毫秒内用户再次点击按钮,那么之前的定时器会被清除,重新开始计时。因此,getBtnValue 函数只会在用户停止点击 3000 毫秒后才执行

function debounce(fn,apply){
    let timer;// 初始状态下,timer是undefined
    return function(){
        // 如果timer有值,清除之前的定时器
        if(timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(()=>{
            fn.apply(this,args)
        },delay)
    } 
}
function getValue(e){
    console.log('1111')
}
const btn = document.createElement('button')
btn.innerHTML = 'btn'
document.body.appendChild(btn)
btn.onclick = debounce(getValue, 3000)

         浏览器环境:timer 会被赋值为一个整数,例如 1、2、3 等;Node.js 环境:timer 会被赋值为一个 Timeout 对象。

节流

描述

有规律执行,减少时间执行次数,拖放,滚屏;

只会在第一个点击时执行一次,后续点击将被忽略,直到 delay时间过去后才能再次执行

代码实现

        function throttle(func, delay) {
        let timer; // 用于保存定时器标识符
        return function() {
            if (timer) return; // 如果 timer 已经存在,说明在 delay 时间内已经触发过,直接返回,跳过本次调用
            const args = arguments; // 保存当前的参数
            const context = this; // 保存当前的执行上下文
            
            // 设置一个定时器,在 delay 毫秒后执行 func
            timer = setTimeout(() => {
                func.apply(context, args); // 执行原始函数,传递当前上下文和参数
                timer = null; // 重置 timer,表示可以再次触发 func
            }, delay);
        };
    }
    const btn = document.createElement('button')
    btn.innerHTML = 'btn'
    document.body.appendChild(btn)
    function handleClick() {
        console.log('Button clicked!');
    }

    btn.onclick = throttle(handleClick, 3000);

  • 点击第一次:创建 timer,设置 delay 毫秒后执行 func
  • delay 期间再次点击:由于 timer 存在,函数直接返回,不会再次执行 func
  • delay 时间到达func 被执行,timer 被重置为 null
  • 允许新的点击执行:可以再次创建新的 timer 并触发 func。

因此,尽管多次点击,只有第一次点击时创建的定时器会生效!!

函数柯里化

描述

使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

代码实现

function curry(fn){
    let args = []
    return function res(...rest){
        if(arguments.length==0){
            //当没有参数传递给 res 时于是调用 fn(...args)
            return fn(...args)
        }else{
            args.push(...rest)
            return res
        }
    }
}
var sumFn = function(...arr){
    return arr.reduce((prev,cur)=>{
        return prev+cur
    })
}
let res = curry(sumFn)(1)(2)(3,4)() //15

手写New

描述

  new 操作符的主要作用是生成一个新对象,并将这个对象与构造函数的原型连接起来,同时构造函数中的代码会在新对象的上下文中执行,给新对象赋予属性和方法。

主要流程是:const person1 = new Person('Alice', 25);

  • 新建一个对象const person1 = {};
  • 设置原型person1.__proto__ = Person.prototype;//隐式原型指向构造函数的显示原型
  • 绑定 this:执行 Person 构造函数时,this 被绑定到 person1
  • 执行构造函数:在 Person 函数中,this.name = name;name 赋值给 person1
  • 返回对象:如果没有显式返回对象,new 操作符会返回 person1

代码实现

function Person(a){
     // 检查 this 是否是 Person 的实例
     //if (!(this instanceof Person)) {
        //throw new Error("Person 只能通过 new 关键字调用");
      //}
    this.a=a
}
function myNew(fn,...args){
    const obj={};
    obj._proto_=fn.prototype
    fn.apply(obj,args)
    return obj
}
const obj = myNew(Person,123)

数组去重

描述

顾名思义:console.log(uniqueArray([1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5])); // [1, 2, 3, 4, 5, 6]

代码实现

const uniqueArray = (arr) => {
    return [...new Set(arr)]
}

实现正则切分千分位(js)

描述

顾名思义:console.log(formatThousands(123456789)); // 输出: 123,456,789

代码实现

function format(num){
    //将数字转为字符串并分割整数和小数部分
    let arr = String(num).split(.)
    let char = arr[0].split('').reverse()
    return char.reduce((pre,cur,curIndex)=>{
        if(curIndex+1) %3 ===0 && curIndex !==char.length -1){
            return ','+cur+pre
        }
        return cur+pre
    },"")    
}

手写call/apply

描述:通过使用call/apply方法,可以将一个对象的方法应用到另一个对象上,区别是传入参数一个是参数列表,一个是数组;都是立即执行

代码实现

Function.prototype.myCall = function(context,...args){
    if (typeof this!=="function"){
        throw new TypeError("被调用的必须是函数")
    }
    context||globalThis
    //用Symbol来创建唯一的fn,防止名字冲突
    const fn = Symbol('key')
     // this是调用myCall的函数test,将函数绑定到上下文对象的新属性上
    context[fn] = this
    const res =  context[fn](...args)//hello,world
    delete context[fn]
    return res
}

const test = {
    name:"xxx",
    hello: function(){
        console.log(`hello,${this.name}!`);
    }
}
const obj = {name:"world"};
test.hello.myCall(obj);//hello,world

手写Bind

描述

与call和apply方法不同,bind方法并不会立即执行函数,而是返回一个新函数,可以稍后调用;

且参数可以分多次传入

代码实现

Function.prototype.myBind = function(thisArg,...args1){
    const fn = this;
    return function(...args2){
        if(typeof fn!=="function"){
            throw new TypeError("被调用的是函数")
        }
        thisArg = thisArg||globalThis;
        let uniqueFn = Symbol("fn")
        thisArg[uniqueFn] = fn;
        //合并参数并调用函数
        const res = thisArg[uniqueFn](...args1,...args2)
        delete thisArg[uniqueFn]
        return res
    }
}
function greet(param1,param2) {
    console.log(`${param1},${this.name}${param2}`)   
}
const boundGreet = greet.myBind(obj,"Hey");
boundGreet("!!")// "Hey, Alice!!"

扁平化

描述

将多维转为一维

代码实现--数组

遍历,检查是数组,递归,不是数组扔进去

let arr = [1, [2, 3], [4, [5, 6, [7, 8]]]];
function flatten(arr){
    let res = []
    let len = arr.length
    for (let i=0;i

代码实现--对象

function flattenObj(){
    let res = {}
    for (let key in obj){
        if(typeof obj[key] ==='object'&& obj[key] !==null){
            flatten(res,obj[key],`${key}`)
        }else{
            res[key]=obj[key]
        }
    }
    function flatten(res,obj,keyname){
        for(let key in obj){
            if(typeof obj[key] ==='object'&& obj[key] !==null){
                flatten(res,obj[key],`${keyname}.${key}`)
            }else{
                res[`${keyname}.${key}`]=obj[key]
            }
        }
    }
    return res
}

const obj = {
    a: 1,
    b: [1, 2, { c: true }],
    c: { e: 2, f: 3 },
    g: null,
  };
let res = flattenObj(obj)
结果:{
  a: 1,
  'b.0': 1,
  'b.1': 2,
  'b.2.c': true,
  'c.e': 2,
  'c.f': 3,
  g: null
}

模拟Promise.all()

描述

        入参是个Promise实例组成的数组,返回值是个promise,因为可以使用.then,如果全部成功,状态变为resolved, 并且返回值组成一个数组传给回调,但凡有一个失败,状态变为rejected, 并将error返回给回调

代码实现

// 添加一个自定义的静态方法
Promise.MyAll = function(promises){
    let arr = []
    count = 0
    return new Promise((resolve,reject)=>{
        promises.forEach((item,i)=>{
            //将 item 转换为一个 Promise
            Promise.resolve(item).then(res=>{
                arr[i]=res
                count+=1
                if(count === promises.length) resolve(arr)
            }).catch(reject)
        })
    })
}

 测试

const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2')
  }, 500)
})
Promise.MyAll([p1, p2])
  .then(res => console.log(res))//['p1','p2']
  .catch(err => console.log(err))

模拟Promise.race()

描述

返回状态以最快的那个为准

代码实现

Promise.MyRace(promises=>{
    return new Promise((resolve,reject)=>{
        for(const x of promises){
            Promise.resolve(x).then(resolve,reject)
        }
    })
})

观察者模式

描述

        观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新

例如公司(被观察者)发中秋福利给员工(观察者)

代码实现

//被观察者
class Subject{
    constructor(){
        this.observerList = []
    }
    addObserver(observer){
        this.observerList.push(observer)
    }
    removeObserver(observer){
        const index = this.observerList.findIndex(o => o.name===observer.name)
        this.observerList.splice(index,1)
    }
    notifyObservers(message){
        const observers = this.observerList;
        observers.forEach(observer=>observer.notified(message))
    }
}
//观察者
class Observer{
    constructor(name,subject){
        this.name = name;
        if(subject){
            subject.addObserver(this)
        }
    }
    notified(message){
        console.log(this.name,message)
    }
}

 测试

const subject = new Subject();
const observerA = new Observer('observerA', subject);
subject.notifyObservers('Hello from subject');//observerA Hello from subject

常见使用场景 

        数据绑定机制通常采用观察者模式。当数据模型变化时,所有绑定了该数据的组件(观察者)都会自动更新。例如,一个购物车系统,当用户添加商品到购物车时,购物车总价会自动更新。这个过程可以通过观察者模式来实现

订阅者模式

描述

发布-订阅是一种消息范式,消息的发送者(发布者)不会将消息直接发送给特定的接收者(订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在

例如:公司发快递给消费者

代码实现

class Publisher {
    constructor(name, context) {
        this.name = name; // 设置发布者的名称
        this.context = context; // 设置发布者所属的 PubSub 实例
    }
    publish(type, content) {
        this.context.publish(type, content); // 调用 PubSub 实例的 publish 方法,发布消息
    }
}

class Subscriber {
    constructor(name, context) {
        this.name = name; // 设置订阅者的名称
        this.context = context; // 设置订阅者所属的 PubSub 实例
    }
    subscribe(type, cb) {
        this.context.subscribe(type, cb); // 调用 PubSub 实例的 subscribe 方法,订阅某个消息类型
    }
}

class PubSub {
    constructor() {
        this.messages = {}; // 存储已发布的消息,按消息类型存储
        this.listeners = {}; // 存储订阅者回调函数,按消息类型存储
    }

    publish(type, content) {
        const existContent = this.messages[type]; // 检查是否已有该类型的消息
        if (!existContent) {
            this.messages[type] = []; // 如果没有,初始化一个数组来存储该类型的消息
        }
        this.messages[type].push(content); // 将消息内容添加到对应类型的消息数组中
    }

    subscribe(type, cb) {
        const existListener = this.listeners[type]; // 检查是否已有订阅者监听该类型的消息
        if (!existListener) {
            this.listeners[type] = []; // 如果没有,初始化一个数组来存储该类型的订阅者回调函数
        }
        this.listeners[type].push(cb); // 将订阅者的回调函数添加到对应类型的监听数组中
    }

    notify(type) {
        const messages = this.messages[type]; // 获取该类型的所有已发布的消息
        const subscribers = this.listeners[type] || []; // 获取该类型的所有订阅者回调函数
        subscribers.forEach((cb, index) => cb(messages[index])); // 将对应的消息传递给订阅者的回调函数
    }
}

 测试

const TYPE_A = 'music'; // 定义一个消息类型
const pubsub = new PubSub(); // 创建一个 PubSub 实例

const publisherA = new Publisher('publisherA', pubsub); // 创建一个发布者,并与 PubSub 实例关联
publisherA.publish(TYPE_A, 'we are young'); // 发布者发布一条类型为 'music' 的消息,内容是 'we are young'

const subscriberA = new Subscriber('subscriberA', pubsub); // 创建一个订阅者,并与 PubSub 实例关联
subscriberA.subscribe(TYPE_A, res => {
    console.log('subscriberA received', res); // 订阅者订阅 'music' 类型的消息,并定义回调函数处理接收到的消息
});

pubsub.notify(TYPE_A); // 通知所有订阅了 'music' 类型的订阅者,调用他们的回调函数并传递消息

常见使用场景 

事件总线:不同组件之间不直接通信,而是通过事件总线来发布和订阅事件。这使得组件之间高度解耦,组件可以独立发展

手写vuex

描述

Vuex 是 Vue.js 的状态管理模式,主要解决组件之间共享状态时的问题

代码实现

你可能感兴趣的:(面试,前端,javascript,面试)