JS手写题记录

1、promise.all

function promiseAll(promiseArr) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promiseAll)) return reject("应传入数组");
        let result = [];

        promiseAll.forEach((promise) => {
            Promise.resolve(promise).then(
                (data) => {
                    result.push(data);
                },
                (err) => {
                    // 一旦有一个错误马上返回
                    return reject(err);
                }
            );
        });
        resolve(result);
    });
}

2、深拷贝(递归)
function cloneDeep(Obj) {
    const ObjType =
        typeof Obj === "object" ?
        Obj instanceof Array ?
        "array" :
        "object" :
        typeof Obj;

    let result = ObjType === "array" ? [] : ObjType === "object" ? {} : "";

    if (typeof Obj === "object") {
        Object.keys(Obj).forEach((key) => {
            // 仍然能遍历,递归
            if (typeof Obj[key] === "object") {
                const objKeyResult = cloneDeep(Obj[key]);
                if (ObjType === "array") {
                    result.push(objKeyResult);
                } else {
                    result[key] = objKeyResult;
                }
            } else {
                // 循环到的这个数值不是Object,加到result里去,结束这一循环
                ObjType === "array" ?
                    result.push(Obj[key]) :
                    ObjType === "object" ?
                    (result[key] = Obj[key]) :
                    (result = Obj[key]);
            }
        });
    } else {
        result = Obj;
    }

    return result;

// 测试
// console.log(
//   JSON.stringify(
//     cloneDeep([
//       {
//         a: [1, { ss: "ss" }, [3], 4, 5, ["sad", "123", true]],
//         age: 10,
//         name: "测试",
//         eat: "测试试一下",
//         show: {
//           skill: "鸡你太美",
//           b: function () {
//             console.log(this.a);
//           },
//         },
//       },
//     ])
//   )
// );
}

3、防抖(自己写的。试过没问题,但是也许其他人有不同的实现方法)
// 防抖
// 在x时间内多次触发同一事件,只执行最后一次。如果反复点击按钮,则连续推迟,直到最后一次。
// 用于:onMouseMove监听、按钮防止连续按、监听onScroll\onResize等连续频繁触发的事件。

/**
 * func => 执行的函数体
 * wait => 等待的时间
 * option => 是否立即执行 'immediate'/'timeout'
 */
function debounce(fun, wait = 300, option = "immediate") {
    let timer = null;
    return () => {
        // 如果在n秒内还有定时未执行,则删除之前的定时任务再次计时
        if (timer) clearTimeout(timer);

        if (option === "immediate") {
            // 默认立即执行,如果上一个定时任务已完成,则可以执行
            let callNow = !timer;

            // 在等待时间后 timer 等于null ,callNow =true
            timer = setTimeout(() => {
                timer = null;
            }, wait);

            //如果 callNow = true 执行
            if (callNow) result = func.apply(this, args);
        } else if (option === "timeout") {
            time = setTimeout(() => {
                func.apply(this, arguments);
            }, wait);
        }

        // 重新设定定时任务
        timer = setTimeout(function() {
            // 如果存在timer定时器,则置空重新存入一个新定时
            timer = null;
        }, wait);

        // 当定时器走完了,才能执行,就是在x秒内连续点击多次,只执行最后一次
        if (callNow) fun.apply(this, arguments);
    };
}

4、节流
①定时器简单版本

// 节流:
// 防抖是延迟执行,而节流是间隔执行,函数节流即每隔一段时间就执行一次,实现原理为设置一个定时器,约定xx毫秒后执行事件,如果时间到了,那么执行函数并重置定时器,
// 和防抖的区别在于,防抖每次触发事件都重置定时器,而节流在定时器到时间后再清空定时器
// 应用场景 => DOM元素拖拽 射击游戏 计算鼠标距离 scroll滚动事件

/**
 * func => 执行的函数体
 * wait => 等待的时间
 * 简单版的,会执行首次
 */
function throttle(func, wait) {
    let timer = null;
    return () => {
        if (!timer) {
            timer = setTimeout(() => {
                clearTimeout(timer);
                func.apply(this, arguments);
            }, wait);
        }
    };
}

包括自定义:可以选择是否立即执行第一次/是否执行最后一次的 时间戳 版本

/**
 * 复杂版的
 * options:{leading : boolean,trailing:boolean}
 */
function throttle(func, wait, options) {
    let timeout = null;
    let previous = 0;
    if (!options) options = { leading: true, trailing: false };

    return () => {
        let now = new Date().getTime();

        // 第一次不会立即执行,remaining=wait,等过了这么久才能执行。
        if (!previous && !options.leading) previous = now;
        // 当前离n毫秒还剩多少,如果超出事件就可以执行了
        const remaining = wait - (now - previous);

        if (remaining <= 0) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(this, ['1', ...arguments]);
        } else if (!timeout && options.trailing) {
            // 最后一次需要执行,如果上一次定时器还没结束,说明是最后一次。
            // 这里意思是如果确实要执行最后一次,就把每一次的最后都记录下来,当timeout为空时表明remaining<0执行过,设定一个定时器计算剩余时间。
            // 如果本次后还未清除此定时器,过了wait时间就会执行,否则那么就不是“最后一次”,remaining<0,自动清除。

            timeout = setTimeout(() => {
                clearTimeout(timeout);
                previous = now;
                func.apply(this, ['2', ...arguments]);
            }, wait);
        }
    };
}

