代码题

79 阅读20分钟

代码题

手写题

1.手写防抖函数

设置clearTimeout为什么还要timer=null 设置延时器之前先清除下延时器,不然每次事件触发都会多一个延时器,延时器之间互相干扰,造成紊乱。

function debounce(fn,delay){
    let timer;
    return function(...args){
        if(timer){
            clearTimeout(timer);
              timer=null;
        }
        timer=setTimeout(()=>{
            fn.apply(this,args)
        },delay);
    }
}
//测试
function task(){
    console.log('run task')
}
const debounceTask=debounce(task,1000)
window.addEventListener('scroll',debounceTask);

2.手写节流函数

function throttle(fn,delay){
    let last=0;
    return (...args)=>{
        const now =Date.now();
        if(now-last>delay){
            last=now;
            fn.apply(this,args)
        }
    }
}
function task(){
    console.log('run task')
}
const throttleTask=throttle(task,1000);
window.addEventListener('scroll',throttleTask);

3.手写浅拷贝深拷贝

浅拷贝是指,一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。如果其中一个对象的引用内存地址发生改变,另一个对象也会发生变化。

浅拷贝

Object.assign()

Object.assign() 是ES6中对象的拷贝方法,接受的第一个参数是目标对象,其余参数是源对象,用法: Object.assign(target, source1, ···) ,该方法可以实现浅拷贝,也可以实现一维对象 的深拷贝。

let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);
console.log(target); // {a: 1, b: 2, c: 3}
扩展运算符

使用扩展运算符可以在构造字面量对象的时候,进行属性的拷贝。语法: let cloneObj = {...obj };

let obj1 = {a:1,b:{c:1}
let obj2 = {...obj1};
obj1.a = 2;
console.log(obj1); 
//{a:2,b:{c:1}}
console.log(obj2);
//{a:1,b:{c:1}}
obj1.b.c = 2;
console.log(obj1); 
//{a:2,b:{c:2}}
console.log(obj2); 
//{a:1,b:{c:2
数组方法实现数组浅拷贝
Array.prototype.slice()

array.slice(start, end)

从已有数组中返回选定的元素,该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝

Array.prototype.concat()

concat() 用于合并两个或多个数组 返回一个新数组

手写浅拷贝
function shallowCopy(object){
        if(!object||typeof object!=="object") return
    let newObject=Array.isArray(object)?[]:{}
    for(let key in object){
      if(object.hasOwnProperty(key)){
        newObject[key]=object[key]
      }
    }
  return newObject
}

深拷贝

JSON.stringify()

JSON.parse(JSON.stringify(obj)) 原理就是利用 JSON.stringify 将 js 对象序列化(JSON字符串),再使用JSON.parse 来反序列化(还原)js 对象

拷贝的对象中如果有函数,undefined, symbol,当使用过 JSON.stringify() 进行处理之后,都会消失

let obj1 = { a: 0,b: {c: 0}};
let obj2 = JSON.parse(JSON.stringify(obj1)
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}
手写深拷贝
function deepClone(obj) {
        let result;
        if (typeof obj == "object") {
          if (obj == null) {
            result = obj;
          } else if (obj instanceof RegExp) {
            result = new RegExp(obj);
          } else if (obj instanceof Date) {
            result = new Date(obj);
          } else if (Array.isArray(obj)) {
            result = [];
            for (let key of obj) {
              result.push(deepClone(key));
            }
          } else {
            result = {};
            for (let key in obj) {
              if (!result.hasOwnProperty(key)) {
                result[key] = deepClone(obj[key]);
              }
            }
          }
        } else {
          result = obj;
        }
        return result;
      }
      let m = {
        a: 1,
        b: ["name", "xxx"],
        c: /^g/,
        d: { e: 1 },
        f: function (a, b) {
          console.log(666);
        },
      };
​
      console.log(deepClone(m));
 function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};
解决循环引用和symbol
const obj = {
    name: "cc",
    age: 30,
    job: { type: "porgrammer", com: "ali" },
    cars: ["passat", "bmw"],
    working: function (str) {
        console.log(`我是${this.name},我正在${str}`)
    },
    da: new Date(),
    reg: new RegExp(),
    xx: undefined ,
};
obj.f=obj
​
​
function clone(obj, map = new Map()) {
    if (typeof obj != 'object') return
    var newObj = Array.isArray(obj) ? [] : {}
    if (map.get(obj)) {
        return map.get(obj);
    }
    map.set(obj, newObj);
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] == 'object') {
                newObj[key] = clone(obj[key], map);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    return newObj;
}
​
var copyobj = clone(obj)
console.log(copyobj);
​
// 终极解决方案,满足循环引用和symbol
//判断是否是基本数据类型
function isPrimitive(value){
  return (typeof value === 'string' || 
  typeof value === 'number' || 
  typeof value === 'symbol' ||
  typeof value === 'boolean')
}
 
//判断是否是一个js对象
function isObject(value){
  return Object.prototype.toString.call(value) === "[object Object]"
}
 
//深拷贝一个值
function cloneEnDeep(value){
  // 记录被拷贝的值,避免循环引用的出现
    let memo = {};
    function baseClone(value){
        let res;
        // 如果是基本数据类型,则直接返回
        if(isPrimitive(value)){
            return value;
        // 如果是引用数据类型,我们浅拷贝一个新值来代替原来的值
        }else if(Array.isArray(value)){
            res = [...value];
        }else if(isObject(value)){
            res = {...value};
        }
 
        // 检测我们浅拷贝的这个对象的属性值有没有是引用数据类型。如果是,则递归拷贝
        //同时使用Reflect可以检测到Symbol类型的属性
        Reflect.ownKeys(res).forEach(key=>{
            if(typeof res[key] === "object" && res[key]!== null){
                //此处我们用memo来记录已经被拷贝过的引用地址。以此来解决循环引用的问题
                if(memo[res[key]]){
                res[key] = memo[res[key]];
                }else{
                memo[res[key]] = res[key];
                res[key] = baseClone(res[key])
                }
            }
        })
        return res;  
    }
    return baseClone(value)
}
 
//======================测试====================
 //定义一个员工个人对象
 let objP = {
            name:"cc",
            age:30,
            job:{type:"porgrammer",com:"ali"},
            cars:["passat","bmw"],
            working:function(str){
                console.log(`我是${this.name},我正在${str}`)
            },
            da:new Date(),
            reg: new RegExp(),
            xx:undefined
        }
 
objP.job = objP;//循环引用
console.log(obj);
console.log(getPerInf(obj));
console.log(obj);
obj.working("吃饭");

4.手写ajax

function handleGet(url) {
        var response = "";
        //1.创建对象
        var xhr = new XMLHttpRequest();
        //2.设置方法
        xhr.open("GET", "http://127.0.0.1:8080/user");
        //3.发送请求
        xhr.send();
        //4.返回结果
        xhr.onreadystatechange = function () {
          if (xhr.readyState == 4) {
            if (xhr.status >= 200 && xhr.status < 300) {
              response = xhr.responseText;
              console.log(response, "1");
            } else {
              return "Error" + xhr.status;
            }
          }
        };
      }

5.使用promise实现ajax

