数组去重

const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];// => [1, '1', 17, true, false, 'true', 'a', {}, {}]复制代码
办法一:利用Set
const res1 = Array.from(new Set(arr));复制代码
办法二:两层for循环+splice
const unique1 = arr => {  let len = arr.length;  for (let i = 0; i < len; i++) {    for (let j = i + 1; j < len; j++) {      if (arr[i] === arr[j]) {        arr.splice(j, 1);        // 每删除一个树,j--保障j的值通过自加后不变。同时,len--,缩小循环次数晋升性能        len--;        j--;      }    }  }  return arr;}复制代码
办法三:利用indexOf
const unique2 = arr => {  const res = [];  for (let i = 0; i < arr.length; i++) {    if (res.indexOf(arr[i]) === -1) res.push(arr[i]);  }  return res;}复制代码

当然也能够用include、filter,思路大同小异。

办法四:利用include
const unique3 = arr => {  const res = [];  for (let i = 0; i < arr.length; i++) {    if (!res.includes(arr[i])) res.push(arr[i]);  }  return res;}复制代码
办法五:利用filter
const unique4 = arr => {  return arr.filter((item, index) => {    return arr.indexOf(item) === index;  });}复制代码
办法六:利用Map
const unique5 = arr => {  const map = new Map();  const res = [];  for (let i = 0; i < arr.length; i++) {    if (!map.has(arr[i])) {      map.set(arr[i], true)      res.push(arr[i]);    }  }  return res;}复制代码

手写 Promise.race

该办法的参数是 Promise 实例数组, 而后其 then 注册的回调办法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能扭转一次, 那么咱们只须要把 Promise.race 中产生的 Promise 对象的 resolve 办法, 注入到数组中的每一个 Promise 实例中的回调函数中即可.

Promise.race = function (args) {  return new Promise((resolve, reject) => {    for (let i = 0, len = args.length; i < len; i++) {      args[i].then(resolve, reject)    }  })}复制代码

手写 apply 函数

apply 函数的实现步骤:

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

模仿Object.create

Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的__proto__。

// 模仿 Object.createfunction create(proto) {  function F() {}  F.prototype = proto;  return new F();}复制代码

实现instanceOf

// 模仿 instanceoffunction 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__;  }}复制代码

查找字符串中呈现最多的字符和个数

例: abbcccddddd -> 字符最多的是d,呈现了5次

let str = "abcabcabcbbccccc";let num = 0;let char = ''; // 使其依照肯定的秩序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"// 定义正则表达式let re = /(\w)\1+/g;str.replace(re,($0,$1) => {    if(num < $0.length){        num = $0.length;        char = $1;            }});console.log(`字符最多的是${char},呈现了${num}次`);复制代码

实现数组的扁平化

(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]复制代码

手写防抖函数

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则从新计时。这能够应用在一些点击申请的事件上,防止因为用户的屡次点击向后端发送屡次申请。

// 函数防抖的实现function debounce(fn, wait) {  let timer = null;  return function() {    let context = this,        args = arguments;    // 如果此时存在定时器的话,则勾销之前的定时器从新记时    if (timer) {      clearTimeout(timer);      timer = null;    }    // 设置定时器,使事件间隔指定事件后执行    timer = setTimeout(() => {      fn.apply(context, args);    }, wait);  };}复制代码

手写类型判断函数

function getType(value) {  // 判断数据是 null 的状况  if (value === null) {    return value + "";  }  // 判断数据是援用类型的状况  if (typeof value === "object") {    let valueClass = Object.prototype.toString.call(value),      type = valueClass.split(" ")[1].split("");    type.pop();    return type.join("").toLowerCase();  } else {    // 判断数据是根本数据类型的状况和函数的状况    return typeof value;  }}复制代码

实现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;};复制代码

实现字符串的repeat办法

输出字符串s,以及其反复的次数,输入反复的后果,例如输出abc,2,输入abcabc。

function repeat(s, n) {    return (new Array(n + 1)).join(s);}复制代码

递归:

function repeat(s, n) {    return (n > 0) ? s.concat(repeat(s, --n)) : "";}复制代码

字符串呈现的不反复最长长度

用一个滑动窗口装没有反复的字符,枚举字符记录最大值即可。用 map 保护字符的索引,遇到雷同的字符,把左边界挪动过来即可。移动的过程中记录最大长度:

var lengthOfLongestSubstring = function (s) {    let map = new Map();    let i = -1    let res = 0    let n = s.length    for (let j = 0; j < n; j++) {        if (map.has(s[j])) {            i = Math.max(i, map.get(s[j]))        }        res = Math.max(res, j - i)        map.set(s[j], j)    }    return res};复制代码

实现 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复制代码

手写 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;};复制代码

实现数组的乱序输入

次要的实现思路就是:

  • 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行替换。
  • 第二次取出数据数组第二个元素,随机产生一个除了索引为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)复制代码

类数组转化为数组

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

Array.prototype.filter()

Array.prototype.filter = function(callback, thisArg) {  if (this == undefined) {    throw new TypeError('this is null or not undefined');  }  if (typeof callback !== 'function') {    throw new TypeError(callback + 'is not a function');  }  const res = [];  // 让O成为回调函数的对象传递(强制转换对象)  const O = Object(this);  // >>>0 保障len为number,且为正整数  const len = O.length >>> 0;  for (let i = 0; i < len; i++) {    // 查看i是否在O的属性(会查看原型链)    if (i in O) {      // 回调函数调用传参      if (callback.call(thisArg, O[i], i, O)) {        res.push(O[i]);      }    }  }  return res;}复制代码

对于>>>0有疑难的:解释>>>0的作用

实现每隔一秒打印 1,2,3,4

// 应用闭包实现for (var i = 0; i < 5; i++) {  (function(i) {    setTimeout(function() {      console.log(i);    }, i * 1000);  })(i);}// 应用 let 块级作用域for (let i = 0; i < 5; i++) {  setTimeout(function() {    console.log(i);  }, i * 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;}复制代码