5、驼峰转-间隔 做测试题的时候遇到的,不止转-,还有别的,怕忘了记一下。

function caseTransform(str) {
    const reg = new RegExp("[A-Z]", "g");
    let result = `${str} ${str[0].toLowerCase()}${str.substring(1)}`;

    let r3 = str.replace(reg, (t, i) => {
        return i === 0 ? t.toLowerCase() : `_${t.toLowerCase()}`;
    });
    let r4 = str.replace(reg, (t, i) => {
        return i === 0 ? t.toLowerCase() : `-${t.toLowerCase()}`;
    });

    return result + " " + r3 + " " + r4;
}

6、解析url中的参数
要求:
1、如key为空,返回全部或{}
2、如key不为空,返回对应值或’',如存在多个对应值,返回数组

function getUrlParam(sUrl, sKey) {
    const result = {};
    const arr = sUrl.split("?")[1].split("#")[0].split("&");
        
    arr.map(str=>{
        let [key, value] = str.split("=");
        if(result[key]){
            result[key] = [...result[key],value];
        }else{
            result[key] = value
        }
    })
        
    return !sKey ? result:result[sKey]?result[sKey]:'';
    
}
// 测试用例 getUrlParam("http://www.nowcoder.com?key=1&key=2&key=3&test=4#hehe",key)

7、格式化时间字符串(单个字符自动补0,否则不补)

  function formatDate(date, formatStr) {
    const obj = {
      yyyy: date.getFullYear(),
      yy: ('' + date.getFullYear()).substr(-2),
      MM: date.getMonth() + 1,
      M: ('0' + (date.getMonth() + 1)).substr(-2),
      dd: date.getDate(),
      d: ('0' + date.getDate()).substr(-2),
      H: date.getHours(),
      HH: ('0' + date.getHours()).substr(-2),
      m: date.getMinutes(),
      mm: ('0' + date.getMinutes()).substr(-2),
      s: date.getSeconds(),
      ss: ('0' + date.getSeconds()).substr(-2),
      w: ['日', '一', '二', '三', '四', '五', '六'][date.getDay()],
    };

    return formatStr.replace(/([\w]+)/gi, text => {
      return obj[text];
    });
  }
  
 // formatDate(new Date(),'yyyy-MM-dd HH:mm:ss 星期w')

8、扁平化数组

  function flatten(arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}

// 需要了解解构赋值,...arr其实把arr内的所有值单独提取了,如果有数组就会被解构一层,然后再一起放到arr内,接下来不断循环。
// console.log(flatten([1, 2, 3, [4, 5, [6, 7, 8]]]))

7、数字千分位加逗号
https://www.cnblogs.com/lwming/p/10943193.html

// 数字千分位加逗号
function numHandle (num){
  const str = num.toString();
  return str.replace(/\d{1,3}(?=(\d{3})+)/g,(text)=>{
    return text+','
  })
// str.split("").reverse().join("").replace(/(\d{3})+?/g,function(s){ return s+",";}).replace(/,$/,"")
}

console.log(numHandle(143242355))

8、
例如你有构造了一个对象 user // { age: 10 },
那么 user.age // 11 user.age // 12 … 如何构造一个这样的对象,让它的属性值每次被访问时就 + 1


通过Object.defineProperty重写get方法。

let user = {age:10}
 
Object.defineProperty(user.prototype,'age',{get:function(){return age++;}});
//Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性。

9、事件总线。on订阅,emit才是发布


// 事件总线 on是订阅者,emit是发布者
class EventBus {
    constructor () {
      this.events = new Map(); // map存储订阅事件,等到触发之后一一触发。多个订阅事件存为数组。
    }

    // 监听,订阅,又名addListener
    on(type,callback){
        const handle = this.events.get(type)
        if(!handle){
            this.events.set(type,callback)
        }else if(typeof handle === 'function'){
            // 已存在键值对,作为数组
            handle = [handle,callback]
        }else{
            handle.push(callback)
        }
    }

    // 发布,触发
    emit(type,...args){
        const handle = this.events.get(type);
        const _this = this;
        if(handle){
            if(typeof handle === 'object'){
                handle.map(fun=>{
                    fun.apply(_this,...args || [])
                })
            }else{
                typeof handle === 'function' && fun.apply(_this,...args || [])
            }
        }

    }

    // 取消订阅。移除监听
    off(type,fun){
        const handle = this.events.get(type);
        if(handle){
                       if(typeof handle === 'object'){
                handle.splice(handle.findIndex(e => e === fun),1)
            }else{
                this.events.delete(type)
            } 
        }
    }
}


// 测试代码
 // 下面是 测试代码
  function test1 (...params) {
    console.log(11, params)
  }

  function test2 (...params) {
    console.log(22, params)
  }

  function test3 (...params) {
    console.log(33, params)
  }

  function test4 (...params) {
    console.log(params)
    console.log(33, params)
  }

  //测试用例
  let eb = new EventBus()
  eb.on('event1', test1)
  eb.on('event1', test2)
  eb.on('event1', test3)
  eb.emit('event1', '第一次')

  eb.off('event1', test1)
  eb.emit('event1', ['第二次1', '第二次2'])

  eb.once('once', test4);
  eb.emit('once', '执行一次', 1, 2, 3)