// promise 封装实现:
function getJSON(url) {
  // 创建一个 promise 对象
  let promise = new Promise(function(resolve, reject) {
    let xhr = new XMLHttpRequest();
    // 新建一个 http 请求
    xhr.open("GET", url, true);
    // 设置状态的监听函数
    xhr.onreadystatechange = function() {
      if (this.readyState !== 4) return;
      // 当请求成功或失败时,改变 promise 的状态
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    // 设置错误监听函数
    xhr.onerror = function() {
      reject(new Error(this.statusText));
    };
    // 设置响应的数据类型
    xhr.responseType = "json";
    // 设置请求头信息
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 http 请求
    xhr.send(null);
  });
  return promise;
}

6.手写 apply call bind

apply实现

判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

判断传入上下文对象是否存在,如果不存在,则设置为 window 。

将函数作为上下文对象的一个属性。

判断参数值是否传入

使用上下文对象来调用这个方法,并保存返回结果。

删除刚才新增的属性

返回结果

Function.prototype.myApply=function (context,...args){
    if(typeof this !=="function"){
        return new Error('typeError')
    }
    context.fn=this
    if(args){
        result=context.fn(...args)
    }else{
        result=context.fn()
    }
    delete context.fn
    return result
}
let obj = {
        a: 10,
        b: 20,
      };
function sum(a, b) {
        this.b = 100;
        console.log(this.a, this.b);
        return a + b;
      }
sum.myApply(obj, 10, 20);

call实现

判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

判断传入上下文对象是否存在,如果不存在,则设置为 window 。

处理传入的参数,截取第一个参数后的所有参数。

将函数作为上下文对象的一个属性。

使用上下文对象来调用这个方法,并保存返回结果。

删除刚才新增的属性。

返回结果。

 Function.prototype.myCall = function (context) {
        context = context || window;
        if (typeof this !== "function") {
          throw new Error("typeError");
        }
        let result;
        context.fn = this;
        let args = [...arguments].slice(1);
        if (args) {
          result = context.fn(...args);
        } else {
          result = context.fn();
        }
        delete context.fn;
        return result;
      };
      var obj = {
        n: 15,
      };
      function sum(n, m) {
        console.log(this);
        return n + m;
      }
​
      console.log(sum.myCall(obj, 10, 20));

bind实现

// bind 函数实现
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 获取参数
  var args = [...arguments].slice(1),
     fn = this;
  return function Fn() {
    // 根据调用方式,传入不同绑定值
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  };
};
var obj = {
        n: 15,
      };
function sum(n, m) {
        console.log(this);
        return n + m;
      }
var ans=sum.myBind(obj, 10, 20)
console.log(ans());

7.手写promise.all

function myPromiseAll(arrayList) {
        return new Promise((resolve, reject) => {
          let resultArr = [],
            count = 0;
          let length = arrayList.length;
          for (let i = 0; i < length; i++) {
            Promise.resolve(arrayList[i]).then(
              (result) => {
                count++;
                resultArr[i] = result;
                if (count == length) {
                  resolve(resultArr);
                }
              },
              (err) => {
                return reject(err);
              }
            );
          }
        });
      }
​
function test(num, delay) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            num == 4 ? reject(num) : resolve(num);
          }, delay);
        });
      }
      let p1 = test(1, 1000);
      let p2 = test(2, 2000);
      let p3 = test(3, 3000);
      let p4 = test(4, 4000);
      myPromiseAll([p1, p2, p3]).then((result) => {
        console.log(result);
      });

8.手写promise.race

function PromiseRace(arrayList) {
        return new Promise((resolve) => {
          for (let i = 0; i < arrayList.length; i++) {
            Promise.resolve(arrayList[i]).then((result) => {
              resolve(result);
            });
          }
        });
      }
      function test(num, time) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            num == 4 ? reject() : resolve(num);
          }, time);
        });
      }
      let p1 = test(1, 2000);
      let p2 = test(2, 1000);
      let p3 = test(3, 5000);
      PromiseRace([p3, p1, p2]).then((res) => {
        console.log(res);
      });

9.实现函数科里化

1.固定参数

理解 arg数组是不断被扩展的 length指的是传入的test函数的参数长度

 function curry(fn) {
        let length = fn.length;
        return function temp() {
          let arg = [...arguments];
          if (arg.length >= length) {
            return fn(...arg);
          } else {
            return function () {
              return temp(...arg, ...arguments);
            };
          }
        };
      }
      function add(a, b, c) {
        return a + b + c;
      }
      let test = curry(add);
      console.log(test(1)(2, 3));

2.不固定参数

function curry(fn) {
        let arg = [...arguments].slice(1);
        let temp = function temp() {
          arg = [...arg, ...arguments];
          return curry(fn, ...arg);
        };
        temp.toString = function () {
          return fn.apply(null, arg);
        };
​
        return temp;
      }
​
      function fn() {
        return [...arguments].reduce((pre, cru) => {
          return pre + cru;
        }, 0);
      }
      let test = curry(fn);
      console.log(test(1)(2, 3)(4).toString());

3.固定参数的add函数

function add(x) {
      let sum = x;
      let temp = function (y) {
        sum += y;
        return temp;
      };
      temp.toString = function () {
        return sum;
      };
      return temp;
    }
    let a = add(1)(2)(4);
    console.log(a.toString());

4.不固定参数的add函数

 function add() {
        let arg = [...arguments];
        let add = function () {
          arg.push(...arguments);
          return add;
        };
        add.valueOf = function () {
          return arg.reduce((pre, cru) => {
            return pre + cru;
          });
        };
        return add;
      }
      console.log(add(1)(2)(3)(4)(5).valueOf());

10.实现new

function news(fn, ...arg) {
        let obj = {};
        obj.__proto__ = fn.prototype;
        fn.apply(obj, arg);
        return obj;
      }
​
      // 使用方法
      function Person(name, age) {
        this.name = name;
        this.age = age;
      }
      let person1 = news(Person, "张三", 15);
      console.log(person1);
​
      let person2 = new Person("李四", 15);
      console.log(person2);

11.实现Instanceof

function myInstanceof(left, right) {
        let proto = Object.getPrototypeOf(left),
          prototype = right.prototype;
​
        while (1) {
          if (!proto) return false;
          if (proto === prototype) return true;
​
          proto = Object.getPrototypeOf(proto);
        }
      }
      console.log(myInstanceof([1, 3, 4], Array));
      console.log(myInstanceof({}, Array));
      console.log(myInstanceof(new Date(), Object));
      console.log(myInstanceof(2, Array));

12.手写promise

链式调用原理

我门常常用到 new Promise().then().then() ,这就是链式调用,用来解决回调地狱

1、为了达成链式,我们默认在第一个then里返回一个promise。规定了一种方法,就是在then里面返回一个新的promise,称为promise2: promise2 = new Promise((resolve, reject)=>{}) •将这个promise2返回的值传递到下一个then中 •如果返回一个普通的值,则将普通的值传递给下一个then中

2、当我们在第一个then中 return 了一个参数(参数未知,需判断)。这个return出来的新的promise就是onFulfilled()或onRejected()的值

规定onFulfilled()或onRejected()的值,即第一个then返回的值,叫做x,判断x的函数叫做resolvePromise

