前端———手撕十道JS面试手写!

Posted 贪吃ღ大魔王

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端———手撕十道JS面试手写!相关的知识,希望对你有一定的参考价值。

解析 URL 参数为对象

function parseParam(url) 
    const paramsStr = /.+\\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
    const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
    let paramsObj = ;
    // 将 params 存到对象中
    paramsArr.forEach(param => 
        if (/=/.test(param))  // 处理有 value 的参数
            let [key, val] = param.split('='); // 分割 key 和 value
            val = decodeURIComponent(val); // 解码
            val = /^\\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
    
            if (paramsObj.hasOwnProperty(key))  // 如果对象有 key,则添加一个值
                paramsObj[key] = [].concat(paramsObj[key], val);
             else  // 如果对象没有这个 key,创建 key 并设置值
                paramsObj[key] = val;
            
         else  // 处理没有 value 的参数
            paramsObj[param] = true;
        
    )
    
    return paramsObj;

字符串模板

function render(template, data) 
    const reg = /\\\\(\\w+)\\\\/; // 模板字符串正则
    if (reg.test(template))  // 判断模板里是否有模板字符串
        const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
        template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
        return render(template, data); // 递归的渲染并返回渲染后的结构
    
    return template; // 如果模板没有模板字符串直接返回

let template = '我是name,年龄age,性别sex';
let person = 
    name: 'zs',
    age: 12

render(template, person); // 我是zs,年龄12,性别undefined

图片懒加载

与普通的图片懒加载不同:

图片全部加载完成后移除事件监听;
加载完的图片,从 imgList 移除;

let imgList = [...document.querySelectorAll('img')]
let length = imgList.length

// 修正错误,需要加上自执行
- const imgLazyLoad = function() 
+ const imgLazyLoad = (function() 
    let count = 0
    
   return function() 
        let deleteIndexList = []
        imgList.forEach((img, index) => 
            let rect = img.getBoundingClientRect()
            if (rect.top < window.innerHeight) 
                img.src = img.dataset.src
                deleteIndexList.push(index)
                count++
                if (count === length) 
                    document.removeEventListener('scroll', imgLazyLoad)
                
            
        )
        imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
   
- 
+ )()

// 这里最好加上防抖处理
document.addEventListener('scroll', imgLazyLoad)

函数防抖

触发高频事件 N 秒后只会执行一次,如果 N 秒内事件再次触发,则会重新计时。
简单版:函数内部支持使用 this 和 event 对象;

function debounce(func, wait) 
    var timeout;
    return function () 
        var context = this;
        var args = arguments;
        clearTimeout(timeout)
        timeout = setTimeout(function()
            func.apply(context, args)
        , wait);
    

使用:

var node = document.getElementById('layout')
function getUserAction(e) 
    console.log(this, e)  // 分别打印:node 这个节点 和 MouseEvent
    node.innerhtml = count++;
;
node.onmousemove = debounce(getUserAction, 1000)

最终版:除了支持 this 和 event 外,还支持以下功能:

支持立即执行;
函数可能有返回值;
支持取消功能;

function debounce(func, wait, immediate) 
    var timeout, result;
    
    var debounced = function () 
        var context = this;
        var args = arguments;
        
        if (timeout) clearTimeout(timeout);
        if (immediate) 
            // 如果已经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function()
                timeout = null;
            , wait)
            if (callNow) result = func.apply(context, args)
         else 
            timeout = setTimeout(function()
                func.apply(context, args)
            , wait);
        
        return result;
    ;

    debounced.cancel = function() 
        clearTimeout(timeout);
        timeout = null;
    ;

    return debounced;

使用:

var setUseAction = debounce(getUserAction, 10000, true);
 使用防抖
node.onmousemove = setUseAction

 取消防抖
setUseAction.cancel()

函数节流

触发高频事件,且 N 秒内只执行一次。
简单版:使用时间戳来实现,立即执行一次,然后每 N 秒执行一次。

