学习笔记2

383 阅读15分钟

写此篇的目的主要是记录平时的学习笔记,方便随时查阅。

发布订阅和观察者模式

let eventObj = {
  listeners: [],
  // 订阅
  subscribe: function (fn) {
    this.listeners.push(fn);
    // 取消订阅
    return () => {
      let index = this.listeners.indexOf(fn);
      this.listeners.splice(index, 1);
    };
  },
  // 发布
  emit: function () {
    this.listeners.forEach((fn) => {
      fn();
    });
  },
};
let unsubscribe = eventObj.subscribe(() => {
  console.log("update1");
});
unsubscribe();
eventObj.subscribe(() => {
  console.log("update2");
});
eventObj.emit();

// 观察者
class Observer {
  constructor(type) {
    this.type = type;
  }
  update(o) {
    console.log(this.type + "监听被观察者状态变化", o.update);
  }
}
// 被观察者
class Subject {
  constructor(type) {
    // 观察集合
    this.observers = [];
    this.type = type;
  }
  // 收集观察者
  attach(o) {
    this.observers.push(o);
  }
  setState(update) {
    console.log("update", update);
    this.update = update;
    this.observers.forEach((o) => {
      o.update(this);
    });
  }
}

let baby = new Subject("baby");
let o1 = new Observer("爸爸");
let o2 = new Observer("妈妈");
baby.attach(o1);
baby.attach(o2);
baby.setState("我在玩呢");

发布订阅模式升级版

//题目描述:实现一个发布订阅模式拥有 on emit once off 方法
class EventEmitter {
    constructor() {
        this.events = {}
    }
    // 订阅
    on(type, callback) {
        if (!this.events[type]) {
            this.events[type] = [callback]
        } else {
            this.events[type].push(callback)
        }
    }
    // 取消订阅
    off(type, callback) {
        if (this.events[type]) {
            this.events[type] = this.events[type].filter((fn) => fn !== callback);
        }
    }
    // 只执行一次订阅事件,触发一次后,就会取消订阅
    once(type, callback) {
        function fn(args) {
            callback(args);
            this.off(type, fn);// 这里是删除订阅 fn函数
        }
        this.on(type, fn)
    }
    // 发布(事件触发)
    emit(type, ...rest) {
        if (this.events[type]) {
            this.events[type].forEach((fn) => {
                fn.call(this, ...rest)
            })
        }
    }
}
let event = new EventEmitter()
const handle1 = (arg) => {
    console.log('click', arg);
}
const handle2 = (arg) => {
    console.log('change', arg);
}
const handle3 = (arg) => {
    console.log('mousemove', arg);
}
// event.on('click', handle1);
// event.on('change', handle2);
// event.emit('click', 'haha')
// event.off('click', handle1);
// event.emit('click', 'haha')
event.once('mousemove', handle3);
event.emit('mousemove', '触发once了111');

单例模式

// 方法1:
class SingleFn{
    static _instance = null
    static getInstance(){
        if(!SingleFn._instance){
            SingleFn._instance = new SingleFn();
        }
        return SingleFn._instance;
    }
    constructor(name,age){
        this.type = '单例模式'
        this.name = name;
        this.age = age;
    }
    say(){
        return 'hello'
    }
}
let u1 = SingleFn.getInstance();
let u2 = SingleFn.getInstance();
console.log('u1===u2',u1 === u2); // true

// 方法2: 可传入 一个构造函数扩展 + 闭包
function SingleFn(con){
    // 排除非函数与箭头函数,箭头函数的prototype是undefined
    if(!(con instanceof Function)|| !(con.prototype)){
        throw Error('请出入合法的构造函数')
    }
    let instance; // 闭包存入instance实例
    return function(){
        if(!instance){
            instance = new con();
        }
        return instance;
    }
}
function Person(){
    this.name = '单例'
}
let Single = SingleFn(Person);
let u1 = new Single();
let u2 = new Single();
let u3 =  Single();
let u4 =  Single();
console.log('u1===u2',u1 === u2);// true
console.log('u3===u4',u3 === u4);// true

// 方法2: constructor函数里判断单例,返回实例
class Single{
    static _instance;
    constructor(){
        if(!Single._instance){
            Single._instance = this;
        }
        return Single._instance;
    }
}
let u1 = new Single();
let u2 = new Single();
console.log('u1===u2',u1===u2);// true

// 方法4:使用包装对象结合闭包的形式实现
const Single = (function(){
    function user(){
        this.name = '单例'
    }
    return function(){
        if(!user._instance){
            user._instance = new user()
        }
        return user._instance;
    }
})();
let u1 =  new Single();
let u2 =  new Single();
console.log('u1===u2',u1 === u2);// true