then(onFulfilled,onRejected) {
    // 声明返回的promise2
    let promise2 = new Promise((resolve, reject)=>{
      if (this.state === 'fulfilled') {
        let x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'rejected') {
        let x = onRejected(this.reason);
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(()=>{
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.onRejectedCallbacks.push(()=>{
          let x = onRejected(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    });
    // 返回promise,完成链式
    return promise2;
  }

规定了一段代码,让不同的promise代码互相套用,叫做resolvePromise •如果 x === promise2,则是会造成循环引用,自己等待自己完成,则报“循环引用”错误

let p = new Promise(resolve => {
  resolve(0);
});
var p2 = p.then(data => {
  // 循环引用,自己等待自己完成,一辈子完不成
  return p2;
})
​

1、判断x

•x 不能是null •x 是普通值 直接resolve(x) • x 是对象或者函数(包括promise), let then = x.then 2、当x是对象或者函数(默认promise) •声明了then •如果取then报错,则走reject() •如果then是个函数,则用call执行then,第一个参数是this,后面是成功的回调和失败的回调 •如果成功的回调还是pormise,就递归继续解析 3、成功和失败只能调用一个 所以设定一个called来防止多次调用

function resolvePromise(promise2, x, resolve, reject){
  // 循环引用报错
  if(x === promise2){
    // reject报错
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  // 防止多次调用
  let called;
  // x不是null 且x是对象或者函数
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+规定,声明then = x的then方法
      let then = x.then;
      // 如果then是函数,就默认是promise了
      if (typeof then === 'function') { 
        // 就让then执行 第一个参数是this   后面是成功的回调 和 失败的回调
        then.call(x, y => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          // resolve的结果依旧是promise 那就继续解析
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          reject(err);// 失败了就失败了
        })
      } else {
        resolve(x); // 直接成功即可
      }
    } catch (e) {
      // 也属于失败
      if (called) return;
      called = true;
      // 取then出错了那就不要在继续执行了
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
​

不完全实现(没有完全实现链式调用)

/*  Promise有三个状态,包括Pending,resolved,rejected */
      //状态定义
      const PENDING = "pending";
      const RESOLVED = "resolved";
      const REJECTED = "rejected";
​
      function MyPromise() {
        //保存初始化状态
        var self = this;
        //初始化状态
        this.state = PENDING;
        //用于保存resolve或者rejected传入的值
        this.value = null;
        //用于保存resolve的回调函数
        this.resolvedCallbacks = [];
        //用于保存reject的回调函数
        this.rejectedCallbacks = [];
​
        //状态转变为resolved方法
        function resolved(value) {
          if (value instanceof MyPromise) {
            return value.then(resolved, rejected);
          }
​
          //保证代码的执行顺序为本轮事件循环的末尾
          setTimeout(() => {
            //   只有状态pending才改变
            if (self.state === PENDING) {
              //改变状态
              self.status = RESOLVED;
              //设置传入的值
              self.value = value;
              //执行回调函数
              self.resolvedCallbacks.forEach((callback) => {
                callback(back);
              });
            }
          }, 0);
        }
​
        //状态转变为rejected方法
        function rejected(value) {
          //保证代码的执行顺序为本轮事件循环的末尾
          setTimeout(() => {
            //   只有状态pending才改变
            if (self.state === PENDING) {
              //改变状态
              self.status = REJECTED;
              //设置传入的值
              self.value = value;
              //执行回调函数
              self.rejectedCallbacks.forEach((callback) => {
                callback(back);
              });
            }
          }, 0);
        }
​
        //将两个方法传入函数执行
        try {
          fn(resolve, reject);
        } catch (e) {
          //遇到错误时,捕获错误,执行reject函数
          reject(e);
        }
​
        MyPromise.prototype.then = function (onResolved, onRejected) {
          //首先判断两个参数是否为函数类型,因为这两个参数是可选参数
          onResolved =
            typeof onResolved === "function"
              ? onResolved
              : function (value) {
                  return value;
                };
          onRejected =
            typeof onRejected === "function"
              ? onRejected
              : function (error) {
                  throw error;
                };
​
          //如果是等待状态,则将函数加入对应列表中
          if (this.status === PENDING) {
            this.resolvedCallbacks.push(onResolved);
            this.rejectedCallbacks.push(onRejected);
          }
          if (this.state === RESOLVED) {
            onResolved(this.value);
          }
          if (this.state === REJECTED) {
            onRejected(this.value);
          }
        };
      }

完全规范实现

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // onRejected如果不是函数,就忽略onRejected,直接扔出错误
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        // 异步
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        // 异步
        setTimeout(() => {
          // 如果报错
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          // 异步
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          // 异步
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        });
      };
    });
    // 返回promise,完成链式
    return promise2;
  }
}
​

全部方法实现

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        });
      };
    });
    return promise2;
  }
  catch(fn){
    return this.then(null,fn);
  }
}
function resolvePromise(promise2, x, resolve, reject){
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') { 
        then.call(x, y => {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if(called)return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if(called)return;
      called = true;
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
//resolve方法
Promise.resolve = function(val){
  return new Promise((resolve,reject)=>{
    resolve(val)
  });
}
//reject方法
Promise.reject = function(val){
  return new Promise((resolve,reject)=>{
    reject(val)
  });
}
//race方法 
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    };
  })
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data);
      },reject);
    };
  });
}
​

13.手写Reduce

Array.prototype.myReduce = function (cb, initialValue) {
  const array = this//获取数组
  let acc = initialValue || array[0]//acc相当于pre
  const startIndex = initialValue ? 0 : 1
  for (let i = startIndex; i < array.length; i++) {
    const cur = array[i]
    acc = cb(acc, cur, i, array)
  }
  return acc
}

14.手写promisify

手动实现一个promisify函数的意思是:我们把一个异步请求的函数,封装成一个可以具有 then方法的函数,并且在then方法中返回异步方法执行结果的这么一个函数

  1. 具有 then 方法
  2. then 方法里返回异步接口执行结果
// 首先定一个需要进行 promisify 的函数
function asyncFn(a, b, callback) {
        // 异步操作,使用 setTimeout 模拟
        console.log('异步请求参数', a, b)
        setTimeout(function() {
                callback('异步请求结果')
        }, 3000)
}

// 我们希望调用的方式是
const proxy = promisify(asyncFn)
proxy(11,22).then(res => {
        // 此处输出异步函数执行结果
        console.log(res)
})

// 定义一个方法, 需要针对异步方法做封装,所以需要一个入参,既需要promisify的原异步方法
function promisify( asyncFn ) {
        // 方法内部我们需要调用asyncFn方法,并传递原始参数,所以需要返回一个方法来接收参数
        return function(...args) { // 由于需要接收参数,所以参数我们可以写为...args
                // 我们需要执行异步操作,并返回一个结果,所以返回一个 promise实例
                return new Promise(resolve => {
                        // asyncFn 需要执行一个回调,所以定义一个回调方法
                        const callback = function(...args) {
                              resolve(args)
                         }
                        args.push(callback)
                        asyncFn.apply(null, args)
                })
        }
}

15.用setTimeout实现setInterval

setTimeout() :在指定的毫秒数后调用函数或计算表达式,只执行一次。setInterval() :按照指定的周期(以毫秒计)来调用函数或计算表达式。方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。

使用递归函数,不断地去执行setTimeout从而达到setInterval的效果

setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。

针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。

实现思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果

function mySetInterval(fn, delay){
  function interval(){
    setTimeout(interval, delay);
    fn();
  }
  setTimeout(interval, delay)
}

限制调用次数

function mySetInterval(fn, delay,count){
  function interval(){
    if(typeof count==='undefined'||count-->0){
      setTimeout(interval, delay);
      try{
        fn()
      }catch(e){
        count = 0;
        throw e.toString();
      }
    }
  }
  setTimeout(interval, delay)
}

16.promise串行执行

const funcArr = [
    () =>
        new Promise((resolve) => {
            setTimeout(() => resolve(1), 2000);
        }),
    () =>
        new Promise((resolve) => {
            setTimeout(() => resolve(2), 1000);
        }),
    () =>
        new Promise((resolve) => {
            setTimeout(() => resolve(3), 3000);
        }),
];
/**
 * @description: 实现Promise的串行
 * @param {*}: 接收一个包含多个返回Promise对象的函数的数组
 * @return {*}: 返回一个Promise对象
 */
function inOrder(arr) {
    const res = []
    return new Promise((resolve,reject) => {
        arr.reduce((pre,cur) => {
            return pre.then(cur).then(data => res.push(data))
        },Promise.resolve()).then(() => resolve(res))       
    })
}

inOrder(funcArr).then(data => console.log(data))

17.基于Promise的fetch

const log = console.log;
function maxRequest(url = ``, times = 3) {
  // 1. 闭包,保存私有属性
  function autoRetry (url, times) {
    console.log('times = ', times);
    times--;
    // 2. fetch 本身返回值就是 Promise,不需要再次使用 Promise 包裹
    return fetch(url).then(value => {
        if(value.status === 200) {
          console.log(`✅ OK`, value);
          // 3. 手动返回 Promise 的 value, 没有返回值 默认返回 undefined
          return value;
        } else {
          throw new Error(`❌  http code error: ${value.status }`);
        }
      }).catch((err) => {
        console.log(`❌  Error`, err);
        if (times < 1) {
          // 4. 方便后续的 thenable 处理 error
          throw new Error('💩  over max request times!');
        } else {
          // 5. 返回递归方法 
          return autoRetry(url, times);
        }
      });
  }
  // 6. 返回一个 Promise 的结果 (成功 Promise 或失败 Promise)
  return autoRetry(url, times);
}

// error test case
maxRequest(`https://cdn.xgqfrms.xyz/json/badges.js`)
  .then(res => res.json())
  .then(json=> console.log('json =', json))
  .catch(err => console.error(`err =`, err))
  .finally(() => {
      console.log('👻  whatever close loading...');
  });

// sucess test case
maxRequest(`https://cdn.xgqfrms.xyz/json/badges.json`)
  .then(res => res.json())
  .then(json=> console.log('json =', json))
  .catch(err => console.error(`err =`, err))
  .finally(() => {
      console.log('👻  whatever close loading...');
  });

