2020 front-end high-frequency interview questions

 

One: Implement a Vue custom instruction lazy loading:

The Intersection Observer API provides a way to asynchronously observe the changes in the intersection of the target element and the ancestor element or the viewport of the top-level document.

Create an IntersectionObserver object, and pass in the corresponding parameters and call back the function. The callback function will be executed when the size of the intersection of the target element and the root element exceeds the threshold.

var observer = new IntersectionObserver(callback, options);

IntersectionObserver is a constructor provided by the browser. It accepts two parameters: callback is the callback function when the visibility changes (that is, the callback function will be executed when the target element appears in the element specified by the root option), and option is the configuration Object (this parameter is optional).

What is returned  observeris an observer instance. The observe method of the instance can specify which DOM node to observe.

Vue custom instructions

The following api comes from the official website custom instructions:

Hook function

  • bind: Called only once, when the instruction is bound to the element for the first time. Here you can perform a one-time initialization setting.

  • inserted: Called when the bound element is inserted into the parent node (only the parent node is guaranteed to exist, but it may not have been inserted into the document).

  • update: Called when the VNode of the component is updated, but it may happen before its child VNode is updated. The value of the instruction may or may not have changed. But you can ignore unnecessary template updates by comparing the values ​​before and after the update

  • componentUpdated: Called after the VNode of the component where the instruction is located and its sub-VNodes are all updated.

  • unbind: Called only once, when the instruction is unbound from the element.

Hook function parameters

The instruction hook function will be passed the following parameters:

  • el: The element bound by the instruction can be used to directly manipulate the DOM.

  • binding: an object containing the following properties:

    • name: The name of the command, excluding the v- prefix.

    • value: the binding value of the instruction, for example: v-my-directive="1 + 1", the binding value is 2.

    • oldValue: The previous value bound by the instruction, only available in the update and componentUpdated hooks. It is available regardless of whether the value has changed.

    • expression: The command expression in the form of a string. For example, in v-my-directive="1 + 1", the expression is "1 + 1".

    • arg: The parameter passed to the instruction, optional. For example, in v-my-directive:foo, the parameter is "foo".

    • modifiers: An object containing modifiers. For example: in v-my-directive.foo.bar, the modifier object is {foo: true, bar: true }.

  • vnode: The virtual node generated by Vue compilation.

  • oldVnode: The last virtual node, only available in the update and componentUpdated hooks.

Implement the v-lazyload instruction