10、斐波那契数列


// 递归(可能会造成栈溢出,具体看ES6函数扩展,尾递归)
function fb1(n){
    if(n <= 1){
        return 1;   
    }else{
        return fb1(n-1) + fb1(n-2);
    }
}

// 优化递归
// F(10)其实是F(0),反向调用,最后n=0,n=1的时候
function Fibonacci (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};

  return Fibonacci (n - 1, ac2, ac1 + ac2);
}

11、promise/async/generator重写同一个函数

// promise : 每个promise都会隐性返回promise,promise有两个参数,resolve返回结果成为下一个then的resolve函数参数,reject函数返回结果类似。

new Promise(res => {
    setTimeout(() => {
        console.log('3s打印,第一个')
    }, 3000)
}).then(() => {
    setTimeout(() => {
        console.log('2s打印,第二个')
    }, 2000)
}).then(() => {
    setTimeout(() => {
        console.log('2s打印,第二个')
    }, 2000)
})

// generator yield后直接跟一个异步,或者promise
function* gen() {
    yield setTimeout(() => {
        console.log("3秒钟之后打印");
    }, 3000);

    yield setTimeout(() => {
        console.log("2秒钟之后打印");
    }, 2000);

    yield setTimeout(() => {
        console.log("1秒钟之后打印");
    }, 1000);
}

const instance = gen();

instance.next();
instance.next();
instance.next();

async 函数 是 Generator 函数的语法糖,它只是将 Generator 函数中的 * 变成了asyncyield 变成了 await,更具有语义性。
async 函数直接执行,不需要通过 next。
async 函数返回的是一个 promise 对象,可以使用 then 方法添加回调函数。

function async asyncFun() {
    await setTimeout(() => {
        console.log("3秒钟之后打印");
    }, 3000);

    await setTimeout(() => {
        console.log("2秒钟之后打印");
    }, 2000);

    await setTimeout(() => {
        console.log("1秒钟之后打印");
    }, 1000);
}

asyncFun()

13、寄生组合式继承
摘自:https://cloud.tencent.com/developer/article/1589613

function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
   alert(this.name);
};
function SubType(name, age){
//继承属性
  SuperType.call(this, name);
  this.age = age;
}
//继承方法
// 重点是这里,子类的原型指向父类,并且还有子类原型的构造函数是子类本身,表示constructor构造了父类的子类实例对象。
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

算法相关:

一、排序

冒泡排序

每一次把最大的数沉底下,n个数排序n-1次,每一次排序,内部数比较n-i-1次(i为循环次数)

function maopao(arr){
	const n = arr.length;
	let temp;

	for(let i=0;i<n-1;i++){
		for(let j=0;j<n-i-1;j++){
			if(arr[j]>arr[j+1]){
				// 交换位置
				temp = a[j];
				a[j] = a[j+1];
				a[j+1] = temp;
			}
		}
	}

	return arr;
}

插入排序

假设序列第一位是有序的,则后面的数是无序的,每一次排序从后面一位有序地插入到前面的有序列中,直到最后一位也插进有序列里,现在数列中就只剩下有序位了。
设置一个哨岗位在无序序列的开头,分内循环和外循环,外循环从无序序列开头开始,循环n-1次,内循环从有序序列结尾(无序序列开头)开始。总之就是有一个哨岗位。


function insertSort(arr){
	const n = arr.length;

	for(let i=1;i<n;i++){
		// 当前要比较的值
		let temp = arr[i];
		
		// 内循环从无序序列的结尾开始,也就是i-1,拿i-1的值和i比,一直比到j=0,0也要比所以是>=
		for(let j=i-1;j>=0;j--){
			if(arr[j]>temp){
				// 如果比temp大,就说明排在它后面,让temp前进一位,一点点挪,temp每一次内循环都是在a[j]后面的。
				// 如果小于temp,说明排它前面,temp不用动了,位置就是这,直接break:
				a[j+1]=a[j];
				a[j]=temp;
			}
		}
	}

	return arr;
}

快速排序

“分而治之”,给一组数据,排一次分成两份,前一部分都比后一部分小,再将每一份继续排,一直到每一份只有一个节点,说明排到头了,此时顺序也就完成了。
基准直接取第一个数,如果是双数,那就是前n/2-1,中1,后n/2。单数中1,前后n/2


function quickSort(arr){
	const n = arr.length;
	if(n<=1){
	// 递归结束,只有单个节点了
		return arr;
	}

	// 基准、基准左边,基准右边
	let base = arr[0],left_arr = [],right_arr = [];

	// 排序
	for(let i=1;i<n;i++){
		if(arr[i]<base){
			left_arr.push(arr[i])
		}else{
			right_arr.push(arr[i])
		}
	}

	// 对左右边继续递归
	left_arr = quickSort(left_arr);
	right_arr = quickSort(right_arr);
	return [...left_arr,base,...right_arr]
}

归并排序

把数据分成最小块,递归合并,应该是快速排序的反过来版。

选择排序

有点像插入排序,但是选择排序的特点应该是:我插进来的数据是我自己从无序序列里选的。
插入排序是和有序序列比较,一个个挪,选择排序是无序序列自己比较,选一个合适的出来排在有序的后边。