节流防抖

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>节流防抖</title>
    <style>
      #content {
        height: 150px;
        line-height: 150px;
        text-align: center;
        color: #fff;
        background-color: #ccc;
        font-size: 80px;
      }
    </style>
  </head>
  <body>
    <!-- https://juejin.im/post/6844903651278848014 -->
    <h1>节流防抖(https://juejin.im/post/6844903651278848014)</h1>
    <div id="content"></div>
    <script>
      let num = 1;
      const content = document.getElementById("content");
      function count() {
        content.innerHTML = num++;
      }
      content.onmousemove = throttle(count, 1000, 1);
      // immediate=false 非立即执行,immediate=true 立即执行;
      // 防抖-debounce-原理:通过clearTimeout不断清除timeId,终止定时器执行,从而达到鼠标一直滑动会一直清除timeId,定时器函数就不会执行
      function debounce(fn, wait, immediate) {
        let timeId;
        return function () {
          const context = this;
          const args = arguments;
          // clearTimeout方法会终止 定时器执行
          // 所以在一直滑动时,timeId不断的被赋值,又不断的被清除,终止定时器的执行,知道停下来等wait时间段才会执行
          if (timeId) clearTimeout(timeId);
          // setTimeout是宏任务会放到任务队列里,会等当前宏任务栈里执行完了,在执行任务队列里的setTimeout回调函数
          if (!immediate) {
            timeId = setTimeout(() => {
              fn.call(context, args);
            }, wait);
          } else {
            let callNow = !timeId;
            timeId = setTimeout(() => {
              timeId = null;
            }, wait);
            if (callNow) fn.call(this, args);
          }
        };
      }
      // 节流-throttle-不清除定时器Id,所以不管鼠标是否一直在动,超过wait时间就会执行,
      function throttle(fn, wait, type) {
        if (type === 1) {
          let previous = 0;
        } else if (type === 2) {
          let timeout = null;
        }
        return function () {
          const context = this;
          const args = arguments;
          if (type === 1) {
            // 时间戳版
            let now = Date.now();
            if (now - previous > wait) {
              fn.call(this, args);
              previous = now;
            }
          } else if (type === 2) {
            // 定时器版
            if (!timeout) {
              timeout = setTimeout(() => {
                timeout = null;
                fn.call(this, args);
              }, wait);
            }
          }
        };
      }
    </script>
  </body>
</html>

节流高级篇

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>截流防抖</title>
</head>
<body>
    <div>
        <h1>防抖截流</h1>
        <div style="width:100%;height:100%;border:1px solid #000;">
            <div id="content" style="width:100%;height:200px;font-size: 20px;"></div>
        </div>
        <button id="button">点击</button>
    </div>
    <script>
        /**
         leading 第一次是否立即执行,默认true,会立即执行一次,leading为false,第一次不会立即执行
         trailing 最后一次点击后是否执行 默认true,最后会执行一次
         */
         // 截流
        function throttleFn(func, wait, options = { leading: true, trailing: true }) {
            let timeout, args, context
            let previous = 0
            const later = function () {
                previous = options.leading === false ? 0 : Date.now()
                func.apply(context, args)
                timeout = null
            }
            const throttled = function() {
                console.log('点击');
                context = this
                args = arguments
                console.log('context',context);
                const now = Date.now()
                // leading=false,表示禁用第一次立即执行
                if (!previous && options.leading === false) previous = now
                const remaining = wait - (now - previous)
                // 点击一下停顿wait时间以上 -> remaining <= 0 ,第一次点击 或者 大于或等于触发事件的间隔时间(wait),事件达到触发时机了
                if (remaining <= 0) {
                    if (timeout) {
                        clearTimeout(timeout)
                        timeout = null
                    }
                    func.apply(context, args)
                    previous = now // 每次记录上次点击的时间戳
                    //快速连续点击-> remaining > 0,最后一次点击 或者 小于触发事件的间隔时间(wait)
                } else if (!timeout && options.trailing !== false) {
                    timeout = setTimeout(later, remaining)
                }
            }
            throttled.cancel = function () {
                clearTimeout(timeout)
                timeout = null
                previous = 0
            }
            return throttled
        }
        let count = 0;
        function handleUpdate(event) {
            console.log('event',event);
            document.getElementById('content').innerText = count++;
        }
        button.addEventListener('click', throttleFn(handleUpdate, 2000, { leading: true, trailing: true }))
    </script>
</body>
</html>

科里化函数

function add(num1, num2, num3, num4) {
  return num1 + num2 + num3 + num4;
}
//1.科里化函数
function handleCury(fn, arr = []) {
  function curying(...args) {
    let context = this;
    let len = fn.length;
    arr.push(...args);
    console.log("arr", arr);
    if (arr.length < len) {
      return handleCury(fn, arr);
    } else {
      return fn.call(context, ...arr);
    }
  }
  return curying;
}
//2.科里化函数
function add(num1, num2, num3, num4) {
  return num1 + num2 + num3 + num4;
}
function handleCury2(fn) {
  let len = fn.length;
  function curying(...args) {
    let context = this;
    if (args.length < len) {
      return function () {
        return curying.apply(null, args.concat([].slice.call(arguments)));
      };
    } else {
      return fn.call(context, ...args);
    }
  }
  return curying;
}
let cury = handleCury2(add);
console.log(cury(2)(3, 5)(6));

3.科里化 判断数据类型
function isType1(type) { //Array object string
    return function (value) {
        return Object.prototype.toString.call(value) === `[object ${type}]`;
    }
}
const isType = (type) => (value) => Object.prototype.toString.call(value) === `[object ${type}]`;
const isArray = isType('Array');
const isObject = isType('Object');

4.判断数据类型
let con1 = [].constructor; // Array
let con2 = {}.constructor; // Object
const fn = function(){}; 
let con3 = fn.constructor; // Function
console.log('con1-con2-con3',{con1,con2,con3});
console.log([] instanceof Array); // true

快速排序

//快速排序
/**
1.先从数列中取出一个数作为基准数
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边
3.再对左右区间重复第二步,直到各区间只有一个数
*/
function qiuckSort(arr) {
  // 递归,
  // 一直找中间值,然后左右划分,直到划分成 数组长度小于2(数组长度是1)为止;
  if (arr.length < 2) return arr;
  let middleIndex = Math.floor(arr.length / 2);
  let left = [];
  let right = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < arr[middleIndex] && i !== middleIndex) {
      //排除中间值
      left.push(arr[i]);
    }
    if (arr[i] > arr[middleIndex] && i !== middleIndex) {
      right.push(arr[i]);
    }
  }
  //左中右-->小中大的顺序
  return [...qiuckSort(left), arr[middleIndex], ...qiuckSort(right)];
}
function qiuckSort2(arr) {
  // 递归,
  // 一直找中间值,然后左右划分,直到划分成 数组长度小于2(数组长度是1)为止;
  if (arr.length < 2) return arr;
  let middleIndex = Math.floor(arr.length / 2);
  let middleValue = arr.splice(middleIndex, 1);
  let left = [];
  let right = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] <= middleValue) {
      //排除中间值
      left.push(arr[i]);
    }
    if (arr[i] > middleValue) {
      right.push(arr[i]);
    }
  }
  //左中右--》小中大的顺序
  return qiuckSort2(left).concat(middleValue, ...qiuckSort2(right));
}
let arr = [2, 4, 1, 5, 7, 8];
console.log(qiuckSort2(arr));

冒泡排序

