1.js防抖与节流
函数防抖:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
function debounce(fn, delay, context){
let timer = null;
return function(){
const args = arguments;
if(timer){
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(context,args);
}, delay);
}
}
函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
function throttle1(fn, delay){
let now = new Date().getTime();
return function(){
if(new Date().getTime() - now >= delay){
fn();
now = new Date().getTime();
}
}
}
function throttle2(fn, delay, context){
let canRun = true;
return function(){
if(!canRun){
return
}
canRun = false;
const args = arguments;
setTimeout(() => {
fn.apply(context, args);
canRun = true;
}, delay);
}
}
区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
2.深拷贝
function deepClone1(obj){
return JSON.parse(JSON.stringify(obj));
}
function deepClone2(obj){
let result = Array.isArray(obj) ? [] : {};
for(let key in obj){
if(obj.hasOwnProperty(key)){
if (typeof obj[key] === 'object' && obj[key] !== null) {
result[key] = deepClone2(obj[key]);
} else {
result[key] = obj[key];
}
}
}
return result
}
3.数组乱序
function mixArr(arr){
return arr.sort(() => {
return Math.random - 0.5
})
}
4.数组flat
function flat(arr, deep = 1){
const result = [];
for(let item of arr){
if (Array.isArray(item) && deep >= 1) {
result.push(...flat(item, deep - 1));
} else {
result.push(item);
}
}
return result
}
5.数组filter
Array.prototype.myFilter = function(fn, context){
if(typeof fn !== 'function'){
throw new TypeError('first params must be function!')
}
const arr = this.concat(),len = arr.length,result = [];
for(let i = 0;i < len;i++){
fn.call(context, arr[i], i, this) && result.push(arr[i]);
}
return result
}
6.手写call、apply、bind
// 通过对象调用方法的特性,将函数中this对象指向call函数this参数,然后使用eval调用函数使得参数添加到要执行的函数中
Function.prototype.mycall = function(){
if (typeof this !== 'function') {
throw new TypeError('mycall must be called by function!')
}
let context = arguments[0],
otherArgs = [],
result;
for(let i = 1;i < arguments.length;i++){
otherArgs.push(`arguments[${i}]`);
}
context.func = this;
result = eval('context.func('+ otherArgs + ')');
delete context.func
return result
}
// apply 实现原理同上
Function.prototype.myapply = function(){
if(typeof this !== 'function'){
throw TypeError('myapply must be called by function.')
}
const thisArg = arguments[0] || window,
arr = arguments[1],
otherArgs = [];
for(let i = 0;i < arr.length;i++){
otherArgs.push('arr['+ i +']');
}
thisArg.func = this;
const result = eval('thisArg.func(' + otherArgs + ')');
delete thisArg.func;
return result
}
// bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
Function.prototype.mybind = function(){
if (typeof this !== 'function') {
throw TypeError("Bind must be called by function")
}
const slice = Array.prototype.slice,
thisArg = Array.prototype.shift.call(arguments),
otherArgs = slice.call(arguments),
self = this,
function F(){};
F.prototype = self.prototype;
const bindFunc = function(){
return self.apply(this instanceof F ? this : thisArg, otherArgs.concat(slice.call(arguments)));
}
bindFunc.prototype = new F();
return bindFunc
}
7.继承
// ES5(寄生组合式继承)
function Parent(name,age) {
this.name = name;
this.age = age;
}
Parent.prototype.sayName = function(){
console.log(this.name);
}
function Child(name, age, score) {
Parent.apply(this,[name, age]);
this.score = score;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.sayScore = function(){
console.log(this.score);
}
// ES6
class Parent {
construtor(name, age){
this.name = name;
this.age = age;
}
sayName(){
console.log(this.name);
}
}
class Child extends Parent {
construtor(name, age, score){
super(name, age);
this.score = score;
}
sayScore(){
console.log(this.score);
}
}
8.instanceof
// instanceof 原理就是检查实例(左侧)的原型链上是否含有构造函数(右侧)的原型对象(prototype)
function myinstanceof(left,right){
let prototype = right.prototype, //获取构造函数的原型对象
proto = Object.getPrototypeOf(left);//获取实例的原型链上的原型
while(proto){
if(proto === prototype){
return true
}
proto = Object.getPrototypeOf(proto);
}
return false
}
//其实使用Object.prototype.toString.call(instance)更准确
- new操作符
function mynew(){
const constructor = Array.prototype.shift.call(arguments);
const context = Object.create(constructor.prototype);
const result = constructor.apply(context, arguments);
return typeof result === 'object' ? result : context;
}
- 函数的柯里化
/*
函数柯里化:
1.参数复用-复用最初函数的参数
2.提前返回-返回接受余下参数且返回结果的新函数
3.延迟执行-返回新函数,等待执行
*/
function curry(fn) {
const slice = Array.prototype.slice;
const args = slice.call(arguments, 1);
return function() {
const otherArgs = slice.call(arguments);
return fn.apply(this,args.concat(otherArgs));
}
}
柯里化扩展:实现一个函数add,其功能是可以无限的调用添加参数,最终输出所有参数相加结果。例如: a(1)(2)(3) 输出 6, a(1)(5)(1)(5)(8) 输出 20,以此类推。
// 实际上这个函数调用后的返回值不是一个number类型,是function类型。但是 infiniteAdd(1)(2)(3) == 6 是 true,且infiniteAdd(1)(2)(3) + 1会输出7,因为==和+ 会进行隐式转换,调用infiniteAdd(1)(2)(3)返回的函数的toString方法最终得到6,然后再继续计算。
function infiniteAdd(){
let arr = [...arguments];
function recursion(){
arr.push(...arguments);
return recursion
}
recursion.toString = function(){
return arr.reduce((a, b) => {
return a + b
}, 0);
}
return recursion
}
11.jsonp
//jsonp 的作用是跨域。原理是通过动态插入script标签来实现跨域,因为script脚本不受同源策略的限制。它由两部分组成:回调函数和数据
function jsonp(config) {
const {url, data} = config;
if(!url){
throw new TypeError('params must includes url property');
}
return new Promise((resolve,reject)=>{
const script = document.createElement('script');
const header = document.querySelector('head');
const callback = `jsonp_${Date.now()}`;
script.src = `${url}?${handleData(Object.assign(data,{callback: callback}))}`;
header.appendChild(script);
window[callback] = function(res){
res ? resolve(res) : reject('fail');
header.removeChild(script);
window[callback] = null;
}
})
}
function handleData(data){
return Object.keys(data).map((item)=>{
return `${item}=${data[item]}`
}).join('&');
}
- lazyMan
实现一个LazyMan,可以按照以下方式调用:
LazyMan("Hank")输出:
Hi! This is Hank!
LazyMan("Hank").sleep(10).eat("dinner")输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
LazyMan("Hank").eat("dinner").eat("supper")输出
Hi This is Hank!
Eat dinner~
Eat supper~
LazyMan("Hank").sleepFirst(5).eat("supper")输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
以此类推。
class Process{
constructor(name){
this.queue = [];
this.name = name;
this.queue.push(()=>{
console.log(`Hi! This is ${this.name}!`);
this.next();
})
setTimeout(() => {
this.next();
}, 0);
}
next(){
const fn = this.queue.shift();
typeof fn === 'function' && fn();
}
sleep(num){
this.queue.push(()=>{
setTimeout(() => {
console.log(`Wake up after ${num}`);
this.next();
}, num * 1000);
})
return this
}
eat(food){
this.queue.push(()=>{
console.log(`Eat ${food}~`);
this.next();
})
return this
}
sleepFirst(num){
this.queue.unshift(()=>{
setTimeout(() => {
console.log(`Wake up after ${num}`);
this.next();
}, num * 1000);
})
return this
}
}
function LazyMan(name){
return new Process(name);
}
13.实现eventEmitter
观察者模式是我们工作中经常能接触到的一种设计模式。用过 jquery的应该对这种设计模式都不陌生。eventEmitter 是 node中的核心,主要方法包括on、emit、off、once。
class EventEmitter {
constructor(){
this.events = {}
}
on(name,cb){
if(!this.events[name]){
this.events[name] = [cb];
}else{
this.events[name].push(cb)
}
}
emit(name,...arg){
if(this.events[name]){
this.events[name].forEach(fn => {
fn.call(this,...arg)
})
}
}
off(name,cb){
if(this.events[name]){
this.events[name] = this.events[name].filter(fn => {
return fn != cb
})
}
}
once(name,fn){
var onlyOnce = () => {
fn.apply(this,arguments);
this.off(name,onlyOnce)
}
this.on(name,onlyOnce);
return this;
}
}
14.sleep函数
//sleep 函数的作用就是延迟指定时间后再执行接下来的函数
function sleep(fn, timestamp) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, timestamp);
}).then(() => {
fn();
})
}
15.将一个同步callback包装成promise形式
同步的 callback 用的最多的是在 node 的回调中,例如下面这种,包装完之后就可以愉快的使用 .then 了。
function promisify(fn, context) {
return (...args) => {
return new Promise((resolve, reject)=>{
fn.apply(context, [...args,(err,data)=>{
err ? reject(err) : resolve(data);
}])
})
}
}
16.写一个函数,可以控制最大并发数
微信小程序最一开始对并发数限制为5个,后来升级到10个,如果超过10个会被舍弃。后来微信小程序升级为不限制并发请求,但超过10个会排队机制。也就是当同时调用的请求超过 10 个时,小程序会先发起 10 个并发请求,超过 10 个的部分按调用顺序进行排队,当前一个请求完成时,再发送队列中的下一个请求。
class Controller {
constructor(num){
this.queue = [];
this.limit = num;
}
run(){
let i = 0;
while(this.queue.length > 0 && i < this.limit){
this.next();
i++;
}
}
next(){
let fn = this.queue.shift();
if(typeof fn === 'function'){
fn().then((data)=>{
console.log(data);
}).catch((err)=>{
console.error('报错啦', err);
}).finally(()=>{
this.next();
})
}
}
addTask(fn){
this.queue.push(fn);
}
}
// 测试用例
// let arr = [1,2,3,4,5,6,7,8,89,9,22,234,100,99],
// controller = new Controller(5),
// sum = 0;
// arr.forEach((item)=>{
// controller.addTask(()=>{
// return new Promise(resolve => {
// sum++;
// setTimeout(() => {
// console.log("并发量", sum);
// sum--;
// resolve(item);
// }, 3000);
// })
// })
// })
// controller.run();
17.手写promise
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
}
});
return promise2;
}
catch(fn) {
return this.then(null, fn);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if (called) return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
18.手写数组reduce方法
Array.prototype.myReduce = function(){
if(Object.prototype.toString.call(this) !== '[object Array]'){
throw new TypeError('myReduce must be called by Array');
}
if(Object.prototype.toString.call(arguments[0]) !== '[object Function]'){
throw new TypeError('first params must be function');
}
let arr = this, accumulator;
if(arr.length === 0){
if(arguments.length < 2){
throw new TypeError('empty Array must provide second params');
}else{
return arguments[1]
}
}
if(arr.length === 1 && arguments.length === 1){
return arr[0]
}
let fn = arguments[0],
len = arr.length,
i;
if(arguments.length < 2){
i = 1;
accumulator = arr[0];
}else{
i = 0
accumulator = arguments[1];
}
for(;i < len;i++){
if(arr.hasOwnProperty(i)){
accumulator = fn(accumulator,arr[i],i,arr)
}
}
return accumulator
}
// MDN Polyfill
Array.prototype.myReduce = function (callback /*, initialValue*/) {
if (this === null) {
throw new TypeError('Array.prototype.myReduce ' + 'called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 1. Let O be ? ToObject(this value).
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// Steps 3, 4, 5, 6, 7
var k = 0;
var value;
if (arguments.length >= 2) {
value = arguments[1];
} else {
while (k < len && !(k in o)) {
k++;
}
// 3. If len is 0 and initialValue is not present,
// throw a TypeError exception.
if (k >= len) {
throw new TypeError('Reduce of empty array ' + 'with no initial value');
}
value = o[k++];
}
// 8. Repeat, while k < len
while (k < len) {
// a. Let Pk be ! ToString(k).
// b. Let kPresent be ? HasProperty(O, Pk).
// c. If kPresent is true, then
// i. Let kValue be ? Get(O, Pk).
// ii. Let accumulator be ? Call(
// callbackfn, undefined,
// « accumulator, kValue, k, O »).
if (k in o) {
value = callback(value, o[k], k, o);
}
// d. Increase k by 1.
k++;
}
// 9. Return accumulator.
return value;
}
//测试用例
['1',null,undefined,,3,4].reduce((a,b) => a+b); //"1nullundefined34"
['1',null,undefined,,3,4].myReduce((a,b) => a+b); //"1nullundefined34"
- 深度优先搜索(DFS)和广度优先搜索(BFS)
深度优先搜索(depth first search),是从根节点开始,沿树的深度进行搜索,尽可能深的搜索分支。当节点所在的边都已经搜多过,则回溯到上一个节点,再搜索其余的边。深度优先搜索采用栈结构,后进先出。
广度优先搜索(breadth first search),是从根节点开始,沿树的宽度进行搜索,如果所有节点都被访问,则算法中止。广度优先搜索采用队列的形式,先进先出。
//DFS
function maxLevel (node){
let max = 0;
function recursion(node, level){
console.log('===============',node.id);
let child = node.children;
if(child && child.length > 0){
child.forEach(item => {
recursion(item, level + 1);
});
} else {
max = max > level ? max : level;
}
}
recursion(node, 1);
return max
}
//BFS
function maxNum (node){
let max = 0, queue = [node], arr = [];
while(queue.length > 0){
let node = queue.shift();
console.log('===============',node.id);
if(node.children && node.children.length > 0){
arr.push(...node.children);
}
if(queue.length === 0){
max = max > arr.length ? max : arr.length;
queue = arr;
arr = []
}
}
return max
}