希尔排序
堆排序

二、链表

懒得CV了,看这个:https://blog.csdn.net/qq_31947477/article/details/105862374

function node(id,name){
	this.id=id;
	this.name=name;
	this.next = null;
}
反转链表
//迭代写法
// 将链表看成已反转和未反转两个,把未反转的一个个插到已反转头部。
// 总结:有点像选择排序,cur是要排序的哨岗,已反转链表尾部连接到未反转的cur.next,然后把cur插到最前面,接下来反复。
// 4个指针。一个head永远指向已反转头部,一个pre指向已反转的尾部,一个cur指向未反转的头部,也就是下一个要变成已反转头部的节点,一个temp就是下一个cur。


/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
var reverseList = function(head){
	// 没节点的叉出去
    if(head == null || head.next == null){return head;}
    
    let pre = head;
    let temp = null;
    let cur = head.next
    while(cur != null){
        temp = cur.next;
        pre.next = cur.next;
        cur.next = head;
        head = cur;
        cur = temp;
        //[head, cur] = [cur, temp]; //解构赋值写法
    }
    return head;
}

查找倒数第k个节点(快慢指针)

function FindKthToTail( pHead ,  k ) {
    if(!pHead || k<=0 ) return null;
    
    let i=0,p1=pHead,p2=pHead;
    while(i<k && p1){
        p1=p1.next;
        i++;
    }
    // 如果数超出长度
    if(!p1 && k-i>=1) return null;
    while(p1){
        p1=p1.next;
        p2=p2.next;
    }
    
    return p2;
}
module.exports = {
    FindKthToTail : FindKthToTail
};

二叉树(遍历:先序中序后序 层级)

二叉树是每个结点最多有两个子树的树结构。它有五种基本形态:二叉树可以是空集;根可以有空的左子树或右子树;或者左、右子树皆为空。

// 先序:根左右
function preOrder(root){
	if(!root || root.key === null) return;

	console.log(root.key);
	preOrder(root.left);
	preOrder(root.right);
}

// 中序:左根右
function midOrder(root){
	if(!root || root.key === null) return;
	
	midOrder(root.left);
	console.log(root.key);
	midOrder(root.right);
}

// 后序:左右根
function backOrder(root){
	if(!root || root.key === null) return;
	
	backOrder(root.left);
	backOrder(root.right);
	console.log(root.key);
}

// 层级遍历
function PrintFromTopToBottom(root)
{
    // 层级遍历
    if(!root) return [];
    let res = [];
    let tree = [root];
    // root是一个TreeNode对象节点数组,然后放进作为队列的一个节点(每次循环只取根),
    // tree是辅助队列。把层级遍历的节点放进tree里,然后再取出来遍历放进res里。
    while(tree.length){
        let node = tree.shift();
        res.push(node.val);
        if(node.left){tree.push(node.left)}
        if(node.right){tree.push(node.right)}
    }
    
    return res;
}
二叉树重建

已知前序中序,重建二叉树
来自CSDN用户roycon 的分析,我直接copy了。

题目如下:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
分析:

二叉树的前序遍历顺序是:先访问根节点,然后前序遍历左子树,再前序遍历右子树。

中序遍历顺序是:中序遍历根节点的左子树,然后是访问根节点,最后中序遍历右子树。

1、二叉树的前序遍历序列一定是该树的根节点

2、中序遍历序列中根节点前面一定是该树的左子树,后面是该树的右子树

从上面可知,题目中前序遍历的第一个节点{1}一定是这棵二叉树的根节点,根据中序遍历序列,可以发现中序遍历序列中节点{1}之前的{4,7,2}是这棵二叉树的左子树,{5,3,8,6}是这棵二叉树的右子树。然后,对于左子树,递归地把前序子序列{2,4,7}和中序子序列{4,7,2}看成新的前序遍历和中序遍历序列。此时,对于这两个序列,该子树的根节点是{2},该子树的左子树为{4,7}、右子树为空,如此递归下去(即把当前子树当做树,又根据上述步骤分析)。{5,3,8,6}这棵右子树的分析也是这样。

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */
function reConstructBinaryTree(pre, vin)
{
    // 根左右 / 左根右
    // write code here
    let result = {};
    if(pre.length<=0 || vin.length<=0) return null;
    let root = pre[0];
    
    if(pre.length>1){
          // 左子树、右子树
    let left = vin.slice(0,vin.indexOf(root));
    let right = vin.slice(vin.indexOf(root)+1,vin.length);
    
    // 前序左子树、右子树
        pre.shift();
    let preleft = pre.slice(0,left.length);
    let preright = pre.slice(left.length,pre.length);
        
        result = {
            val:root,
            left:reConstructBinaryTree(preleft,left),
            right:reConstructBinaryTree(preright,right)
        }
    }else if(pre.length===1){
        result = new TreeNode(pre[0])
    }
    
    return result;
}

做些题记录下

散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表。\

