实现一下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>

函数珂里化

指的是将一个承受多个参数的函数 变为 承受一个参数返回一个函数的固定模式,这样便于再次调用,例如f(1)(2)

经典面试题:实现add(1)(2)(3)(4)=10;add(1)(1,2,3)(2)=9;

function add() {  const _args = [...arguments];  function fn() {    _args.push(...arguments);    return fn;  }  fn.toString = function() {    return _args.reduce((sum, cur) => sum + cur);  }  return fn;}

前端手写面试题具体解答

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

实现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;};

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

手写 bind 函数

bind 函数的实现步骤:

  1. 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  2. 保留以后函数的援用,获取其余传入参数值。
  3. 创立一个函数返回
  4. 函数外部应用 apply 来绑定函数调用,须要判断函数作为构造函数的状况,这个时候须要传入以后函数的 this 给 apply 调用,其余状况都传入指定的上下文对象。
// 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)    );  };};

实现日期格式化函数

输出:

dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01dateFormat(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}

转化为驼峰命名

var s1 = "get-element-by-id"// 转化为 getElementById
var f = function(s) {    return s.replace(/-\w/g, function(x) {        return x.slice(1).toUpperCase();    })}

替换a,b的值,不能用长期变量

奇妙的利用两个数的和、差:

a = a + bb = a - ba = a - b

判断是否是电话号码

function isPhone(tel) {    var regx = /^1[34578]\d{9}$/;    return regx.test(tel);}

手写 call 函数

call 函数的实现步骤:

  1. 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  3. 解决传入的参数,截取第一个参数后的所有参数。
  4. 将函数作为上下文对象的一个属性。
  5. 应用上下文对象来调用这个办法,并保留返回后果。
  6. 删除方才新增的属性。
  7. 返回后果。
// call函数实现Function.prototype.myCall = function(context) {  // 判断调用对象  if (typeof this !== "function") {    console.error("type error");  }  // 获取参数  let args = [...arguments].slice(1),      result = null;  // 判断 context 是否传入,如果未传入则设置为 window  context = context || window;  // 将调用函数设为对象的办法  context.fn = this;  // 调用函数  result = context.fn(...args);  // 将属性删除  delete context.fn;  return result;};

实现节流函数(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
  • 动画场景:防止短时间内屡次触发动画引起性能问题

实现数组的乱序输入

次要的实现思路就是:

  • 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行替换。
  • 第二次取出数据数组第二个元素,随机产生一个除了索引为1的之外的索引值,并将第二个元素与该索引值对应的元素进行替换
  • 依照下面的法则执行,直到遍历实现
var arr = [1,2,3,4,5,6,7,8,9,10];for (var i = 0; i < arr.length; i++) {  const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;  [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];}console.log(arr)

还有一办法就是倒序遍历:

var arr = [1,2,3,4,5,6,7,8,9,10];let length = arr.length,    randomIndex,    temp;  while (length) {    randomIndex = Math.floor(Math.random() * length--);    temp = arr[length];    arr[length] = arr[randomIndex];    arr[randomIndex] = temp;  }console.log(arr)

实现数组的map办法

Array.prototype._map = function(fn) {   if (typeof fn !== "function") {        throw Error('参数必须是一个函数');    }    const res = [];    for (let i = 0, len = this.length; i < len; i++) {        res.push(fn(this[i]));    }    return res;}

throttle(节流)

高频工夫触发,但n秒内只会执行一次,所以节流会浓缩函数的执行频率。

const throttle = (fn, time) => {  let flag = true;  return function() {    if (!flag) return;    flag = false;    setTimeout(() => {      fn.apply(this, arguments);      flag = true;    }, time);  }}

节流常利用于鼠标一直点击触发、监听滚动事件。

将VirtualDom转化为实在DOM构造

这是以后SPA利用的外围概念之一

// vnode构造:// {//   tag,//   attrs,//   children,// }//Virtual DOM => DOMfunction render(vnode, container) {  container.appendChild(_render(vnode));}function _render(vnode) {  // 如果是数字类型转化为字符串  if (typeof vnode === 'number') {    vnode = String(vnode);  }  // 字符串类型间接就是文本节点  if (typeof vnode === 'string') {    return document.createTextNode(vnode);  }  // 一般DOM  const dom = document.createElement(vnode.tag);  if (vnode.attrs) {    // 遍历属性    Object.keys(vnode.attrs).forEach(key => {      const value = vnode.attrs[key];      dom.setAttribute(key, value);    })  }  // 子数组进行递归操作  vnode.children.forEach(child => render(child, dom));  return dom;}

实现数组的扁平化

(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, 4,5]

(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]

图片懒加载

能够给img标签对立自定义属性data-src='default.png',当检测到图片呈现在窗口之后再补充src属性,此时才会进行图片资源加载。

function lazyload() {  const imgs = document.getElementsByTagName('img');  const len = imgs.length;  // 视口的高度  const viewHeight = document.documentElement.clientHeight;  // 滚动条高度  const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;  for (let i = 0; i < len; i++) {    const offsetHeight = imgs[i].offsetTop;    if (offsetHeight < viewHeight + scrollHeight) {      const src = imgs[i].dataset.src;      imgs[i].src = src;    }  }}// 能够应用节流优化一下window.addEventListener('scroll', lazyload);

实现 add(1)(2)(3)

函数柯里化概念: 柯里化(Currying)是把承受多个参数的函数转变为承受一个繁多参数的函数,并且返回承受余下的参数且返回后果的新函数的技术。

1)粗犷版

function add (a) {return function (b) {     return function (c) {      return a + b + c;     }}}console.log(add(1)(2)(3)); // 6

2)柯里化解决方案

  • 参数长度固定
var add = function (m) {  var temp = function (n) {    return add(m + n);  }  temp.toString = function () {    return m;  }  return temp;};console.log(add(3)(4)(5)); // 12console.log(add(3)(6)(9)(25)); // 43

对于add(3)(4)(5),其执行过程如下:

  1. 先执行add(3),此时m=3,并且返回temp函数;
  2. 执行temp(4),这个函数内执行add(m+n),n是此次传进来的数值4,m值还是上一步中的3,所以add(m+n)=add(3+4)=add(7),此时m=7,并且返回temp函数
  3. 执行temp(5),这个函数内执行add(m+n),n是此次传进来的数值5,m值还是上一步中的7,所以add(m+n)=add(7+5)=add(12),此时m=12,并且返回temp函数
  4. 因为前面没有传入参数,等于返回的temp函数不被执行而是打印,理解JS的敌人都晓得对象的toString是批改对象转换字符串的办法,因而代码中temp函数的toString函数return m值,而m值是最初一步执行函数时的值m=12,所以返回值是12。
  5. 参数长度不固定
function add (...args) {    //求和    return args.reduce((a, b) => a + b)}function currying (fn) {    let args = []    return function temp (...newArgs) {        if (newArgs.length) {            args = [                ...args,                ...newArgs            ]            return temp        } else {            let val = fn.apply(this, args)            args = [] //保障再次调用时清空            return val        }    }}let addCurry = currying(add)console.log(addCurry(1)(2)(3)(4, 5)())  //15console.log(addCurry(1)(2)(3, 4, 5)())  //15console.log(addCurry(1)(2, 3, 4, 5)())  //15

实现数组的filter办法

Array.prototype._filter = function(fn) {    if (typeof fn !== "function") {        throw Error('参数必须是一个函数');    }    const res = [];    for (let i = 0, len = this.length; i < len; i++) {        fn(this[i]) && res.push(this[i]);    }    return res;}