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 函数中的 * 变成了async ,yield 变成了 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 函数中的 * 变成了async ,yield 变成了 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);
});
};