实现Vue reactive响应式

// Dep moduleclass Dep {  static stack = []  static target = null  deps = null  constructor() {    this.deps = new Set()  }  depend() {    if (Dep.target) {      this.deps.add(Dep.target)    }  }  notify() {    this.deps.forEach(w => w.update())  }  static pushTarget(t) {    if (this.target) {      this.stack.push(this.target)    }    this.target = t  }  static popTarget() {    this.target = this.stack.pop()  }}// reactivefunction reactive(o) {  if (o && typeof o === 'object') {    Object.keys(o).forEach(k => {      defineReactive(o, k, o[k])    })  }  return o}function defineReactive(obj, k, val) {  let dep = new Dep()  Object.defineProperty(obj, k, {    get() {      dep.depend()      return val    },    set(newVal) {      val = newVal      dep.notify()    }  })  if (val && typeof val === 'object') {    reactive(val)  }}// watcherclass Watcher {  constructor(effect) {    this.effect = effect    this.update()  }  update() {    Dep.pushTarget(this)    this.value = this.effect()    Dep.popTarget()    return this.value  }}// 测试代码const data = reactive({  msg: 'aaa'})new Watcher(() => {  console.log('===> effect', data.msg);})setTimeout(() => {  data.msg = 'hello'}, 1000)

应用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;}

实现数组元素求和

  • arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10]let sum = arr.reduce( (total,i) => total += i,0);console.log(sum);
  • arr=[1,2,3,[[4,5],6],7,8,9],求和
var = arr=[1,2,3,[[4,5],6],7,8,9]let arr= arr.toString().split(',').reduce( (total,i) => total += Number(i),0);console.log(arr);

递归实现:

let arr = [1, 2, 3, 4, 5, 6] function add(arr) {    if (arr.length == 1) return arr[0]     return arr[0] + add(arr.slice(1)) }console.log(add(arr)) // 21

JSONP

script标签不遵循同源协定,能够用来进行跨域申请,长处就是兼容性好但仅限于GET申请

const jsonp = ({ url, params, callbackName }) => {  const generateUrl = () => {    let dataSrc = '';    for (let key in params) {      if (Object.prototype.hasOwnProperty.call(params, key)) {        dataSrc += `${key}=${params[key]}&`;      }    }    dataSrc += `callback=${callbackName}`;    return `${url}?${dataSrc}`;  }  return new Promise((resolve, reject) => {    const scriptEle = document.createElement('script');    scriptEle.src = generateUrl();    document.body.appendChild(scriptEle);    window[callbackName] = data => {      resolve(data);      document.removeChild(scriptEle);    }  })}

实现一个双向绑定

defineProperty 版本

// 数据const data = {  text: 'default'};const input = document.getElementById('input');const span = document.getElementById('span');// 数据劫持Object.defineProperty(data, 'text', {  // 数据变动 --> 批改视图  set(newVal) {    input.value = newVal;    span.innerHTML = newVal;  }});// 视图更改 --> 数据变动input.addEventListener('keyup', function(e) {  data.text = e.target.value;});

proxy 版本

// 数据const data = {  text: 'default'};const input = document.getElementById('input');const span = document.getElementById('span');// 数据劫持const handler = {  set(target, key, value) {    target[key] = value;    // 数据变动 --> 批改视图    input.value = value;    span.innerHTML = value;    return value;  }};const proxy = new Proxy(data, handler);// 视图更改 --> 数据变动input.addEventListener('keyup', function(e) {  proxy.text = e.target.value;});

实现一个拖拽