数据处理题

1.实现日期格式化函数

dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日const dateFormat = (dateInput, format)=>{
    var day = dateInput.getDate() 
    var month = dateInput.getMonth() + 1  
    var year = dateInput.getFullYear()   
    format = format.replace(/yyyy/, year)
    format = format.replace(/MM/,month)
    format = format.replace(/dd/,day)
    return format
}

2.数组扁平化

简单版

(1)递归实现

普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接:

let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
  let result = [];
​
  for(let i = 0; i < arr.length; i++) {
    if(Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i]));
    } else {
      result.push(arr[i]);
    }
  }
  return result;
}
flatten(arr);  //  [1, 2, 3, 4,5]

(2)reduce 函数迭代

从上面普通的递归函数中可以看出,其实就是对数组的每一项进行处理,那么其实也可以用reduce 来实现数组的拼接,从而简化第一种方法的代码,改造后的代码如下所示:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
    return arr.reduce(function(prev, next){
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}
console.log(flatten(arr));//  [1, 2, 3, 45]

(3)扩展运算符实现

这个方法的实现,采用了扩展运算符和 some 的方法,两者共同使用,达到数组扁平化的目的:

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

(4)split 和 toString

可以通过 split 和 toString 两个方法来共同实现数组扁平化,由于数组会默认带一个 toString 的方法,所以可以把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组,如下面的代码所示:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
    return arr.toString().split(',');
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

通过这两个方法可以将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。

(5)ES6 中的 flat

我们还可以直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:arr.flat([depth])

其中 depth 是 flat 的参数,depth 是可以传递数组的展开深度(默认不填、数值是 1),即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
  return arr.flat(Infinity);
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

可以看出,一个嵌套了两层的数组,通过将 flat 方法的参数设置为 Infinity,达到了我们预期的效果。其实同样也可以设置成 2,也能实现这样的效果。在编程过程中,如果数组的嵌套层数不确定,最好直接使用 Infinity,可以达到扁平化。

(6)正则和 JSON 方法 在第4种方法中已经使用 toString 方法,其中仍然采用了将 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤掉字符串中的数组的方括号,最后再利用 JSON.parse 把它转换成数组:

let arr = [1, [2, [3, [4, 5]]], 6];
function flatten(arr) {
  let str = JSON.stringify(arr);
  str = str.replace(/([|])/g, '');
  str = '[' + str + ']';
  return JSON.parse(str); 
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

限制层数

function flat(arr,num=1){
  return num>0?
    arr.reduce(pre,cur)=>pre.concat(Array.isArray(cur))?flat(cur,num-1):cur),[])
   :arr.slice();
}
function flatten(arr,level){
		function walk(arr,currLevel){
			let res=[];
			for(let item of arr){
				if(Array.isArray(item)&&currLevel<level){
					res=res.concat(walk(item,currLevel+1))
				}else{
					res.push(item)
				}
			}
			return res
		}
		return walk(arr,1)
}

3.数组去重

给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。

ES6方法(使用数据结构集合):

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];

Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]

ES5方法:使用map存储不重复的数字

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];

uniqueArray(array); // [1, 2, 3, 5, 9, 8]

function uniqueArray(array) {
  let map = {};
  let res = [];
  for(var i = 0; i < array.length; i++) {
    if(!map.hasOwnProperty([array[i]])) {
      map[array[i]] = 1;
      res.push(array[i]);
    }
  }
  return res;
}

4.大数运算

大数相加

function add(a,b){
   let a=a.split(''),b=b.split('');
   let sum=[],flag=0;
    while(a.length||b.length){
          let num1=parseInt(a.pop())||0;
          let num2=parseInt(b.pop())||0;
        let temp=num1+num2+flag;
        if(temp>9){
            flag=1
            temp=temp%10
        }else{
            flag=0
        }
        sum.unshift(temp)
  }
  if(flag)sum.unshift(1)  //最后一次运算是否需要进位
    return sum.join('')
}

大数相乘

multiply('11', '99')为例,思路如下:

  1. 初始化一个数组res用来存放计算结果

  2. 双重for循环,从后往前,遍历str1和str2,将

    str1[i]*str2[j]+res[i+j]
    

    的结果存储到res中。

    • 一次循环后,res = [empty, 9,9]
    • 二次循环后,res = [9, 18,9]
  3. 从后往前遍历res

    • res[index - 1] += parseInt(res[index] / 10),res当前项取整和res前一项相加(完成>10进一的操作)
    • res[index] %= 10,res当前项取余

function multiply(str1, str2) {
  if (str1 == '0' || str2 == '0') return '0'
  let res = [],
    i = str1.length - 1,
    j = str2.length - 1;
  for (; i >= 0; i--) {
    let n1 = str1[i] - '0'
    j = str2.length - 1
    for (; j >= 0; j--) {
      let n2 = str2[j] - '0'
      res[i + j] = res[i + j] || 0
      res[i + j] += n1 * n2
    }
  }
  let index = res.length - 1
  while (index >= 1) {
    res[index - 1] += parseInt(res[index] / 10)
    res[index] %= 10
    index--
  }
  return res.join('')
}
var num1 = '546212878237823';
var num2 = '42362078923598';
console.log(multiply(num1, num2))

5.数字千分位逗号隔开

简单写法

let number =123456789
function formatNum(number){
  let str=''
  let arr=number.toString().split('')
  let length=arr.length
  while(length>3){
  str=`,${arr.splice(-3).join('')}`+str
  length=arr.length
  }
  return arr.join('')+str
}
console.log(formatNum(number))

考虑小数

function formatNum(number) {
    var arr = (number + '').split('.');
    var int = arr[0].split('');
    var fraction = arr[1] || '';
    var r = "";
    var len = int.length;
    int.reverse().forEach(function (v, i) {
        if (i !== 0 && i % 3 === 0) {
            r = v + "," + r;
        } else {
            r = v + r;
        }
    })
    return r + (!!fraction ? "." + fraction : '');
}
//!!相当于将转换成布尔类型

考虑负数

function formatNum(num) {
    let numPrefix = ''
    let numArr = ''
    let numDist = ''

    // 处理负数情况
    if (num < 0) {
      numPrefix = '-'
      numArr = String(num).slice(1).split('').reverse()
    } else {
      numArr = String(num).split('').reverse()
    }

    for (let i = 0; i < numArr.length; i++) {
      numDist += numArr[i]
      if ((i + 1) % 3 === 0 && (i + 1) < numArr.length) {
        numDist += ','
      }
    }

    return numPrefix + numDist.split('').reverse().join('')
  }

正则

    let num = '12345678'
    let reg = /(?=\B(\d{3})+$)/g
    console.log(num.replace(reg,",")) //12,345,678

6.解析URL Params为对象

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 结果
{ user: 'anonymous',
  id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
  city: '北京', // 中文需解码
  enabled: true, // 未指定值得 key 约定为 true
}
*/
function parseParam(url) {
  const paramsStr = /.+?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
  const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
  let paramsObj = {};
  // 将 params 存到对象中
  paramsArr.forEach(param => {
    if (/=/.test(param)) { // 处理有 value 的参数
      let [key, val] = param.split('='); // 分割 key 和 value
      val = decodeURIComponent(val); // 解码
      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
      if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
        paramsObj[key] = [].concat(paramsObj[key], val);
      } else { // 如果对象没有这个 key,创建 key 并设置值
        paramsObj[key] = val;
      }
    } else { // 处理没有 value 的参数
      paramsObj[param] = true;
    }
  })
  return paramsObj;
}

7.字符串和驼峰式转换

字符串转驼峰

数组法
//空格式
let a='hello world'
var b=a.split(' ').map(item=>{
    return item[0].toUpperCase()+item.substr(1,item.length)
}).join('')
console.log(b)

//横线式
let str='hello-world'
function getCamelCase(str) {
    let arr = str.split('-');
    return arr.map((item, index) => {
        if (index === 0) {
            return item;
        } else {
           return item.chartAt(0).toUpperCase() + item.slice(1); 
        }
    }).join('');
}
console.log(getCamelCase(str))
正则法
let a='hello world'
let b = a.replace((/\s\w/g),function(v){
	return v.substring(1).toUpperCase()
})
console.log(b)

