高频
一. 柯里化函数(Currying)和反柯里化
简介
柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
个人理解偏函数是一种特殊柯里化,偏函数
核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。
按照Stoyan Stefanov --《JavaScript Pattern》作者 的说法,所谓“柯里化”就是使函数理解并处理部分应用
柯里化有3个常见作用:
- 参数复用
- 提前返回
- 延迟计算/运行
简单实现
function currying(fn, ...rest1) {
return function(...rest2) {
return (null, (rest2))
}
}
例:将一个sayHello函数柯里化
function sayHello(name, age, fruit) {
((`我叫 ${name},我 ${age} 岁了, 我喜欢吃 ${fruit}`))
}
const curryingShowMsg1 = currying(sayHello, '小明')
curryingShowMsg1(22, '苹果') // 我叫 小明,我 22 岁了, 我喜欢吃 苹果
const curryingShowMsg2 = currying(sayHello, '小衰', 20)
curryingShowMsg2('西瓜') // 我叫 小衰,我 20 岁了, 我喜欢吃 西瓜
高阶(递归)柯里化函数
function curryingHelper(fn, len) {
const length = len || // 第一遍运行length是函数fn一共需要的参数个数,以后是剩余所需要的参数个数
return function(...rest) {
return >= length // 检查是否传入了fn所需足够的参数
? (this, rest)
: curryingHelper((this, [fn].concat(rest)), length - ) // 在通用currying函数基础上
}
}
例:
function sayHello(name, age, fruit) { (`我叫 ${name},我 ${age} 岁了, 我喜欢吃 ${fruit}`) }
const betterShowMsg = curryingHelper(sayHello)
betterShowMsg('小衰', 20, '西瓜') // 我叫 小衰,我 20 岁了, 我喜欢吃 西瓜
betterShowMsg('小猪')(25, '南瓜') // 我叫 小猪,我 25 岁了, 我喜欢吃 南瓜
betterShowMsg('小明', 22)('倭瓜') // 我叫 小明,我 22 岁了, 我喜欢吃 倭瓜
betterShowMsg('小拽')(28)('冬瓜') // 我叫 小拽,我 28 岁了, 我喜欢吃 冬瓜
二. 排序
// 冒泡排序 选出最大
var paixuArr = [34,45,2,4,77,13,0,67,89,3,795,24]
function mao(arr){
let len =
for(let i=0; i < len - 1; i++) {
for(let j=0; j < len - i - 1; j++) {
if(arr[j] > arr[j+1]){
let temp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = temp
}
}
}
return arr
}
// 选择排序
function xuanze(arr) {
let len =
for(let i=0; i < len - 1; i++) {
for(let j=i + 1; j < len; j++) {
if(arr[i] > arr[j]){
let temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
}
}
return arr
}
// 快速排序
function kuaisu(arr){
let len =
if(len <= 1) {return arr}
let firstValue = arr[0]
let leftList = []
let rightList = []
for(let i = 1; i < len; i ++){
if(arr[i] >= arr[0]){
(arr[i])
} else {
(arr[i])
}
}
return kuaisu(leftList).concat([firstValue], kuaisu(rightList))
}
三. setTimeout 实现 setInterval
使用key 标识当前执行的 setInterval, 存储在 timeWorker 中(对象是为了兼容多个 interval 并存)
值为 当前执行的 setTimeout,
当需要清除时,先找到要清除的 setInterval key,然后 clear 对应的 setTimeout
var timeWorker = {}
var mySetInterval= function(fn, time) {
// 定义一个key,来标识此定时器
var key = Symbol();
// 定义一个递归函数,持续调用定时器
var execute = function(fn, time) {
timeWorker[key] = setTimeout(function(){
fn();
execute(fn, time);
}, time)
}
execute(fn, time);
// 返回key
return key;
}
var myClearInterval = function(key) {
if (key in timeWorker) {
clearTimeout(timeWorker[key]);
delete timeWorker[key];
}
}
四. 二叉树广度、深度(前序、中序、后序)遍历
二叉树的遍历有深度优先和广度优先两种。 前序、中序、后序遍历都属于深度优先遍历。层次遍历属于广度优先遍历,从上到下,从左到右或从右到左一层一层的遍历。
1、二叉树的深度优先遍历-递归
// 前/先序:根左右
var DLR = function(root, res) {
if(!root) return ;
();
&& DLR(, res);
&& DLR(, res);
}
// 中序:左根右
var LDR = function(root, res) {
if(!root) return ;
&& LDR(, res);
();
if() LDR(, res);
}
// 后序:左右根
var LRD = function(root, res) {
if(!root) return ;
&& LRD(, res);
&& LRD(, res);
();
}
2、二叉树的深度优先遍历-非递归-迭代-采用栈(先进后出)
// 前序:根左右
var DLR = function(root) {
if(!root) return [];
let res = [];
let s = [root];
while( > 0) {
let p = (); // 取第一个
();
&& ();
&& ();
}
return res;
}
// 中序:左根右
// 先将根节点入栈,找到所有左节点入栈,直到没有左节点为止
// 然后出栈存入结果数组,每出一个,对比该根节点的右子节点是否有左节点,若有则入栈,否则继续出栈
var LDR = function(root) {
let res = [];
let s = []; // 栈
let p = root; // 指针
while(p || > 0) { // 直至左节点为空,即没有左节点为止
while (p) {
(p);
p = ;
}
// 出栈,存放根节点
p = ();
();
p = ;
}
return res;
}
// 后序:左右根
// 后序遍历比较复杂,但是看见网上有个比较好记住的办法:
// 按照与前序相似的方法(前序压栈的顺序是先右后左,这里是先左后右),先得到一个结果,然后对结果倒序一下。
var LRD = function(root) {
if(!root) return [];
let res = [];
let s = [root];
while( > 0) {
let p = (); // 取第一个
();
&& ();
&& ();
}
return ();
}
3、二叉树的广度优先优先-非递归-采用队列
// 二叉树的遍历-层次遍历
// 从上往下,从左到右/从右到左
var levelOrder = function(root) {
if(!root) return [];
let res = [];
let q = [root];
while( > 0){
let tmp = [];
let temp = [];
for(let i in q){
let p = q[i];
();
&& ();
&& ();
}
q = tmp;
(temp);
}
return res;
};
五. 继承
1. 重写原型对象
业务中较少使用,缺点:
(1). 父类属性直接赋值给子类原型,子类会共享属性
(2). 无法向父类传参
缺点:所有对于原型链上的修改,都会变成通用调整。
function Game () { = 'LOL' }
= function () { return }
function LOL () {}
= new Game()
= LOL
2. 构造函数继承
(1)经典继承
优点:解决共享属性 + 向父类传参问题
缺点:原型链上的共享方法无法直接读取继承(getName)
function Game (arg) { = 'LOL'}
= function () { return }
function LOL (arg) {
(this, arg)
}
(2)组合继承
解决原型链上的共享方法无法直接读取继承(getName)问题
缺点:性能(两次执行 = ‘LOL’)
function Game (arg) { = 'LOL'}
= function () { return }
function LOL (arg) {
(this, arg)
}
= new Game()
= LOL
3. 寄生组合继承
function Game (arg) { = 'LOL'}
= function () { return }
function LOL (arg) {
(this, arg)
}
= ()
= LOL
new Object() 和 () 区别?
后者可以继承传入的 arg 参数
4. 多重继承
function Game (arg) { = 'LOL'}
= function () { return }
function Store () { = 'Steam' }
= function () { return }
function LOL (arg) {
(this, arg)
}
= ()
(, )
= LOL
六. 经典闭包
函数外部可以获取到函数作用域内的变量值
function mail() {
let content = '这是内容'
return function () {
(content)
}
}
const getMail = mail()
getMail() // 可以输出
如何实现私有属性? 采用闭包。
Class Mail {
constructor () {
let _content = '这是内容'
= () => {
(_content)
}
}
}
箭头函数无 constructor,无法实例化
七. 发布订阅者
class Publisher {
constructor() {
= []
}
// 以电话号码作为订阅的标识
add(phoneNumber,cb) {
({
phoneNumber,
cb
})
}
// 取消订阅
remove(phoneNumber) {
const index = (item => === phoneNumber)
(index, 1)
}
// 通知
publish(msg) {
(item => {
(msg)
})
}
getSubscriberList() {
()
}
}
const publisher = new Publisher()
(12345678 ,function (msg) {
('第一个订阅者收到了消息:' + msg)
})
let i = 1
// 通知
setInterval(() => {
(i++)
()
}, 1000)
setTimeout(() => {
(999888, function (msg) {
('第二个订阅者收到:' + msg)
})
}, 1000 * 5)
八. 移动端1px边框
- 用图片
- 用阴影模拟
.box-shadow-1px {
box-shadow: inset 0px -1px 1px -1px #c8c7cc;
}
- 用伪类
.border-1px:before {
content: " ";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
border-top: 1px solid #D9D9D9;
color: #D9D9D9;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
九. 合并二维有序数组成一维有序数组,归并排序的思路
var arr = [1, 2, 5, 6, [3, 4]]
var arr1 = ().split(',')
var arr2 = (Infinity)
function flatten(arr, deep = 1) {
return deep > 0 ?
((acc, val)=> ((val) ? flatten(val, deep-1) : val), [])
: ()
}
// (flatten(arr, Infinity));
十. 斐波那契数列 1 1 2 3 5 8 13 , arr[n] = arr[n-1] + arr[n-2]
// 循环
function feibona1(n) {
let n1 = 1;
let n2 = 1;
let sum = 1;
for (let i = 1; i < n; n++){
sum = n1 + n2
n1 = n2
n2 = sum
}
return sum
}
// 普通递归
function feibona2(n){
if(n === 1 || n ===2){
return 1
}
return feibona2(n-2) + feibona2(n-1)
}
// 改进递归-把前两位数字做成参数避免重复计算
function feibona3(n){
function fei(m, v1, v2){
if(m === 1) return v1
if(m === 2) return v2
return fei(m-1, v2, v1+v2)
}
fei(n, 1, 1)
}
十一. 字符串出现的不重复最长长度
var lengthOfLongestSubstring = function(s) {
var res = 0; // 用于存放当前最长无重复子串的长度
var str = ""; // 用于存放无重复子串
var len = ;
for(var i = 0; i < len; i++) {
var char = (i);
var index = (char);
if(index === -1) {
str += char;
res = res < ? : res;
} else {
str = (index + 1) + char;
}
}
return res;
};
// (longStr('abcdaouritbjhlfcldfkmdf'));
十二. 简单实现loadsh的_get
function get(obj, keys, det){
return (/\./).reduce((o, v) => {
return (o || {})[v]},
obj) || det
}
get({ 'a': [{ 'b': { 'c': 3 } }] }, 'a.', '1111')
十三. 实现add(1)(2)(3)
function add(...args){
return (
(a,b)=> a+b
)
}
function currying(fn){
let args=[]
return function temp(...newArgs){
if(){
args = [...args, newArgs]
return temp
} else {
let val = (this, args)
args = []
return val
}
}
}
let addCurry = currying(add)
// (addCurry(1)(2)(3)(4, 5)())
// (addCurry(1)(2)(3, 4, 5)())
// (addCurry(1)(2, 3, 4, 5)())
// (add(1)(2)(3).toString())
十四. 手写数组转树 & 树转数组
数组->树
const arrtree = [
{id:1, parentId: null, name: 'a'},
{id:2, parentId: null, name: 'b'},
{id:6, parentId: 3, name: 'f'},
{id:7, parentId: 4, name: 'g'},
{id:8, parentId: 7, name: 'h'},
{id:3, parentId: 1, name: 'c'},
{id:4, parentId: 2, name: 'd'},
{id:5, parentId: 1, name: 'e'}
]
function arrayTree(arr){
if(!(arr) || !) return
let map = {}
(v => map[] = v)
let list = []
(v => {
const item = map[]
if(item){
( || ( = [])).push(v)
} else {
(v)
}
})
return list
}
// (arrayTree(arrtree))
树->数组
// 将树数据转化为平铺数据
flatTreeData(treeData: any[], childKey = 'children') {
const arr: any[] = [];
const expanded = (data: any) => {
if (data && > 0) {
data
.filter((d: any) => d)
.forEach((e: any) => {
(e);
expanded(e[childKey] || []);
});
}
};
expanded(treeData);
return arr;
}
十五. 手写一个数据双向绑定
let input = ('value')
let text = ('text')
let data = { value: '' }
=
=
// 数据劫持
(data, 'value', {
set: function (val) {
= val
= val
},
get: function () {
return
}
})
('keyup', function (){
=
(data);
})
十六. 手写 new
const customNew = (Func, ...args) => {
// 创建一个空的简单JavaScript对象
const obj = ({})
// 链接该对象到prototype
obj.__proto__ =
// 将创建的对象作为this的上下文
const res = (obj, args)
// 如果该函数没有返回对象,则返回this
return res instanceof Object ? res : obj
}
十七. 判断一个单词是否回文
function checkPalindrom(str) {
return ('').reverse().join() === str
}
十八. 实现call、apply、bind
= function (context, ...args) {
if (this === ) {
return undefined; // 用于防止 () 直接调用
}
const _this = context || window;
_this.fn = this
const res = _this.fn(...args)
delete _this.fn
return res
}
= function (context, args) {
if (this === ) {
return undefined; // 用于防止 () 直接调用
}
const _this = context || window;
_this.fn = this
let res
if ((args)) {
res = _this.fn(...args)
} else {
res = _this.fn()
}
delete _this.fn
return res
}
= function (context) {
if (this === ) {
throw new TypeError('Error')
}
const _this = this
return function F(...args) {
if (this instanceof F) {
return new _this(...args)
} else {
return _this.apply(context, args)
}
}
}
十九. 手写 instanceof
右边变量的原型在左边变量的原型链上
const customInstanceof = (target, origin) => {
let proto = target.__proto__, index = 0
while (proto !== ) {
proto = proto.__proto__
if (!proto) {
return false
}
index += 1
}
(index);
return true
}
二十. 数组去重
// ES6
(new Set(arr))
// 遍历
var arrreset = [1,2,3,4,3,4,5,2,7,4]
var arrreset1 = (new Set(arrreset))
let arrreset2 = []
(v => {
if((v) === -1){
(v)
}
})
二十一. 手写
// 将传入的对象作为原型
function create(obj) {
function F(){}
= obj
return new F()
}
二十二. 深拷贝deepclone
export function deepClone(source: any) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments');
}
const targetObj: any = === Array ? [] : {};
(source).forEach((keys: any) => {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys]);
} else {
targetObj[keys] = source[keys];
}
});
return targetObj;
}
() 缺点
- 对象中有时间类型的时候,序列化之后会变成字符串类型。
- 对象中有undefined和Function类型数据的时候,序列化之后会直接丢失。
- 对象中有NaN、Infinity和-Infinity的时候,序列化之后会显示 null。
- 对象循环引用的时候,会直接报错。
const obj = {
nan:NaN,
infinityMax:1.7976931348623157E+10308,
infinityMin:-1.7976931348623157E+10308,
undef: undefined,
fun: () => { ('叽里呱啦,阿巴阿巴') },
date:new Date,
}
二十三. 防抖、节流+装饰器
/**
* 节流
* @param fn 函数
* @param delay 延迟时间(毫秒单位),默认200毫秒
* @returns {Function}
*/
export function throttle(fn: any, delay = 200) {
let lastCall = 0
return function (...args: any[]) {
const now = new Date().getTime()
if (now - lastCall < delay) return
lastCall = now
fn(...args)
}
}
/**
* 防抖
* @param fn {Function} 实际要执行的函数
* @param delay {Number} 延迟时间,也就是阈值,单位是毫秒(ms)
* @return {Function} 返回一个“去弹跳”了的函数
*/
function debounce(fn, delay) {
let timer = null
return function(...arg) {
clearTimeout(timer)
timer = setTimeout(() => {
fn(...arg)
}, delay)
}
}
/**
* 装饰器防抖
* @param {number} delay
* @return {*}
*/
export const Debounce = (delay = 500) => {
let timeoutDeb = null;
return (target, propertyKey, descriptor) => {
const original = ;
= function(...args) {
clearTimeout(timeoutDeb);
timeoutDeb = setTimeout(() => {
(this, ...args);
}, delay);
};
};
};
低频
一. rgb转16进制颜色
利用正则 match 方法取出r、g、b 数值,然后分别转换16进制(不足两位补0),最后拼接加 # 转大写
function rgb2hex(rgb) {
const rgb = (/\d+/g); // 分别取出连续数字即为 r/g/b
const hex = (n) => {
return ("0" + Number(n).toString(16)).slice(-2);
}
return ((acc, cur) => acc + hex, '#').toUpperCase()
}
获取 rgb 还可以通过 split(‘,’)
const rgb = (/(?:\(|\)|rgb|RGB)*/g, '').split(',')
二. 千位符
function toTousands(num){
let n = ().split('.')[0],
decal = ().indexOf('.') !== 1 ? '.' + ().split('.')[1] : '';
let result = ''
while( > 3) {
result = ',' + (-3) + result
n = (0, -3)
}
return (n || '') + result + decal
}
三. JS手写最大并发控制实现方式
class LimitResquest {
constructor(limit){
= limit
= 0
= []
}
request(reqFn) {
if(!reqFn || !(reqFn instanceof Function)){
('当前请求不是一个function', reqFn)
return
}
(reqFn)
if( < ){
()
}
}
async run() {
try{
++
const fn = ()
await fn()
} catch(e) {
(e)
} finally{
--
if( > 0){
()
}
}
}
}
let a = () => new Promise((resolve) => {
setTimeout(() => {resolve(1)}, 1000)
}).then((data) => (data))
let b = () => new Promise((resolve) => {
setTimeout(() => {resolve(2)}, 1000)
}).then((data) => (data))
let c = () => new Promise((resolve) => {
setTimeout(() => {resolve(3)}, 1000)
}).then((data) => (data))
let d = () => new Promise((resolve) => {
setTimeout(() => {resolve(4)}, 1000)
}).then((data) => (data))
let limitResquest = new LimitResquest(2)
(a)
(b)
(c)
(d)
(a)
(b)
(c)
(d)
四. 实现一个简单的路由
class Route{
constructor(){
= {} // 整个路由对象
= '' // 当前路由
= (this)
('load', , false)
('hashchange', , false)
}
storeRoute(path, cb){
[path] = cb || function() {}
}
freshRoute(){
= (1) || '/'
[]()
}
}
五. rem 自适应
function resetFontSize(){
var baseFontsize = 100
var designWidth = 750
var width =
var currFonrsize = (width/designWidth) * baseFontsize
('html')[0].fontSzie = currFonrsize + 'px'
}
= function (){
resetFontSize()
}
resetFontSize()
六. 懒加载
// 获取所有图片的标签
const imgs = ('img')
// 获取可视区域的高度
const viewHeight = ||
// num 计算当前显示到哪一张图片,避免每次都从第一张图片开始检查是否需要
let num = 0, len =
function lazyLoad() {
for(let i = num; i < len; i ++) {
// 用可视区域的高度减去匀速顶部距离可视区域的高度
let distance = viewHeight - migs[i].getBoundingClientRect().top
if(distance > 20){
imgs[i].src = imgs[i].getAttribute('data-src')
}
}
}
// 最好添加节流
('sroll', lazyLoad)
七. 手写fetch拦截器
(function() {
let interceptor_req = [], interceptor_res = []
function c_fetch(url, init = {}) {
= || 'GET'
interceptor_req.forEach(interceptor => {
init = interceptor(init)
})
return new Promise((resolve, reject) => {
fetch(url, init).then(res => {
interceptor_res.forEach(interceptor => {
res = interceptor(res)
})
resolve(res)
}).catch(err => {
(err)
})
})
}
c_fetch.interceptor = {
request: {
use: function(callback){
interceptor_req.push(callback)
}
},
response: {
use: function(callback){
interceptor_res.push(callback)
}
}
}
// export default c_fetch
})()