Promise实现

基于Promise封装Ajax

  • 返回一个新的Promise实例
  • 创立HMLHttpRequest异步对象
  • 调用open办法,关上url,与服务器建设链接(发送前的一些解决)
  • 监听Ajax状态信息
  • 如果xhr.readyState == 4(示意服务器响应实现,能够获取应用服务器的响应了)

    • xhr.status == 200,返回resolve状态
    • xhr.status == 404,返回reject状态
  • xhr.readyState !== 4,把申请主体的信息基于send发送给服务器
function ajax(url) {  return new Promise((resolve, reject) => {    let xhr = new XMLHttpRequest()    xhr.open('get', url)    xhr.onreadystatechange = () => {      if (xhr.readyState == 4) {        if (xhr.status >= 200 && xhr.status <= 300) {          resolve(JSON.parse(xhr.responseText))        } else {          reject('申请出错')        }      }    }    xhr.send()  //发送hppt申请  })}let url = '/data.json'ajax(url).then(res => console.log(res))  .catch(reason => console.log(reason))

创立10个标签,点击的时候弹出来对应的序号

var afor(let i=0;i<10;i++){ a=document.createElement('a') a.innerHTML=i+'<br>' a.addEventListener('click',function(e){     console.log(this)  //this为以后点击的<a>     e.preventDefault()  //如果调用这个办法,默认事件行为将不再触发。     //例如,在执行这个办法后,如果点击一个链接(a标签),浏览器不会跳转到新的 URL 去了。咱们能够用 event.isDefaultPrevented() 来确定这个办法是否(在那个事件对象上)被调用过了。     alert(i) }) const d=document.querySelector('div') d.appendChild(a)  //append向一个已存在的元素追加该元素。}

前端手写面试题具体解答

递归反转链表

// node节点class Node {  constructor(element,next) {    this.element = element    this.next = next  } }class LinkedList { constructor() {   this.head = null // 默认应该指向第一个节点   this.size = 0 // 通过这个长度能够遍历这个链表 } // 减少O(n) add(index,element) {   if(arguments.length === 1) {     // 向开端增加     element = index // 以后元素等于传递的第一项     index = this.size // 索引指向最初一个元素   }  if(index < 0 || index > this.size) {    throw new Error('增加的索引不失常')  }  if(index === 0) {    // 间接找到头部 把头部改掉 性能更好    let head = this.head    this.head = new Node(element,head)  } else {    // 获取以后头指针    let current = this.head    // 不停遍历 直到找到最初一项 增加的索引是1就找到第0个的next赋值    for (let i = 0; i < index-1; i++) { // 找到它的前一个      current = current.next    }    // 让创立的元素指向上一个元素的下一个    // 看图了解next层级 ![](http://img-repo.poetries.top/images/20210522115056.png)    current.next = new Node(element,current.next) // 让以后元素指向下一个元素的next  }  this.size++; } // 删除O(n) remove(index) {  if(index < 0 || index >= this.size) {    throw new Error('删除的索引不失常')  }  this.size--  if(index === 0) {    let head = this.head    this.head = this.head.next // 挪动指针地位    return head // 返回删除的元素  }else {    let current = this.head    for (let i = 0; i < index-1; i++) { // index-1找到它的前一个      current = current.next    }    let returnVal = current.next // 返回删除的元素    // 找到待删除的指针的上一个 current.next.next     // 如删除200, 100=>200=>300 找到200的上一个100的next的next为300,把300赋值给100的next即可    current.next = current.next.next     return returnVal  } } // 查找O(n) get(index) {  if(index < 0 || index >= this.size) {    throw new Error('查找的索引不失常')  }  let current = this.head  for (let i = 0; i < index; i++) {    current = current.next  }  return current } reverse() {  const reverse = head=>{    if(head == null || head.next == null) {      return head    }    let newHead = reverse(head.next)    // 从这个链表的最初一个开始反转,让以后下一个元素的next指向本人,本人指向null    // ![](http://img-repo.poetries.top/images/20210522161710.png)    // 刚开始反转的是最初两个    head.next.next = head    head.next = null    return newHead  }  return reverse(this.head) }}let ll = new LinkedList()ll.add(1)ll.add(2)ll.add(3)ll.add(4)// console.dir(ll,{depth: 1000})console.log(ll.reverse())

设计一个办法提取对象中所有value大于2的键值对并返回最新的对象

实现:

var obj = { a: 1, b: 3, c: 4 }foo(obj) // { b: 3, c: 4 }

办法有很多种,这里提供一种比拟简洁的写法,用到了ES10Object.fromEntries()

var obj = { a: 1, b: 3, c: 4 }function foo (obj) {  return Object.fromEntries(    Object.entries(obj).filter(([key, value]) => value > 2)  )}var obj2 = foo(obj) // { b: 3, c: 4 }console.log(obj2)
// ES8中 Object.entries()的作用:var obj = { a: 1, b: 2 }var entries = Object.entries(obj); // [['a', 1], ['b', 2]]// ES10中 Object.fromEntries()的作用:Object.fromEntries(entries); // { a: 1, b: 2 }

二分查找

function search(arr, target, start, end) {  let targetIndex = -1;  let mid = Math.floor((start + end) / 2);  if (arr[mid] === target) {    targetIndex = mid;    return targetIndex;  }  if (start >= end) {    return targetIndex;  }  if (arr[mid] < target) {    return search(arr, target, mid + 1, end);  } else {    return search(arr, target, start, mid - 1);  }}// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];// const position = search(dataArr, 6, 0, dataArr.length - 1);// if (position !== -1) {//   console.log(`指标元素在数组中的地位:${position}`);// } else {//   console.log("指标元素不在数组中");// }

手写 Promise.race

该办法的参数是 Promise 实例数组, 而后其 then 注册的回调办法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能扭转一次, 那么咱们只须要把 Promise.race 中产生的 Promise 对象的 resolve 办法, 注入到数组中的每一个 Promise 实例中的回调函数中即可.

Promise.race = function (args) {  return new Promise((resolve, reject) => {    for (let i = 0, len = args.length; i < len; i++) {      args[i].then(resolve, reject)    }  })}

应用 reduce 求和

arr = [1,2,3,4,5,6,7,8,9,10],求和

let arr = [1,2,3,4,5,6,7,8,9,10]arr.reduce((prev, cur) => { return prev + cur }, 0)

arr = [1,2,3,[[4,5],6],7,8,9],求和

let arr = [1,2,3,4,5,6,7,8,9,10]arr.flat(Infinity).reduce((prev, cur) => { return prev + cur }, 0)

arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和

let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}] arr.reduce((prev, cur) => {    return prev + cur["a"];}, 0)

验证是否是邮箱

function isEmail(email) {    var regx = /^([a-zA-Z0-9_\-])[email protected]([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;    return regx.test(email);}

实现一个治理本地缓存过期的函数

封装一个能够设置过期工夫的localStorage存储函数
class Storage{  constructor(name){      this.name = 'storage';  }  //设置缓存  setItem(params){      let obj = {          name:'', // 存入数据  属性          value:'',// 属性值          expires:"", // 过期工夫          startTime:new Date().getTime()//记录何时将值存入缓存,毫秒级      }      let options = {};      //将obj和传进来的params合并      Object.assign(options,obj,params);      if(options.expires){      //如果options.expires设置了的话      //以options.name为key,options为值放进去          localStorage.setItem(options.name,JSON.stringify(options));      }else{      //如果options.expires没有设置,就判断一下value的类型          let type = Object.prototype.toString.call(options.value);          //如果value是对象或者数组对象的类型,就先用JSON.stringify转一下,再存进去          if(Object.prototype.toString.call(options.value) == '[object Object]'){              options.value = JSON.stringify(options.value);          }          if(Object.prototype.toString.call(options.value) == '[object Array]'){              options.value = JSON.stringify(options.value);          }          localStorage.setItem(options.name,options.value);      }  }  //拿到缓存  getItem(name){      let item = localStorage.getItem(name);      //先将拿到的试着进行json转为对象的模式      try{          item = JSON.parse(item);      }catch(error){      //如果不行就不是json的字符串,就间接返回          item = item;      }      //如果有startTime的值,阐明设置了生效工夫      if(item.startTime){          let date = new Date().getTime();          //何时将值取出减去刚存入的工夫,与item.expires比拟,如果大于就是过期了,如果小于或等于就还没过期          if(date - item.startTime > item.expires){          //缓存过期,革除缓存,返回false              localStorage.removeItem(name);              return false;          }else{          //缓存未过期,返回值              return item.value;          }      }else{      //如果没有设置生效工夫,间接返回值          return item;      }  }  //移出缓存  removeItem(name){      localStorage.removeItem(name);  }  //移出全副缓存  clear(){      localStorage.clear();  }}

用法

let storage = new Storage();storage.setItem({  name:"name",  value:"ppp"})

上面我把值取出来

let value = storage.getItem('name');console.log('我是value',value);
设置5秒过期
let storage = new Storage();storage.setItem({  name:"name",  value:"ppp",  expires: 5000})
// 过期后再取出来会变为 falselet value = storage.getItem('name');console.log('我是value',value);

实现apply办法

apply原理与call很类似,不多赘述

// 模仿 applyFunction.prototype.myapply = function(context, arr) {  var context = Object(context) || window;  context.fn = this;  var result;  if (!arr) {    result = context.fn();  } else {    var args = [];    for (var i = 0, len = arr.length; i < len; i++) {      args.push("arr[" + i + "]");    }    result = eval("context.fn(" + args + ")");  }  delete context.fn;  return result;};

应用 setTimeout 实现 setInterval

setInterval 的作用是每隔一段指定工夫执行一个函数,然而这个执行不是真的到了工夫立刻执行,它真正的作用是每隔一段时间将事件退出事件队列中去,只有当以后的执行栈为空的时候,能力去从事件队列中取出事件执行。所以可能会呈现这样的状况,就是以后执行栈执行的工夫很长,导致事件队列里边积攒多个定时器退出的事件,当执行栈完结的时候,这些事件会顺次执行,因而就不能到距离一段时间执行的成果。

针对 setInterval 的这个毛病,咱们能够应用 setTimeout 递归调用来模仿 setInterval,这样咱们就确保了只有一个事件完结了,咱们才会触发下一个定时器事件,这样解决了 setInterval 的问题。

实现思路是应用递归函数,一直地去执行 setTimeout 从而达到 setInterval 的成果

function mySetInterval(fn, timeout) {  // 控制器,管制定时器是否继续执行  var timer = {    flag: true  };  // 设置递归函数,模仿定时器执行。  function interval() {    if (timer.flag) {      fn();      setTimeout(interval, timeout);    }  }  // 启动定时器  setTimeout(interval, timeout);  // 返回控制器  return timer;}

解析 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;}

实现instanceOf

// 模仿 instanceoffunction instance_of(L, R) {  //L 示意左表达式,R 示意右表达式  var O = R.prototype; // 取 R 的显示原型  L = L.__proto__; // 取 L 的隐式原型  while (true) {    if (L === null) return false;    if (O === L)      // 这里重点:当 O 严格等于 L 时,返回 true      return true;    L = L.__proto__;  }}

实现AJAX申请

AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新以后网页的对应局部,而不必刷新整个网页。

创立AJAX申请的步骤:

  • 创立一个 XMLHttpRequest 对象。
  • 在这个对象上应用 open 办法创立一个 HTTP 申请,open 办法所须要的参数是申请的办法、申请的地址、是否异步和用户的认证信息。
  • 在发动申请前,能够为这个对象增加一些信息和监听函数。比如说能够通过 setRequestHeader 办法来为申请增加头信息。还能够为这个对象增加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变动时会触发onreadystatechange 事件,能够通过设置监听函数,来解决申请胜利后的后果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接管实现,这个时候能够通过判断申请的状态,如果状态是 2xx 或者 304 的话则代表返回失常。这个时候就能够通过 response 中的数据来对页面进行更新了。
  • 当对象的属性和监听函数设置实现后,最初调用 sent 办法来向服务器发动申请,能够传入参数作为发送的数据体。
const SERVER_URL = "/server";let xhr = new XMLHttpRequest();// 创立 Http 申请xhr.open("GET", SERVER_URL, true);// 设置状态监听函数xhr.onreadystatechange = function() {  if (this.readyState !== 4) return;  // 当申请胜利时  if (this.status === 200) {    handle(this.response);  } else {    console.error(this.statusText);  }};// 设置申请失败时的监听函数xhr.onerror = function() {  console.error(this.statusText);};// 设置申请头信息xhr.responseType = "json";xhr.setRequestHeader("Accept", "application/json");// 发送 Http 申请xhr.send(null);

Promise并行限度

就是实现有并行限度的Promise调度器问题

class Scheduler {  constructor() {    this.queue = [];    this.maxCount = 2;    this.runCounts = 0;  }  add(promiseCreator) {    this.queue.push(promiseCreator);  }  taskStart() {    for (let i = 0; i < this.maxCount; i++) {      this.request();    }  }  request() {    if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {      return;    }    this.runCounts++;    this.queue.shift()().then(() => {      this.runCounts--;      this.request();    });  }}const timeout = time => new Promise(resolve => {  setTimeout(resolve, time);})const scheduler = new Scheduler();const addTask = (time,order) => {  scheduler.add(() => timeout(time).then(()=>console.log(order)))}addTask(1000, '1');addTask(500, '2');addTask(300, '3');addTask(400, '4');scheduler.taskStart()// 2// 3// 1// 4

用Promise实现图片的异步加载

let imageAsync=(url)=>{            return new Promise((resolve,reject)=>{                let img = new Image();                img.src = url;                img.nlad=()=>{                    console.log(`图片申请胜利,此处进行通用操作`);                    resolve(image);                }                img.nerrr=(err)=>{                    console.log(`失败,此处进行失败的通用操作`);                    reject(err);                }            })        }imageAsync("url").then(()=>{    console.log("加载胜利");}).catch((error)=>{    console.log("加载失败");})

实现类的继承

类的继承在几年前是重点内容,有n种继承形式各有优劣,es6遍及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深刻了解的去看红宝书即可,咱们目前只实现一种最现实的继承形式。

function Parent(name) {    this.parent = name}Parent.prototype.say = function() {    console.log(`${this.parent}: 你打篮球的样子像kunkun`)}function Child(name, parent) {    // 将父类的构造函数绑定在子类上    Parent.call(this, parent)    this.child = name}/**  1. 这一步不必Child.prototype =Parent.prototype的起因是怕共享内存,批改父类原型对象就会影响子类 2. 不必Child.prototype = new Parent()的起因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性3. Object.create是创立了父类原型的正本,与父类原型齐全隔离*/Child.prototype = Object.create(Parent.prototype);Child.prototype.say = function() {    console.log(`${this.parent}好,我是练习时长两年半的${this.child}`);}// 留神记得把子类的结构指向子类自身Child.prototype.constructor = Child;var parent = new Parent('father');parent.say() // father: 你打篮球的样子像kunkunvar child = new Child('cxk', 'father');child.say() // father好,我是练习时长两年半的cxk

手写防抖函数

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则从新计时。这能够应用在一些点击申请的事件上,防止因为用户的屡次点击向后端发送屡次申请。

// 函数防抖的实现function debounce(fn, wait) {  let timer = null;  return function() {    let context = this,        args = arguments;    // 如果此时存在定时器的话,则勾销之前的定时器从新记时    if (timer) {      clearTimeout(timer);      timer = null;    }    // 设置定时器,使事件间隔指定事件后执行    timer = setTimeout(() => {      fn.apply(context, args);    }, wait);  };}

对象数组列表转成树形构造(解决菜单)

[    {        id: 1,        text: '节点1',        parentId: 0 //这里用0示意为顶级节点    },    {        id: 2,        text: '节点1_1',        parentId: 1 //通过这个字段来确定子父级    }    ...]转成[    {        id: 1,        text: '节点1',        parentId: 0,        children: [            {                id:2,                text: '节点1_1',                parentId:1            }        ]    }]

实现代码如下:

function listToTree(data) {  let temp = {};  let treeData = [];  for (let i = 0; i < data.length; i++) {    temp[data[i].id] = data[i];  }  for (let i in temp) {    if (+temp[i].parentId != 0) {      if (!temp[temp[i].parentId].children) {        temp[temp[i].parentId].children = [];      }      temp[temp[i].parentId].children.push(temp[i]);    } else {      treeData.push(temp[i]);    }  }  return treeData;}

二叉树深度遍历

// 二叉树深度遍历class Node {  constructor(element, parent) {    this.parent = parent // 父节点     this.element = element // 以后存储内容    this.left = null // 左子树    this.right = null // 右子树  }}class BST {  constructor(compare) {    this.root = null // 树根    this.size = 0 // 树中的节点个数    this.compare = compare || this.compare  }  compare(a,b) {    return a - b  }  add(element) {    if(this.root === null) {      this.root = new Node(element, null)      this.size++      return    }    // 获取根节点 用以后增加的进行判断 放右边还是放左边    let currentNode = this.root     let compare    let parent = null     while (currentNode) {      compare = this.compare(element, currentNode.element)      parent = currentNode // 先将父亲保存起来      // currentNode要不停的变动      if(compare > 0) {        currentNode = currentNode.right      } else if(compare < 0) {        currentNode = currentNode.left      } else {        currentNode.element = element // 相等时 先笼罩后续解决      }    }    let newNode = new Node(element, parent)    if(compare > 0) {      parent.right = newNode    } else if(compare < 0) {      parent.left = newNode    }    this.size++  }  // 前序遍历  preorderTraversal(visitor) {    const traversal = node=>{      if(node === null) return       visitor.visit(node.element)      traversal(node.left)      traversal(node.right)    }    traversal(this.root)  }  // 中序遍历  inorderTraversal(visitor) {    const traversal = node=>{      if(node === null) return       traversal(node.left)      visitor.visit(node.element)      traversal(node.right)    }    traversal(this.root)  }  // 后序遍历  posterorderTraversal(visitor) {    const traversal = node=>{      if(node === null) return       traversal(node.left)      traversal(node.right)      visitor.visit(node.element)    }    traversal(this.root)  }  // 反转二叉树:无论先序、中序、后序、层级都能够反转  invertTree() {    const traversal = node=>{      if(node === null) return       let temp = node.left       node.left = node.right       node.right = temp      traversal(node.left)      traversal(node.right)    }    traversal(this.root)    return this.root  }}

先序遍历

二叉树的遍历形式

// 测试var bst = new BST((a,b)=>a.age-b.age) // 模仿sort办法bst.add({age: 10})bst.add({age: 8})bst.add({age:19})bst.add({age:6})bst.add({age: 15})bst.add({age: 22})bst.add({age: 20})// 先序遍历// console.log(bst.preorderTraversal(),'先序遍历')// console.log(bst.inorderTraversal(),'中序遍历')// ![](http://img-repo.poetries.top/images/20210522214837.png)// console.log(bst.posterorderTraversal(),'后序遍历')// 深度遍历:先序遍历、中序遍历、后续遍历// 广度遍历:档次遍历(同层级遍历)// 都可拿到树中的节点// 应用访问者模式class Visitor {  constructor() {    this.visit = function (elem) {      elem.age = elem.age*2    }  }}// bst.posterorderTraversal({//   visit(elem) {//     elem.age = elem.age*10//   }// })// 不能通过索引操作 拿到节点去操作// bst.posterorderTraversal(new Visitor())console.log(bst.invertTree(),'反转二叉树')