前端面试题精选

1.memorize函数实现应用

首先什么是memorize函数,memorize直译:记忆,缓存等意思,到了计算机层面就翻译为缓存函数,缓存函数就是把计算的结果,存在函数中,当再次调用的时候就可以直接调用。这种方法就是用空间来换取时间

    const memorize = function(fn) {
        const cache = {}
        return function(...args) { // 参数值,是一个数组,数组的值是需要计算的值
            const _args = JSON.stringify(args)
            return cache[_args] || (cache[_args] = fn.apply(fn, args)) 
            // 缓存函数的核心判断依据,传入的键名对应的键值为null 则调用add方法 反之从cache中拿取
        }
    }
    const add = function(a) {
        return a + 1
    }
    const adder = memorize(add) // memorize只调用一次 后面adder调用的都是return的函数
    adder(1) // 2    cache: { '[1]': 2 }
    adder(1) // 2    cache: { '[1]': 2 }
    adder(2) // 3    cache: { '[1]': 2, '[2]': 3 }

2. bind,apply,call的区别和实现

javascript 权威指南上的解释是: call()、apply()可以看做是某个对象的方法,通过调用方法的形式来间接调用函数。bind()就是将某个函数绑定到某个对象上。

    var obj = {
        x:1
    }
    function foo() {
        console.log(this.x)
    }
    foo.call(obj)  //1

call()和apply()的第一个参数相同,就是指定对象,他们的根本区别就在于传入的参数。
call传入参数形式为call(obj, 1, 2, 3)
apply传入参数形式为apply(obj, [1,2,3])

bind()方法和前两者不同在于:bind()方法会返回执行上下文被改变的函数,而不会立即执行,而前两者是直接执行该函数。他的参数和call()相同.

3.数组去重

去重方法有很多种,常用必需会写的几种去重方法有

1. 遍历去重
2. json键名不唯一去重
3. new Set()去重

3.1 遍历去重

    let arr = [1, 2, 3, 4, 5, 6, 7, 7, 7, 3, 111, 1, 3]

    for (let i = 0; i < arr.length; i++) {
        for (let j = i + 1; j < arr.length; j++) {
            if (arr[i] == arr[j]) {
                arr.splice(j, 1)
                i = i - 1
            }
        }
    }

3.2 json键名去重

    let arr = [1, 2, 3, 4, 5, 6, 7, 7, 7, 3, 111, 1, 3]
    let obj = {}
    let result = []
    
    for(let i = 0 ; i < arr.length; i++) {
        obj[arr[i]] = null
    }
    
    for(let key in obj) {
        result.push(key)
    }

3.3 new Set()去重

    let arr = [1, 2, 3, 4, 5, 6, 7, 7, 7, 3, 111, 1, 3]
    let newArr = new Set(arr) // 返回一个set对象
    let result = []
    
    newArr.forEach(val => {
        result.push(val)
    })

4.数组扁平化

数组扁平化简单来说就是把一个多维数组变为一维数组,核心思想:

1.递归
2.数值toString() + split()
3.Array.prototype.flat(depth) // Infinity代表无限扁平

递归

function flatten(arr) {
    var res = [];
    arr.map(item => {
        if(Array.isArray(item)) {
            res = res.concat(flatten(item));
        } else {
            res.push(item);
        }
    });
    return res;
}

数值的扁平化toString() + split()

    let arr = [1,2,3,[2,3,4],45,7,[24,[2,3]]]
    function flatten(arr) {
        return arr.toString().split(',').map(val => {
            return Number(val)
        })
    }

flat()

    let arr = [1,2,3,[2,3,4],45,7,[24,[2,3]]]
    arr.flat(infinity)

5.debounce和throttle的实现和使用场景

去抖动和节流,二者都是随着时间推移控制执行函数的次数来达到较少资源消耗,特别在事件触发上,尤为重要。

debounce的作用是,当调用动作触发一段时间后,才会执行该动作,若在这段时间间隔内又调用此动作则将重新计算时间间隔。

简单来说就是:调用会一直刷新动作,动作是被延迟执行,直到停止调用等待了一段时间后再执行动作。看下面简单debounce实现节流例子:

    function easyDebounce(method, scope) {
        clearTimeout(method.tId); // onresize会一直被触发,只要在100毫秒内被连续触发计时器都会刷新直到时间大于100毫秒
        method.tId= setTimeout(function(){
            method.call(scope);
        }, 100);
    }
    
    function resizeDiv(){
        var div = document.getElementById("myDiv");
        div.style.height = div.offsetWidth + "px";
    }
    
    // 节流在resize事件中最常用
    window.onresize = function(){
        easyDebounce(resizeDiv);
    };

throttle预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新的时间周期。throttle可以基于debounce实现,只需要加上一个阀值(最小值)如果时间小于定义的阀值 则不触发动作,大于则触发并刷新周期,而且第一次必定触发。节流不实用定时器,debounce使用定时器

    var throttleV2 = function(action, delay){
        var statTime = 0;
    
        return function() { // 每次resize执行的函数 闭包保证statTime可以一直使用
            var currTime = +new Date();
    
            if (currTime - statTime > delay) {
                action.apply(this, arguments);
                statTime = currTime ; // 每次调用完之后刷新时间
            }
        }
    }    
    
    // example
    function resizeHandler() {
        console.log("resize");
    }
    
    window.onresize = throttleV2(resizeHandler, 300);

6.vue数据双向绑定

vue实现数据的双向绑定根本API是Object.defineProperty(),通过这个方法重写get,和set属性来达到数据双向绑定

详细解读一下Object.defineProperty()这个东西到底干了啥

    var Book = {}
    var name = '';
    Object.defineProperty(Book, 'name', {
      set: function (value) {
        name = value;
        console.log('书名:' + value);
      },
      get: function () {
        return '《' + name + '》'
      }
    })
     
    Book.name = '前端学习';
    console.log(Book.name); 

双向绑定是用发布订阅模式实现,所以涉及到监听器Observer、订阅者Watcher、解析器Compile,以及消息订阅器dep收集watcher。

图片来自互联网:
clipboard.png

7.vue scoped实现原理

vue scoped的实现是通过在对应的dom节点添加data-v-哈希值,并在style每个属性后面加上这串data-v-hash来实现样式的唯一性,起到一个样式作用域的效果。
但是实际应用中添加scoped也会带来一些麻烦,比如设置完scoped属性之后无法修改子组件的样式。再次有两点供大家参考:

  1. 不用scoped属性,每个组件使用良好的命名规范,每个组件的根节点取唯一的类名。
  2. 使用scoped属性,在需要操作子组件样式的时候使用‘>>>’来修改子组件的节点样式。

持续更新中~~~


风吹一个大耳东
400 声望28 粉丝

钻到底~