1、promise.all
function promiseAll(promiseArr) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promiseAll)) return reject("应传入数组");
        let result = [];

        promiseAll.forEach((promise) => {
            Promise.resolve(promise).then(
                (data) => {
                    result.push(data);
                },
                (err) => {
                    // 一旦有一个错误马上返回
                    return reject(err);
                }
            );
        });
        resolve(result);
    });
}
2、深拷贝(递归)
 const deepClone = (object) => {
   const _object = {};
   const keys = Object.keys(object);
   keys.forEach((key) => {
     const a = object[key]
     if(typeof a !== 'object'){
       _object[key] = a;
     }else{
       if(Array.isArray(a)){
         // 是数组
         _object[key] = a.slice(0);
       }else{
         // 是对象
         _object[key] = deepClone(a)
       }
     }
   })
   return _object;
 }

 console.log(deepClone({a:'111',b:1,c:[1,2,3,{c:11}],d:{e:122,f:{g:'2222'},h:[1,2,3.4]}}))

// 测试
// console.log(
//   JSON.stringify(
//     cloneDeep([
//       {
//         a: [1, { ss: "ss" }, [3], 4, 5, ["sad", "123", true]],
//         age: 10,
//         name: "测试",
//         eat: "测试试一下",
//         show: {
//           skill: "鸡你太美",
//           b: function () {
//             console.log(this.a);
//           },
//         },
//       },
//     ])
//   )
// );
}
3、防抖(自己写的。试过没问题,但是也许其他人有不同的实现方法)
// 防抖
// 在x时间内多次触发同一事件,只执行最后一次。如果反复点击按钮,则连续推迟,直到最后一次。
// 用于:onMouseMove监听、按钮防止连续按、监听onScroll\onResize等连续频繁触发的事件。

/**
 * func => 执行的函数体
 * wait => 等待的时间
 * option => 是否立即执行 'immediate'/'timeout'
 */
function debounce(fun, wait = 300, option = "immediate") {
    let timer = null;
    return () => {
        // 如果在n秒内还有定时未执行,则删除之前的定时任务再次计时
        if (timer) clearTimeout(timer);

        if (option === "immediate") {
            // 默认立即执行,如果上一个定时任务已完成,则可以执行
            let callNow = !timer;

            // 在等待时间后 timer 等于null ,callNow =true
            timer = setTimeout(() => {
                timer = null;
            }, wait);

            //如果 callNow = true 执行
            if (callNow) result = func.apply(this, args);
        } else if (option === "timeout") {
            time = setTimeout(() => {
                func.apply(this, arguments);
            }, wait);
        }

        // 重新设定定时任务
        timer = setTimeout(function() {
            // 如果存在timer定时器,则置空重新存入一个新定时
            timer = null;
        }, wait);

        // 当定时器走完了,才能执行,就是在x秒内连续点击多次,只执行最后一次
        if (callNow) fun.apply(this, arguments);
    };
}
4、节流

①定时器简单版本

// 节流:
// 防抖是延迟执行,而节流是间隔执行,函数节流即每隔一段时间就执行一次,实现原理为设置一个定时器,约定xx毫秒后执行事件,如果时间到了,那么执行函数并重置定时器,
// 和防抖的区别在于,防抖每次触发事件都重置定时器,而节流在定时器到时间后再清空定时器
// 应用场景 => DOM元素拖拽 射击游戏 计算鼠标距离 scroll滚动事件

/**
 * func => 执行的函数体
 * wait => 等待的时间
 * 简单版的,会执行首次
 */
function throttle(func, wait) {
    let timer = null;
    return () => {
        if (!timer) {
            timer = setTimeout(() => {
                clearTimeout(timer);
                func.apply(this, arguments);
            }, wait);
        }
    };
}

包括自定义:可以选择是否立即执行第一次/是否执行最后一次的 时间戳 版本

/**
 * 复杂版的
 * options:{leading : boolean,trailing:boolean}
 */
function throttle(func, wait, options) {
    let timeout = null;
    let previous = 0;
    if (!options) options = { leading: true, trailing: false };

    return () => {
        let now = new Date().getTime();

        // 第一次不会立即执行,remaining=wait,等过了这么久才能执行。
        if (!previous && !options.leading) previous = now;
        // 当前离n毫秒还剩多少,如果超出事件就可以执行了
        const remaining = wait - (now - previous);

        if (remaining <= 0) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(this, ['1', ...arguments]);
        } else if (!timeout && options.trailing) {
            // 最后一次需要执行,如果上一次定时器还没结束,说明是最后一次。
            // 这里意思是如果确实要执行最后一次,就把每一次的最后都记录下来,当timeout为空时表明remaining<0执行过,设定一个定时器计算剩余时间。
            // 如果本次后还未清除此定时器,过了wait时间就会执行,否则那么就不是“最后一次”,remaining<0,自动清除。

            timeout = setTimeout(() => {
                clearTimeout(timeout);
                previous = now;
                func.apply(this, ['2', ...arguments]);
            }, wait);
        }
    };
}
5、驼峰转-间隔 做测试题的时候遇到的,不止转-,还有别的,怕忘了记一下。
function caseTransform(str) {
    const reg = new RegExp("[A-Z]", "g");
    let result = `${str} ${str[0].toLowerCase()}${str.substring(1)}`;

    let r3 = str.replace(reg, (t, i) => {
        return i === 0 ? t.toLowerCase() : `_${t.toLowerCase()}`;
    });
    let r4 = str.replace(reg, (t, i) => {
        return i === 0 ? t.toLowerCase() : `-${t.toLowerCase()}`;
    });

    return result + " " + r3 + " " + r4;
}
6、解析url中的参数