let a='hello-world'
let b = a.replace((/-\w/g),function(v){
	return v.substring(1).toUpperCase()
})
console.log(b)

驼峰转字符串

数组法
//横线式
const str = 'helloWorld';
function getKebabCase(str) {
    let arr = str.split('');
    let result = arr.map((item) => {
        if (item.toUpperCase() === item) {
            return '-' + item.toLowerCase();
        } else {
            return item;
        }
    }).join('');
    return result;
}
console.log(getKebabCase(str));
const str = 'helloWorld';
function getKebabCase(prev, cur, index, array) {
    if (/[A-Z]/.test(cur)) {
        cur = cur.toLowerCase();
        if (index === 0) {
            return prev + cur;
        } else {
            return prev + '-' + cur;
        }
    } else {
        return prev + cur;
    }
}

function toKebabCase(arr) {
    if (typeof arr === 'string') {
        arr = arr.split('');
    }
    return arr.reduce(getKebabCase, '');
}

let test1 = toKebabCase(str); 
let test2 = [].reduce.call(st, getKebabCase, '');  
正则法
const str = 'helloWorld';
function getKebabCase(str) {
    let temp = str.replace(/[A-Z]/g, function(i) {
        return '-' + i.toLowerCase();
    })
    if (temp.slice(0,1) === '-') {
        temp = temp.slice(1);   //如果首字母是大写,执行replace时会多一个-,需要去掉
    }
    return temp;
}
console.log(getKebabCase(str));

8.Js对象转树形结构

// 转换前:
source = [{
            id: 1,
            pid: 0,
            name: 'body'
          }, {
            id: 2,
            pid: 1,
            name: 'title'
          }, {
            id: 3,
            pid: 2,
            name: 'div'
          }]
// 转换为: 
tree = [{
          id: 1,
          pid: 0,
          name: 'body',
          children: [{
            id: 2,
            pid: 1,
            name: 'title',
            children: [{
              id: 3,
              pid: 2,
              name: 'div'
            }]
          }]
        }]
source = [{
            id: 1,
            pid: 0,
            name: 'body'
          }, {
            id: 2,
            pid: 1,
            name: 'title'
          }, {
            id: 3,
            pid: 2,
            name: 'div'
          }]
//
function treeing (arr) {
  let tree = []
  const map = {}
  for (let item of arr) {
    // 一个新的带children的结构
    let newItem = map[item.id] = {
      ...item,
      children: []
    }
    if (map[item.pid]) { // 父节点已存进map则在父节点的children添加新元素
      let parent = map[item.pid]
      parent.children.push(newItem)
    } else { // 没有父节点,在根节点添加父节点
      tree.push(newItem)
    }
  }
  return tree
}
//
function toTree(data) {
                let result = []
                if(!Array.isArray(data)) {
                    return result
                }
                data.forEach(item => {
                    delete item.children;
                });
                let map = {};
                data.forEach(item => {
                    map[item.id] = item;
                });
                data.forEach(item => {
                    let parent = map[item.pid];
                    if(parent) {
                        (parent.children || (parent.children = [])).push(item);
                    } else {
                        result.push(item);
                    }
                });
                return result;
            }

console.log(treeing(source))

9.字符串去重和反转

字符串去重

let str = '11223344aabbcc'
function strSeparate(s) {
    return [...new Set([...s])].join('');
    // or return [...new Set(s.split(''))].join('')
}
console.log(strSeparate(str))

let str = '11223344aabbcc'
function strSeparate(s) {
    // 使用展开运算符,字符串转换成数组
    s = [...str];
    let arr = [];
    for(let i = 0; i < s.length; i++) {
        if(arr.indexOf(s[i]) == -1) {
            arr.push(s[i])
        }
    }
    return arr.join('');
}
console.log(strSeparate(str))

10.数字反转

var reverse = function(x) {
    let res = 0;
    while(x){
        res = res * 10 + x % 10;
        if(res > Math.pow(2, 31) - 1 || res < Math.pow(-2, 31)) return 0;
        x = ~~(x / 10);
    }
    return res;
};
var reverse = function (x) {
    let y = parseInt(x.toString().split("").reverse().join(""));
    if (x < 0)
        y = - y;
    return y > 2147483647 || y < -2147483648 ? 0 : y;
};

11.计算目录树的深度

得出depth即为树的高度得出depth即为树

定义变量depth为0

定义一个空数组temp,然后遍历tree,如果tree有children,就push到temp里面

开始while循环,如果temp长度不为0,depth++;如果temp长度为0,停止

得出depth即为树的高度

得出depth即为树的高度得出depth即为树的高度得出depth即为树的高度

const tree = {
  name: 'root',
  children: [
    { name: '叶子1-1' },
    { name: '叶子1-2' },
    {
      name: '叶子2-1',
      children: [{
        name: '叶子3-1',
        children: [{
          name: '叶子4-1'
        }]
      }]
    }
  ]
}
 
function getDepth(tree) {
  let depth = 0
 
  if (tree) {
    let arr = [tree]
    let temp = arr
    while (temp.length) {
      arr = temp
      temp = []
      for (let i = 0; i < arr.length; i++) {
        if (arr[i].children && arr[i].children.length) {
          for (let j = 0; j < arr[i].children.length; j++) {
            temp.push(arr[i].children[j])
          }
        }
      }
      depth++
    }
  }
  return depth
}console.log(getDepth(tree)); //输出4

12.树形结构获取路径名

const treeData = [
  {
    name: "root",
    children: [
      { name: "src", children: [{ name: "index.html" }] },
      { name: "public", children: [] },
    ],
  },
];

const RecursiveTree = (data) => {
  data.map((item) => {
    console.log(item.name);
    if (item.children) {
      RecursiveTree(item.children);
    }
  });
};

RecursiveTree(treeData);

13.数组转化成树形结构

递归形式

  const arr = [
  {id:"01", name: "张大大", pid:"", job: "项目经理"},
  {id:"02", name: "小亮", pid:"01", job: "产品leader"},
  {id:"03", name: "小美", pid:"01", job: "UIleader"},
  {id:"04", name: "老马", pid:"01", job: "技术leader"},
  {id:"05", name: "老王", pid:"01", job: "测试leader"},
  {id:"06", name: "老李", pid:"01", job: "运维leader"},
  {id:"07", name: "小丽", pid:"02", job: "产品经理"},
  {id:"08", name: "大光", pid:"02", job: "产品经理"},
  {id:"09", name: "小高", pid:"03", job: "UI设计师"},
  {id:"10", name: "小刘", pid:"04", job: "前端工程师"},
  {id:"11", name: "小华", pid:"04", job: "后端工程师"},
  {id:"12", name: "小李", pid:"04", job: "后端工程师"},
  {id:"13", name: "小赵", pid:"05", job: "测试工程师"},
  {id:"14", name: "小强", pid:"05", job: "测试工程师"},
  {id:"15", name: "小涛", pid:"06", job: "运维工程师"}
]
function toTree(list,parId){
	let len = list.length
	function loop(parId){
		let res = [];
		for(let i = 0; i < len; i++){
			let item = list[i]
			if(item.pid === parId){
				item.children = loop(item.id)
				res.push(item)
			}
		}
		return res
	}
	return loop(parId)
}

let result = toTree(arr,'')
console.log(result);

