乐趣区

关于javascript:那些年面挂的js手写题

数组去重办法汇总

首先: 我晓得多少种去重形式

1. 双层 for 循环

function distinct(arr) {for (let i=0, len=arr.length; i<len; i++) {for (let j=i+1; j<len; j++) {if (arr[i] == arr[j]) {arr.splice(j, 1);
                // splice 会扭转数组长度,所以要将数组长度 len 和下标 j 减一
                len--;
                j--;
            }
        }
    }
    return arr;
}

思维: 双重 for 循环是比拟蠢笨的办法,它实现的原理很简略:先定义一个蕴含原始数组第一个元素的数组,而后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不反复则增加到新数组中,最初返回新数组;因为它的工夫复杂度是O(n^2),如果数组长度很大,效率会很低

2. Array.filter() 加 indexOf/includes

function distinct(a, b) {let arr = a.concat(b);
    return arr.filter((item, index)=> {//return arr.indexOf(item) === index
        return arr.includes(item)
    })
}

思维: 利用 indexOf 检测元素在数组中第一次呈现的地位是否和元素当初的地位相等,如果不等则阐明该元素是反复元素

3. ES6 中的 Set 去重

function distinct(array) {return Array.from(new Set(array));
}

思维: ES6 提供了新的数据结构 Set,Set 构造的一个个性就是成员值都是惟一的,没有反复的值。

4. reduce 实现对象数组去反复

var resources = [{ name: "张三", age: "18"},
    {name: "张三", age: "19"},
    {name: "张三", age: "20"},
    {name: "李四", age: "19"},
    {name: "王五", age: "20"},
    {name: "赵六", age: "21"}
]
var temp = {};
resources = resources.reduce((prev, curv) => {
 // 如果长期对象中有这个名字,什么都不做
 if (temp[curv.name]) { }else {
    // 如果长期对象没有就把这个名字加进去,同时把以后的这个对象退出到 prev 中
    temp[curv.name] = true;
    prev.push(curv);
 }
 return prev
}, []);
console.log("后果", resources);

这种办法是利用高阶函数 reduce 进行去重,这里只须要留神 initialValue 得放一个空数组[],不然没法push

数组扁平化

数组扁平化是指将一个多维数组变为一个一维数组

const arr = [1, [2, [3, [4, 5]]], 6];
// => [1, 2, 3, 4, 5, 6]
办法一:应用 flat()
const res1 = arr.flat(Infinity);
办法二:利用正则
const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');

但数据类型都会变为字符串

办法三:正则改进版本
const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') +']');
办法四:应用 reduce
const flatten = arr => {return arr.reduce((pre, cur) => {return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
  }, [])
}
const res4 = flatten(arr);
办法五:函数递归
const res5 = [];
const fn = arr => {for (let i = 0; i < arr.length; i++) {if (Array.isArray(arr[i])) {fn(arr[i]);
    } else {res5.push(arr[i]);
    }
  }
}
fn(arr);

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

打印出以后网页应用了多少种 HTML 元素

一行代码能够解决:

const fn = () => {return [...new Set([...document.querySelectorAll('*')].map(el => el.tagName))].length;
}

值得注意的是:DOM 操作返回的是 类数组,须要转换为数组之后才能够调用数组的办法。

参考:前端手写面试题具体解答

实现 prototype 继承

所谓的原型链继承就是让新实例的原型等于父类的实例:

// 父办法
function SupperFunction(flag1){this.flag1 = flag1;}

// 子办法
function SubFunction(flag2){this.flag2 = flag2;}

// 父实例
var superInstance = new SupperFunction(true);

// 子继承父
SubFunction.prototype = superInstance;

// 子实例
var subInstance = new SubFunction(false);
// 子调用本人和父的属性
subInstance.flag1;   // true
subInstance.flag2;   // false

实现 jsonp

// 动静的加载 js 文件
function addScript(src) {const script = document.createElement('script');
  script.src = src;
  script.type = "text/javascript";
  document.body.appendChild(script);
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 设置一个全局的 callback 函数来接管回调后果
function handleRes(res) {console.log(res);
}
// 接口返回的数据格式
handleRes({a: 1, b: 2});

Promise.all

Promise.all是反对链式调用的,实质上就是返回了一个 Promise 实例,通过 resolvereject来扭转实例状态。

Promise.myAll = function(promiseArr) {return new Promise((resolve, reject) => {const ans = [];
    let index = 0;
    for (let i = 0; i < promiseArr.length; i++) {promiseArr[i]
      .then(res => {ans[i] = res;
        index++;
        if (index === promiseArr.length) {resolve(ans);
        }
      })
      .catch(err => reject(err));
    }
  })
}

实现 instanceOf

// 模仿 instanceof
function 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__;
  }
}

类数组转化为数组

类数组是具备 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'));

Function.prototype.bind

Function.prototype.bind = function(context, ...args) {if (typeof this !== 'function') {throw new Error("Type Error");
  }
  // 保留 this 的值
  var self = this;

  return function F() {
    // 思考 new 的状况
    if(this instanceof F) {return new self(...args, ...arguments)
    }
    return self.apply(context, [...args, ...arguments])
  }
}

