乐趣区

关于javascript:前端二面必会手写面试题汇总

实现 Vue reactive 响应式

// Dep module
class 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()
  }
}

// reactive
function 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)
  }
}

// watcher
class 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') // 帮咱们执行 generator

const 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  // true
NaN === 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 
},{})
// 后果
-: 1
a: 8
c: 1
d: 4
e: 1
f: 4
g: 1
h: 2
i: 2
j: 4
k: 1
l: 3
m: 2
n: 1
q: 5
r: 1
s: 6
u: 1
w: 2
x: 1
z: 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)))
}
退出移动版