非递归形式

  const arr = [
    { 'id': '29', 'pid': '', 'name': '总裁办' },
    { 'id': '2c', 'pid': '', 'name': '财务部' },
    { 'id': '2d', 'pid': '2c', 'name': '财务核算部' },
    { 'id': '2f', 'pid': '2c', 'name': '薪资管理部' },
    { 'id': 'd2', 'pid': '', 'name': '技术部' },
    { 'id': 'd3', 'pid': 'd2', 'name': 'Java研发部' }
  ]

  function tranListToTreeData(list) {
    // 1. 定义两个中间变量
    const treeList = [],  // 最终要产出的树状数据的数组
      map = {}        // 存储映射关系


    // 2. 建立一个映射关系,并给每个元素补充children属性.
    // 映射关系: 目的是让我们能通过id快速找到对应的元素
    // 补充children:让后边的计算更方便
    list.forEach(item => {
      if (!item.children) {
        item.children = []
      }
      map[item.id] = item
    })
    //  {
    //    "29": { 'id': '29', 'pid': '',     'name': '总裁办', children:[] },
    //    '2c': { 'id': '2c', 'pid': '',     'name': '财务部', children:[] },
    //    '2d': { 'id': '2d', 'pid': '2c', 'name': '财务核算部', children:[]},
    //    '2f': { 'id': '2f', 'pid': '2c', 'name': '薪资管理部', children:[]},
    //    'd2': { 'id': 'd2', 'pid': '',     'name': '技术部', children:[]},
    //    'd3': { 'id': 'd3', 'pid': 'd2', 'name': 'Java研发部', children:[]}
    //  }

    // 3. 循环
    list.forEach(item => {
      // 对于每一个元素来说,先找它的上级
      //    如果能找到,说明它有上级,则要把它添加到上级的children中去
      //    如果找不到,说明它没有上级,直接添加到 tree3List
      const parent = map[item.pid]
      if (parent) {
        parent.children.push(item)
      } else {
        treeList.push(item)
      }
    })
    // 4. 返回出去
    return treeList
  }

  const treeList = tranListToTreeData(arr)
  console.log(treeList);

14.对象扁平化

1.带数组的

输入

{
    a: 'a',
    b: [1, { c: true }, [3]],
    d: { e: undefined, f: 3 },
    g: null,
}

输出

{
    a: "a",
    b[0]: 1,
    b[1].c: true,
    b[2][0]: 3,
    d.f: 3
    // null和undefined直接舍去
}
var myObj={
	a: 'a',
		b: [1, { c: true }, [3]],
			d: { e: undefined, f: 3 },
	g: null,
}

function treeToObj(myObj) {
	function getObj(myObj, str, toObj) {
		const level = Object.keys(myObj)
		let levelStr = str.slice()
		if (levelStr != '') {
			levelStr = levelStr + '.'
		}
		for (key of level) {
			if (myObj[key]) {
				if (typeof (myObj[key]) === 'object') {
					getObj(myObj[key], levelStr + key, toObj)
				} else {
					toObj[levelStr + key] = myObj[key]
				}
			}
		}
		return toObj
	}
	return getObj(myObj,'',{})
}

console.log(treeToObj(myObj))
var myObj = {
	a: 'a',
	b: [1, { c: true }, [3]],
	d: { e: undefined, f: 3 },
	g: null,
}

function treeToObj(myObj) {
	const res = {}
	function toObj(obj, str = null) {
		for (let key in obj) {
			if (typeof obj[key] === "number" || typeof obj[key] === "string" || typeof obj[key] === "boolean") {
				if (str === null) {
					res[key] = obj[key]
				} else if (Array.isArray(obj)) {
					res[str + '[' + key + ']'] = obj[key]
				} else {
					res[str + '.' + key] = obj[key]
				}
			} else if (Array.isArray(obj[key])) {
				if (str === null) {
					toObj(obj[key], key)
				} else if (Array.isArray(obj)) {
					toObj(obj[key], str + '[' + key + ']')
				} else {
					toObj(obj[key], str + '.' + key)
				}
			} else if (typeof obj[key] === 'object') {
				if (str === null) {
					toObj(obj[key], key)
				} else if (Array.isArray(obj)) {
					toObj(obj[key], str + '[' + key + ']')
				} else {
					toObj(obj[key], str + '.' + key)
				}
			}
		}
		return res;
	}
	return  toObj(myObj,'')
}
console.log(treeToObj(myObj))

2.纯对象

var entry = {
  a: {
    b: {
      c: {
        dd: 'abcdd'
      }
    },
    d: {
      xx: 'adxx'
    },
    e: 'ae'
  }
}

// 要求转换成如下对象
var output = {
  'a.b.c.dd': 'abcdd',
  'a.d.xx': 'adxx',
  'a.e': 'ae'
}
var entry = {
  a: {
    b: {
      c: {
        dd: 'abcdd'
      }
    },
    d: {
      xx: 'adxx'
    },
    e: 'ae'
  }
}
function flatObj(obj,parentKey="",result={}){
	for(const key in obj){
		if(obj.hasOwnProperty(key)){
			let keyName=`${parentKey}${key}`
			if(typeof obj[key]==='object'){
				flatObj(obj[key],keyName+".",result)
			}else{
				result[keyName]=obj[key]
			}
		}
	}
	return result
}
console.log(flatObj(entry))

15.根据表达式计算字母数量

描述:输入一串字符串,根据字符串求出每个字母的数量并返回结果对象。(数字为1时可省略) 示例一:输入:A3B2,输出:{"A": 3, "B": 2} 示例二:输入:A(A(A2B)2)3C2,输出:{"A": 16, "B": 6, "C": 2}


var countOfAtoms = function(formula) {
    //记录各个原子的值
    let dict = {}
    //栈中初始数字。用于表示单个原子的数字情况
    let stack = [1]
    //原子名
    let str = ''
    //暂且记录的原子数字情况,压入栈或者消耗后要重置。
    let count = 1;
    //从后往前遍历。
    for(let i = formula.length - 1; i >= 0; i-- ){
        const value = formula[i];
        //如果是数字。则记录
        if(!isNaN(value)){
            if(count === 1){
                count = value
            }else{
                count = parseInt(value + count)
            }
        };
        //如果是小写字母,则记录
        if(value >= 'a' && value <= 'z'){
            str = str + value;
        }
        //如果是右括号,则将记录的数字和栈顶部数字相乘并输出
        if(value === ')'){
            stack.push(stack[stack.length - 1] * count);
            count = 1;
        }
        //如果是左括号,则出栈
        if(value === '('){
            stack.pop();
        }
        //如果是大写字母,则将栈顶的值赋值给该原子。如果字典里有这个原子,则累加。重置临时记录的数字和原子值
        if(value >= 'A' && value <= 'Z'){
            let atomsValue = count * stack[stack.length-1];
           
            str = value + str;
            if(dict[str] === undefined){
                dict[str] = atomsValue;
            }else{
                dict[str] = dict[str] + atomsValue
            }
            count = 1;
            str = '';
        }
    }
    //按照字典排序
    let newkey = Object.keys(dict).sort();
    var result = {};//创建一个新的对象,用于存放排好序的键值对
    for (var i = 0; i < newkey.length; i++) {//遍历newkey数组
        result[newkey[i]] = dict[newkey[i]];//向新创建的对象中按照排好的顺序依次增加键值对
    }
    //按照要求的格式输出。
    let resultString = ''
    for(var key in result){
        resultString = resultString + key;
        if(result[key] > 1){
            resultString = resultString + result[key];
        }
    }
    return resultString
};

类似于726. 原子的数量

16.对象树遍历

const tree = {
	name: 'root',
	children: [
		{
			name: 'c1',
			children: [
				{
						name: 'c11',
					children: []		
					},
					{
						name: 'c12',
					children: []		
				}
			]
		},
		{
			name: 'c2',
			children: [
				{
						name: 'c21',
					children: []		
					},
					{
						name: 'c22',
					children: []		
				}
			]
		}
	]
}

// 深度优先的方式遍历 打印 name
// ['root', 'c1','c11', 'c12', 'c2', 'c21', 'c22']
function solve(root) {
            let stack = [],
                result = [];
            if(!root) return [];
            stack.push(root)
            while(stack.length) {
                let node = stack.pop()
                if(node == null ) continue
                result.push(node.name)
                for(let i = node.children.length-1; i >= 0; i--) {
                    // 这里就是面试的重点,应该从后面的节点压入栈中
                    stack.push(node.children[i])
                }
            }
            return result
        }
​

17.手写JSON.stringify和JSON.parse

1.JSON.stringify JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。

JSON.stringify(value[, replacer [, space]])

Boolean | Number| String 类型会自动转换成对应的原始值。 undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)。 不可枚举的属性会被忽略。 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。 2.JSON.parse JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换(操作)。

JSON.parse(text[, reviver])

用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)。

JSON.stringify