<!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>
            img {
                width: 100%;
                height: 300px;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <p v-for="item in imgs" :key="item">
                <img v-lazyload="item">
            </p>
        </div>
    </body>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
        Vue.directive("lazyload", {
            // 指令的定义
            bind: function(el, binding) {
                let lazyImageObserver = new IntersectionObserver((entries, observer) => {
                    entries.forEach((entry, index) => {
                        let lazyImage = entry.target;
                        // 相交率,默认是相对于浏览器视窗
                        if(entry.intersectionRatio > 0) {
                            lazyImage.src = binding.value;
                            // 当前图片加载完之后需要去掉监听
                            lazyImageObserver.unobserve(lazyImage);
                        }

                    })
                })
                lazyImageObserver.observe(el);
            },
        });
        var app = new Vue({
            el: "#app",
            data: {
                imgs: [
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657907683.jpeg',
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657913523.jpeg',
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657925550.jpeg',
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657930289.jpeg',
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657934750.jpeg',
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657918315.jpeg',
                ]
            },
        });
    </script>
</html>

Two: Count the most frequently occurring tags in HTML tags

const tags = document.getElementsByTagName('*');
let map = new Map();
let maxStr = '';
let max = 0;
// 只是使用下标来获取,没有使用数组的方法,所以不需要将类数组转为数组
for(let i = 0; i < tags.length; i++) {
    let value = map.get(tags[i].tagName)
    if(value) {
        map.set(tags[i].tagName, ++value)
    } else {
        map.set(tags[i].tagName, 1);
    }
    if(value > max) {
        maxStr = tags[i].tagName;
        max = value;
    }
}
console.log(`当前最多的标签为 ${maxStr},个数为 ${max}` );

Three: simply implement a Promise

class Promise {
    constructor(fn) {
        /**
         *  三种状态 
         *  pending:进行中
         *  fulfilled:已成功
         *  rejected: 已失败
         */
        this.status = 'pending';
        this.resoveList = []; // 成功后回调函数
        this.rejectList = []; // 失败后的回调函数

        fn(this.resolve.bind(this), this.reject.bind(this));
    }
    then(scb, fcb) {
        if (scb) {
            this.resoveList.push(scb);
        }
        if(fcb) {
            this.rejectList.push(fcb);
        }
        return this;
    }
    catch(cb) {
        if (cb) {
            this.rejectList.push(cb);
        }
        return this;
    }
    resolve(data) {
        if (this.status !== 'pending') return;
        this.status = 'fulfilled';
        setTimeout(() => {
            this.resoveList.forEach(s => {
                data = s(data);
            })
        })
    }
    reject(err) {
        if (this.status !== 'pending') return;
        this.status = 'rejected';
        setTimeout(() => {
            this.rejectList.forEach(f => {
                err = f(err);
            })
        })
    }
    /**
     * 实现Promise.resolve
     * 1.参数是一个 Promise 实例, 那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
     * 2.如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
    */ 
    static resolve(data) {
        if (data instanceof Promise) {
            return data;
        } else {
            return new Promise((resolve, reject) => {
                resolve(data);
            })
        }
    }
    // 实现Promise.reject
    static reject(err) {
        if (err instanceof Promise) {
            return err;
        } else {
            return new Promise((resolve, reject) => {
                reject(err);
            })
        }
    }
    /**
     * 实现Promise.all
     * 1. Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
     * 2. 返回值组成一个数组
    */
    static all(promises) {
        return new Promise((resolve, reject) => {
            let promiseCount = 0;
            let promisesLength = promises.length;
            let result = [];
            for(let i = 0; i < promises.length; i++) {
                // promises[i]可能不是Promise类型,可能不存在then方法,中间如果出错,直接返回错误
                Promise.resolve(promises[i])
                    .then(res => {
                        promiseCount++;
                        // 注意这是赋值应该用下标去赋值而不是用push,因为毕竟是异步的,哪个promise先完成还不一定
                        result[i] = res;
                        if(promiseCount === promisesLength) {
                        return resolve(result);
                        }
                    },(err) => {
                        return reject(err);
                    }
                )
            }
        })
    }
    /**
     * 实现Promise.race
     * 1. Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
     * 2. 返回那个率先改变的 Promise 实例的返回值
    */
    static race(promises) {
        return new Promise((resolve, reject) => {
            for(let i = 0; i < promises.length; i++) {
                Promise.resolve(promises[i])
                    .then(res => {
                        return resolve(res);
                    },(err) =>{
                        return reject(err);
                    }
                )
            }
        })
    }
}

Test case

1. Promise.then

const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('resolve');
        resolve(222);
    }, 1000)
})

p.then(data => {
    setTimeout(() => {
        console.log('data', data);
    })
    return 3333;
}).then(data2 => {
    console.log('data2', data2);
}).catch(err => {
    console.error('err', err);
});

2. Promise.reject

const p1 = Promise.reject('出错了');
p1.then(null, function (s) {
    console.log(s); // 出错了
});

3. Promise.all && Promise.race

const q1 = new Promise((resolve, reject) => {
    resolve('hello')
});

const q2 = new Promise((resolve, reject) => {
    resolve('world')
});
Promise.all([q1, q2]).then(res => {
    console.log(res); // [ 'hello', 'world' ]
});
Promise.race([q1, q2]).then(res => {
    console.log(res); // hello
});

Four: function anti-shake and throttling

Function debounce (debounce)

Anti-shake: No matter how high the event trigger frequency, it must be executed n seconds after the event is triggered . If the event is triggered within n seconds of the execution of an event, the time of the new event shall prevail, and it will be executed after n seconds. , In short, the event will not be triggered within n seconds after the event is triggered, and it will be executed again after n seconds.

Ideas:

  1. Return a function;

  2. Cancel the previous timer every time an event is triggered

Need to pay attention to the problem:

  1. this points to

  2. Passing of parameters

  3. Do you want to call it immediately

