类数组转化为数组

类数组是具备length属性,但不具备数组原型上的办法。常见的类数组有arguments、DOM操作方法返回的后果。

办法一:Array.from
Array.from(document.querySelectorAll('div'))
办法二:Array.prototype.slice.call()
Array.prototype.slice.call(document.querySelectorAll('div'))
办法三:扩大运算符
[...document.querySelectorAll('div')]
办法四:利用concat
Array.prototype.concat.apply([], document.querySelectorAll('div'));

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

滚动加载

原理就是监听页面滚动事件,剖析clientHeightscrollTopscrollHeight三者的属性关系。

window.addEventListener('scroll', function() {  const clientHeight = document.documentElement.clientHeight;  const scrollTop = document.documentElement.scrollTop;  const scrollHeight = document.documentElement.scrollHeight;  if (clientHeight + scrollTop >= scrollHeight) {    // 检测到滚动至页面底部,进行后续操作    // ...  }}, false);

Array.prototype.reduce()

Array.prototype.reduce = function(callback, initialValue) {  if (this == undefined) {    throw new TypeError('this is null or not defined');  }  if (typeof callback !== 'function') {    throw new TypeError(callbackfn + ' is not a function');  }  const O = Object(this);  const len = this.length >>> 0;  let accumulator = initialValue;  let k = 0;  // 如果第二个参数为undefined的状况下  // 则数组的第一个有效值作为累加器的初始值  if (accumulator === undefined) {    while (k < len && !(k in O)) {      k++;    }    // 如果超出数组界线还没有找到累加器的初始值,则TypeError    if (k >= len) {      throw new TypeError('Reduce of empty array with no initial value');    }    accumulator = O[k++];  }  while (k < len) {    if (k in O) {      accumulator = callback.call(undefined, accumulator, O[k], k, O);    }    k++;  }  return accumulator;}

实现简略路由

// 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]()  }}

分片思维解决大数据量渲染问题

题目形容: 渲染百万条构造简略的大数据时 怎么应用分片思维优化渲染

let ul = document.getElementById("container");// 插入十万条数据let total = 100000;// 一次插入 20 条let once = 20;//总页数let page = total / once;//每条记录的索引let index = 0;//循环加载数据function loop(curTotal, curIndex) {  if (curTotal <= 0) {    return false;  }  //每页多少条  let pageCount = Math.min(curTotal, once);  window.requestAnimationFrame(function () {    for (let i = 0; i < pageCount; i++) {      let li = document.createElement("li");      li.innerText = curIndex + i + " : " + ~~(Math.random() * total);      ul.appendChild(li);    }    loop(curTotal - pageCount, curIndex + pageCount);  });}loop(total, index);

扩大思考 :对于大数据量的简略 dom 构造渲染能够用分片思维解决 如果是简单的 dom 构造渲染如何解决?

这时候就须要应用虚构列表了,虚构列表和虚构表格在日常我的项目应用还是很多的

参考 前端进阶面试题具体解答

应用ES5和ES6求函数参数的和

ES5:

function sum() {    let sum = 0    Array.prototype.forEach.call(arguments, function(item) {        sum += item * 1    })    return sum}

ES6:

function sum(...nums) {    let sum = 0    nums.forEach(function(item) {        sum += item * 1    })    return sum}

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))

实现apply办法

思路: 利用this的上下文个性。apply其实就是改一下参数的问题
Function.prototype.myApply = function(context = window, args) {  // this-->func  context--> obj  args--> 传递过去的参数  // 在context上加一个惟一值不影响context上的属性  let key = Symbol('key')  context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的办法  // let args = [...arguments].slice(1)   //第一个参数为obj所以删除,伪数组转为数组  let result = context[key](...args); // 这里和call传参不一样  // 革除定义的this 不删除会导致context属性越来越多  delete context[key];   // 返回后果  return result;}
// 应用function f(a,b){ console.log(a,b) console.log(this.name)}let obj={ name:'张三'}f.myApply(obj,[1,2])  //arguments[1]

setTimeout与setInterval实现

setTimeout 模仿实现 setInterval

题目形容: setInterval 用来实现循环定时调用 可能会存在肯定的问题 能用 setTimeout 解决吗

实现代码如下:

function mySetInterval(fn, t) {  let timerId = null;  function interval() {    fn();    timerId = setTimeout(interval, t); // 递归调用  }  timerId = setTimeout(interval, t); // 首次调用  return {    // 利用闭包的个性 保留timerId    cancel:() => {      clearTimeout(timerId)    }  }}
// 测试var a = mySetInterval(()=>{  console.log(111);},1000)var b = mySetInterval(() => {  console.log(222)}, 1000)// 终止定时器a.cancel()b.cancel()
为什么要用 setTimeout 模仿实现 setIntervalsetInterval 的缺点是什么?
setInterval(fn(), N);
下面这句代码的意思其实是fn()将会在 N 秒之后被推入工作队列。在 setInterval 被推入工作队列时,如果在它后面有很多工作或者某个工作等待时间较长比方网络申请等,那么这个定时器的执行工夫和咱们预约它执行的工夫可能并不统一
// 最常见的呈现的就是,当咱们须要应用 ajax 轮询服务器是否有新数据时,必定会有一些人会应用 setInterval,然而无论网络情况如何,它都会去一遍又一遍的发送申请,最初的间隔时间可能和原定的工夫有很大的出入// 做一个网络轮询,每一秒查问一次数据。let startTime = new Date().getTime();let count = 0;setInterval(() => {    let i = 0;    while (i++ < 10000000); // 假如的网络提早    count++;    console.log(        "与原设定的距离时差了:",        new Date().getTime() - (startTime + count * 1000),        "毫秒"    );}, 1000)// 输入:// 与原设定的距离时差了: 567 毫秒// 与原设定的距离时差了: 552 毫秒// 与原设定的距离时差了: 563 毫秒// 与原设定的距离时差了: 554 毫秒(2次)// 与原设定的距离时差了: 564 毫秒// 与原设定的距离时差了: 602 毫秒// 与原设定的距离时差了: 573 毫秒// 与原设定的距离时差了: 633 毫秒
再次强调 ,定时器指定的工夫距离,示意的是何时将定时器的代码增加到音讯队列,而不是何时执行代码。所以真正何时执行代码的工夫是不能保障的,取决于何时被主线程的事件循环取到,并执行。
setInterval(function, N)//即:每隔N秒把function事件推到音讯队列中

上图可见,setInterval 每隔 100ms 往队列中增加一个事件;100ms 后,增加 T1 定时器代码至队列中,主线程中还有工作在执行,所以期待,some event 执行完结后执行 T1定时器代码;又过了 100msT2 定时器被增加到队列中,主线程还在执行 T1 代码,所以期待;又过了 100ms,实践上又要往队列里推一个定时器代码,但因为此时 T2 还在队列中,所以 T3 不会被增加(T3 被跳过),后果就是此时被跳过;这里咱们能够看到,T1 定时器执行完结后马上执行了 T2 代码,所以并没有达到定时器的成果

setInterval有两个毛病

  • 应用setInterval时,某些距离会被跳过
  • 可能多个定时器会间断执行
能够这么了解 :每个setTimeout产生的工作会间接push到工作队列中;而setInterval在每次把工作push到工作队列前,都要进行一下判断(看上次的工作是否仍在队列中)。因此咱们个别用setTimeout模仿setInterval,来躲避掉下面的毛病

setInterval 模仿实现 setTimeout

const mySetTimeout = (fn, t) => {  const timer = setInterval(() => {    clearInterval(timer);    fn();  }, t);};
// 测试// mySetTimeout(()=>{//   console.log(1);// },1000)

实现Object.create

Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的 __proto__
// 模仿 Object.createfunction create(proto) {  function F() {}  F.prototype = proto;  return new F();}

实现一个迷你版的vue

入口

// js/vue.jsclass Vue {  constructor (options) {    // 1. 通过属性保留选项的数据    this.$options = options || {}    this.$data = options.data || {}    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el    // 2. 把data中的成员转换成getter和setter,注入到vue实例中    this._proxyData(this.$data)    // 3. 调用observer对象,监听数据的变动    new Observer(this.$data)    // 4. 调用compiler对象,解析指令和差值表达式    new Compiler(this)  }  _proxyData (data) {    // 遍历data中的所有属性    Object.keys(data).forEach(key => {      // 把data的属性注入到vue实例中      Object.defineProperty(this, key, {        enumerable: true,        configurable: true,        get () {          return data[key]        },        set (newValue) {          if (newValue === data[key]) {            return          }          data[key] = newValue        }      })    })  }}

实现Dep

class Dep {  constructor () {    // 存储所有的观察者    this.subs = []  }  // 增加观察者  addSub (sub) {    if (sub && sub.update) {      this.subs.push(sub)    }  }  // 发送告诉  notify () {    this.subs.forEach(sub => {      sub.update()    })  }}

实现watcher

class Watcher {  constructor (vm, key, cb) {    this.vm = vm    // data中的属性名称    this.key = key    // 回调函数负责更新视图    this.cb = cb    // 把watcher对象记录到Dep类的动态属性target    Dep.target = this    // 触发get办法,在get办法中会调用addSub    this.oldValue = vm[key]    Dep.target = null  }  // 当数据发生变化的时候更新视图  update () {    let newValue = this.vm[this.key]    if (this.oldValue === newValue) {      return    }    this.cb(newValue)  }}

实现compiler

class Compiler {  constructor (vm) {    this.el = vm.$el    this.vm = vm    this.compile(this.el)  }  // 编译模板,解决文本节点和元素节点  compile (el) {    let childNodes = el.childNodes    Array.from(childNodes).forEach(node => {      // 解决文本节点      if (this.isTextNode(node)) {        this.compileText(node)      } else if (this.isElementNode(node)) {        // 解决元素节点        this.compileElement(node)      }      // 判断node节点,是否有子节点,如果有子节点,要递归调用compile      if (node.childNodes && node.childNodes.length) {        this.compile(node)      }    })  }  // 编译元素节点,解决指令  compileElement (node) {    // console.log(node.attributes)    // 遍历所有的属性节点    Array.from(node.attributes).forEach(attr => {      // 判断是否是指令      let attrName = attr.name      if (this.isDirective(attrName)) {        // v-text --> text        attrName = attrName.substr(2)        let key = attr.value        this.update(node, key, attrName)      }    })  }  update (node, key, attrName) {    let updateFn = this[attrName + 'Updater']    updateFn && updateFn.call(this, node, this.vm[key], key)  }  // 解决 v-text 指令  textUpdater (node, value, key) {    node.textContent = value    new Watcher(this.vm, key, (newValue) => {      node.textContent = newValue    })  }  // v-model  modelUpdater (node, value, key) {    node.value = value    new Watcher(this.vm, key, (newValue) => {      node.value = newValue    })    // 双向绑定    node.addEventListener('input', () => {      this.vm[key] = node.value    })  }  // 编译文本节点,解决差值表达式  compileText (node) {    // console.dir(node)    // {{  msg }}    let reg = /\{\{(.+?)\}\}/    let value = node.textContent    if (reg.test(value)) {      let key = RegExp.$1.trim()      node.textContent = value.replace(reg, this.vm[key])      // 创立watcher对象,当数据扭转更新视图      new Watcher(this.vm, key, (newValue) => {        node.textContent = newValue      })    }  }  // 判断元素属性是否是指令  isDirective (attrName) {    return attrName.startsWith('v-')  }  // 判断节点是否是文本节点  isTextNode (node) {    return node.nodeType === 3  }  // 判断节点是否是元素节点  isElementNode (node) {    return node.nodeType === 1  }}

实现Observer

class Observer {  constructor (data) {    this.walk(data)  }  walk (data) {    // 1. 判断data是否是对象    if (!data || typeof data !== 'object') {      return    }    // 2. 遍历data对象的所有属性    Object.keys(data).forEach(key => {      this.defineReactive(data, key, data[key])    })  }  defineReactive (obj, key, val) {    let that = this    // 负责收集依赖,并发送告诉    let dep = new Dep()    // 如果val是对象,把val外部的属性转换成响应式数据    this.walk(val)    Object.defineProperty(obj, key, {      enumerable: true,      configurable: true,      get () {        // 收集依赖        Dep.target && dep.addSub(Dep.target)        return val      },      set (newValue) {        if (newValue === val) {          return        }        val = newValue        that.walk(newValue)        // 发送告诉        dep.notify()      }    })  }}

应用

<!DOCTYPE html><html lang="cn"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <meta http-equiv="X-UA-Compatible" content="ie=edge">  <title>Mini Vue</title></head><body>  <div id="app">    <h1>差值表达式</h1>    <h3>{{ msg }}</h3>    <h3>{{ count }}</h3>    <h1>v-text</h1>    <div v-text="msg"></div>    <h1>v-model</h1>    <input type="text" v-model="msg">    <input type="text" v-model="count">  </div>  <script src="./js/dep.js"></script>  <script src="./js/watcher.js"></script>  <script src="./js/compiler.js"></script>  <script src="./js/observer.js"></script>  <script src="./js/vue.js"></script>  <script>    let vm = new Vue({      el: '#app',      data: {        msg: 'Hello Vue',        count: 100,        person: { name: 'zs' }      }    })    console.log(vm.msg)    // vm.msg = { test: 'Hello' }    vm.test = 'abc'  </script></body></html>

实现一个迭代器生成函数

ES6对迭代器的实现

JS原生的汇合类型数据结构,只有Array(数组)和Object(对象);而ES6中,又新增了MapSet。四种数据结构各自有着本人特地的外部实现,但咱们仍期待以同样的一套规定去遍历它们,所以ES6在推出新数据结构的同时也推出了一套 对立的接口机制 ——迭代器(Iterator)。

ES6约定,任何数据结构只有具备Symbol.iterator属性(这个属性就是Iterator的具体实现,它实质上是以后数据结构默认的迭代器生成函数),就能够被遍历——精确地说,是被for...of...循环和迭代器的next办法遍历。 事实上,for...of...的背地正是对next办法的重复调用。

在ES6中,针对ArrayMapSetStringTypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都能够通过for...of...进行遍历。原理都是一样的,此处咱们拿最简略的数组进行举例,当咱们用for...of...遍历数组时:

const arr = [1, 2, 3]const len = arr.lengthfor(item of arr) {   console.log(`以后元素是${item}`)}
之所以可能按程序一次一次地拿到数组里的每一个成员,是因为咱们借助数组的Symbol.iterator生成了它对应的迭代器对象,通过重复调用迭代器对象的next办法拜访了数组成员,像这样:
const arr = [1, 2, 3]// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()// 对迭代器对象执行next,就能一一拜访汇合的成员iterator.next()iterator.next()iterator.next()

丢进控制台,咱们能够看到next每次会按程序帮咱们拜访一个汇合成员:

for...of...做的事件,根本等价于上面这通操作:
// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()// 初始化一个迭代后果let now = { done: false }// 循环往外迭代成员while(!now.done) {    now = iterator.next()    if(!now.done) {        console.log(`当初遍历到了${now.value}`)    }}
能够看出,for...of...其实就是iterator循环调用换了种写法。在ES6中咱们之所以可能开心地用for...of...遍历各种各种的汇合,全靠迭代器模式在背地给力。

ps:此处举荐浏览迭代协定 (opens new window),置信大家读过后会对迭代器在ES6中的实现有更深的了解。

Promise.race

Promise.race = function(promiseArr) {  return new Promise((resolve, reject) => {    promiseArr.forEach(p => {      // 如果不是Promise实例须要转化为Promise实例      Promise.resolve(p).then(        val => resolve(val),        err => reject(err),      )    })  })}

实现Array.isArray办法

Array.myIsArray = function(o) {  return Object.prototype.toString.call(Object(o)) === '[object Array]';};console.log(Array.myIsArray([])); // true

实现一个compose函数

组合多个函数,从右到左,比方:compose(f, g, h) 最终失去这个后果 (...args) => f(g(h(...args))).

题目形容:实现一个 compose 函数

// 用法如下:function fn1(x) {  return x + 1;}function fn2(x) {  return x + 2;}function fn3(x) {  return x + 3;}function fn4(x) {  return x + 4;}const a = compose(fn1, fn2, fn3, fn4);console.log(a(1)); // 1+4+3+2+1=11

实现代码如下

function compose(...funcs) {  if (!funcs.length) return (v) => v;  if (funcs.length === 1) {    return funcs[0]  }  return funcs.reduce((a, b) => {    return (...args) => a(b(...args)))  }}
compose创立了一个从右向左执行的数据流。如果要实现从左到右的数据流,能够间接更改compose的局部代码即可实现
  • 更换Api接口:把reduce改为reduceRight
  • 交互包裹地位:把a(b(...args))改为b(a(...args))

二分查找

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("指标元素不在数组中");// }

实现一个链表构造

链表构造

看图了解next层级

// 链表 从头尾删除、减少 性能比拟好// 分为很多类 罕用单向链表、双向链表// js模仿链表构造:增删改查// 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层级    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 }}var ll = new LinkedList()ll.add(0,100) // Node { ellement: 100, next: null }ll.add(0,200) // Node { element: 200, next: Node { element: 100, next: null } }ll.add(1,500) // Node {element: 200,next: Node { element: 100, next: Node { element: 500, next: null } } }ll.add(300)ll.remove(0)console.log(ll.get(2),'get')console.log(ll.head)module.exports = LinkedList

模仿new操作

3个步骤:

  1. ctor.prototype为原型创立一个对象。
  2. 执行构造函数并将this绑定到新创建的对象上。
  3. 判断构造函数执行返回的后果是否是援用数据类型,若是则返回构造函数执行的后果,否则返回创立的对象。
function newOperator(ctor, ...args) {  if (typeof ctor !== 'function') {    throw new TypeError('Type Error');  }  const obj = Object.create(ctor.prototype);  const res = ctor.apply(obj, args);  const isObject = typeof res === 'object' && res !== null;  const isFunction = typeof res === 'function';  return isObject || isFunction ? res : obj;}

实现lodash的chunk办法--数组按指定长度拆分

题目

/** * @param input * @param size * @returns {Array} */_.chunk(['a', 'b', 'c', 'd'], 2)// => [['a', 'b'], ['c', 'd']]_.chunk(['a', 'b', 'c', 'd'], 3)// => [['a', 'b', 'c'], ['d']]_.chunk(['a', 'b', 'c', 'd'], 5)// => [['a', 'b', 'c', 'd']]_.chunk(['a', 'b', 'c', 'd'], 0)// => []

实现

function chunk(arr, length) {  let newArr = [];  for (let i = 0; i < arr.length; i += length) {    newArr.push(arr.slice(i, i + length));  }  return newArr;}