1. 防抖
防抖是指定时间内只触发一次函数,如果还没到指定时间,就触发了事件,则计时器会重新设置,保证只有最后一次才会触发函数。一般是为了防止用户连续点击事件触发按钮。
function debounce(fn, ms) {
let timer = null;
return function() {
if(timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, ms);
}
}
2. 节流
节流是保证间隔时间内只触发一次函数,也就是每隔一段时间,就会触发一次函数,这一点跟防抖是不一样的。一般是在监听页面滚动时用的比较多。
function throttle(fn, ms) {
let preTime = null;
return function() {
let nowTime = new Date().getTime();
if(!preTime || nowTime - preTime > ms) {
fn.apply(this, arguments);
preTime = nowTime;
}
}
}
3. 实现call函数
call的常规用法是fn.call(this, param1, param2);
Function.prototype.myCall = function(context) {
if(typeof this !== 'function') throw new TypeError('type error');
let result;
let args = [...arguments].slice(1);
context = context || window;
context.fn = this;
result = context.fn(...args);
delete context.fn;
return result;
}
4. 实现apply函数
apply的常规用法是fn.apply(this, [param1, param2]);
Function.prototype.myApply = function(context) {
if(typeof this !== 'function') throw new TypeError('type error');
let result = null;
let args = arguments[1];
context = context || window;
context.fn = this;
if(args) {
result = context.fn(...args);
}else{
result = context.fn();
}
delete context.fn;
return result;
}
5. 实现bind函数
bind的常规用法是fn.bind(this, param1, param2)();
Function.prototype.myBind = function(context) {
if(typeof this !== 'function') throw new TypeError('type error');
let fn = this, args = [...arguments].slice(1);
return function Fn() {
return fn.apply(
this instanceof Fn? this: context,
args.concat(...arguments)
);
}
}
6. 实现发布订阅模式
class EventEmitter {
constructor() {
this.caches = [];
}
on(name, fn) {
if(this.caches[name]) {
this.caches[name].push(fn);
}else{
this.caches[name] = [fn];
}
}
off(name, fn) {
const task = this.caches[name];
if(task) {
const idx = task.findIndex(f => {
return f === fn || f.callback === fn;
});
if(idx > -1) {
task.splice(idx, 1);
}
}
}
emit(name, once = false) {
const task = this.cache[name];
if(task) {
for(let fn of task) {
fn();
}
if(once) {
delete this.cache[name];
}
}
}
}
7. 实现斐波那契数列
斐波那契数列的公式是: F(0) = 0; F(1) = 1; F(n) = F(n - 1) + F(n - 2);
function fib(n) {
if(n < 0) throw new Error('数字不能小于0');
if(n == 0 || n == 1) return n;
let arr = [];
arr[0] = 0, arr[1] = 1;
for(let i = 1; i < n; i++) {
arr[i + 1] = arr[i] + arr[i - 1];
}
return arr[n];
}
8. 实现add(1)(2)(3)
const addFn = (...args) => args.reduce((count, curr) => count + curr, 0);
function curring(fn) {
let args = [];
return function temp(...newArgs) {
if(newArgs.length > 0) {
args.push(...newArgs);
return temp;
}else{
let val = fn.apply(this, args);
args = [];
return val;
}
}
}
const add = curring(addFn);
console.log(add(1)(2)(3)());
9. 实现flat函数
数组的flat函数用来将数组维度打平,比如arr.flat(2)就是打平两个维度,直接arr.flat()等同于arr.flat(1),arr.flat(Infinity)是打平任意深度的数组。
Array.prototype.myFlat(depth = 1) {
if(Object.prototype.toString.call(this).slice(8, -1) !== 'Array'){
throw new TypeError('type is not Array');
}
return this.reduce((res, curr) => {
return res.concat(
Array.isArray(curr) && depth > 1? curr.myFlat(depth - 1): curr
);
}, []);
}
10. 实现定时函数,时间间隔是a,a+b,a+2b,a+(n*b),并定义清除定时的方法
function myTimeout(fn, a, b) {
let count = 0;
let timer = null;
const loop = () => {
timer = setTimeout(() => {
fn();
count++;
loop();
}, a + count * b);
}
loop();
return () => {
if(timer) clearTimeout(timer);
}
}
11. 实现lodash中的.get()
lodash中的get使用方法如下:
let obj = { a: { b: { val: 10 } } };
let val = _.get(obj, 'a.b.val'); //找到val会返回10,如果找不到会返回undefined而不会报错
function myGet(source, path, defaultValue = undefined) {
const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.');
let result = source;
for(let p of paths) {
result = Object(result[p]);
if(result === undefined) return defaultValue;
}
return result;
}
12. 实现Promise
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
const resolvePromise = (promise, x, resolve, reject) => {
if(x === promise) throw new Error('循环引用');
if(x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called;
try{
let then = x.then;
if(typeof then === 'function') {
then.call(x, y => {
if(called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
}, e => {
if(called) return;
called = true;
reject(e);
});
}else{
resolve(x);
}
}catch(err){
if(called) return;
called = true;
reject(err);
}
}
}
function myPromise(constructor) {
let self = this;
self.status = PENDING;
self.value = undefined;
self.error = undefined;
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(val) {
if(val instanceof myPromise) {
return val.then(resolve, reject);
}
setTimeout(() => {
if(self.status === PENDING) {
self.status = FULFILLED;
self.value = val;
self.onFulfilledCallbacks.forEach(cb => {
return cb(self.value);
});
}
},0);
}
function reject(error) {
setTimeout(() => {
if(self.status === PENDING) {
self.status = REJECTED;
self.error = error;
self.onRejectedCallbacks.forEach(cb => {
return cb(self.error);
});
}
},0);
}
try{
constructor(resolve, reject);
}catch(err) {
reject(err);
}
}
myPromise.prototype.then = function(onFulfilled, onRejected) {
const self = this;
let newPromise;
onFulfilled = typeof onFulfilled === 'function'? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'? onRejected : err => { throw err };
if(self.status === FULFILLED) {
return newPromise = new myPromise((resolve, reject) => {
setTimeout(() => {
try{
let x = onFulfilled(self.value);
resolvePromise(newPromise, x, resolve, reject);
}catch(err){
reject(err);
}
}, 0);
});
}
if(self.status === REJECTED) {
return newPromise = new myPromise((resolve, reject) => {
setTimeout(() => {
try{
let x = onRejected(self.error);
resolvePromise(newPromise, x, resolve, reject);
}catch(err){
reject(err);
}
}, 0);
});
}
if(self.status === PENDING) {
return newPromise = new myPromise((resolve, reject) => {
self.onFulfilledCallbacks.push(val => {
try{
let x = onFulfilled(val);
resolvePromise(newPromise, x, resolve, reject);
}catch(e){
reject(e);
}
});
self.onRejectedCallbacks.push(err => {
try{
let x = onRejected(err);
resolvePromise(newPromise, x, resolve, reject);
}catch(e){
reject(e);
}
});
});
}
}
13. 实现Promise.all
Promise.all用来处理需要等待多个异步请求结果的场景,常规使用如下:
const p1 = new Promise((resolve, reject) => {
resolve('p1成功');
});
const p2 = new Promise((resolve, reject) => {
resolve('p2成功');
});
const p3 = new Promise((resolve, reject) => {
reject('p3失败');
});
Promise.all([p1,p2]).then(resArr => {
//最终返回是一个包含了多个结果的数组
console.log(resArr);
}).catch(err => {
//多个异步请求中,只要有一个promise返回了reject,就会进入失败回调
//上面的p3如果加入运行数组中,就会导致进入catch回调
console.error(err);
});
手写实现如下:
Promise.prototype.myAll = function(promises) {
if(!Array.isArray(promises)) throw new TypeError('必须传入一个数组');
return new Promise((resolve, reject) => {
let resArr = [];
let count = 0;
const resByKey = (res, index) => {
resArr[index] = res;
count++;
if(count === promises.length) {
resolve(resArr);
}
};
promises.forEach((promise, index) => {
promise.then(res => {
resByKey(res, index);
}, reject);
});
});
}
14. 实现Promise.race
Promise.race用来处理多个并行的promise请求,且只返回最先出结果的请求,使用示例如下:
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1成功');
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p2失败');
}, 200);
});
//这里最终会进入catch回调打印出p2失败,因为p2比p1先返回了结果
Promise.race([p1, p2]).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
手写实现如下:
Promise.prototype.myRace = function(promises) {
if(!Array.isArray(promises)) throw new TypeError('必须传入一个数组');
return new Promise((resolve, reject) => {
promises.forEach(p => {
p.then(res => {
resolve(res);
}, err => {
reject(err);
});
});
});
}
15. 实现new
new具体做了哪些事情,可以看看掘友写的这篇文章,讲的还是比较清楚的:面试官问:能否模拟实现JS的new操作符
function myNew(constructor) {
if(typeof constructor !== 'function') throw new TypeError('constructor must be a function');
myNew.target = constructor;
let obj = Object.create(constructor.prototype);
let args = [...arguments].slice(1);
let result = constructor.apply(obj, args);
const isObject = typeof result === 'object' && result !== null;
const isFunction = typeof result === 'function';
if(isObject || isFunction) {
return result;
}else{
return obj;
}
}
16. 手写数组转树
let arr = [
{id: 1, name: '部门1', parentId: 0},
{id: 2, name: '部门2', parentId: 1},
{id: 3, name: '部门3', parentId: 1},
{id: 4, name: '部门4', parentId: 2},
{id: 5, name: '部门5', parentId: 3},
{id: 6, name: '部门6', parentId: 4},
{id: 7, name: '部门7', parentId: 2},
{id: 8, name: '部门8', parentId: 3},
];
function convert(arr) {
let map = arr.reduce((acc, cur) => {
acc[cur.id] = cur;
return acc;
}, {});
let result = [];
for(let key in map) {
const item = map[key];
if(item.parentId == 0) {
result.push(item);
}else{
const parent = map[item.parentId];
if(parent) {
parent.children = parent.children || [];
parent.children.push(item);
}
}
}
return result;
}
console.log(convert(arr));
17. 使用ES6的Proxy实现数组负索引,例如,可以简单地使用arr[-1]替代arr[arr.length-1]访问最后一个元素,[-2]访问倒数第二个元素
function proxyArray(arr) {
const len = arr.length;
return new Proxy(arr, {
get(target, key) {
key = Number(key);
while(key < 0) {
key += len;
}
return target[key];
}
});
}
var a = proxyArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
console.log(a[1]); // 2
console.log(a[-10]); // 9
console.log(a[-20]); // 8
18. 实现深拷贝
function deepClone(obj) {
if(obj === null) return null;
if(typeof obj !== 'object') return obj;
if(obj instanceof Date) return new Date(obj);
if(obj instanceof RegExp) return new RegExp(obj);
let newObj = Object.create(obj.prototype);
Object.keys(obj).forEach(key => {
newObj[key] = deepClone(obj[key]);
});
return newObj;
}
19. 实现事件委托
<ul>
<li class="li">1</li>
<li class="li">2</li>
<li class="li">3</li>
</ul>
<script type="text/javascript">
function myEntrust(to, evtType, from, fn) {
const dom = document.querySelector(to);
dom.addEventListener(evtType, function(evt) {
let target = evt.target || evt.srcElement;
if(/^[.]/.test(from)) {
if(target.className === from.slice(1)) {
fn.call(target);
}
}else{
if(target.nodeName.toLowerCase() === from) {
fn.call(target);
}
}
});
}
myEntrust('ul', 'click', '.li', function(){
console.log('li~~');
});
</script>