关于javascript:前端一面必会手写面试题边面边更

39次阅读

共计 10097 个字符,预计需要花费 26 分钟才能阅读完成。

实现 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)); // 12
console.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)())  //15
console.log(addCurry(1)(2)(3, 4, 5)())  //15
console.log(addCurry(1)(2, 3, 4, 5)())  //15

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

例: 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}次 `);

转化为驼峰命名

var s1 = "get-element-by-id"

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

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

instanceof

instanceof运算符用于检测构造函数的 prototype 属性是否呈现在某个实例对象的原型链上。

const myInstanceof = (left, right) => {
  // 根本数据类型都返回 false
  if (typeof left !== 'object' || left === null) return false;
  let proto = Object.getPrototypeOf(left);
  while (true) {if (proto === null) return false;
    if (proto === right.prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
}

数组去重

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

参考 前端进阶面试题具体解答

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

查找文章中呈现频率最高的单词

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

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

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

a = a + b
b = a - b
a = a - b

AJAX

const getJSON = function(url) {return new Promise((resolve, reject) => {const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
    xhr.open('GET', url, false);
    xhr.setRequestHeader('Accept', 'application/json');
    xhr.onreadystatechange = function() {if (xhr.readyState !== 4) return;
      if (xhr.status === 200 || xhr.status === 304) {resolve(xhr.responseText);
      } else {reject(new Error(xhr.responseText));
      }
    }
    xhr.send();})
}

实现千位分隔符

// 保留三位小数
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'

function parseToMoney(num) {num = parseFloat(num.toFixed(3));
  let [integer, decimal] = String.prototype.split.call(num, '.');
  integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');
  return integer + '.' + (decimal ? decimal : '');
}

正则表达式(使用了正则的前向申明和反前向申明):

function parseToMoney(str){
    // 仅仅对地位进行匹配
    let re = /(?=(?!\b)(\d{3})+$)/g; 
   return str.replace(re,','); 
}

数组扁平化

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

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

原型继承

这里只写寄生组合继承了,两头还有几个演变过去的继承但都有一些缺点

function Parent() {this.name = 'parent';}
function Child() {Parent.call(this);
  this.type = 'children';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

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

手写 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]
})

实现深拷贝

  • 浅拷贝: 浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为援用类型的话,那么会将这个援用的地址复制给对象,因而两个对象会有同一个援用类型的援用。浅拷贝能够应用 Object.assign 和开展运算符来实现。
  • 深拷贝: 深拷贝绝对浅拷贝而言,如果遇到属性值为援用类型的时候,它新建一个援用类型并将对应的值复制给它,因而对象取得的一个新的援用类型而不是一个原有类型的援用。深拷贝对于一些对象能够应用 JSON 的两个函数来实现,然而因为 JSON 的对象格局比 js 的对象格局更加严格,所以如果属性值里边呈现函数或者 Symbol 类型的值时,会转换失败

(1)JSON.stringify()

  • JSON.parse(JSON.stringify(obj))是目前比拟罕用的深拷贝办法之一,它的原理就是利用 JSON.stringifyjs 对象序列化(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;
}

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

用一个滑动窗口装没有反复的字符,枚举字符记录最大值即可。用 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
};

实现非负大整数相加

JavaScript 对数值有范畴的限度,限度如下:

Number.MAX_VALUE // 1.7976931348623157e+308
Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_VALUE // 5e-324
Number.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,以便于下一次相加
  • 反复上述操作,直至计算完结

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

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

正文完
 0