function jsonStringify (obj) {
  let type = typeof obj;
  if (type !== "object" || type === null) {
    if (/string|undefined|function/.test(type)) {
      obj = '"' + obj + '"';
    }
    return String(obj);
  } else {
    let json = []
    arr = (obj && obj.constructor === Array);
    for (let k in obj) {
      let v = obj[k];
      let type = typeof v;
      if (/string|undefined|function/.test(type)) {
        v = '"' + v + '"';
      } else if (type === "object") {
        v = jsonStringify(v);
      }
      json.push((arr ? "" : '"' + k + '":') + String(v));
    }
    return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
  }
}
jsonStringify({ x: 5 })
// "{"x":5}"
jsonStringify([1, "false", false])
// "[1,"false",false]"
jsonStringify({ b: undefined })
// "{"b":"undefined"}"

场景题

1.循环打印黄绿蓝

普通递归

const task = (timer, light, callback) => {
    setTimeout(() => {
        if (light === 'red') {
            red()
        }
        else if (light === 'green') {
            green()
        }
        else if (light === 'yellow') {
            yellow()
        }
        callback()
    }, timer)
}
const step = () => {
    task(3000, 'red', () => {
        task(2000, 'green', () => {
            task(1000, 'yellow', step)
        })
    })
}
step()

使用promise实现

const task = (timer, light) => 
    new Promise((resolve, reject) => {
        setTimeout(() => {
            if (light === 'red') {
                red()
            }
            else if (light === 'green') {
                green()
            }
            else if (light === 'yellow') {
                yellow()
            }
            resolve()
        }, timer)
    })
const step = () => {
    task(3000, 'red')
        .then(() => task(2000, 'green'))
        .then(() => task(2100, 'yellow'))
        .then(step)
}
step()

用 async/await 实现

const task = (timer, light) => 
    new Promise((resolve, reject) => {
        setTimeout(() => {
            if (light === 'red') {
                red()
            }
            else if (light === 'green') {
                green()
            }
            else if (light === 'yellow') {
                yellow()
            }
            resolve()
        }, timer)
    })
    
const taskRunner =  async () => {
    await task(3000, 'red')
    await task(2000, 'green')
    await task(2100, 'yellow')
    taskRunner()
}
taskRunner()

2.图片懒加载

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>
  <style>
    .container {
      width: 1000px;
      margin: 0 auto;
      background-color: pink;
    }
    .container > img {
      display: block;
      width: 400px;
      height: 400px;
      margin-bottom: 50px;
    }
  </style>
  <body>
    <div class="container">
      <img src="./img/loading.jpg" data-src="./img/pic.png" />
      <img src="./img/loading.jpg" data-src="./img/pic.png" />
      <img src="./img/loading.jpg" data-src="./img/pic.png" />
      <img src="./img/loading.jpg" data-src="./img/pic.png" />
      <img src="./img/loading.jpg" data-src="./img/pic.png" />
      <img src="./img/loading.jpg" data-src="./img/pic.png" />
    </div>
    <script>
      function lazyLoad() {
        var scrollTop =
          document.body.scrollTop || document.documentElement.scrollTop;
        var winHeight = window.innerHeight;
        for (var i = 0; i < imgs.length; i++) {
          if (imgs[i].offsetTop < scrollTop + winHeight) {
            imgs[i].src = imgs[i].getAttribute("data-src");
          }
        }
      }
      window.onscroll = lazyLoad();
    </script>
  </body>
</html>

3.promise实现图片异步加载

let imageAsync=(url)=>{
    return new Promise((resolve,reject)=>{
        let img=new Image();
        img.src=url;
        img.onload=()=>{
            resolve(image)
        }
        img.onerror=(err)=>{
            reject(err)
        }
    })
}
imageAsync("url").then(()=>{
    console.log('加载成功')
}).catch((error)=>{
    console.log('加载失败')
})

4.每隔一秒打印

// 使用闭包实现
for (var i = 0; i < 5; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i);
    }, i * 1000);
  })(i);
}
// 使用 let 块级作用域
for (let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, i * 1000);
}
function sleep(time){
    for(let i=0;i<5;i++){
        Promise.resolve(i).then((result)=>{
            setTimeout(()=>{
                console.log(result)
            },time*result)
        })
    }
}
sleep(1000)

5.双向数据绑定

 //=>发布订阅的类
      class EventEmitter {
        constructor() {
          this.arrayList = {};
        }
        on(name, fn) {
          if (this.arrayList[name] && !this.arrayList[name].includes(fn)) {
            this.arrayList[name].push(fn);
          } else {
            this.arrayList[name] = [fn];
          }
          console.log(this.arrayList);
        }
        off(name, fn) {
          if (this.arrayList[name]) {
            let index = this.arrayList[name].indexOf(fn);
            this.arrayList[name].splice(index, 1);
            console.log(this.arrayList);
          }
        }
        emit(name, once = false, ...arg) {
          if (this.arrayList[name]?.length > 0) {
            for (let key of this.arrayList[name]) {
              key.call(this, arg);
            }
          }
          if (!once) {
            delete this.arrayList[name];
          }
          console.log(this.arrayList);
        }
      }
      let s1 = new EventEmitter();
      let f1 = function () {
        console.log(666);
      };
      let f2 = function () {
        console.log(777);
      };
      var input = document.querySelector("#ipt");
      input.oninput = function (e) {
        obj.value = e.target.value;
      };
      let obj = {
        value: "",
      };
      Object.defineProperty(obj, "value", {
        get() {
          console.log("我被读了");
        },
        set(newVal) {
          s1.on("value1", function () {
            console.log("我的值是" + newVal);
            console.log("我被改了");
          });
          input.value = newVal;
          return newVal;
        },
      });

6.观察者模式

在观察者模式中,只有两个主体,分别是目标对象Subject,观察者Observer

  • 观察者需Observer要实现update方法,供目标对象调用。update方法中可以执行自定义的业务代码。
  • 目标对象Subject也通常被叫做被观察者或主题,它的职能很单一,可以理解为,它只管理一种事件。Subject需要维护自身的观察者数组observerList,当自身发生变化时,通过调用自身的notify方法,依次通知每一个观察者执行update方法。

观察者模式

// 观察者
class Observer {
    /**
     * 构造器
     * @param {Function} cb 回调函数,收到目标对象通知时执行
     */
    constructor(cb){
        if (typeof cb === 'function') {
            this.cb = cb
        } else {
            throw new Error('Observer构造器必须传入函数类型!')
        }
    }
    /**
     * 被目标对象通知时执行
     */
    update() {
        this.cb()
    }
}
​
// 目标对象
class Subject {
    constructor() {
        // 维护观察者列表
        this.observerList = []
    }
    /**
     * 添加一个观察者
     * @param {Observer} observer Observer实例
     */
    addObserver(observer) {
        this.observerList.push(observer)
    }
    /**
     * 通知所有的观察者
     */
    notify() {
        this.observerList.forEach(observer => {
            observer.update()
        })
    }
}
​
const observerCallback = function() {
    console.log('我被通知了')
}
const observer = new Observer(observerCallback)
​
const subject = new Subject();
subject.addObserver(observer);
subject.notify();
​
  • 角色很明确,没有事件调度中心作为中间者,目标对象Subject和观察者Observer都要实现约定的成员方法。
  • 双方联系更紧密,目标对象的主动性很强,自己收集和维护观察者,并在状态变化时主动通知观察者更新。

7.发布订阅模式

发布订阅模式图解

class PubSub {
    constructor() {
        // 维护事件及订阅行为
        this.events = {}
    }
    /**
     * 注册事件订阅行为
     * @param {String} type 事件类型
     * @param {Function} cb 回调函数
     */
    subscribe(type, cb) {
        if (!this.events[type]) {
            this.events[type] = []
        }
        this.events[type].push(cb)
    }
    /**
     * 发布事件
     * @param {String} type 事件类型
     * @param  {...any} args 参数列表
     */
    publish(type, ...args) {
        if (this.events[type]) {
            this.events[type].forEach(cb => {
                cb(...args)
            })
        }
    }
    /**
     * 移除某个事件的一个订阅行为
     * @param {String} type 事件类型
     * @param {Function} cb 回调函数
     */
    unsubscribe(type, cb) {
        if (this.events[type]) {
            const targetIndex = this.events[type].findIndex(item => item === cb)
            if (targetIndex !== -1) {
                this.events[type].splice(targetIndex, 1)
            }
            if (this.events[type].length === 0) {
                delete this.events[type]
            }
        }
    }
    /**
     * 移除某个事件的所有订阅行为
     * @param {String} type 事件类型
     */
    unsubscribeAll(type) {
        if (this.events[type]) {
            delete this.events[type]
        }
    }
}
​