要求:
1、如key为空,返回全部或{}
2、如key不为空,返回对应值或’',如存在多个对应值,返回数组

function getUrlParam(sUrl, sKey) {
    const result = {};
    const arr = sUrl.split("?")[1].split("#")[0].split("&");
        
    arr.map(str=>{
        let [key, value] = str.split("=");
        if(result[key]){
            result[key] = [...result[key],value];
        }else{
            result[key] = value
        }
    })
        
    return !sKey ? result:result[sKey]?result[sKey]:'';
    
}
// 测试用例 getUrlParam("http://www.nowcoder.com?key=1&key=2&key=3&test=4#hehe",key)

7、格式化时间字符串(单个字符自动补0,否则不补)
  function formatDate(date, formatStr) {
    const obj = {
      yyyy: date.getFullYear(),
      yy: ('' + date.getFullYear()).substr(-2),
      MM: date.getMonth() + 1,
      M: ('0' + (date.getMonth() + 1)).substr(-2),
      dd: date.getDate(),
      d: ('0' + date.getDate()).substr(-2),
      H: date.getHours(),
      HH: ('0' + date.getHours()).substr(-2),
      m: date.getMinutes(),
      mm: ('0' + date.getMinutes()).substr(-2),
      s: date.getSeconds(),
      ss: ('0' + date.getSeconds()).substr(-2),
      w: ['日', '一', '二', '三', '四', '五', '六'][date.getDay()],
    };

    return formatStr.replace(/([\w]+)/gi, text => {
      return obj[text];
    });
  }
  
 // formatDate(new Date(),'yyyy-MM-dd HH:mm:ss 星期w')
8、扁平化数组
  function flatten(arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}

// 需要了解解构赋值,...arr其实把arr内的所有值单独提取了,如果有数组就会被解构一层,然后再一起放到arr内,接下来不断循环。
// console.log(flatten([1, 2, 3, [4, 5, [6, 7, 8]]]))

7、数字千分位加逗号

https://www.cnblogs.com/lwming/p/10943193.html

// 数字千分位加逗号
function numHandle (num){
  const str = num.toString();
  return str.replace(/\d{1,3}(?=(\d{3})+)/g,(text)=>{
    return text+','
  })
// str.split("").reverse().join("").replace(/(\d{3})+?/g,function(s){ return s+",";}).replace(/,$/,"")
}

console.log(numHandle(143242355))
8、

例如你有构造了一个对象 user // { age: 10 },
那么 user.age // 11 user.age // 12 … 如何构造一个这样的对象,让它的属性值每次被访问时就 + 1


通过Object.defineProperty重写get方法。

let user = {age:10}
 
Object.defineProperty(user.prototype,'age',{get:function(){return age++;}});
//Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性。

9、事件总线。on订阅,emit才是发布

// 事件总线 on是订阅者,emit是发布者
class EventBus {
    constructor () {
      this.events = new Map(); // map存储订阅事件,等到触发之后一一触发。多个订阅事件存为数组。
    }

    // 监听,订阅,又名addListener
    on(type,callback){
        const handle = this.events.get(type)
        if(!handle){
            this.events.set(type,callback)
        }else if(typeof handle === 'function'){
            // 已存在键值对,作为数组
            handle = [handle,callback]
        }else{
            handle.push(callback)
        }
    }

    // 发布,触发
    emit(type,...args){
        const handle = this.events.get(type);
        const _this = this;
        if(handle){
            if(typeof handle === 'object'){
                handle.map(fun=>{
                    fun.apply(_this,...args || [])
                })
            }else{
                typeof handle === 'function' && fun.apply(_this,...args || [])
            }
        }

    }

    // 取消订阅。移除监听
    off(type,fun){
        const handle = this.events.get(type);
        if(handle){
                       if(typeof handle === 'object'){
                handle.splice(handle.findIndex(e => e === fun),1)
            }else{
                this.events.delete(type)
            } 
        }
    }
}


// 测试代码
 // 下面是 测试代码
  function test1 (...params) {
    console.log(11, params)
  }

  function test2 (...params) {
    console.log(22, params)
  }

  function test3 (...params) {
    console.log(33, params)
  }

  function test4 (...params) {
    console.log(params)
    console.log(33, params)
  }

  //测试用例
  let eb = new EventBus()
  eb.on('event1', test1)
  eb.on('event1', test2)
  eb.on('event1', test3)
  eb.emit('event1', '第一次')

  eb.off('event1', test1)
  eb.emit('event1', ['第二次1', '第二次2'])

  eb.once('once', test4);
  eb.emit('once', '执行一次', 1, 2, 3)
10、斐波那契数列

// 递归(可能会造成栈溢出,具体看ES6函数扩展,尾递归)
function fb1(n){
    if(n <= 1){
        return 1;   
    }else{
        return fb1(n-1) + fb1(n-2);
    }
}

// 优化递归
// F(10)其实是F(0),反向调用,最后n=0,n=1的时候
function Fibonacci (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};

  return Fibonacci (n - 1, ac2, ac1 + ac2);
}

11、promise/async/generator重写同一个函数
// promise : 每个promise都会隐性返回promise,promise有两个参数,resolve返回结果成为下一个then的resolve函数参数,reject函数返回结果类似。

