前端———手撕十道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面试手写!的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript手撕前端面试题:手写new操作符 | 手写Object.freeze
JavaScript手撕前端面试题:手写Object.create | 手写Function.call | 手写Function.bind
前端面试题 ---- 手撕JavaScript call apply bind 函数(超详细)