手写 Promise.all

1) 外围思路

  1. 接管一个 Promise 实例的数组或具备 Iterator 接口的对象作为参数
  2. 这个办法返回一个新的 promise 对象,
  3. 遍历传入的参数,用 Promise.resolve()将参数 ” 包一层 ”,使其变成一个 promise 对象
  4. 参数所有回调胜利才是胜利,返回值数组与参数程序统一
  5. 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。

2)实现代码

一般来说,Promise.all 用来解决多个并发申请,也是为了页面数据结构的不便,将一个页面所用到的在不同接口的数据一起申请过去,不过,如果其中一个接口失败了,多个申请也就失败了,页面可能啥也出不来,这就看以后页面的耦合水平了

function promiseAll(promises) {return new Promise(function(resolve, reject) {if(!Array.isArray(promises)){throw new TypeError(`argument must be a array`)
    }
    var resolvedCounter = 0;
    var promiseNum = promises.length;
    var resolvedResult = [];
    for (let i = 0; i < promiseNum; i++) {Promise.resolve(promises[i]).then(value=>{
        resolvedCounter++;
        resolvedResult[i] = value;
        if (resolvedCounter == promiseNum) {return resolve(resolvedResult)
          }
      },error=>{return reject(error)
      })
    }
  })
}
// test
let p1 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(1)
    }, 1000)
})
let p2 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(2)
    }, 2000)
})
let p3 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(3)
    }, 3000)
})
promiseAll([p3, p1, p2]).then(res => {console.log(res) // [3, 1, 2]
})

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

验证是否是身份证

function isCardNo(number) {var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
    return regx.test(number);
}

实现简略路由

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

渲染几万条数据不卡住页面

渲染大数据时,正当应用 createDocumentFragmentrequestAnimationFrame,将操作切分为一小段一小段执行。

setTimeout(() => {
  // 插入十万条数据
  const total = 100000;
  // 一次插入的数据
  const once = 20;
  // 插入数据须要的次数
  const loopCount = Math.ceil(total / once);
  let countOfRender = 0;
  const ul = document.querySelector('ul');
  // 增加数据的办法
  function add() {const fragment = document.createDocumentFragment();
    for(let i = 0; i < once; i++) {const li = document.createElement('li');
      li.innerText = Math.floor(Math.random() * total);
      fragment.appendChild(li);
    }
    ul.appendChild(fragment);
    countOfRender += 1;
    loop();}
  function loop() {if(countOfRender < loopCount) {window.requestAnimationFrame(add);
    }
  }
  loop();}, 0)

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

将 VirtualDom 转化为实在 DOM 构造

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

// vnode 构造:// {
//   tag,
//   attrs,
//   children,
// }

//Virtual DOM => DOM
function 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;
}

实现 apply 办法

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

// 模仿 apply
Function.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;
};

手写 Promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  // 保留初始化状态
  var self = this;

  // 初始化状态
  this.state = PENDING;

  // 用于保留 resolve 或者 rejected 传入的值
  this.value = null;

  // 用于保留 resolve 的回调函数
  this.resolvedCallbacks = [];

  // 用于保留 reject 的回调函数
  this.rejectedCallbacks = [];

  // 状态转变为 resolved 办法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态扭转必须期待前一个状态扭转后再进行扭转
    if (value instanceof MyPromise) {return value.then(resolve, reject);
    }

    // 保障代码的执行程序为本轮事件循环的开端
    setTimeout(() => {
      // 只有状态为 pending 时能力转变,if (self.state === PENDING) {
        // 批改状态
        self.state = RESOLVED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.resolvedCallbacks.forEach(callback => {callback(value);
        });
      }
    }, 0);
  }

  // 状态转变为 rejected 办法
  function reject(value) {
    // 保障代码的执行程序为本轮事件循环的开端
    setTimeout(() => {
      // 只有状态为 pending 时能力转变
      if (self.state === PENDING) {
        // 批改状态
        self.state = REJECTED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.rejectedCallbacks.forEach(callback => {callback(value);
        });
      }
    }, 0);
  }

  // 将两个办法传入函数执行
  try {fn(resolve, reject);
  } catch (e) {
    // 遇到谬误时,捕捉谬误,执行 reject 函数
    reject(e);
  }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(value) {return value;};

  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(error) {throw error;};

  // 如果是期待状态,则将函数退出对应列表中
  if (this.state === PENDING) {this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }

  // 如果状态曾经凝固,则间接执行对应状态的函数

  if (this.state === RESOLVED) {onResolved(this.value);
  }

  if (this.state === REJECTED) {onRejected(this.value);
  }
};

实现单例模式

外围要点: 用闭包和 Proxy 属性拦挡

function proxy(func) {
    let instance;
    let handler = {constructor(target, args) {if(!instance) {instance = Reflect.constructor(fun, args);
            }
            return instance;
        }
    }
    return new Proxy(func, handler);
}
退出移动版