new Promise(res => {
    setTimeout(() => {
        console.log('3s打印,第一个')
    }, 3000)
}).then(() => {
    setTimeout(() => {
        console.log('2s打印,第二个')
    }, 2000)
}).then(() => {
    setTimeout(() => {
        console.log('2s打印,第二个')
    }, 2000)
})

// generator yield后直接跟一个异步,或者promise
function* gen() {
    yield setTimeout(() => {
        console.log("3秒钟之后打印");
    }, 3000);

    yield setTimeout(() => {
        console.log("2秒钟之后打印");
    }, 2000);

    yield setTimeout(() => {
        console.log("1秒钟之后打印");
    }, 1000);
}

const instance = gen();

instance.next();
instance.next();
instance.next();

async 函数 是 Generator 函数的语法糖,它只是将 Generator 函数中的 * 变成了asyncyield 变成了 await,更具有语义性。
async 函数直接执行,不需要通过 next。
async 函数返回的是一个 promise 对象,可以使用 then 方法添加回调函数。

function async asyncFun() {
    await setTimeout(() => {
        console.log("3秒钟之后打印");
    }, 3000);

    await setTimeout(() => {
        console.log("2秒钟之后打印");
    }, 2000);

    await setTimeout(() => {
        console.log("1秒钟之后打印");
    }, 1000);
}

asyncFun()
13、寄生组合式继承

摘自:https://cloud.tencent.com/developer/article/1589613

function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
   alert(this.name);
};
function SubType(name, age){
//继承属性
  SuperType.call(this, name);
  this.age = age;
}
//继承方法
// 重点是这里,子类的原型指向父类,并且还有子类原型的构造函数是子类本身,表示constructor构造了父类的子类实例对象。
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

算法相关:

一、排序

冒泡排序

每一次把最大的数沉底下,n个数排序n-1次,每一次排序,内部数比较n-i-1次(i为循环次数)

function maopao(arr){
	const n = arr.length;
	let temp;

	for(let i=0;i<n-1;i++){
		for(let j=0;j<n-i-1;j++){
			if(arr[j]>arr[j+1]){
				// 交换位置
				temp = a[j];
				a[j] = a[j+1];
				a[j+1] = temp;
			}
		}
	}

	return arr;
}

插入排序

假设序列第一位是有序的,则后面的数是无序的,每一次排序从后面一位有序地插入到前面的有序列中,直到最后一位也插进有序列里,现在数列中就只剩下有序位了。
设置一个哨岗位在无序序列的开头,分内循环和外循环,外循环从无序序列开头开始,循环n-1次,内循环从有序序列结尾(无序序列开头)开始。总之就是有一个哨岗位。


function insertSort(arr){
	const n = arr.length;

	for(let i=1;i<n;i++){
		// 当前要比较的值
		let temp = arr[i];
		
		// 内循环从无序序列的结尾开始,也就是i-1,拿i-1的值和i比,一直比到j=0,0也要比所以是>=
		for(let j=i-1;j>=0;j--){
			if(arr[j]>temp){
				// 如果比temp大,就说明排在它后面,让temp前进一位,一点点挪,temp每一次内循环都是在a[j]后面的。
				// 如果小于temp,说明排它前面,temp不用动了,位置就是这,直接break:
				a[j+1]=a[j];
				a[j]=temp;
			}
		}
	}

	return arr;
}

快速排序

“分而治之”,给一组数据,排一次分成两份,前一部分都比后一部分小,再将每一份继续排,一直到每一份只有一个节点,说明排到头了,此时顺序也就完成了。
基准直接取第一个数,如果是双数,那就是前n/2-1,中1,后n/2。单数中1,前后n/2


function quickSort(arr){
	const n = arr.length;
	if(n<=1){
	// 递归结束,只有单个节点了
		return arr;
	}

	// 基准、基准左边,基准右边
	let base = arr[0],left_arr = [],right_arr = [];

	// 排序
	for(let i=1;i<n;i++){
		if(arr[i]<base){
			left_arr.push(arr[i])
		}else{
			right_arr.push(arr[i])
		}
	}

	// 对左右边继续递归
	left_arr = quickSort(left_arr);
	right_arr = quickSort(right_arr);
	return [...left_arr,base,...right_arr]
}

归并排序

把数据分成最小块,递归合并,应该是快速排序的反过来版。

选择排序

有点像插入排序,但是选择排序的特点应该是:我插进来的数据是我自己从无序序列里选的。
插入排序是和有序序列比较,一个个挪,选择排序是无序序列自己比较,选一个合适的出来排在有序的后边。

希尔排序
堆排序

二、链表

懒得CV了,看这个:https://blog.csdn.net/qq_31947477/article/details/105862374

function node(id,name){
	this.id=id;
	this.name=name;
	this.next = null;
}
反转链表
//迭代写法
// 将链表看成已反转和未反转两个,把未反转的一个个插到已反转头部。
// 总结:有点像选择排序,cur是要排序的哨岗,已反转链表尾部连接到未反转的cur.next,然后把cur插到最前面,接下来反复。
// 4个指针。一个head永远指向已反转头部,一个pre指向已反转的尾部,一个cur指向未反转的头部,也就是下一个要变成已反转头部的节点,一个temp就是下一个cur。