方法一
// 冒泡排序:双重循环 i和j都从0开始
function sort(arr) {
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - i - 1; j++) {
      let temp;
      if (arr[j] > arr[j + 1]) {
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  return arr;
}
let arr = [3, 11, 6, 7, 21, 4, 9, 12, 56];
console.log(sort(arr));
// 0<=i<4;
// i=0;0<=j<4;  1,3,6,2,7
// i=1;0<=j<3;  1,3,2,6,7
// i=2;0<=j<2;  1,2,3,6,7
// i=3;0<=j<1;  1,2,3,6,7

选择排序

// 方法二 减少了跟i自身的比较,效率更快
/** 选择排序/打擂台法:
    规律:通过比较首先选出最小的数放在第一个位置上,然后在其余的数中选出次小数放在第二个位置上,依此类推,直到所有的数成为有序序列。
*/
function BubbleSort2(arr){
    //比较的轮数
    for(let i=0;i<arr.length-1;i++){
        //每轮比较的次数
        for(let j=i+1;j<arr.length;j++){
            if(arr[i]>arr[j]){
                let temp;
                temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

请实现 DOM2JSON 一个函数,可以把一个 DOM 节点输出 JSON 的格式

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>请实现 DOM2JSON 一个函数,可以把一个 DOM 节点输出 JSON 的格式</title>
</head>
<body>
  <h3>DOM-to-JSON</h3>
  <div id='root'>
    <span>
      <a></a>
    </span>
    <span>
      <a></a>
      <a></a>
    </span>
  </div>
  <script>
    /*
    把上面dom结构转成下面的JSON格式
    {
      tag: 'DIV',
        children: [
          {
            tag: 'SPAN',
            children: [
              { tag: 'A', children: [] }
            ]
          },
          {
            tag: 'SPAN',
            children: [
              { tag: 'A', children: [] },
              { tag: 'A', children: [] }
            ]
          }
        ]
    }
    */
    let rootNode = document.getElementById('root');
    function domToJson(rootNode) {
      let obj = {};
      obj.tag = rootNode.tagName;
      obj.children = [];
      rootNode.childNodes.forEach(child => {
        if(child.tagName){
          obj.children.push(domToJson(child));
        }
      });
      return obj;
    }
    console.log(JSON.stringify(domToJson(rootNode),null,2));;
  </script>
</body>
</html>

比较两个版本号的大小

 /*
 * @Description: 比较两个版本号的大小
 * 题目描述:有一组版本号如下 v1 = '0.1.1' 和 v2= '2.3.3'。
 */
function compareVersion(v1, v2) {
    const len1 = v1.split('.')
    const len2 = v2.split('.')
    const maxLen = Math.max(len1.length, len2.length)
    if (len1.length < maxLen) {
        len1.push('0')
    }
    if (len2.length < maxLen) {
        len2.push('0')
    }
    for (let i = 0; i < maxLen; i++) {
        const s1 = len1[i]
        const s2 = len2[i]
        if (s1 > s2) {
            return 1;
        } else if (s1 < s2) {
            return -1;
        }
    }
    return 0;
}
console.log('compareVersion',compareVersion('0.1.1.1','2.3.3')); //-1

比较版本号排序

/*
 * @Description: 写版本号排序的方法
 * 题目描述:有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。
 * 现在需要对其进行排序,排序的结果为 [ '0.1.1', '0.302.1', '2.3.3', '4.2', '4.3.4.5', '4.3.5' ]
 */
let arr = [2, 5, 7, 3];
arr.sort((a, b) => {
    return a - b; // return a-b 升序; return b-a 降序
})
console.log('arr', arr);// arr [ 2, 3, 5, 7 ]
// 比较版本号
const arr2 = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'];
arr2.sort((a, b) => {
    let i = 0;
    const arr1 = a.split('.');
    const arr2 = b.split('.');
    while (true) {
        const s1 = arr1[i];
        const s2 = arr2[i];
        i++
        if(s1===undefined || s2 === undefined){
           return s1.length - s2.length;
        }
        if (s1 === s2) continue;
        return s1 - s2;
    }
});
console.log(arr2);// [ '0.1.1', '0.302.1', '2.3.3', '4.2', '4.3.4.5', '4.3.5' ]

二分查找

function binarySearch(arr, target) {
    if (!Array.isArray(arr)) return;
    arr.sort((a, b) => a - b);// 排序
    let start = 0;
    let end = arr.length - 1;
    while (start <= end) {
        let midIndex = Math.floor((start + end) / 2);
        let mid = arr[midIndex];
        if (mid === target) {
            // return mid;
            return midIndex;
        } else if (mid < target) {
            start = mid + 1;
        } else {
            end = mid - 1;
        }
    }
    return -1;
}
let arr = [2, 4, 1, 7, 8, , 9]; // [1, 2, 4, 7, 8, 9]
let target = 7;
console.log(binarySearch(arr, target));

实现call,apply,bind

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      //1.call
      function fn(...str) {
        console.log("this", this);
        console.log("str", str);
      }
      Function.prototype.callFn = function (context, ...args) {
        let fn = this;
        context = Object(context) || window;
        let key = Symbol(); //随便定义一个key
        context[key] = fn; // context={ key:fn };此时fn内部的this就是context了
        let result = context[key](...args); //context.key 谁调用this就是谁,
        delete context[key]; // 给上下文添加了 key属性,为了不污染 上下文context,调用后删掉key属性
        return result;
      };
      // console.log(fn.callFn(null, "hello", "world"));
      // let obj = {
      //   key: function () {
      //     console.log("this就是obj对象", this);// obj
      //   },
      //   fn: () => {
      //     console.log("如果是箭头函数,this就是window对象", this);// window
      //   },		
      // };
      // console.log(obj.key());
      // 2.apply
      Function.prototype.applyFn = function (context, args) {
        let fn = this;
        context = Object(context) || window; //上下文必须是 object对象
        let key = Symbol();
        context[key] = fn;
        let result = context[key](...args);
        delete context[key];
        return result;
      };
      // console.log(fn.applyFn(null, ["hello", "world"]));
      // 3.bind
      Function.prototype.myBind = function (context, args) {
        let fn = this; // 这个this是myBind函数的实例->Animal
        args = args ? args : [];
        context = Object(context) || window;
        function fn1() {}
        fn1.prototype = fn.prototype;
        return function newFn(...newArgs) {
          let _this = this; // 这个this是newFn函数实例->instance
          let newContext = _this instanceof newFn ? _this : context;
          let result = fn.apply(newContext, [args, ...newArgs]);
          return result;
        };
        newFn.prototype = new fn1();
      };
      function Animal(name, age) {
        this.name = name;
        this.age = age;
        console.log("Animal内部的this-3", this);
        // return {list:[],data:{name:'Mary'}}
      }
      let obj = { score: 0, id: 1 };
      Animal.prototype.flag = "哺乳类";
      let bindFn = Animal.myBind(obj, "lc");

      let instance = new bindFn(222);
      console.log("instance", instance);//{name: "lc", age: 222}
    </script>
  </body>
</html>

实现 Object.create()

 //4.实现 Object.create()
      Object.create = function (obj) {
        function fn() {}
        fn.prototype = obj;
        return new fn();
      };
      Object.create = function (obj) {
        let o = {};
        o.__proto__ = obj;
        return o;
      };
      Object.create = function (obj) {
        let o = {};
        Object.setPrototypeOf(o, obj);
        return o;
      };

模拟new

function Animal(type, name) {
  this.type = type;
  this.name = name;
}
// let cat = new Animal("animal", "cat");
Animal.prototype.say = function () {
  console.log("say");
};
// let instance = new Animal("animal", "cat");
// console.log("instance", instance);
// new的核心原理:
// 1.let obj={},创建新对象obj;
// 2.obj.__proto__=constructor.prototype,obj的原型对象继承构造函数的原型对象,
// 3.constructor.call(obj,...args),obj调用构造函数,使得构造函数里面的this指向obj,obj继承了构造函数的私有属性
// 4.let result=constructor.call(obj,...args),判断result返回值的类型,如果result是引用类型就返回result,如果是基本数据类型还是会返回obj,默认也是返回obj(this)
function mockNew() {
  let constructor = [].shift.call(arguments);
  let args = arguments;
  //创建新的空对象
  let obj = {};
  //obj继承构造函数的原型对象
  obj.__proto__ = constructor.prototype;
  //执行构造函数  并且将obj调用构造函数,指向构造函数内部的this
  //obj既拥有构造函数的私有属性又继承了构造函数的原型属性
  let result = constructor.call(obj, ...args);
  //对构造函数的返回值做判断,如果构造函数返回的是引用类型就返回该引用值,如果不是引用,会默认返回this(实例对象)
  return typeof result === "object" ? result : obj;
}
let instance = mockNew(Animal, "animal", "cat");
console.log("返回实例", instance);

数组扁平化

//1.递归扁平化
function handleFlat(arr) {
  if (!Array.isArray(arr)) {
    throw Error("need array");
  }
  let newArr = [];
  function flat(arr) {
    arr.forEach((item) => {
      if (Array.isArray(item)) {
        flat(item);
      } else {
        newArr.push(item);
      }
    });
  }
  flat(arr);
  return newArr;
}
let arr = [12, [35, 5], [56, 23, [67, 9]]];
// console.log(handleFlat(arr));

// 2.reduce
function flat(arr, init = []) {
  return arr.reduce((pre, cur) => {
    if (Array.isArray(cur)) {
      flat(cur, pre);
    } else {
      pre.push(cur);
    }
    return pre;
  }, init);
}
// reduce
function flat2(arr) {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flat2(cur) : cur);
  }, []);
}
let arr = [12, [35, 5], [56, 23, [67, 9]]];
console.log(flat2(arr));

// 3.迭代
function flaten(arr){
    while((arr.some((item)=>Array.isArray(item)))){
        arr = [].concat(...arr);
    }
    return arr;
}
let arr = [[1, 2, 3], [4, 5, [6]], [7]];
console.log(flaten(arr));

let arr = [[1, 2, 3], [4, 5, [6]], [7]];
console.log('...arr',...arr);// 扁平最外层 [1,2,3] [4,5,[6]] [7]
arr = [].concat([1,2,3],[4,5,[6]],[7]);
console.log('arr',arr); // [1,2,3,4,5,[6],7]

//4.数组 栈的思想
function flatStack(arr) {
  let result = [];
  let stack = [];
  stack = arr.slice();
  while (stack.length !== 0) {
    let val = stack.pop();
    if (Array.isArray(val)) {
      stack.push(...val); //分解数组层级,此时stack.length!==0,继续循环
    } else {
      result.unshift(val);
    }
  }
  return result;
}
let arr = [12, [35, 5], [56, 23, [67, 9]]];
console.log(flatStack(arr));

compose函数(koa中间件,redux)

//1.compose 洋葱模型 中间件koa,express,react-redux用到
async function add(ctx, next) {
  console.log("1");
  await next();
  console.log("2");
}
async function minus(ctx, next) {
  console.log("3");
  await next();
  console.log("4");
}
async function say(ctx, next) {
  console.log("5");
  await next();
  console.log("6");
}
let obj = {
  ctx: { a: 1, b: 2, c: 3, d: 4 },
  middles: [],
  use: function (fn) {
    this.middles.push(fn);
  },
  compose: function (ctx) {
    return function () {
      let middles = this.middles;
      let index = -1;
      function dispatch(i) {
        if (index === i) return Promise.reject(`next called multiples`);
        if (i === middles.length) return Promise.resolve();
        index = i;
        let middleware = middles[i];
        return Promise.resolve(middleware(ctx, () => dispatch(i + 1)));
      }
      return dispatch(0);
    };
  },
  listen: function () {
    const composeFn = this.compose(this.ctx);
    composeFn().then(() => {
      console.log("listen");
    });
  },
};
//订阅中间件
obj.use(add);
obj.use(minus);
obj.use(say);
//执行所有中间件
obj.listen();

//koa compose+异步迭代用函数递归
async function a(ctx, next) {
  console.log("1");
  await next();
  console.log("2");
}
async function b(ctx, next) {
  console.log("3");
  await next();
  console.log("4");
}
async function c(ctx, next) {
  console.log("5");
  await next();
  console.log("6");
}
function compose(middles) {
  let index = -1;
  return function (ctx) {
    function dispatch(i) {
      if (index === i) return Promise.reject(`next called multiples`);
      if (i === middles.length) return Promise.resolve();
      index = i;
      let middleware = middles[i];
      return Promise.resolve(middleware(ctx, () => dispatch(i + 1)));
    }
    return dispatch(0);
  };
}
let composeFn = compose([a, b, c]);
let ctx = {};
composeFn(ctx).then(() => {
  console.log("composeFn");
});

//compose +reducer
// async function a(ctx, next) {
//   console.log("1");
//   await async function b(ctx, next) {
//     console.log("3");
//     await async function c(ctx, next) {
//       console.log("5");
//       console.log("6");
//     };
//     console.log("4");
//   };
//   console.log("2");
// }
// 2.react-redux compose+reduce
function a1(str1) {
  console.log("1");
  return "a1" + str1;
}
function b1(str2) {
  console.log("2");
  return "b1" + str2;
}
function c1(str3) {
  console.log("3");
  return "c1" + str3;
}
function composeReducer(composes) {
  return composes.reduce((pre, cur) => {
    return function (...args) {
      return pre(cur(...args));
    };
  });
}
let str = composeReducer([a1, b1, c1])("hello");
console.log(str);

// 3.compose+reduceRight
function a2(str1) {
  console.log("1");
  return "a2" + str1;
}
function b2(str2) {
  console.log("2");
  return "b2" + str2;
}
function c2(str3) {
  console.log("3");
  return "c2" + str3;
}
function composeRightReducer(composes) {
  let lastMiddle = composes.pop();
  return function (...args) {
    return composes.reduceRight((pre, cur) => {
      return cur(pre);
    }, lastMiddle(...args));
  };
}
let str = composeRightReducer([a2, b2, c2])("hello");
console.log(str);

实现reduce函数

//实现reduce函数
Array.prototype.reduceFn = function (callback, pre) {
  for (let i = 0; i < this.length; i++) {
    if (typeof pre === "undefined") {
      pre = callback(this[i], this[i + 1], i + 1, this);
      i++;
    } else {
      pre = callback(pre, this[i], i, this);
    }
  }
  return pre;
};
let arr = [1, 3, 5];
let total = arr.reduceFn((pre, cur, index, arr) => {
  return pre + cur;
}, 1);
console.log("total", total);

快照沙箱

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      //快照沙箱:激活状态和失活状态
      // 1)一年前给 window拍照记录
      // 2)一年后(现在)又给window拍照
      // 3)失活状态时,将一年前保存的window和现在的window做对比,将新增不同的属性保存起来;还原到一年前的window
      // 4)再次激活时,恢复到上次保存的window状态
      class SnapShotSandBox {
        constructor() {
          this.proxy = window;
          this.modifyPropsMap = {};
          this.snapShotWindow = {};
          this.active();
        }
        active() {
          for (let p in window) {
            //初始化时的window
            if (window.hasOwnProperty(p)) {
              this.snapShotWindow[p] = window[p]; //拍照
            }
          }
          Object.keys(this.modifyPropsMap).forEach((p) => {
            window[p] = this.modifyPropsMap[p];
          });
        }
        inactive() {
          //现在最新的window
          for (let p in window) {
            if (window.hasOwnProperty(p)) {
              if (window[p] !== this.snapShotWindow[p]) {
                this.modifyPropsMap[p] = window[p]; //将现在window新增的属性保存起来
                window[p] = this.snapShotWindow[p]; //还原到初始化前window的状态
              }
            }
          }
        }
      }
      let sandBox = new SnapShotSandBox(); // 1.初始化激活状态,记录了window
      (function (window) {
        window.name = "aa";
        window.age = 2;
        console.log(window.name, window.age); //aa 2 //此时是最新的window,添加了a,b属性
        sandBox.inactive(); // 失活状态
        console.log(window.name, window.age); //undefined undefined //2.失活状态,先记录新增修改的属性,再还原到之前的window状态
        sandBox.active(); //3.再次激活,还原之前新添加了a,b属性的状态
        console.log(window.name, window.age); //aa 2
      })(sandBox.proxy);
      
      //2.Proxy代理沙箱
      class ProxySandbox {
        constructor() {
          const rawWindow = window;
          const fakeWindow = {};
          const proxy = new Proxy(fakeWindow, {
            set(target, p, value) {
              target[p] = value;
              return true;
            },
            get(target, p) {
              return target[p] || rawWindow[p];
            },
          });
          this.proxy = proxy;
        }
      }
      let sandbox1 = new ProxySandbox();
      let sandbox2 = new ProxySandbox();
      window.a = 1;
      ((window) => {
        window.a = "hello";
        window.b = "111";
        console.log("window.b", window.b);
        console.log("window.a", window.a);
      })(sandbox1.proxy);
      ((window) => {
        window.a = "world";
        console.log("window.b", window.b);
        console.log("window.a", window.a);
      })(sandbox2.proxy);
    </script>
  </body>
</html>

对象扁平化

// 原始数据
var entryObj = {
    a: {
        b: {
            c: {
                dd: 'abcdd'
            }
        },
        d: {
            xx: 'adxx'
        },
        e: 'ae'
    }
};
// 要求的结果
// let obj = {
//     "a.b.c.dd":'abcdd',
//     "a.d.xx":'adxx',
//     "a.e":'ae'
// }
var obj = {
    "obj1": {
        "obj11": 1,
        "obj12": 2
    },
    "obj2": {
        "obj21": 3,
        "obj22": {
            "obj33": 4,
        },
    }
};
//使用 Object.keys() + 递归
function flattenObj(obj, prekey = '', resobj = {}) {
    prekey = prekey ? prekey + '.' : '';
    Object.keys(obj).forEach((item) => {
        let temp = obj[item];
        if (temp && typeof temp === 'object') {
            flattenObj(temp, prekey + item, resobj);
        } else {
            resobj[`${prekey}${item}`] = temp;
        }
    })
    return resobj
}

// 使用 Object.entries() + 递归
function flat(obj, key = "", res = {}) { 
    for (let [k, v] of Object.entries(obj)) { 
      if (typeof v === "object") { 
        let tmp = key + k + "." 
        flat(v, tmp, res) 
      } else { 
        let tmp = key + k 
        res[tmp] = v 
      } 
    } 
    return res 
  }
let result = flattenObj(entryObj);
console.log(result);

// 原始数据
var entryObj = {
    a: {
        b: {
            c: {
                dd: 'abcdd'
            }
        },
        d: {
            xx: 'adxx'
        },
        e: 'ae'
    }
};
let entries = Object.entries(entryObj);
console.log('entries',entries); // [ [ 'a', { b: [Object], d: [Object], e: 'ae' } ] ]
for(let [k,v] of entries){
    console.log('[k,v]',[k,v])   
}

计算n以内的质数

// n = 7  2,3,5,7
function countPrimes(n) {
    let arr = [];
    for (let i = 2; i <= n; i++) {
        if (isPrimes(i)) {
            arr.push(i);
        }
    }
    return arr;
}
// 判断是否是质数,被1和本身整除的数才是质数
function isPrimes(num) {
    let isPrimes = true;
    // 这里排除了 1和本身
    for (let i = 2; i < num; i++) {
        // 如果 [2,n-1] 之间,有一个数能整除,说明就不是质数
        if (num % i === 0) {
            isPrimes = false;
            break;
        }
    }
    return isPrimes;
}
console.log('countPrimes(20)', countPrimes(20));

vue模板编译原理 new Function + with()语法

let obj = { age: 11, name: 'hello' }
let code = 'return name + age'
let str = `with(this) ${code}`; // this就是传进来的obj

function compileFn() {
    let fn = new Function(str);
    let res = fn.call(obj) // fn里的this就是obj
    return res;
}
let result = compileFn();
console.log('result', result); // 'hello11'

深拷贝

let obj = {
    name: 'Colin',
    age: 20,
    intresting: {
        name: 'eating',
        title: '吃饭'
    },
    list: [
        { id: 1, title: 'hello' },
        { id: 2, title: 'world' },
        {
            id: 3,
            title: '对象',
            hobby: {
                name: 'play',
                id: 4,
                title: '玩'
            }
        }
    ]
}
let arr = ['name', 'age', { id: 1, title: 'haha', hobby: { name: '旅游' } }];
function isObject(obj) {
    return typeof obj === 'object';
}
/**
 * WeakMap:
 * 1.只接受对象作为键名(null除外),不接受其他类型的值作为键名,它的key的引用是弱引用
 * 2.一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。
*/ 
function deepClone(obj, hash = new WeakMap()) {
    if (typeof obj !== 'object' || obj === null) return obj; // 判断是否是 object,这里和第47行 判断只需要其一就行了
    let Constructor = obj.constructor;
    let result;
    switch (Constructor) {
        case RegExp:
            result = new Constructor(obj);
            return result;
        case Date:
            result = new Constructor(obj.getTime());
            return result;
        default:
            break;
    }
    
    if (hash.has(obj)){
        return hash.get(obj);
    }    
    result = new Constructor(); // 引用
    hash.set(obj, result);
    
    for (let key in obj) {
        // hasOwnProperty只枚举对象自身的属性,不会遍历原型上的属性
        if (obj.hasOwnProperty(key)) {
            // result[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key]; // 判断是否是 object
            result[key] = deepClone(obj[key], hash); // result引用赋值
        }
    }
    return result;
}
obj.obj = obj; // 循环引用
let o1 = deepClone(obj);
o1.list[0].title = 'lc';
console.log('deepClone-o1', o1);
console.log('obj', obj);

树形结构扁平化转成一维数组

let data = [
    {
        id:1,
        value:'a',
        children:[
            {
                id:11,
                value:'a-a',
                children:[
                    {
                        id:111,
                        value:'a-a-a',
                        children:[]
                    },
                    {
                        id:112,
                        value:'a-a-b',
                        children:[]
                    }
                ] 
            },
            {
                id:12,
                value:'a-b',
                children:[]
            }
        ]
    },
    {
        id:2,
        value:'b-b',
        children:[]
    }
]
function treeToList (data) {
    let res = [];
    const dfs = tree => {
        tree.forEach(item => {
            if(item.children) {
                dfs(item.children)
                delete item.children
            }
            res.push(item)
        })
    }
    dfs(data);
    return res
}

/**
 * 将树形结构转化成一维数组,并给数组每项标记 level层级字段
 * @param {*} data 数组
 * @param {*} level 层级,最外层是0,越往内层,依次加1
 */
function treeToList2(data) {
    const arr = [];
    const flat = (data, level = 0) => {
        data.forEach((item) => {
            if (item.children.length) {
                flat(item.children, level + 1);// 这里仅仅是level+1,并没有将level+1后的值赋值给level
            }
            arr.push({ ...item, level, children: [] });
        })
    }
    flat(data)
    return arr;
}
console.log('treeToList2', JSON.stringify(treeToList2(data), null, 2));

数组等份截取

const list = [{id:1},{id:2},{id:3},{id:4},{id:5},{id:6},{id:7}];
function handleSlice(list, num) {
    // let index = 0;
    const arr = [];
    for (let i = 0; i < list.length; i += num) {
        arr.push(list.slice(i, i + num))
    }
    //   while (index<list.length){
    //       arr.push(list.slice(index,index+=num))
    //   }
    return arr;
}
let newList = handleSlice(list, 5);
console.log('newList', JSON.stringify(newList, null, 2));

数组去重

let arr = [
    { key: "04-28", count: 1 },
    { key: "10-12", count: 2 },
    { key: "09-04", count: 3 },
    { key: "09-04", count: 4 },
    { key: "06-01", count: 5 },
    { key: "01-17", count: 6 }
];

function handleUnique(arr) {
    let newArr = [];
    let newArrKeys = new Set();
    arr.forEach((item) => {
        if (!newArrKeys.has(item.key)) {
            newArrKeys.add(item.key);
            newArr.push(item);
        }
    })
    return newArr;
}
console.log(handleUnique(arr));

一维数组转化为tree树形结构

let arr = [
    {id: 1,name: '1',pid: 0},
    {id: 2,name: '1-1',pid: 1},
    {id: 3,name: '1-1-1',pid: 2},
    {id: 4,name: '1-2',pid: 1},
    {id: 5,name: '1-2-2',pid: 4},
    {id: 6,name: '1-1-1-1',pid: 3},
    {id: 7,name: '2'}
]

// 转化后的结果
//   const res = [
//     {
//       "id": 1,
//       "name": "1",
//       "pid": 0,
//       "children": [
//         {
//           "id": 2,
//           "name": "1-1",
//           "pid": 1,
//           "children": [
//             {
//               "id": 3,
//               "name": "1-1-1",
//               "pid": 2,
//               "children": [
//                 {
//                   "id": 6,
//                   "name": "1-1-1-1",
//                   "pid": 3,
//                   "children": []
//                 }
//               ]
//             }
//           ]
//         },
//         {
//           "id": 4,
//           "name": "1-2",
//           "pid": 1,
//           "children": [
//             {
//               "id": 5,
//               "name": "1-2-2",
//               "pid": 4,
//               "children": []
//             }
//           ]
//         }
//       ]
//     }
//   ]
方法1:递归
function handleTree(arr, parentId = 0) {
    let tree = [];
    arr.forEach((item)=>{
        if(item.pid === parentId){
            let vnode = {
                ...item,
                children:handleTree(arr,item.id)
            }
            tree.push(vnode)
        }
    })
    return tree;
}
console.log('handleTree(arr)',JSON.stringify(handleTree(arr),null,2));

//方法2:不用递归
//主要思路是先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储
function handleTreeToArray(data){
    const arr = [];
    const map = {};
    for(let i of data){
        map[i.id] = {...i,children:[]};
    }
    for(let item of data){
        // map[item.id] 当前项-子级
        const childItem = map[item.id];
        // 父级
        const parentItem = map[item.pid];// parentItem没有值会被过滤掉
        if(item.pid===0){
            arr.push(childItem);
        }else{
            // 通过给parentItem对象的children添加子项,并且引用不变
            parentItem?.children.push(childItem)
        }
    }
    return arr;
}

找出最长回文字符串


function longestPalindrome(str) {
    let palindromeStr = ""; //记录最长回文串
    let tempPalindrome = ""; //记录当前回文串
    for (let i = 0; i < str.length; i++) { //i记录当前遍历字符串的开始位置,循环依次向后遍历
        tempPalindrome = ""; //每次新的一轮开始时,将临时记录回文串的变量清空
        for (let j = i; j < str.length; j++) { //每次开始循环是以当前i所在的下标位置为开始遍历字符串的起始位置,直到遍历到结束位置
            tempPalindrome += str.charAt(j); //逐个增加字符串的长度
            if (isPalindrome(tempPalindrome) && tempPalindrome.length > palindromeStr.length) {
                //将当前的字符串传入isPalindrome进行回文判断,如果是回文串,则判断当前回文串长度是否大于之前记录的最长回文串的长度,
                // 如果大于之前的回文串,则更新之前的记录即可
                palindromeStr = tempPalindrome; //更新回文串
            }
        }
    }
    return palindromeStr; //返回最终的最长的回文串
}

function isPalindrome(s) { //判断是否为回文串
    let rev = s.split('').reverse().join(''); //字符串逆转操作
    return rev === s;
}
//测试
console.log(longestPalindrome("ddabbade"));//输出dabbad

每3位分割数字字符串(兼容有小数位和没有小数位的数)

// 每3位分割数字字符串
const formatMoney=(nStr)=> {
  nStr += '';
  let x = nStr.split('.');
  console.log('x',x);
  let x1 = x[0];
  // x.length>1,说明有小数位
  let x2 = x.length > 1 ? '.' + x[1] : '';
  let rgx = /(\d+)(\d{3})/; // 整数位最少4(1+3)位数
  while (rgx.test(x1)) {
    console.log('x1-1',x1);
      x1 = x1.replace(rgx, '$1' + ',' + '$2');
    console.log('x1-2',x1);
  }
  return x1 + x2;
}
let val = formatMoney('1122123456.12');
console.log('val',val);

兼容多种格式,金额每3位分割展示格式化的函数formatAmount

/**
 * 2.实现一个金额展示格式化的函数formatAmount ,金额展示规则为整数部分每三位用逗号分隔,小数部分展示00
 * 示例1:formatAmount(2688);=>"2,688.00"
 * 示例2:formatAmount('2e6');=>"2,000,000.00"
 * 示例3:formatAmount(-2.3333333);=>"-2.33"
 * 示例2:formatAmount('Alibaba');=>"-"   
 */
function formatAmount(num) {
  if (isNaN(Number(num))) {
    return '-'
  }
  if (typeof num === 'string') {
    num = Number(num);
  }
  num = num.toString();
  const arr = num.split(".");
  let n1 = arr[0];
  let n2 = arr[1];
  const reg = /(\d+)(\d{3})/;
  let str = "";
  while (reg.test(n1)) {
    n1 = n1.replace(reg, "$1" + "," + "$2");
  }
  if (n2) {
    str = n1 + "." + n2.slice(0, 2);// 有小数点时保留两位小数
  } else {
    str = n1 + '.00';
  }
  return str;
}
// console.log(formatAmount(2688.12));// 2,688.12
// console.log(formatAmount(2688));// 2,688.00
// console.log(formatAmount('2e6'));// 2,000,000.00
// console.log(formatAmount(-2.3333333));// -2.33
// console.log(formatAmount('Alibaba'))// -
// console.log(Number('Alibaba'));// NaN
// console.log(typeof NaN);// number

实现一个normalize函数,能将输入的特定字符串转化为特定结构化数据

/*
 * 实现一个normalize函数,能将输入的特定字符串转化为特定结构化数据
 * 注:字符串仅由小写字母和[ ]组成,且字符串不会包含多余空格
 * 示例一:'abc' -> {value:'abc'}
 * 示例二:'[abc[bcd[def]]]' ->{value:'abc',children:{value:'bcd',children:{value:'def'}}}
 */
// {value:'abc'},
// {value:'abc',children:{value:'bcd'}},
// {value:'abc',children:{value:'bcd',children:{value:'def'}}},

function normalize(str) {
  const reg = /\w+/g;
  const arr = str.match(reg);
  console.log('arr',arr);
  const obj = {}
  arr.reduce((pre, cur, index, arr) => {
    pre.value = cur;
    if (index < arr.length - 1) {
      pre.children = {}
    }
    return pre.children;
  }, obj)
  return obj;
}
console.log('normalize()', normalize('abc'));// {value:'abc'}
console.log('normalize()', normalize('[abc[bcd[def]]]'));//{value:'abc',children:{value:'bcd',children:{value:'def'}}}

instanceof 原理

function _instanceof(left,right){
    let l = left.__proto__;
    let r = right.prototype;
    while(true){
        if(l === null){
            return false;
        }
        if(l === r){
            return true;
        }
        l = l.__proto__;
    }
}
function Animal(name){
    this.name = name
}
let cat = new Animal('tom');
console.log('cat.__proto__ === Animal.prototype',cat.__proto__ === Animal.prototype); // true
console.log(_instanceof(cat,Animal));// true

Map和Set

let map1 = new Map([['a',1],['b','lc'],['age',12]]);
console.log('map1', map1.get('a')); //1

// 利用set求交集,并集,差集
let arr1=[1,2,3];
let arr2=[2,3,4,5,6];
//交集
function intersection(arr1,arr2){
    let set1 = new Set(arr1);
    let set2 = new Set(arr2);
    let inter = [...set1].filter((item)=>set2.has(item));
    return inter; 
}
console.log('交集',intersection(arr1,arr2)); //[2,3]

//并集
function union(arr1,arr2){
    let set1 = new Set([...arr1,...arr2]);
    return [...set1]; 
}
console.log('并集',union(arr1,arr2)); // [1,2,3,4,5,6]

// 求差集
function deleteFn(arr1,arr2){
    let set1=new Set(arr1);
    let set2=new Set(arr2);
    return [...set1].filter((item)=>!set2.has(item)); 
}
console.log('差集',deleteFn(arr1,arr2));// [1]

递归匹配过滤 动态路由(数组对象)

let roles = [
    "Home","Dashbord","Permission","PageUser",
    "PageAdmin","Roles","Github"
];
// 筛选出 roles数组里有的对象
const asyncRoutes = [
    {
      path: '/permission',
      name: 'Permission',
    //   component: Layout,
      redirect: '/permission/page-use',
      meta: {
        title: '权限许可',
        icon: 'el-icon-lock'
      },
      children: [
        {
          path: 'page-user',
          name: 'PageUser',
          component: () => import('@/views/permission/page-user'),
          meta: { title: '用户页面', icon: 'el-icon-user' }
        },
        {
          path: 'page-admin',
          name: 'PageAdmin',
          component: () => import('@/views/permission/page-admin'),
          meta: {
            title: '管理员页面',
            icon: 'el-icon-user-solid'
          }
        },
      ]
    },
    {
      path: 'https://github.com/gcddblue/vue-admin-webapp',
      name: 'Github',
      meta: { icon: 'el-icon-link', title: '项目链接' }
    },
    {
      path: '*',
      name: '404',
      redirect: '/404',
      hidden: true
    }
  ]
// 递归过滤(遍历asyncRoutes动态路由) 
function forSearchArr(route, roles) {
    let arrNew = []
    for (let item of route) {
      let itemNew = { ...item } //浅拷贝
      if (roles.includes(itemNew.name)) {
        if (itemNew.children) {
          itemNew.children = forSearchArr(itemNew.children, roles)
        }
        arrNew.push(itemNew)
      }
    }
    return arrNew
  }
  let newRouteArrs = forSearchArr(asyncRoutes,roles);
  console.log('newRouteArrs',JSON.stringify(newRouteArrs,null,2));

Object.defineProperty,defineReactive实现

// 递归深度观察对象,使其成响应式对象,
// defineReactive = 递归 + Object.defineProperty
let obj = {
    name: 'tom',
    age: 1,
    hobby:{
        play:'打球'
    }
}
function observer(obj) {
    if (typeof obj !== 'object') return obj;
    Object.keys(obj).forEach((key) => {
        defineReactive(obj, key, obj[key]);
    })
}
function defineReactive(obj, key, value) {
    // 如果value属性值是object,继续代理观测
    observer(value);
    Object.defineProperty(obj, key, {
        get() {
            console.log('get',key, value);
            return value;
        },
        set(newVal) {
            console.log('set更新',key, newVal);
            value = newVal;
        }
    })
}

observer(obj);
obj.hobby.play = 'hh1'; // 会触发 hobby的getter,同时会触发 play的setter

Promise错误捕获

//promise的reject只能是promise().catch()捕获 
async function main() {
    console.log("1");
    try {
        console.log("2");
        await f1();
    } catch (error) { // try catch 无法捕获 promise的reject
        console.log("6");
        console.log("error----", error);
    }
}
function f1() {
    new Promise((resolve, reject) => {
        resolve("1");
        console.log("3");
    }).then(() => {
        console.log("4");
        f2();
    });
}
function f2() {
    new Promise((resolve, reject) => {
        console.log("5");
        reject("error: f2"); // triggerUncaughtException(err, true /* fromPromise */);
    }).catch((err)=>{
        console.log('7');
        console.log('err',err);
    })
    // .catch((err)=>{
    //     console.log('7');
    //     console.log('err',err);
    // })
}
main(); // 分别打印: 1 2 3 4 5 7 err 'error: f2'

// try catch 捕获 throw new Error
function tryFn() {
    try {
        console.log('try');
        throw new Error('error-报错');
    } catch (err) {
        console.log('err', err);
    }
}
tryFn(); // 分别打印:try -> err Error: error-报错

实现有并行限制的 Promise 调度器

/*
 * @Description: 实现有并行限制的 Promise 调度器
 * 题目描述:JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个
 *  
 addTask(1000,"1");
 addTask(500,"2");
 addTask(300,"3");
 addTask(400,"4");
 的输出顺序是:2 3 1 4

 整个的完整执行流程:

一开始1、2两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4
 */
class Scheduler {
    constructor(limit) {
        this.queue = [];
        this.maxRun = limit;
        this.runningCounts = 0;
    }
    add(time, order) {
        const promiseFn = () => {
            return new Promise((resolve) => {
                setTimeout(() => {
                    resolve(order);
                }, time)
            })
        }
        this.queue.push(promiseFn)
    }
    start() {
        for (let i = 0; i < this.maxRun; i++) {
            this.request()
        }
    }
    request() {
        if (!this.queue || !this.queue.length || this.runningCounts >= this.maxRun) {
            return;
        }
        this.runningCounts++
        const firstFn = this.queue.shift();
        firstFn().then((res) => {
            this.runningCounts--;
            console.log(res);
            this.request();
        })
    }
}
let schedulerInstance = new Scheduler(2);
const addTask = (time, order) => {
    schedulerInstance.add(time, order);
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');

schedulerInstance.start(); // 2,3,1,4

每次调用返回的函数的时候,只会输出最后一个promsie的结果

/*
 * @Description:
 *  编写一个高阶函数,这个高阶函数接收一个返回promise的函数作为参数,然后返回一个函数。
 *  每次调用返回的函数的时候,只会输出最后一个promsie的结果。
 * // 示例
let count = 1;
// 这是一个函数,返回promise
let promiseFunction = () =>
    new Promise(resolve =>
        setTimeout(() => {
            resolve(count++);
        })
    );

let lastFn = lastPromise(promiseFunction);
lastFn().then(console.log); // 无输出
lastFn().then(console.log); // 无输出
lastFn().then(console.log); // 3

 */
let count = 1;
// 这是一个函数,返回promise
let promiseFunction = () => {
    return new Promise(resolve =>
        setTimeout(() => {
            resolve(count++);
        })
    );
}

function lastPromise(promiseFunction) {
    let time1 = 0;
    let time2 = 0;
    return function(){
        // lastPromise.time1优先加1
        time1++;
        return new Promise((resolve,reject)=>{
            // 等promiseFunction函数里的setTimeout,resolve之后,time2加1
             promiseFunction().then((res)=>{ 
                time2++;
                if(time1===time2){
                    resolve(res)
                }
            })
        })
    }
}
let lastFn = lastPromise(promiseFunction)
lastFn().then((res)=>{
    console.log(res)
});
lastFn().then((res)=>{
    console.log(res)
});
lastFn().then((res)=>{
    console.log(res)
});

this指向

/*
 * 1.this 的指向并不是在创建的时候就可以确定的,在 es5 中,this 永远指向最后调用它的那个对象。
 * 2.众所周知,ES6 的箭头函数是可以避免 ES5 中使用 this 的坑的。箭头函数的 this 始终指向函数定义时的 this,而非执行时。
 *   箭头函数需要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,
 *   则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。
  */
// es5 this是调用时或者执行时(运行时)决定的
// 栗1
var name = "windowsName";
var a = {
    name: "Cherry",
    fn: function () {
        console.log(this.name); // Cherry
    }
}
a.fn();

// 栗2
var name = "windowsName";
var a = {
    name: "Cherry",
    fn: function () {
        console.log(this.name);// windowsName
    }
}
var f = a.fn;
f();

// 栗3
var name = "windowsName";
var a = {
    name: "Cherry",
    func1: function () {
        console.log(this.name)
    },
    func2: function () {
        setTimeout(function () {
            console.log('this', this); // window
            this.func1(); //打印: this.func1 is not a function

        }, 100);
        // 优化如下:
        // setTimeout(function () {
        //     console.log('this', this); // window
        //     this.func1(); // 打印:Cherry
        // }.bind(a), 100);  // bind改变this指向a
    }
};
a.func2()     // this.func1 is not a function

// 栗4 => es6 箭头函数的this是定义时或创建时 决定的
var name = "windowsName";
var a = {
    name : "Cherry",
    func1: function () {
        console.log(this.name)     
    },
    func2: function () {
        console.log('this',this); // a对象
        setTimeout( () => { // 箭头函数的this是由定义时的非箭头函数的块级作用域决定的
            this.func1()
        },100);
    }
};
a.func2()     // Cherry

保留几位小数

 /**
 * 
 * @param {*} num 数值
 * @param {*} decimal  保留位数
 */
const formatDecimal = (num, decimal) => {
    num = num.toString()
    const index = num.indexOf('.')
    if (index !== -1) {// 浮点型小数
        num = num.substring(0, decimal + index + 1)
        console.log('num', num);
        return parseFloat(num).toFixed(decimal);// 可以过滤掉小数点后面都是0的小数,如 parseFloat(123.0) === 123
    } else {// 整数即没有小数点时
        num = num.substring(0)
        return num
    }
}
console.log(formatDecimal(123.0, 2));// 特殊0小数位的:123
console.log(formatDecimal(123.456, 2));// 保留两位小数: 123.45
console.log(formatDecimal(123.456, 3));// 保留三位小数: 123.456
console.log('parseFloat(123.0)', parseFloat(123.0));// 123
console.log('123.12'.substring(0, 3 + 1));// 123.  substring(startIndex,endIndex) // [) 左闭又开
console.log('123.12345'.substring(0)); // 123.12345

大数相加

/*
 * @Description: 题目描述:实现一个add方法完成两个大数相加
 * 
let a = "9007199254740991";
let b = "1234567899999999999";
function add(a ,b){
   //...
}
 */

function add(a, b) {
    let maxLen = Math.max(a.length, b.length);
    a = a.padStart(maxLen, 0);
    b = b.padStart(maxLen, 0);
    console.log(a,b);
    let cur = 0;// 当前值
    let carry = 0;// 进位值
    let sum = '';// 相加的值
    for (let i = maxLen - 1; i >= 0; i--) {
        cur = parseInt(a[i]) + parseInt(b[i]) + carry;
        carry = Math.floor(cur / 10);
        sum =  cur % 10 + sum;
    }
    console.log('carry',carry);
    console.log('sum',sum);
    if (carry > 0) {
        sum = '' + carry + sum;
    }
    return sum;
}
let a = "9000";
let b = "1234";
console.log('111',add(a, b));
// console.log(a[a.length - 1]);// 1

LeetCode 二叉树 前中后序遍历,层序遍历

                  root
         left-1           right-1
    left-2  right-2      

const root = {
    val:'root',
    left:{
        val:'left-1',
        left:{
            val:'left-2',
            left:null,
            right:null
        },
        right:{
            val:'right-2',
            left:null,
            right:null
        }
    },
    right:{
        val:'right-1',
        left:null,
        right:null
    }
}
// 递归
function BineryTree(root){
    let res = [];
    const preOrderTraverseNode=(node)=>{
        if(node===null) return;
        // 如果node.left === null,preOrderTraverseNode(node.left)这个函数就会被 return终止函数,
        // 然后接着往下执行preOrderTraverseNode(node.right)
        res.push(node.val);
        preOrderTraverseNode(node.left); 
        preOrderTraverseNode(node.right);
    }
    preOrderTraverseNode(root);
    return res;
}
console.log('BineryTree(root)',BineryTree(root));

// 栈 ->先进后出栈,前序遍历的顺序是:根->左->右,所以先把右入栈再把左入栈,然后左先出栈,右后出栈,[].pop每次取出最后一格
function preOrderTraverseNode1(root){
    let stack = [];
    let res = [];
    if(root ===null) return res;
    stack.push(root);
    while(stack.length>0){
        let currentNode = stack.pop(); // 第一个是root节点
        res.push(currentNode.val);// 先根,再左,最后右
        if(currentNode.right!==null){
            stack.push(currentNode.right);// 先把右边的入栈,先进后出栈
        }
        if(currentNode.left!==null){
            stack.push(currentNode.left);// 再把左边的入栈,后进先出栈
        }
    }
    
    return res;
}
console.log('preOrderTraverseNode1-stack',preOrderTraverseNode1(root));

// 层序遍历-先进先出
function levelOrderTraverseNode(root){
    let stack = [];
    let res = [];
    stack.push(root);
    while(stack.length>0){
        let currentNode = stack.shift();
        res.push(currentNode.val);
        if(currentNode.left!==null){
            stack.push(currentNode.left);
        }
        if(currentNode.right!==null){
            stack.push(currentNode.right);
        }
    }
    return res;
}
console.log('levelOrderTraverseNode(root)-层序遍历',levelOrderTraverseNode(root));

LeetCode 最长无重复字符子串的长度

// 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
// 示例 1:
// 输入: "abcabcbb"
// 输出: 3 
// 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
// 示例 2:
// 输入: "bbbbb"
// 输出: 1
// 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
// 示例 3:
// 输入: "pwwkew"
// 输出: 3
// 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
//      请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

const lengthOfLongestSubstring = function (s) {
    console.log('s',s.charAt(0));
    let arr = [], max = 0
    for (let i = 0; i < s.length; i++) {
        let index = arr.indexOf(s[i])
        if (index !== -1) {
            arr.splice(0, index + 1);
        }
        arr.push(s.charAt(i)); //arr.push(s[i])
        max = Math.max(arr.length, max)
    }
    console.log('arr',arr);// ['k','e','w']
    return max
};
console.log(lengthOfLongestSubstring("pwwkew"));

let arr = ['a','b','c','b']
let index = arr.indexOf('b');
console.log('index',index);
console.log(arr.splice(0,index+1)); //['a','b'] splice(startIndex,deleCount)
console.log('arr',arr); // ['c','b']

LeetCode 求root节点总数量

二叉树节点:
                  root
         left-1           right-1
    left-2  right-2      

const root = {
    val: 'root',
    left: {
        val: 'left-1',
        left: {
            val: 'left-2',
            left: null,
            right: null
        },
        right: {
            val: 'right-2',
            left: null,
            right: null
        }
    },
    right: {
        val: 'right-1',
        left: null,
        right: null
    }
}
// 求root的节点数量
function getNodeNum(node) {
    if (node === null) return 0;
    return getNodeNum(node.left) + getNodeNum(node.right) + 1;
}
console.log(getNodeNum(root)); // 5
//求root节点的最大深度(最大层数)
function getMaxDepth(node) {
    if (node === null) return 0;
    let left = getMaxDepth(node.left);
    let right = getMaxDepth(node.right);
    return Math.max(left, right) + 1;
}
console.log(getMaxDepth(root));// 3
//求root节点的最小深度
function getMinDepth(node){
    if(node===null) return 0;
    let left = getMinDepth(node.left);
    let right = getMinDepth(node.right);
    return Math.min(left,right) + 1;
}
console.log(getMinDepth(root));

LeetCode 给出一个区间的集合,请合并所有重叠的区间

/*
 * https://blog.csdn.net/qq_41096610/article/details/107531031
 * @Description: 给出一个区间的集合,请合并所有重叠的区间。
 * 
示例 1:
输入: [[1,3],[2,6],[8,10],[15,18]] 输出: [[1,6],[8,10],[15,18]]
解释: 区间[1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:
输入: [[1,4],[4,5]] 输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
 */

//  方法1;
// splice方法 改变原数组
var merge = function(intervals) {
    if(intervals.length <= 1) { return intervals }
    intervals.sort((a,b) => a[0]-b[0]) // 行进行升序排序
    let arr = []
    for(var i=0;i<intervals.length-1;i++){ //边界intervals.length-1,因为删除元素变短了,此时,i--
        const a2 = intervals[i][1]        //取[[1,4],[4,5]]中第一个数组元素4
        const b1 = intervals[i+1][0]      //取[[1,4],[4,5]]中第二个数组元素4
        const b2 = intervals[i+1][1]      //取[[1,4],[4,5]]中第二个元素5
        if(a2 >= b1){
            intervals[i][1] = a2 > b2 ? a2 : b2
            intervals.splice(i+1,1) //删除后面多余的元素,删除数量为1
            arr.push(a2,b1)
            i--  //根据此例子,当i=0时,因为删除arr[1]项之后,数组的长度发生了变化,需减1-1,0-1=-1,然后i++,此时又从-1+1 = 0开始遍历
        }
        console.log('i++之后',i);
    }
    return intervals
};
let arr = [[1,3],[2,6],[8,10],[9,11],[15,18]];
console.log(merge(arr));

// 方法2:
/**i和j双指针,不改变原数组,返回新数组
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge2 = function(intervals) {
    if(intervals.length <= 1) { return intervals }
    intervals.sort((a,b) => {
        return a[0] - b[0]
    })
    let res = [intervals[0]], j = 0;
    for(let i = 1; i < intervals.length; i++) {
        if(res[j][1] >= intervals[i][1]){
            res[j] = res[j]
        }else if(res[j][1] >= intervals[i][0] && res[j][1] < intervals[i][1]){
            res[j][1] = intervals[i][1]
        }else{
            res.push(intervals[i])
            j++
        }
    }
    return res;
};
let arr = [[1,3],[8,10],[2,6],[15,18]];
console.log(merge2(arr));

LeetCode 验证括号是否都闭合

示例 1:
输入:(){}[]
输出:true

示例 2:
输入:()[{
输出:false

示例 2:
输入:([{}])
输出:true
 */

//  方法1;
function valid(s) {
    if (s.length < 2) return false;
    let memo = new Map();
    let result = []
    memo.set("(", ")")
    memo.set("{", "}")
    memo.set("[", "]")
    for (let i = 0; i < s.length; i++) {
        if (memo.has(s[i])) {
            result.push(memo.get(s[i]))
        } else {
            if (result.pop() !== s[i]) {
                return false;
            }
        }
    }
    if(result.length>0) return false;
    return true;
}
console.log(valid('([{}])'));// true

LeetCode m*n网格里找两点之间总共有多少种路径

/*
 * https://leetcode-cn.com/problems/unique-paths/
 * 
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28

示例 2:
输入:m = 3, n = 2
输出:3
 */

// m=3,n=7 =>28(3行7列)
function uniquePath2(m, n) { // 3*7
    const memo = [];
    for (let i = 0; i < n; i++) {
        memo.push([])
    }
    for (let col = 0; col < n; col++) { // 第一行都是1 二维数组
        memo[col][0] = 1;
    }
    for (let row = 0; row < m; row++) { // 第一列都是1 二维数组
        memo[0][row] = 1;
    }
    for (let col = 1; col < n; col++) { // 第2列开始
        for (let row = 1; row < m; row++) { // 第2行开始
            memo[col][row] = memo[col][row-1] + memo[col-1][row]
        }
    }
    return memo[n-1][m-1]
}
console.log(uniquePath2(3, 7));// 28

LeetCode 反转链表

/**
 * 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
 * 输入:head = [1,2,3,4,5]
   输出:[5,4,3,2,1] 
 */
let root = {
    val: '1',
    next: {
        val: '2',
        next: {
            val: '3',
            next: {
                val: '4',
                next: {
                    val: '5',
                    next: null
                }
            }
        }
    }
}
// [1->2->3->4->5] -> [1<-2<-3<-4<-5]
function reverseList(head) {
    if (head === null) return null;
    let pre = null;
    let cur = head;
    while (cur !== null) {
        let nxt = cur.next;
        cur.next = pre;
        pre = cur;
        cur = nxt;
    }
    return pre;
}
console.log(JSON.stringify(reverseList(root), null, 2));
// 打印结果:
/**
 {
  "val": "5",
  "next": {
    "val": "4",
    "next": {
      "val": "3",
      "next": {
        "val": "2",
        "next": {
          "val": "1",
          "next": null
        }
      }
    }
  }
}
 */

LeetCode 合并两个有序数组

/*
 * https://leetcode-cn.com/problems/merge-sorted-array
 * @Description: 合并两个有序数组
 * 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
   初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
    示例 1:
    输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
    输出:[1,2,2,3,5,6]

    示例 2:
    输入:nums1 = [1], m = 1, nums2 = [], n = 0
    输出:[1]
 */
// 方法1 :先合并在sort排序
function merge(num1, num2) {
    num1 = num1.filter((i) => i);
    num2 = num2.filter((j) => j);
    num1 = num1.concat(num2).sort((a, b) => a - b);
    return num1;
}
let nums1 = [1, 2, 3, 0, 0, 0], m = 3;
let nums2 = [2, 5, 6], n = 3;
console.log(merge(nums1, nums2));

// 方法2 :我们为两个数组分别设置一个指针p1和p2来作为队列的头部指针
const merge2 = function (nums1, m, nums2, n) {
    let p1 = 0, p2 = 0;
    const sorted = new Array(m + n).fill(0);
    let cur;
    while (p1 < m || p2 < n) {
        if (p1 === m) {
            cur = nums2[p2++];
        } else if (p2 === n) {
            cur = nums1[p1++];
        } else if (nums1[p1] < nums2[p2]) {
            cur = nums1[p1++];
        } else {
            cur = nums2[p2++];
        }
        sorted[p1 + p2 - 1] = cur; //p1=3,p2=3,cur=6 [1,2,2,3,5,6]
    }
    for (let i = 0; i < m + n; ++i) {
        nums1[i] = sorted[i];
    }
};
let nums1 = [1, 2, 3, 0, 0, 0], m = 3;
let nums2 = [2, 5, 6], n = 3;
merge2(nums1, m, nums2, n);
console.log('nums1', nums1); // [1,2,2,3,5,6]

let nums1 = [1, 2, 3], p1 = 0;
let cur;
cur = nums1[p1++];
// 等价
// cur = nums1[p1];
// p1 = p1 + 1
console.log({ cur, p1 }); // { cur: 1, p1: 1 }