function debounce(fn, wait, immediate) {
    let timer = null;
    //  返回一个函数
    return function(...args) {
        // 每次触发事件时都取消之前的定时器
        clearTimeout(timer);
        // 判断是否要立即执行一次
        if(immediate && !timer) {
            fn.apply(this, args);
        }
        // setTimeout中使用箭头函数,就是让 this指向 返回的该闭包函数,而不是 debounce函数的调用者
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, wait)
    }
}

Save a marker (timer) through the closure to save the value returned by setTimeout. Whenever you want to trigger a function, you need to clear the previous setTimeout first, and then create a new setTimeout, so that you can ensure the wait after the function is executed. If the function is to be triggered during the interval, fn will not be executed

scenes to be used

  1. Monitor resize or scroll, execute some business processing logic

window.addEventListener('resize', debounce(handleResize, 200));
window.addEventListener('scroll', debounce(handleScroll, 200));

Some high-frequency trigger functions need to be considered for anti-shake

  • window 的 resize、scroll

  • mousedown、mousemove

  • keyup、keydown ...

  1. Search input box, search 200 milliseconds after input

debounce(fetchSearchData, 200);

Memory can be interpreted this way: anti-shake function is  executed only after the triggering event n seconds when listening scroll event and a resize event, just run after n seconds on it, each time they do not need a trigger  scrollor  resizetime on Execution, execution within n seconds is meaningless (users may not feel it, and it is easy to cause a freeze).

Function throttling (throttle)

Function throttling: No matter how high the event trigger frequency is, it will only be executed once per unit time.

There are two ways to achieve it: using timestamps and timers

Use timestamp

function throttle(fn, wait)  {
    // 记录上一次执行的时间戳
    let previous = 0;
    return function(...args) {
        // 当前的时间戳,然后减去之前的时间戳,大于设置的时间间隔,就执行函数,否则不执行
        if(Date.now() - previous > wait) {
            // 更新上一次的时间戳为当前时间戳
            previous = Date.now();
            fn.apply(this, args);
        }
    }
}

The first event must be triggered, and the last time it will not be triggered (for example, when the mouse stops moving, the triggering event will stop immediately)

Use timer

function throttle(fn, wait)  {
    // 设置一个定时器
    let timer = null;
    return function(...args) {
        // 判断如果定时器不存在就执行,存在则不执行
        if(!timer) {
            // 设置下一个定时器
            timer = setTimeout(() => {
                // 然后执行函数,清空定时器
                timer = null;
                fn.apply(this, args)
            }, wait)
        }
    }
}

The first event will not be triggered (fn is executed in setTimeout, so the first trigger event waits at least wait milliseconds before executing), the last time it must be triggered

Combination of timer and time stamp

The combination of the two can be achieved, the first event will trigger, the last event will also trigger

function throttle(fn, wait)  {
    // 记录上一次执行的时间戳
    let previous = 0;
    // 设置一个定时器
    let timer = null;
    return function(...args) {
        // 当前的时间戳,然后减去之前的时间戳,大于设置的时间间隔
        if(Date.now() - previous > wait) {
            clearTimeout(timer);
            timer = null
            // 更新上一次的时间戳为当前时间戳
            previous = Date.now();
            fn.apply(this, args);
        } else if(!timer) {
            // 设置下一个定时器
            timer = setTimeout(() => {
                timer = null;
                fn.apply(this, args)
            }, wait)
        }
    }
}

Five: realize a two-way binding of vue

// vue 2.x双向绑定
        var obj = {};
        var demo = document.getElementById('aa');
        var inp = document.getElementById('input');
        Object.defineProperty(obj,'txt',{
            get:function(val){
                return val;
            },
            set:function(newVal){
                inp.value = newVal;
                demo.innerHTML = newVal;
            }
        })
        document.addEventListener('input', function(e){
            obj.txt = e.target.value;
        })
// vue 3.x双向绑定
        var obj = {};
        var obj1 = new Proxy(obj, {
            // target就是第一个参数obj, receive就是返回的obj(返回的proxy对象)
            get: function (target, key, receive) {
                // 返回该属性值
                return target[key];
            },
            set: function (target, key, newVal, receive) {
                // 执行赋值操作
                target[key] = newVal;
                document.getElementById('text').innerHTML = target[key];
            }
        })
        document.addEventListener('keyup', function (e) {
            obj1[0] = e.target.value;
        });

 

 

 

Guess you like

Origin blog.csdn.net/AN0692/article/details/108866655