实现find办法
find
接管一个办法作为参数,办法外部返回一个条件find
会遍历所有的元素,执行你给定的带有条件返回值的函数- 合乎该条件的元素会作为
find
办法的返回值 - 如果遍历完结还没有合乎该条件的元素,则返回
undefined
var users = [ {id: 1, name: '张三'}, {id: 2, name: '张三'}, {id: 3, name: '张三'}, {id: 4, name: '张三'}]Array.prototype.myFind = function (callback) { // var callback = function (item, index) { return item.id === 4 } for (var i = 0; i < this.length; i++) { if (callback(this[i], i)) { return this[i] } }}var ret = users.myFind(function (item, index) { return item.id === 2})console.log(ret)
实现数组去重
给定某无序数组,要求去除数组中的反复数字并且返回新的无反复数组。
ES6办法(应用数据结构汇合):
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
ES5办法:应用map存储不反复的数字
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];uniqueArray(array); // [1, 2, 3, 5, 9, 8]function uniqueArray(array) { let map = {}; let res = []; for(var i = 0; i < array.length; i++) { if(!map.hasOwnProperty([array[i]])) { map[array[i]] = 1; res.push(array[i]); } } return res;}
前端手写面试题具体解答
查找字符串中呈现最多的字符和个数
例: abbcccddddd -> 字符最多的是d,呈现了5次
let str = "abcabcabcbbccccc";let num = 0;let char = ''; // 使其依照肯定的秩序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"// 定义正则表达式let re = /(\w)\1+/g;str.replace(re,($0,$1) => { if(num < $0.length){ num = $0.length; char = $1; }});console.log(`字符最多的是${char},呈现了${num}次`);
图片懒加载
能够给img标签对立自定义属性data-src='default.png'
,当检测到图片呈现在窗口之后再补充src属性,此时才会进行图片资源加载。
function lazyload() { const imgs = document.getElementsByTagName('img'); const len = imgs.length; // 视口的高度 const viewHeight = document.documentElement.clientHeight; // 滚动条高度 const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop; for (let i = 0; i < len; i++) { const offsetHeight = imgs[i].offsetTop; if (offsetHeight < viewHeight + scrollHeight) { const src = imgs[i].dataset.src; imgs[i].src = src; } }}// 能够应用节流优化一下window.addEventListener('scroll', lazyload);
实现字符串翻转
在字符串的原型链上增加一个办法,实现字符串翻转:
String.prototype._reverse = function(a){ return a.split("").reverse().join("");}var obj = new String();var res = obj._reverse ('hello');console.log(res); // olleh
须要留神的是,必须通过实例化对象之后再去调用定义的办法,不然找不到该办法。
解析 URL Params 为对象
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';parseParam(url)/* 后果{ user: 'anonymous', id: [ 123, 456 ], // 反复呈现的 key 要组装成数组,能被转成数字的就转成数字类型 city: '北京', // 中文需解码 enabled: true, // 未指定值得 key 约定为 true}*/
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;}
实现Event(event bus)
event bus既是node中各个模块的基石,又是前端组件通信的依赖伎俩之一,同时波及了订阅-公布设计模式,是十分重要的根底。
简略版:
class EventEmeitter { constructor() { this._events = this._events || new Map(); // 贮存事件/回调键值对 this._maxListeners = this._maxListeners || 10; // 设立监听下限 }}// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) { let handler; // 从贮存事件键值对的this._events中获取对应事件回调函数 handler = this._events.get(type); if (args.length > 0) { handler.apply(this, args); } else { handler.call(this); } return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) { // 将type事件以及对应的fn函数放入this._events中贮存 if (!this._events.get(type)) { this._events.set(type, fn); }};
面试版:
class EventEmeitter { constructor() { this._events = this._events || new Map(); // 贮存事件/回调键值对 this._maxListeners = this._maxListeners || 10; // 设立监听下限 }}// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) { let handler; // 从贮存事件键值对的this._events中获取对应事件回调函数 handler = this._events.get(type); if (args.length > 0) { handler.apply(this, args); } else { handler.call(this); } return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) { // 将type事件以及对应的fn函数放入this._events中贮存 if (!this._events.get(type)) { this._events.set(type, fn); }};// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) { let handler; handler = this._events.get(type); if (Array.isArray(handler)) { // 如果是一个数组阐明有多个监听者,须要顺次此触发外面的函数 for (let i = 0; i < handler.length; i++) { if (args.length > 0) { handler[i].apply(this, args); } else { handler[i].call(this); } } } else { // 单个函数的状况咱们间接触发即可 if (args.length > 0) { handler.apply(this, args); } else { handler.call(this); } } return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) { const handler = this._events.get(type); // 获取对应事件名称的函数清单 if (!handler) { this._events.set(type, fn); } else if (handler && typeof handler === "function") { // 如果handler是函数阐明只有一个监听者 this._events.set(type, [handler, fn]); // 多个监听者咱们须要用数组贮存 } else { handler.push(fn); // 曾经有多个监听者,那么间接往数组里push函数即可 }};EventEmeitter.prototype.removeListener = function(type, fn) { const handler = this._events.get(type); // 获取对应事件名称的函数清单 // 如果是函数,阐明只被监听了一次 if (handler && typeof handler === "function") { this._events.delete(type, fn); } else { let postion; // 如果handler是数组,阐明被监听屡次要找到对应的函数 for (let i = 0; i < handler.length; i++) { if (handler[i] === fn) { postion = i; } else { postion = -1; } } // 如果找到匹配的函数,从数组中革除 if (postion !== -1) { // 找到数组对应的地位,间接革除此回调 handler.splice(postion, 1); // 如果革除后只有一个函数,那么勾销数组,以函数模式保留 if (handler.length === 1) { this._events.set(type, handler[0]); } } else { return this; } }};
实现具体过程和思路见实现event
字符串查找
请应用最根本的遍从来实现判断字符串 a 是否被蕴含在字符串 b 中,并返回第一次呈现的地位(找不到返回 -1)。
a='34';b='1234567'; // 返回 2a='35';b='1234567'; // 返回 -1a='355';b='12354355'; // 返回 5isContain(a,b);
function isContain(a, b) { for (let i in b) { if (a[0] === b[i]) { let tmp = true; for (let j in a) { if (a[j] !== b[~~i + ~~j]) { tmp = false; } } if (tmp) { return i; } } } return -1;}
实现非负大整数相加
JavaScript对数值有范畴的限度,限度如下:
Number.MAX_VALUE // 1.7976931348623157e+308Number.MAX_SAFE_INTEGER // 9007199254740991Number.MIN_VALUE // 5e-324Number.MIN_SAFE_INTEGER // -9007199254740991
如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER
)进行加法运算,然而又想输入个别模式,那么应用 + 是无奈达到的,一旦数字超过 Number.MAX_SAFE_INTEGER
数字会被立刻转换为迷信计数法,并且数字精度相比以前将会有误差。
实现一个算法进行大数的相加:
function sumBigNumber(a, b) { let res = ''; let temp = 0; a = a.split(''); b = b.split(''); while (a.length || b.length || temp) { temp += ~~a.pop() + ~~b.pop(); res = (temp % 10) + res; temp = temp > 9 } return res.replace(/^0+/, '');}
其次要的思路如下:
- 首先用字符串的形式来保留大数,这样数字在数学示意上就不会发生变化
- 初始化res,temp来保留两头的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
- 将两个数组的对应的位进行相加,两个数相加的后果可能大于10,所以可能要仅为,对10进行取余操作,将后果保留在以后位
- 判断以后位是否大于9,也就是是否会进位,若是则将temp赋值为true,因为在加法运算中,true会主动隐式转化为1,以便于下一次相加
- 反复上述操作,直至计算完结
实现JSON.parse
var json = '{"name":"cxk", "age":25}';var obj = eval("(" + json + ")");
此办法属于黑魔法,极易容易被xss攻打,还有一种new Function
大同小异。
模仿new
new操作符做了这些事:
- 它创立了一个全新的对象
- 它会被执行[[Prototype]](也就是__proto__)链接
- 它使this指向新创建的对象
- 通过new创立的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
- 如果函数没有返回对象类型Object(蕴含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象援用
// objectFactory(name, 'cxk', '18')function objectFactory() { const obj = new Object(); const Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; const ret = Constructor.apply(obj, arguments); return typeof ret === "object" ? ret : obj;}
Function.prototype.bind
Function.prototype.bind = function(context, ...args) { if (typeof this !== 'function') { throw new Error("Type Error"); } // 保留this的值 var self = this; return function F() { // 思考new的状况 if(this instanceof F) { return new self(...args, ...arguments) } return self.apply(context, [...args, ...arguments]) }}
封装异步的fetch,应用async await形式来应用
(async () => { class HttpRequestUtil { async get(url) { const res = await fetch(url); const data = await res.json(); return data; } async post(url, data) { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await res.json(); return result; } async put(url, data) { const res = await fetch(url, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(data) }); const result = await res.json(); return result; } async delete(url, data) { const res = await fetch(url, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(data) }); const result = await res.json(); return result; } } const httpRequestUtil = new HttpRequestUtil(); const res = await httpRequestUtil.get('http://golderbrother.cn/'); console.log(res);})();
throttle(节流)
高频工夫触发,但n秒内只会执行一次,所以节流会浓缩函数的执行频率。
const throttle = (fn, time) => { let flag = true; return function() { if (!flag) return; flag = false; setTimeout(() => { fn.apply(this, arguments); flag = true; }, time); }}
节流常利用于鼠标一直点击触发、监听滚动事件。
循环打印红黄绿
上面来看一道比拟典型的问题,通过这个问题来比照几种异步编程办法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯一直交替反复亮灯?
三个亮灯函数:
function red() { console.log('red');}function green() { console.log('green');}function yellow() { console.log('yellow');}
这道题简单的中央在于须要“交替反复”亮灯,而不是“亮完一次”就完结了。
(1)用 callback 实现
const task = (timer, light, callback) => { setTimeout(() => { if (light === 'red') { red() } else if (light === 'green') { green() } else if (light === 'yellow') { yellow() } callback() }, timer)}task(3000, 'red', () => { task(2000, 'green', () => { task(1000, 'yellow', Function.prototype) })})
这里存在一个 bug:代码只是实现了一次流程,执行后红黄绿灯别离只亮一次。该如何让它交替反复进行呢?
下面提到过递归,能够递归亮灯的一个周期:
const step = () => { task(3000, 'red', () => { task(2000, 'green', () => { task(1000, 'yellow', step) }) })}step()
留神看黄灯亮的回调里又再次调用了 step 办法 以实现循环亮灯。
(2)用 promise 实现
const task = (timer, light) => new Promise((resolve, reject) => { setTimeout(() => { if (light === 'red') { red() } else if (light === 'green') { green() } else if (light === 'yellow') { yellow() } resolve() }, timer) })const step = () => { task(3000, 'red') .then(() => task(2000, 'green')) .then(() => task(2100, 'yellow')) .then(step)}step()
这里将回调移除,在一次亮灯完结后,resolve 以后 promise,并仍然应用递归进行。
(3)用 async/await 实现
const taskRunner = async () => { await task(3000, 'red') await task(2000, 'green') await task(2100, 'yellow') taskRunner()}taskRunner()
event模块
实现node中回调函数的机制,node中回调函数其实是外部应用了观察者模式。
观察者模式:定义了对象间一种一对多的依赖关系,当指标对象Subject产生扭转时,所有依赖它的对象Observer都会失去告诉。
function EventEmitter() { this.events = new Map();}// 须要实现的一些办法:// addListener、removeListener、once、removeAllListeners、emit// 模仿实现addlistener办法const wrapCallback = (fn, once = false) => ({ callback: fn, once });EventEmitter.prototype.addListener = function(type, fn, once = false) { const hanlder = this.events.get(type); if (!hanlder) { // 没有type绑定事件 this.events.set(type, wrapCallback(fn, once)); } else if (hanlder && typeof hanlder.callback === 'function') { // 目前type事件只有一个回调 this.events.set(type, [hanlder, wrapCallback(fn, once)]); } else { // 目前type事件数>=2 hanlder.push(wrapCallback(fn, once)); }}// 模仿实现removeListenerEventEmitter.prototype.removeListener = function(type, listener) { const hanlder = this.events.get(type); if (!hanlder) return; if (!Array.isArray(this.events)) { if (hanlder.callback === listener.callback) this.events.delete(type); else return; } for (let i = 0; i < hanlder.length; i++) { const item = hanlder[i]; if (item.callback === listener.callback) { hanlder.splice(i, 1); i--; if (hanlder.length === 1) { this.events.set(type, hanlder[0]); } } }}// 模仿实现once办法EventEmitter.prototype.once = function(type, listener) { this.addListener(type, listener, true);}// 模仿实现emit办法EventEmitter.prototype.emit = function(type, ...args) { const hanlder = this.events.get(type); if (!hanlder) return; if (Array.isArray(hanlder)) { hanlder.forEach(item => { item.callback.apply(this, args); if (item.once) { this.removeListener(type, item); } }) } else { hanlder.callback.apply(this, args); if (hanlder.once) { this.events.delete(type); } } return true;}EventEmitter.prototype.removeAllListeners = function(type) { const hanlder = this.events.get(type); if (!hanlder) return; this.events.delete(type);}
小孩报数问题
有30个小孩儿,编号从1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 而后下一个小孩 从新报数 1、2、3,问最初剩下的那个小孩儿的编号是多少?
function childNum(num, count){ let allplayer = []; for(let i = 0; i < num; i++){ allplayer[i] = i + 1; } let exitCount = 0; // 来到人数 let counter = 0; // 记录报数 let curIndex = 0; // 以后下标 while(exitCount < num - 1){ if(allplayer[curIndex] !== 0) counter++; if(counter == count){ allplayer[curIndex] = 0; counter = 0; exitCount++; } curIndex++; if(curIndex == num){ curIndex = 0 }; } for(i = 0; i < num; i++){ if(allplayer[i] !== 0){ return allplayer[i] } }}childNum(30, 3)
给定两个数组,写一个办法来计算它们的交加
例如:给定 nums1 = [1, 2, 2, 1],nums2 = [2, 2],返回 [2, 2]。
function union (arr1, arr2) { return arr1.filter(item => { return arr2.indexOf(item) > - 1; })} const a = [1, 2, 2, 1]; const b = [2, 3, 2]; console.log(union(a, b)); // [2, 2]
手写类型判断函数
function getType(value) { // 判断数据是 null 的状况 if (value === null) { return value + ""; } // 判断数据是援用类型的状况 if (typeof value === "object") { let valueClass = Object.prototype.toString.call(value), type = valueClass.split(" ")[1].split(""); type.pop(); return type.join("").toLowerCase(); } else { // 判断数据是根本数据类型的状况和函数的状况 return typeof value; }}
实现斐波那契数列
// 递归function fn (n){ if(n==0) return 0 if(n==1) return 1 return fn(n-2)+fn(n-1)}// 优化function fibonacci2(n) { const arr = [1, 1, 2]; const arrLen = arr.length; if (n <= arrLen) { return arr[n]; } for (let i = arrLen; i < n; i++) { arr.push(arr[i - 1] + arr[ i - 2]); } return arr[arr.length - 1];}// 非递归function fn(n) { let pre1 = 1; let pre2 = 1; let current = 2; if (n <= 2) { return current; } for (let i = 2; i < n; i++) { pre1 = pre2; pre2 = current; current = pre1 + pre2; } return current;}