/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
var reverseList = function(head){
	// 没节点的叉出去
    if(head == null || head.next == null){return head;}
    
    let pre = head;
    let temp = null;
    let cur = head.next
    while(cur != null){
        temp = cur.next;
        pre.next = cur.next;
        cur.next = head;
        head = cur;
        cur = temp;
        //[head, cur] = [cur, temp]; //解构赋值写法
    }
    return head;
}

查找倒数第k个节点(快慢指针)

function FindKthToTail( pHead ,  k ) {
    if(!pHead || k<=0 ) return null;
    
    let i=0,p1=pHead,p2=pHead;
    while(i<k && p1){
        p1=p1.next;
        i++;
    }
    // 如果数超出长度
    if(!p1 && k-i>=1) return null;
    while(p1){
        p1=p1.next;
        p2=p2.next;
    }
    
    return p2;
}
module.exports = {
    FindKthToTail : FindKthToTail
};

二叉树(遍历:先序中序后序 层级)

二叉树是每个结点最多有两个子树的树结构。它有五种基本形态:二叉树可以是空集;根可以有空的左子树或右子树;或者左、右子树皆为空。

// 先序:根左右
function preOrder(root){
	if(!root || root.key === null) return;

	console.log(root.key);
	preOrder(root.left);
	preOrder(root.right);
}

// 中序:左根右
function midOrder(root){
	if(!root || root.key === null) return;
	
	midOrder(root.left);
	console.log(root.key);
	midOrder(root.right);
}

// 后序:左右根
function backOrder(root){
	if(!root || root.key === null) return;
	
	backOrder(root.left);
	backOrder(root.right);
	console.log(root.key);
}

// 层级遍历
function PrintFromTopToBottom(root)
{
    // 层级遍历
    if(!root) return [];
    let res = [];
    let tree = [root];
    // root是一个TreeNode对象节点数组,然后放进作为队列的一个节点(每次循环只取根),
    // tree是辅助队列。把层级遍历的节点放进tree里,然后再取出来遍历放进res里。
    while(tree.length){
        let node = tree.shift();
        res.push(node.val);
        if(node.left){tree.push(node.left)}
        if(node.right){tree.push(node.right)}
    }
    
    return res;
}
二叉树重建

已知前序中序,重建二叉树
来自CSDN用户roycon 的分析,我直接copy了。

题目如下:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
分析:

二叉树的前序遍历顺序是:先访问根节点,然后前序遍历左子树,再前序遍历右子树。

中序遍历顺序是:中序遍历根节点的左子树,然后是访问根节点,最后中序遍历右子树。

1、二叉树的前序遍历序列一定是该树的根节点

2、中序遍历序列中根节点前面一定是该树的左子树,后面是该树的右子树

从上面可知,题目中前序遍历的第一个节点{1}一定是这棵二叉树的根节点,根据中序遍历序列,可以发现中序遍历序列中节点{1}之前的{4,7,2}是这棵二叉树的左子树,{5,3,8,6}是这棵二叉树的右子树。然后,对于左子树,递归地把前序子序列{2,4,7}和中序子序列{4,7,2}看成新的前序遍历和中序遍历序列。此时,对于这两个序列,该子树的根节点是{2},该子树的左子树为{4,7}、右子树为空,如此递归下去(即把当前子树当做树,又根据上述步骤分析)。{5,3,8,6}这棵右子树的分析也是这样。

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */
function reConstructBinaryTree(pre, vin)
{
    // 根左右 / 左根右
    // write code here
    let result = {};
    if(pre.length<=0 || vin.length<=0) return null;
    let root = pre[0];
    
    if(pre.length>1){
          // 左子树、右子树
    let left = vin.slice(0,vin.indexOf(root));
    let right = vin.slice(vin.indexOf(root)+1,vin.length);
    
    // 前序左子树、右子树
        pre.shift();
    let preleft = pre.slice(0,left.length);
    let preright = pre.slice(left.length,pre.length);
        
        result = {
            val:root,
            left:reConstructBinaryTree(preleft,left),
            right:reConstructBinaryTree(preright,right)
        }
    }else if(pre.length===1){
        result = new TreeNode(pre[0])
    }
    
    return result;
}

图片转base64
  getImageBase64 = (image)=>{
    const ext = _.indexOf(image,'png')>-1? 'png':'jpeg';
    return new Promise((resolve, reject) => {
      // 通过构造函数来创建的 img 实例,在赋予 src 值后就会立刻下载图片,相比 createElement() 创建 <img> 省去了 append(),也就避免了文档冗余和污染
      let Img = new Image();
      Img.src = image;
      Img.onload = () => { // 要先确保图片完整获取到,这是个异步事件
        let dataURL = '';
        let canvas = document.createElement('canvas'); // 创建canvas元素
        let width = Img.width; // 确保canvas的尺寸和图片一样
        let height = Img.height;
        canvas.width = width;
        canvas.height = height;
        canvas.getContext('2d').drawImage(Img, 0, 0, width, height); // 将图片绘制到canvas中
        dataURL = canvas.toDataURL(`image/${ext}`); // 转换图片为dataURL
        resolve(dataURL);
      };
    });

  }
  getImageBase64 = file => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => resolve(reader.result);
      reader.onerror = error => reject(error);
    });
  };
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值