[toc]

必赛三六科技发展有限公司面试题(北京)

1.请实现一个模块math,支持链式调用math.add(2,4).minus(3).times(2);

  1. class Math {
  2. constructor(value) {
  3. let hasInitValue = true;
  4. if (value === undefined) {
  5. value = NaN;
  6. hasInitValue = false;
  7. }
  8. Object.defineProperties(this, {
  9. value: {
  10. enumerable: true,
  11. value: value,
  12. },
  13. hasInitValue: {
  14. enumerable: false,
  15. value: hasInitValue,
  16. },
  17. });
  18. }
  19. add(...args) {
  20. const init = this.hasInitValue ? this.value : args.shift();
  21. const value = args.reduce((pv, cv) => pv + cv, init);
  22. return new Math(value);
  23. }
  24. minus(...args) {
  25. const init = this.hasInitValue ? this.value : args.shift();
  26. const value = args.reduce((pv, cv) => pv - cv, init);
  27. return new Math(value);
  28. }
  29. times(...args) {
  30. const init = this.hasInitValue ? this.value : args.shift();
  31. const value = args.reduce((pv, cv) => pv * cv, init);
  32. return new Math(value);
  33. }
  34. divide(...args) {
  35. const init = this.hasInitValue ? this.value : args.shift();
  36. const value = args.reduce((pv, cv) => pv / cv, init);
  37. return new Math(value);
  38. }
  39. toJSON() {
  40. return this.valueOf();
  41. }
  42. toString() {
  43. return String(this.valueOf());
  44. }
  45. valueOf() {
  46. return this.value;
  47. }
  48. [Symbol.toPrimitive](hint) {
  49. const value = this.value;
  50. if (hint === 'string') {
  51. return String(value);
  52. } else {
  53. return value;
  54. }
  55. }
  56. }
  57. export default new Math();

2.请简述ES6代码转成ES5代码的实现思路。

  • 将代码字符串解析成抽象语法树,即所谓的 AST
  • 对 AST 进行处理,在这个阶段可以对 ES6 代码进行相应转换,即转成 ES5 代码
  • 根据处理后的 AST 再生成代码字符串

比如,可以使用 @babel/parser 的 parse 方法,将代码字符串解析成 AST;使用 @babel/core 的 transformFromAstSync 方法,对 AST 进行处理,将其转成 ES5 并生成相应的代码字符串;过程中,可能还需要使用 @babel/traverse 来获取依赖文件等。

3.请实现一个深拷贝。

  1. <!--普通的深层拷贝函数: -->
  2. function deepCopy( source ) {
  3. if (!isObject(source)) return source; //如果不是对象的话直接返回
  4. let target = Array.isArray( source ) ? [] : {} //数组兼容
  5. for ( var k in source ) {
  6. if (source.hasOwnProperty(k)) {
  7. if ( typeof source[ k ] === 'object' ) {
  8. target[ k ] = deepCopy( source[ k ] )
  9. } else {
  10. target[ k ] = source[ k ]
  11. }
  12. }
  13. }
  14. return target
  15. }
  16. function isObject(obj) {
  17. return typeof obj === 'object' && obj !== null
  18. }
  19. // 缺点:(1)无法保持引用(2)当数据的层次很深,会栈溢出
  20. <!--防栈溢出函数-->
  21. function cloneLoop(x) {
  22. const root = {};
  23. // 栈
  24. const loopList = [
  25. {
  26. parent: root,
  27. key: undefined,
  28. data: x,
  29. }
  30. ];
  31. while(loopList.length) {
  32. // 深度优先
  33. const node = loopList.pop();
  34. const parent = node.parent;
  35. const key = node.key;
  36. const data = node.data;
  37. // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
  38. let res = parent;
  39. if (typeof key !== 'undefined') {
  40. res = parent[key] = {};
  41. }
  42. for(let k in data) {
  43. if (data.hasOwnProperty(k)) {
  44. if (typeof data[k] === 'object') {
  45. // 下一次循环
  46. loopList.push({
  47. parent: res,
  48. key: k,
  49. data: data[k],
  50. });
  51. } else {
  52. res[k] = data[k];
  53. }
  54. }
  55. }
  56. }
  57. return root;
  58. }
  59. <!--最简单的深拷贝方式-->
  60. function clone(obj) {
  61. return JSON.parse(JSON.stringify(obj));
  62. }

4.什么是防抖和节流?有什么区别?如何实现?

  1. 防抖和节流都是指防止用户短时间内多次重复触发某一行为
  2. 区别在于:防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
  3. 高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。
  1. <!--防抖-->
  2. function debounce (func, wait = 50, immediate = true) {
  3. let timer, context, args
  4. // 延迟执行函数
  5. const later = () => setTimeout(() => {
  6. // 延迟函数执行完毕,清空缓存的定时器序号
  7. timer = null
  8. // 延迟执行的情况下,函数会在延迟函数中执行
  9. // 使用到之前缓存的参数和上下文
  10. if (!immediate) {
  11. func.apply(context, args)
  12. context = args = null
  13. }
  14. }, wait);
  15. // 这里返回的函数是每次实际调用的函数
  16. return function(...params) {
  17. // 如果没有创建延迟执行函数(later),就创建一个
  18. if (!timer) {
  19. timer = later()
  20. // 如果是立即执行,调用函数
  21. // 否则缓存参数和调用上下文
  22. if (immediate) {
  23. func.apply(this, params)
  24. } else {
  25. context = this
  26. args = params
  27. }
  28. // 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个
  29. // 这样做延迟函数会重新计时
  30. } else {
  31. clearTimeout(timer)
  32. timer = later()
  33. }
  34. }
  35. }
  36. <!--节流-->
  37. function throttle(fn) {
  38. let canRun = true; // 通过闭包保存一个标记
  39. return function () {
  40. if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
  41. canRun = false; // 立即设置为 false
  42. // 将外部传入的函数的执行放在 setTimeout 中
  43. setTimeout(() => {
  44. fn.apply(this, arguments);
  45. // 最后在 setTimeout 执行完毕后再把标记设置为 true(关键)
  46. //表示可以执行下一次循环了。当定时器没有执行的时候
  47. //标记永远是 false,在开头被 return 掉
  48. canRun = true;
  49. }, 500);
  50. };
  51. }

5.如何实现函数柯里化?

在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。———— from 维基百科

  1. <!--方法一-->
  2. const curry = ( fn, arr = []) => {
  3. return (...args) => {
  4. //判断参数总数是否和fn参数个数相等
  5. if([...arr, ...args].length === fn.length){
  6. return fn(...arr, ...args) //拓展参数,调用fn
  7. }else{
  8. return curry(fn,[...arr, ...args]) //迭代,传入现有的所有参数
  9. }
  10. }
  11. }
  12. <!--方法二-->
  13. const curry = ( fn, arr = []) => (...args) => ( a => a.length === fn.length? fn(...a) : curry(fn, a))([...arr, ...args])

6.[“1”,”2”,”3”,”4”,”5”,6,7,8,9,10,11,12,13,14,15].map(parselnt);

  1. [1, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, 9, 11, 13, 15, 17, 19]
  2. <!--parseInt(数字, 该数字为几进制的数)-->