转化为驼峰命名
 var s1 = "get-element-by-id"
// 转化为 getElementById
var f = function(s) {    return s.replace(/-\w/g, function(x) {        return x.slice(1).toUpperCase();    })}
       复制代码
 实现一个 call
call 做了什么:
将函数设为对象的属性
执行 &删除这个函数
指定 this 到函数并传入给定参数执行函数
如果不传入参数,默认指向为 window
 // 模拟 call bar.mycall(null);//实现一个call方法:Function.prototype.myCall = function(context) {  //此处没有考虑context非object情况  context.fn = this;  let args = [];  for (let i = 1, len = arguments.length; i < len; i++) {    args.push(arguments[i]);  }  context.fn(...args);  let result = 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;}
       复制代码
 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;  }}
       复制代码
 
参考:前端手写面试题详细解答
手写 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);  }};
       复制代码
 实现深拷贝
浅拷贝: 浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为引用类型的话,那么会将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。浅拷贝可以使用  Object.assign 和展开运算符来实现。
深拷贝: 深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败
(1)JSON.stringify()
JSON.parse(JSON.stringify(obj))是目前比较常用的深拷贝方法之一,它的原理就是利用JSON.stringify 将js对象序列化(JSON 字符串),再使用JSON.parse来反序列化(还原)js对象。
这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。
 let obj1 = {  a: 0,              b: {                 c: 0                 }            };let obj2 = JSON.parse(JSON.stringify(obj1));obj1.a = 1;obj1.b.c = 1;console.log(obj1); // {a: 1, b: {c: 1}}console.log(obj2); // {a: 0, b: {c: 0}}
       复制代码
 (2)函数库 lodash 的_.cloneDeep 方法
该函数库也有提供_.cloneDeep 用来做 Deep Copy
 var _ = require('lodash');var obj1 = {    a: 1,    b: { f: { g: 1 } },    c: [1, 2, 3]};var obj2 = _.cloneDeep(obj1);console.log(obj1.b.f === obj2.b.f);// false
       复制代码
 (3)手写实现深拷贝函数
 // 深拷贝的实现function deepCopy(object) {  if (!object || typeof object !== "object") return;
  let newObject = Array.isArray(object) ? [] : {};
  for (let key in object) {    if (object.hasOwnProperty(key)) {      newObject[key] =        typeof object[key] === "object" ? deepCopy(object[key]) : object[key];    }  }
  return newObject;}
       复制代码
 手写 Object.create
思路:将传入的对象作为原型
 function create(obj) {  function F() {}  F.prototype = obj  return new F()}
       复制代码
 查找字符串中出现最多的字符和个数
例: 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}次`);
       复制代码
 实现 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),其执行过程如下:
先执行 add(3),此时 m=3,并且返回 temp 函数;
执行 temp(4),这个函数内执行 add(m+n),n 是此次传进来的数值 4,m 值还是上一步中的 3,所以 add(m+n)=add(3+4)=add(7),此时 m=7,并且返回 temp 函数
执行 temp(5),这个函数内执行 add(m+n),n 是此次传进来的数值 5,m 值还是上一步中的 7,所以 add(m+n)=add(7+5)=add(12),此时 m=12,并且返回 temp 函数
由于后面没有传入参数,等于返回的 temp 函数不被执行而是打印,了解 JS 的朋友都知道对象的 toString 是修改对象转换字符串的方法,因此代码中 temp 函数的 toString 函数 return m 值,而 m 值是最后一步执行函数时的值 m=12,所以返回值是 12。
 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
       复制代码
 实现非负大整数相加
JavaScript 对数值有范围的限制,限制如下:
 Number.MAX_VALUE // 1.7976931348623157e+308Number.MAX_SAFE_INTEGER // 9007199254740991Number.MIN_VALUE // 5e-324Number.MIN_SAFE_INTEGER // -9007199254740991
       复制代码
 
如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER)进行加法运算,但是又想输出一般形式,那么使用 + 是无法达到的,一旦数字超过 Number.MAX_SAFE_INTEGER 数字会被立即转换为科学计数法,并且数字精度相比以前将会有误差。
实现一个算法进行大数的相加:
 function sumBigNumber(a, b) {  let res = '';  let temp = 0;
  a = a.split('');  b = b.split('');
  while (a.length || b.length || temp) {    temp += ~~a.pop() + ~~b.pop();    res = (temp % 10) + res;    temp  = temp > 9  }  return res.replace(/^0+/, '');}
       复制代码
 
其主要的思路如下:
首先用字符串的方式来保存大数,这样数字在数学表示上就不会发生变化
初始化 res,temp 来保存中间的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
将两个数组的对应的位进行相加,两个数相加的结果可能大于 10,所以可能要仅为,对 10 进行取余操作,将结果保存在当前位
判断当前位是否大于 9,也就是是否会进位,若是则将 temp 赋值为 true,因为在加法运算中,true 会自动隐式转化为 1,以便于下一次相加
重复上述操作,直至计算结束
解析 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;}
       复制代码
 查找文章中出现频率最高的单词
 function findMostWord(article) {  // 合法性判断  if (!article) return;  // 参数处理  article = article.trim().toLowerCase();  let wordList = article.match(/[a-z]+/g),    visited = [],    maxNum = 0,    maxWord = "";  article = " " + wordList.join("  ") + " ";  // 遍历判断单词出现次数  wordList.forEach(function(item) {    if (visited.indexOf(item) < 0) {      // 加入 visited       visited.push(item);      let word = new RegExp(" " + item + " ", "g"),        num = article.match(word).length;      if (num > maxNum) {        maxNum = num;        maxWord = item;      }    }  });  return maxWord + "  " + maxNum;}
       复制代码
 验证是否是身份证
 function isCardNo(number) {    var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;    return regx.test(number);}
       复制代码
 类数组转化为数组
类数组是具有 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'));
       复制代码
 数组去重
 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;}
       复制代码
 实现数组的乱序输出
主要的实现思路就是:
 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)
       复制代码
 实现 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;   // truesubInstance.flag2;   // false
       复制代码
 手写 Promise.all
1) 核心思路
接收一个 Promise 实例的数组或具有 Iterator 接口的对象作为参数
这个方法返回一个新的 promise 对象,
遍历传入的参数,用 Promise.resolve()将参数"包一层",使其变成一个 promise 对象
参数所有回调成功才是成功,返回值数组与参数顺序一致
参数数组其中一个失败,则触发失败状态,第一个触发失败的 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)      })    }  })}// testlet 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]})
       复制代码
 实现数组去重
给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。
ES6 方法(使用数据结构集合):
 const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
       复制代码
 
ES5 方法:使用 map 存储不重复的数字
 const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
uniqueArray(array); // [1, 2, 3, 5, 9, 8]
function uniqueArray(array) {  let map = {};  let res = [];  for(var i = 0; i < array.length; i++) {    if(!map.hasOwnProperty([array[i]])) {      map[array[i]] = 1;      res.push(array[i]);    }  }  return res;}
       复制代码
 打印出当前网页使用了多少种 HTML 元素
一行代码可以解决:
 const fn = () => {  return [...new Set([...document.querySelectorAll('*')].map(el => el.tagName))].length;}
       复制代码
 
值得注意的是:DOM 操作返回的是类数组,需要转换为数组之后才可以调用数组的方法。
评论