<style>  html, body {    margin: 0;    height: 100%;  }  #box {    width: 100px;    height: 100px;    background-color: red;    position: absolute;    top: 100px;    left: 100px;  }</style>
<div id="box"></div>
window.onload = function () {  var box = document.getElementById('box');  box.onmousedown = function (ev) {    var oEvent = ev || window.event; // 兼容火狐,火狐下没有window.event    var distanceX = oEvent.clientX - box.offsetLeft; // 鼠标到可视区右边的间隔 - box到页面右边的间隔    var distanceY = oEvent.clientY - box.offsetTop;    document.onmousemove = function (ev) {      var oEvent = ev || window.event;      var left = oEvent.clientX - distanceX;      var top = oEvent.clientY - distanceY;      if (left <= 0) {        left = 0;      } else if (left >= document.documentElement.clientWidth - box.offsetWidth) {        left = document.documentElement.clientWidth - box.offsetWidth;      }      if (top <= 0) {        top = 0;      } else if (top >= document.documentElement.clientHeight - box.offsetHeight) {        top = document.documentElement.clientHeight - box.offsetHeight;      }      box.style.left = left + 'px';      box.style.top = top + 'px';    }    box.onmouseup = function () {      document.onmousemove = null;      box.onmouseup = null;    }  }}

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

小孩报数问题

有30个小孩儿,编号从1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 而后下一个小孩 从新报数 1、2、3,问最初剩下的那个小孩儿的编号是多少?

function childNum(num, count){    let allplayer = [];        for(let i = 0; i < num; i++){        allplayer[i] = i + 1;    }    let exitCount = 0;    // 来到人数    let counter = 0;      // 记录报数    let curIndex = 0;     // 以后下标    while(exitCount < num - 1){        if(allplayer[curIndex] !== 0) counter++;            if(counter == count){            allplayer[curIndex] = 0;                             counter = 0;            exitCount++;          }        curIndex++;        if(curIndex == num){            curIndex = 0                       };               }        for(i = 0; i < num; i++){        if(allplayer[i] !== 0){            return allplayer[i]        }          }}childNum(30, 3)

实现Ajax

步骤

  • 创立 XMLHttpRequest 实例
  • 收回 HTTP 申请
  • 服务器返回 XML 格局的字符串
  • JS 解析 XML,并更新部分页面
  • 不过随着历史进程的推动,XML 曾经被淘汰,取而代之的是 JSON。

理解了属性和办法之后,依据 AJAX 的步骤,手写最简略的 GET 申请。

实现Array.isArray办法

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

实现async/await

剖析

// generator生成器  生成迭代器iterator// 默认这样写的类数组是不能被迭代的,短少迭代办法let likeArray = {'0': 1, '1': 2, '2': 3, '3': 4, length: 4}// // 应用迭代器使得能够开展数组// // Symbol有很多元编程办法,能够改js自身性能// likeArray[Symbol.iterator] = function () {//   // 迭代器是一个对象 对象中有next办法 每次调用next 都须要返回一个对象 {value,done}//   let index = 0//   return {//     next: ()=>{//       // 会主动调用这个办法//       console.log('index',index)//       return {//         // this 指向likeArray//         value: this[index],//         done: index++ === this.length//       }//     }//   }// }// let arr = [...likeArray]// console.log('arr', arr)// 应用生成器返回迭代器// likeArray[Symbol.iterator] = function *() {//   let index = 0//   while (index != this.length) {//     yield this[index++]//   }// }// let arr = [...likeArray]// console.log('arr', arr)// 生成器 碰到yield就会暂停// function *read(params) {//   yield 1;//   yield 2;// }// 生成器返回的是迭代器// let it = read()// console.log(it.next())// console.log(it.next())// console.log(it.next())// 通过generator来优化promise(promise的毛病是不停的链式调用)const fs = require('fs')const path = require('path')// const co = require('co') // 帮咱们执行generatorconst promisify = fn=>{  return (...args)=>{    return new Promise((resolve,reject)=>{      fn(...args, (err,data)=>{        if(err) {          reject(err)        }         resolve(data)      })    })  }}// promise化let asyncReadFile = promisify(fs.readFile)function * read() {  let content1 = yield asyncReadFile(path.join(__dirname,'./data/name.txt'),'utf8')  let content2 = yield asyncReadFile(path.join(__dirname,'./data/' + content1),'utf8')  return content2}// 这样写太繁琐 须要借助co来实现// let re = read()// let {value,done} = re.next()// value.then(data=>{//   // 除了第一次传参没有意义外 剩下的传参都赋予了上一次的返回值 //   let {value,done} = re.next(data) //   value.then(d=>{//     let {value,done} = re.next(d)//     console.log(value,done)//   })// }).catch(err=>{//   re.throw(err) // 手动抛出谬误 能够被try catch捕捉// })// 实现co原理function co(it) {// it 迭代器  return new Promise((resolve,reject)=>{    // 异步迭代 须要依据函数来实现    function next(data) {      // 递归得有停止条件      let {value,done} = it.next(data)      if(done) {        resolve(value) // 间接让promise变成胜利 用以后返回的后果      } else {        // Promise.resolve(value).then(data=>{        //   next(data)        // }).catch(err=>{        //   reject(err)        // })        // 简写        Promise.resolve(value).then(next,reject)      }    }    // 首次调用    next()  })}co(read()).then(d=>{  console.log(d)}).catch(err=>{  console.log(err,'--')})

整体看一下构造

function asyncToGenerator(generatorFunc) {    return function() {      const gen = generatorFunc.apply(this, arguments)      return new Promise((resolve, reject) => {        function step(key, arg) {          let generatorResult          try {            generatorResult = gen[key](arg)          } catch (error) {            return reject(error)          }          const { value, done } = generatorResult          if (done) {            return resolve(value)          } else {            return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))          }        }        step("next")      })    }}

剖析

function asyncToGenerator(generatorFunc) {  // 返回的是一个新的函数  return function() {    // 先调用generator函数 生成迭代器    // 对应 var gen = testG()    const gen = generatorFunc.apply(this, arguments)    // 返回一个promise 因为内部是用.then的形式 或者await的形式去应用这个函数的返回值的    // var test = asyncToGenerator(testG)    // test().then(res => console.log(res))    return new Promise((resolve, reject) => {      // 外部定义一个step函数 用来一步一步的跨过yield的妨碍      // key有next和throw两种取值,别离对应了gen的next和throw办法      // arg参数则是用来把promise resolve进去的值交给下一个yield      function step(key, arg) {        let generatorResult        // 这个办法须要包裹在try catch中        // 如果报错了 就把promise给reject掉 内部通过.catch能够获取到谬误        try {          generatorResult = gen[key](arg)        } catch (error) {          return reject(error)        }        // gen.next() 失去的后果是一个 { value, done } 的构造        const { value, done } = generatorResult        if (done) {          // 如果曾经实现了 就间接resolve这个promise          // 这个done是在最初一次调用next后才会为true          // 以本文的例子来说 此时的后果是 { done: true, value: 'success' }          // 这个value也就是generator函数最初的返回值          return resolve(value)        } else {          // 除了最初完结的时候外,每次调用gen.next()          // 其实是返回 { value: Promise, done: false } 的构造,          // 这里要留神的是Promise.resolve能够承受一个promise为参数          // 并且这个promise参数被resolve的时候,这个then才会被调用          return Promise.resolve(            // 这个value对应的是yield前面的promise            value          ).then(            // value这个promise被resove的时候,就会执行next            // 并且只有done不是true的时候 就会递归的往下解开promise            // 对应gen.next().value.then(value => {            //    gen.next(value).value.then(value2 => {            //       gen.next()             //            //      // 此时done为true了 整个promise被resolve了             //      // 最内部的test().then(res => console.log(res))的then就开始执行了            //    })            // })            function onResolve(val) {              step("next", val)            },            // 如果promise被reject了 就再次进入step函数            // 不同的是,这次的try catch中调用的是gen.throw(err)            // 那么天然就被catch到 而后把promise给reject掉啦            function onReject(err) {              step("throw", err)            },          )        }      }      step("next")    })  }}

Object.is

Object.is解决的次要是这两个问题:

+0 === -0  // trueNaN === NaN // false
const is= (x, y) => {  if (x === y) {    // +0和-0应该不相等    return x !== 0 || y !== 0 || 1/x === 1/y;  } else {    return x !== x && y !== y;  }}

滚动加载

原理就是监听页面滚动事件,剖析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);

Promise

// 模仿实现Promise// Promise利用三大伎俩解决回调天堂:// 1. 回调函数提早绑定// 2. 返回值穿透// 3. 谬误冒泡// 定义三种状态const PENDING = 'PENDING';      // 进行中const FULFILLED = 'FULFILLED';  // 已胜利const REJECTED = 'REJECTED';    // 已失败class Promise {  constructor(exector) {    // 初始化状态    this.status = PENDING;    // 将胜利、失败后果放在this上,便于then、catch拜访    this.value = undefined;    this.reason = undefined;    // 胜利态回调函数队列    this.onFulfilledCallbacks = [];    // 失败态回调函数队列    this.onRejectedCallbacks = [];    const resolve = value => {      // 只有进行中状态能力更改状态      if (this.status === PENDING) {        this.status = FULFILLED;        this.value = value;        // 胜利态函数顺次执行        this.onFulfilledCallbacks.forEach(fn => fn(this.value));      }    }    const reject = reason => {      // 只有进行中状态能力更改状态      if (this.status === PENDING) {        this.status = REJECTED;        this.reason = reason;        // 失败态函数顺次执行        this.onRejectedCallbacks.forEach(fn => fn(this.reason))      }    }    try {      // 立刻执行executor      // 把外部的resolve和reject传入executor,用户可调用resolve和reject      exector(resolve, reject);    } catch(e) {      // executor执行出错,将谬误内容reject抛出去      reject(e);    }  }  then(onFulfilled, onRejected) {    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;    onRejected = typeof onRejected === 'function'? onRejected :      reason => { throw new Error(reason instanceof Error ? reason.message : reason) }    // 保留this    const self = this;    return new Promise((resolve, reject) => {      if (self.status === PENDING) {        self.onFulfilledCallbacks.push(() => {          // try捕捉谬误          try {            // 模仿微工作            setTimeout(() => {              const result = onFulfilled(self.value);              // 分两种状况:              // 1. 回调函数返回值是Promise,执行then操作              // 2. 如果不是Promise,调用新Promise的resolve函数              result instanceof Promise ? result.then(resolve, reject) : resolve(result);            })          } catch(e) {            reject(e);          }        });        self.onRejectedCallbacks.push(() => {          // 以下同理          try {            setTimeout(() => {              const result = onRejected(self.reason);              // 不同点:此时是reject              result instanceof Promise ? result.then(resolve, reject) : resolve(result);            })          } catch(e) {            reject(e);          }        })      } else if (self.status === FULFILLED) {        try {          setTimeout(() => {            const result = onFulfilled(self.value);            result instanceof Promise ? result.then(resolve, reject) : resolve(result);          });        } catch(e) {          reject(e);        }      } else if (self.status === REJECTED) {        try {          setTimeout(() => {            const result = onRejected(self.reason);            result instanceof Promise ? result.then(resolve, reject) : resolve(result);          })        } catch(e) {          reject(e);        }      }    });  }  catch(onRejected) {    return this.then(null, onRejected);  }  static resolve(value) {    if (value instanceof Promise) {      // 如果是Promise实例,间接返回      return value;    } else {      // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED      return new Promise((resolve, reject) => resolve(value));    }  }  static reject(reason) {    return new Promise((resolve, reject) => {      reject(reason);    })  }  static all(promiseArr) {    const len = promiseArr.length;    const values = new Array(len);    // 记录曾经胜利执行的promise个数    let count = 0;    return new Promise((resolve, reject) => {      for (let i = 0; i < len; i++) {        // Promise.resolve()解决,确保每一个都是promise实例        Promise.resolve(promiseArr[i]).then(          val => {            values[i] = val;            count++;            // 如果全副执行完,返回promise的状态就能够扭转了            if (count === len) resolve(values);          },          err => reject(err),        );      }    })  }  static race(promiseArr) {    return new Promise((resolve, reject) => {      promiseArr.forEach(p => {        Promise.resolve(p).then(          val => resolve(val),          err => reject(err),        )      })    })  }}

实现类的继承

实现类的继承-简版

类的继承在几年前是重点内容,有n种继承形式各有优劣,es6遍及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深刻了解的去看红宝书即可,咱们目前只实现一种最现实的继承形式。
// 寄生组合继承function Parent(name) {  this.name = name}Parent.prototype.say = function() {  console.log(this.name + ` say`);}Parent.prototype.play = function() {  console.log(this.name + ` play`);}function Child(name, parent) {  // 将父类的构造函数绑定在子类上  Parent.call(this, parent)  this.name = 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.name + ` say`);}// 留神记得把子类的结构指向子类自身Child.prototype.constructor = Child;
// 测试var parent = new Parent('parent');parent.say() var child = new Child('child');child.say() child.play(); // 继承父类的办法

ES5实现继承-具体

第一种形式是借助call实现继承

function Parent1(){    this.name = 'parent1';}function Child1(){    Parent1.call(this);    this.type = 'child1'    }console.log(new Child1);
这样写的时候子类尽管可能拿到父类的属性值,然而问题是父类中一旦存在办法那么子类无奈继承。那么引出上面的办法

第二种形式借助原型链实现继承:

function Parent2() {    this.name = 'parent2';    this.play = [1, 2, 3]  }  function Child2() {    this.type = 'child2';  }  Child2.prototype = new Parent2();  console.log(new Child2());

看似没有问题,父类的办法和属性都可能拜访,但实际上有一个潜在的有余。举个例子:

var s1 = new Child2();  var s2 = new Child2();  s1.play.push(4);  console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]

明明我只扭转了s1的play属性,为什么s2也跟着变了呢?很简略,因为两个实例应用的是同一个原型对象

第三种形式:将前两种组合:

function Parent3 () {    this.name = 'parent3';    this.play = [1, 2, 3];  }  function Child3() {    Parent3.call(this);    this.type = 'child3';  }  Child3.prototype = new Parent3();  var s3 = new Child3();  var s4 = new Child3();  s3.play.push(4);  console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]
之前的问题都得以解决。然而这里又徒增了一个新问题,那就是Parent3的构造函数会多执行了一次(Child3.prototype = new Parent3();)。这是咱们不愿看到的。那么如何解决这个问题?

第四种形式: 组合继承的优化1

function Parent4 () {    this.name = 'parent4';    this.play = [1, 2, 3];  }  function Child4() {    Parent4.call(this);    this.type = 'child4';  }  Child4.prototype = Parent4.prototype;
这里让将父类原型对象间接给到子类,父类构造函数只执行一次,而且父类属性和办法均能拜访,然而咱们来测试一下
var s3 = new Child4();  var s4 = new Child4();  console.log(s3)
子类实例的构造函数是Parent4,显然这是不对的,应该是Child4。

第五种形式(最举荐应用):优化2

function Parent5 () {    this.name = 'parent5';    this.play = [1, 2, 3];  }  function Child5() {    Parent5.call(this);    this.type = 'child5';  }  Child5.prototype = Object.create(Parent5.prototype);  Child5.prototype.constructor = Child5;
这是最举荐的一种形式,靠近完满的继承。

实现简略路由

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

实现一下hash路由

根底的html代码:

<html>  <style>    html, body {      margin: 0;      height: 100%;    }    ul {      list-style: none;      margin: 0;      padding: 0;      display: flex;      justify-content: center;    }    .box {      width: 100%;      height: 100%;      background-color: red;    }  </style>  <body>  <ul>    <li>      <a href="#red">红色</a>    </li>    <li>      <a href="#green">绿色</a>    </li>    <li>      <a href="#purple">紫色</a>    </li>  </ul>  </body></html>

简略实现:

<script>  const box = document.getElementsByClassName('box')[0];  const hash = location.hash  window.onhashchange = function (e) {    const color = hash.slice(1)    box.style.background = color  }</script>

封装成一个class:

<script>  const box = document.getElementsByClassName('box')[0];  const hash = location.hash  class HashRouter {    constructor (hashStr, cb) {      this.hashStr = hashStr      this.cb = cb      this.watchHash()      this.watch = this.watchHash.bind(this)      window.addEventListener('hashchange', this.watch)    }    watchHash () {      let hash = window.location.hash.slice(1)      this.hashStr = hash      this.cb(hash)    }  }  new HashRouter('red', (color) => {    box.style.background = color  })</script>

手写常见排序

冒泡排序

冒泡排序的原理如下,从第一个元素开始,把以后元素和下一个索引元素进行比拟。如果以后元素大,那么就替换地位,反复操作直到比拟到最初一个元素,那么此时最初一个元素就是该数组中最大的数。下一轮反复以上操作,然而此时最初一个元素曾经是最大数了,所以不须要再比拟最初一个元素,只须要比拟到 length - 1 的地位。
function bubbleSort(list) {  var n = list.length;  if (!n) return [];  for (var i = 0; i < n; i++) {    // 留神这里须要 n - i - 1    for (var j = 0; j < n - i - 1; j++) {      if (list[j] > list[j + 1]) {        var temp = list[j + 1];        list[j + 1] = list[j];        list[j] = temp;      }    }  }  return list;}

疾速排序

快排的原理如下。随机选取一个数组中的值作为基准值,从左至右取值与基准值比照大小。比基准值小的放数组右边,大的放左边,比照实现后将基准值和第一个比基准值大的值替换地位。而后将数组以基准值的地位分为两局部,持续递归以上操作
ffunction quickSort(arr) {  if (arr.length<=1){    return arr;  }  var baseIndex = Math.floor(arr.length/2);//向下取整,选取基准点  var base = arr.splice(baseIndex,1)[0];//取出基准点的值,  // splice 通过删除或替换现有元素或者原地增加新的元素来批改数组,并以数组模式返回被批改的内容。此办法会扭转原数组。  // slice办法返回一个新的数组对象,不会更改原数组  //这里不能间接base=arr[baseIndex],因为base代表的每次都删除的那个数  var left=[];  var right=[];  for (var i = 0; i<arr.length; i++){    // 这里的length是变动的,因为splice会扭转原数组。    if (arr[i] < base){      left.push(arr[i]);//比基准点小的放在右边数组,    }  }else{    right.push(arr[i]);//比基准点大的放在左边数组,  }  return quickSort(left).concat([base],quickSort(right));}

抉择排序

function selectSort(arr) {  // 缓存数组长度  const len = arr.length;  // 定义 minIndex,缓存以后区间最小值的索引,留神是索引  let minIndex;  // i 是以后排序区间的终点  for (let i = 0; i < len - 1; i++) {    // 初始化 minIndex 为以后区间第一个元素    minIndex = i;    // i、j别离定义以后区间的上下界,i是左边界,j是右边界    for (let j = i; j < len; j++) {      // 若 j 处的数据项比以后最小值还要小,则更新最小值索引为 j      if (arr[j] < arr[minIndex]) {        minIndex = j;      }    }    // 如果 minIndex 对应元素不是目前的头部元素,则替换两者    if (minIndex !== i) {      [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];    }  }  return arr;}// console.log(selectSort([3, 6, 2, 4, 1]));

插入排序

function insertSort(arr) {  for (let i = 1; i < arr.length; i++) {    let j = i;    let target = arr[j];    while (j > 0 && arr[j - 1] > target) {      arr[j] = arr[j - 1];      j--;    }    arr[j] = target;  }  return arr;}// console.log(insertSort([3, 6, 2, 4, 1]));

实现节流函数(throttle)

防抖函数原理:规定在一个单位工夫内,只能触发一次函数。如果这个单位工夫内触发屡次函数,只有一次失效。

// 手写简化版

// 节流函数const throttle = (fn, delay = 500) => {  let flag = true;  return (...args) => {    if (!flag) return;    flag = false;    setTimeout(() => {      fn.apply(this, args);      flag = true;    }, delay);  };};

实用场景:

  • 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
  • 缩放场景:监控浏览器resize
  • 动画场景:防止短时间内屡次触发动画引起性能问题

实现模板字符串解析性能

let template = '我是{{name}},年龄{{age}},性别{{sex}}';let data = {  name: '姓名',  age: 18}render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) {  const reg = /\{\{(\w+)\}\}/; // 模板字符串正则  if (reg.test(template)) { // 判断模板里是否有模板字符串    const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染    return render(template, data); // 递归的渲染并返回渲染后的构造  }  return template; // 如果模板没有模板字符串间接返回}

reduce用法汇总

语法

array.reduce(function(total, currentValue, currentIndex, arr), initialValue);/*  total: 必须。初始值, 或者计算完结后的返回值。  currentValue: 必须。以后元素。  currentIndex: 可选。以后元素的索引;                       arr: 可选。以后元素所属的数组对象。  initialValue: 可选。传递给函数的初始值,相当于total的初始值。*/
reduceRight() 该办法用法与reduce()其实是雷同的,只是遍历的程序相同,它是从数组的最初一项开始,向前遍历到第一项

1. 数组求和

const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num);// 设定初始值求和const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num, 10);  // 以10为初始值求和// 对象数组求和var result = [  { subject: 'math', score: 88 },  { subject: 'chinese', score: 95 },  { subject: 'english', score: 80 }];const sum = result.reduce((accumulator, cur) => accumulator + cur.score, 0); const sum = result.reduce((accumulator, cur) => accumulator + cur.score, -10);  // 总分扣除10分

2. 数组最大值

const a = [23,123,342,12];const max = a.reduce((pre,next)=>pre>cur?pre:cur,0); // 342

3. 数组转对象

var streams = [{name: '技术', id: 1}, {name: '设计', id: 2}];var obj = streams.reduce((accumulator, cur) => {accumulator[cur.id] = cur; return accumulator;}, {});

4. 扁平一个二维数组

var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];var res = arr.reduce((x, y) => x.concat(y), []);

5. 数组去重

实现的基本原理如下:① 初始化一个空数组② 将须要去重解决的数组中的第1项在初始化数组中查找,如果找不到(空数组中必定找不到),就将该项增加到初始化数组中③ 将须要去重解决的数组中的第2项在初始化数组中查找,如果找不到,就将该项持续增加到初始化数组中④ ……⑤ 将须要去重解决的数组中的第n项在初始化数组中查找,如果找不到,就将该项持续增加到初始化数组中⑥ 将这个初始化数组返回
var newArr = arr.reduce(function (prev, cur) {    prev.indexOf(cur) === -1 && prev.push(cur);    return prev;},[]);

6. 对象数组去重

const dedup = (data, getKey = () => { }) => {    const dateMap = data.reduce((pre, cur) => {        const key = getKey(cur)        if (!pre[key]) {            pre[key] = cur        }        return pre    }, {})    return Object.values(dateMap)}

7. 求字符串中字母呈现的次数

const str = 'sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha';const res = str.split('').reduce((pre,next)=>{ pre[next] ? pre[next]++ : pre[next] = 1 return pre },{})
// 后果-: 1a: 8c: 1d: 4e: 1f: 4g: 1h: 2i: 2j: 4k: 1l: 3m: 2n: 1q: 5r: 1s: 6u: 1w: 2x: 1z: 1

8. compose函数

redux compose 源码实现
function compose(...funs) {    if (funs.length === 0) {        return arg => arg;    }    if (funs.length === 1) {       return funs[0];    }    return funs.reduce((a, b) => (...arg) => a(b(...arg)))}