发布订阅模式中,对于发布者Publisher和订阅者Subscriber没有特殊的约束,他们好似是匿名活动,借助事件调度中心提供的接口发布和订阅事件,互不了解对方是谁。

松散耦合,灵活度高,常用作事件总线

易理解,可类比于DOM事件中的dispatchEventaddEventListener

8.单例模式

相关文章

9.工厂模式

相关文章

10.Eventbus

class EventBus{
  constructor(){
    this._cache = {}
  }
​
  on(type,callback){
    this._cache[type] = this._cache[type] || []
    this._cache[type].push(callback)
  }
​
  off(type,callback){
    const fns = this._cache[type]
    if(!Array.isArray(fns)){
      return
    }
​
    if(!callback){
      fns.length = 0
      return
    }
​
    while(fns.indexOf(callback) !== -1){
      fns.splice(fns.indexOf(callback),1)
    }
  }
​
  emit(type,data){
    const fns = this._cache[type]
    if(!Array.isArray(fns)){
      return
    }
​
    fns.forEach(item => {
      item(data)
    })
  }
}
​
const bus = new EventBus()
​
function cyCallback1(res){
  console.log('cy on1: ',res)
}
​
function cyCallback2(res){
  console.log('cy on2: ',res)
}
​
bus.on('cy',cyCallback1)
bus.on('cy',cyCallback1)
​
bus.on('cy',cyCallback2)
​
bus.off('cy',cyCallback1)
​
bus.emit('cy','cy emit')
​

11.实现checkbox全选反选

js实现

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title></title>
		<style type="text/css">
			* {
				padding: 0px;
				margin: 0px auto;
			}			
			body {
				padding: 30px;
				width: 300px;
				height: 300px;
			}
		</style>
		<script type="text/javascript">
			window.onload = function() {
				//获取五个多选框items
				var items = document.getElementsByName("items");

				//1.checkAllBtn绑定单击响应函数
				var checkAllBtn = document.getElementById("checkAllBtn");
				checkAllBtn.onclick = function() {
					//遍历items,使每个复选框都被选中
					for(var i = 0; i < items.length; i++) {
						items[i].checked = true;
					}
				};

				//2.checkNoBtn绑定单击响应函数
				var checkNoBtn = document.getElementById("checkNoBtn");
				checkNoBtn.onclick = function() {
					//遍历items,使每个复选框都不被选中
					for(var i = 0; i < items.length; i++) {
						items[i].checked = false;
					}
				};

				//3.checkRevBtn绑定单击响应函数
				var checkRevBtn = document.getElementById("checkRevBtn");
				checkRevBtn.onclick = function() {
					//遍历items,进行反选
					for(var i = 0; i < items.length; i++) {
//						if(items[i].checked) {
//							items[i].checked = false;
//						} else if(items[i].checked == false) {
//							items[i].checked = true;
//						}
						items[i].checked = !items[i].checked;
					}

				};

			};
		</script>
	</head>

	<body>
		<input type="button" id="checkAllBtn" value="全选" />
		<input type="button" id="checkNoBtn" value="不选" />
		<input type="button" id="checkRevBtn" value="反选" />
		<div id="select">
			<input type="checkbox" name="items" value="旅游" />旅游<br />
			<input type="checkbox" name="items" value="阅读" />阅读<br />
			<input type="checkbox" name="items" value="运动" />运动<br />
			<input type="checkbox" name="items" value="音乐" />音乐<br />
			<input type="checkbox" name="items" value="跳舞" />跳舞
		</div>
	</body>
</html>

vue实现

<template>
  <div>
    <span>全选</span>
    <input type="checkbox" v-model="checkAll" />
    <div v-for="(item,index) in test" :key="index">
      <span>{{item.name}}</span>
	  <input type="checkbox" v-model="item.isSelected" />
    </div>
  </div>
</template>
<script>
  export default {
  data() {
    return {
      test: [
        { name: "测试1", isSelected: true },
        { name: "测试2", isSelected: true },
        { name: "测试3", isSelected: true },
        { name: "测试4", isSelected: true },
        { name: "测试5", isSelected: true }
      ]
    };
  },
  computed: {
    checkAll: {
      get() {
        // 返回什么结果接赋予给 checkAll 属性
        return this.test.every(item => item.isSelected);
      },
      set(val) {
        // val 是给 checkAll 赋予值的时候传递过来的
        return this.test.forEach(item => (item.isSelected = val));
      }
    }
  }
}
</script>

12.实现可拖拽div

<div id="dragdiv"></div>

var  dragging=false;
var position =null;

dragdiv.addEventListener('mousedown',function(e){
	dragging=true
	position=[e.clientX,e.clientY]
})
document.addEventListener('mousemove',function(e){
	if(dragging===false)  return null
	const x=e.clientX
	const y=e.clientY
	const deltaX=x-position[0]
	const deltaY=y-position[1]
	const left=parseInt(dragdiv.style.left||0)
	const top=parseInt(dragdiv.style.top||0)
	dragdiv.style.left=left+deltaX+'px'
	dragdiv.style.top=top+deltaY+'px'
	position=[x,y]
})
document.addEventListener('mouseup',function(e){
	dragging=false
})

13.封装异步的fetch,使用async await方式来使用

(async () => {
    class HttpRequestUtil {
        async get(url) {
            const res = await fetch(url);
            const data = await res.json();
            return data;
        }
        async post(url, data) {
            const res = await fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
        async put(url, data) {
            const res = await fetch(url, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                data: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
        async delete(url, data) {
            const res = await fetch(url, {
                method: 'DELETE',
                headers: {
                    'Content-Type': 'application/json'
                },
                data: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
    }
    const httpRequestUtil = new HttpRequestUtil();
    const res = await httpRequestUtil.get('http://golderbrother.cn/');
    console.log(res);
})();

14.实现简单路由

// hash路由
class Route{
  constructor(){
    // 路由存储对象
    this.routes = {}
    // 当前hash
    this.currentHash = ''
    // 绑定this,避免监听时this指向改变
    this.freshRoute = this.freshRoute.bind(this)
    // 监听
    window.addEventListener('load', this.freshRoute, false)
    window.addEventListener('hashchange', this.freshRoute, false)
  }
  // 存储
  storeRoute (path, cb) {
    this.routes[path] = cb || function () {}
  }
  // 更新
  freshRoute () {
    this.currentHash = location.hash.slice(1) || '/'
    this.routes[this.currentHash]()
  }
}

15.判断对象是否存在循环引用

const isCycleObject = (obj,parent) => {
    const parentArr = parent || [obj];
    for(let i in obj) {
        if(typeof obj[i] === 'object') {
            let flag = false;
            parentArr.forEach((pObj) => {
                if(pObj === obj[i]){
                    flag = true;
                }
            })
            if(flag) return true;
            flag = isCycleObject(obj[i],[...parentArr,obj[i]]);
            if(flag) return true;
        }
    }
    return false;
}


const a = 1;
const b = {a};
const c = {b};
const o = {d:{a:3},c}
o.c.b.aa = a;

console.log(isCycleObject(o)

16.实现并发控制

同时进行2个并发请求

class Scheduler{
	list=[];
	maxNum=2;
	workingNum=0;
	add(promiseCreator){
		this.list.push(promiseCreator)
	}
	start(){
			for(let i=0;i<this.maxNum;i++){
				this.doNext()
			}
	}
	doNext(){
		if(this.list.length&&this.workingNum<this.maxNum){
			this.workingNum++
			this.list.shift()().then(()=>{
				this.workingNum--
				this.doNext()
			})
		}
	}
}
var timeout=time=>new Promise(resolve=>setTimeout(resolve,time))

var scheduler=new Scheduler()

var addTask=(time,order)=>{
	scheduler.add(()=>timeout(time).then(()=>console.log(order)))
}
addTask(1000,1)
addTask(500,2)
addTask(300,3)
addTask(400,4)

scheduler.start()

\