function throttle(func, wait) 
    var context, args;
    var previous = 0;

    return function() 
        var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) 
            func.apply(context, args);
            previous = now;
        
    

最终版:支持取消节流;另外通过传入第三个参数,options.leading 来表示是否可以立即执行一次,opitons.trailing 表示结束调用的时候是否还要执行一次,默认都是 true。
注意设置的时候不能同时将 leading 或 trailing 设置为 false。

function throttle(func, wait, options) 
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = ;

    var later = function() 
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if (!timeout) context = args = null;
    ;

    var throttled = function() 
        var now = new Date().getTime();
        if (!previous && options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) 
            if (timeout) 
                clearTimeout(timeout);
                timeout = null;
            
            previous = now;
            func.apply(context, args);
            if (!timeout) context = args = null;
         else if (!timeout && options.trailing !== false) 
            timeout = setTimeout(later, remaining);
        
    ;
    
    throttled.cancel = function() 
        clearTimeout(timeout);
        previous = 0;
        timeout = null;
    
    return throttled;

JSONP

JSONP 核心原理:script 标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求;

const jsonp = ( url, params, callbackName ) => 
    const generateUrl = () => 
        let dataSrc = ''
        for (let key in params) 
            if (params.hasOwnProperty(key)) 
                dataSrc += `$key=$params[key]&`
            
        
        dataSrc += `callback=$callbackName`
        return `$url?$dataSrc`
    
    return new Promise((resolve, reject) => 
        const scriptEle = document.createElement('script')
        scriptEle.src = generateUrl()
        document.body.appendChild(scriptEle)
        window[callbackName] = data => 
            resolve(data)
            document.removeChild(scriptEle)
        
    )

AJAX

const getJSON = function(url) 
    return new Promise((resolve, reject) => 
        const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
        xhr.open('GET', url, false);
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.onreadystatechange = function() 
            if (xhr.readyState !== 4) return;
            if (xhr.status === 200 || xhr.status === 304) 
                resolve(xhr.responseText);
             else 
                reject(new Error(xhr.responseText));
            
        
        xhr.send();
    )

call

使用一个指定的 this 值和一个或多个参数来调用一个函数。
实现要点:

this 可能传入 null;
传入不固定个数的参数;
函数可能有返回值;

Function.prototype.call2 = function (context) 
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) 
        args.push('arguments[' + i + ']');
    

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;

apply

apply 和 call 一样,唯一的区别就是 call 是传入不固定个数的参数,而 apply 是传入一个数组。
实现要点:

this 可能传入 null;
传入一个数组;
函数可能有返回值;

Function.prototype.apply2 = function (context, arr) 
    var context = context || window;
    context.fn = this;

    var result;
    if (!arr) 
        result = context.fn();
     else 
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) 
            args.push('arr[' + i + ']');
        
        result = eval('context.fn(' + args + ')')
    

    delete context.fn
    return result;

bind

bind 方法会创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
实现要点:

bind() 除了 this 外,还可传入多个参数;
bing 创建的新函数可能传入多个参数;
新函数可能被当做构造函数调用;
函数可能有返回值;

Function.prototype.bind2 = function (context) 
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () ;

    var fBound = function () 
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;

以上是关于前端———手撕十道JS面试手写!的主要内容,如果未能解决你的问题,请参考以下文章

前端———手撕十道JS面试手写!

JavaScript手撕前端面试题:手写new操作符 | 手写Object.freeze

JavaScript手撕前端面试题:手写Object.create | 手写Function.call | 手写Function.bind

前端面试题 ---- 手撕JavaScript call apply bind 函数(超详细)

❤️手撕这十道HiveSQL题还不能吊打面试官,却能保你不被吊打❤️推荐收藏

❤️手撕这十道HiveSQL题还不能吊打面试官,却能保你不被吊打❤️推荐收藏