阅读 348

经典面试题讲解(持续更新中)

1. 写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

知识点:key和就地复合

  1. 更准确

因为带key就不是就地复用了,在sameNode函数 a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。 2. 更快 利用key的唯一性生成map对象来获取对应节点,比遍历方式更快。(这个观点,就是我最初的那个观点。从这个角度看,map会比遍历更快。

2. ['1', '2', '3'].map(parseInt) what & why ?

知识点:map和parseInt

结果:[1,NaN,NaN]
复制代码
  • parseInt()

  • 作用:将一个字符串 string 转换为 radix 进制的整数,radix 为介于2-36之间的数

  • 个人记忆:把二进制到36进制的数转换为十进制

  • 参数: string:要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。, radix(可选):从 2 到 36,代表该进位系统的数字。例如说指定 10 就等于指定十进位(如果不写默认为十进制;为0时,且string参数不以“0x”和“0”开头时,按照10为基数处理

  • 例子

parseInt('111',2) // 7
111是一个2进制的数字转换成10进制为7
复制代码
['1', '2', '3'].map(parseInt) ===>
['1', '2', '3'].map((item, index) => parseInt(item,index)) ===>
parseInt('1', 0) => 1 parseInt 的第二个参数是0,按照10为基础处理结果是1
parseInt('2', 1) => NaN 第二个参数为1,首先不可以为1,就算为1,sting为‘2’也不对,所以NaN
parseInt('3', 2) => NaN 同上2进制最大为2
复制代码

再来几题


['10','10','10','10','10'].map(parseInt)
结果:[10,NaN,2,3,4]
复制代码

3、函数的防抖与节流(juejin.cn/post/684490…)

  • 防抖

    1. 仅仅执行最后一次点击,如果持续点击10s,仅仅执行最后一次(节流会执行10次)
    2. 应用场景:一般可以使用在用户输入停止一段时间过后再去获取数据,而不是每次输入都去获取
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>防抖</title>
</head>
<body>
  <button id="debounce">点我防抖!</button>

  <script>
    window.onload = function() {
      // 1、获取这个按钮,并绑定事件
      var myDebounce = document.getElementById("debounce");
      myDebounce.addEventListener("click", debounce(sayDebounce));
    }

    // 2、防抖功能函数,接受传参
    function debounce(fn, delay = 200) {
      // 4、创建一个标记用来存放定时器的返回值
      let timeout = null;
      return function() {
        // 5、每次当用户点击/输入的时候,把前一个定时器清除
        clearTimeout(timeout);
        // 6、然后创建一个新的 setTimeout,
        // 这样就能保证点击按钮后的 interval 间隔内
        // 如果用户还点击了的话,就不会执行 fn 函数
        timeout = setTimeout(() => {
          fn.call(this, arguments);
        }, delay);
      };
    }

    // 3、需要进行防抖的事件处理
    function sayDebounce() {
      // ... 有些需要防抖的工作,在这里执行
      console.log("防抖成功!");
    }

  </script>
</body>
</html>

复制代码

防抖:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。

结合上面的代码,我们可以了解到,在触发点击事件后,如果用户再次点击了,我们会清空之前的定时器,重新生成一个定时器。意思就是:这件事儿需要等待,如果你反复催促,我就重新计时!

  • 节流
    1. 时间间隔内仅仅执行第一次,持续点击10s会执行10次(防抖只执行1次)
    2. 比如懒加载时要监听计算滚动条的位置,但不必每次滑动都触发,可以降低计算的频率,而不必去浪费资源;另外还有做商品预览图的放大镜效果时,不必每次鼠标移动都计算位置。
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>节流</title>
</head>
<body>

  <button id="throttle">点我节流!</button>

  <script>
    window.onload = function() {
      // 1、获取按钮,绑定点击事件
      var myThrottle = document.getElementById("throttle");
      myThrottle.addEventListener("click", throttle(sayThrottle));
    }

    // 2、节流函数体
    function throttle(fn, delay = 200) {
      // 4、通过闭包保存一个标记
      let canRun = true;
      return function() {
        // 5、在函数开头判断标志是否为 true,不为 true 则中断函数
        if(!canRun) {
          return;
        }
        // 6、将 canRun 设置为 false,防止执行之前再被执行
        canRun = false;
        // 7、定时器
        setTimeout( () => {
          fn.call(this, arguments);
          // 8、执行完事件(比如调用完接口)之后,重新将这个标志设置为 true
          canRun = true;
        }, delay);
      };
    }

    // 3、需要节流的事件
    function sayThrottle() {
      console.log("节流成功!");
    }

  </script>
</body>
</html>

复制代码

4. settimeout在async 之后执行

直接拿题练手

先做等会看答案
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');
复制代码
答案:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
复制代码

再来一题

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    //async2做出如下更改:
    new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
    });
}
console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();

new Promise(function(resolve) {
    console.log('promise3');
    resolve();
}).then(function() {
    console.log('promise4');
});

console.log('script end');
复制代码
答案:
script start
async1 start
promise1
promise3
script end
promise2
async1 end
promise4
setTimeout
复制代码

5. 下面数组打印结果是什么

for(var i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i)
  })
}
复制代码
10个10
如何才能打印1-9
1、
for(let i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i)
  })
}
2、
for(var i = 0; i < 10; i++) {
  (function(i) {
    setTimeout(() => {
      console.log(i)
    }, 0)
  })(i)
}
你还有多少方法?
复制代码

6. 将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]
复制代码
[...new Set(arr.flat(Infinity))].sort((a,b) => a-b)
复制代码

知识点:sort和Set、Map flat直接去mdn查就好,讲的很专业

  1. sort
最开始我得出的答案是[...new Set(arr.flat(Infinity))].sort()发现答案不对
数字排序:sort((a, b) => a - b)升序    sort((a, b) => b - a)降序
字符串排序:如果sort()不写默认按照字符串升序排列所以11,会排在2前面
sort((a, b) => {
    if (a > b) return 1
    if (a < b) return -1
    return 0
}) // 升序
sort((a, b) => {
    if (a > b) return -1
    if (a < b) return 1
    return 0
}) // 降序
复制代码
  1. Set、Map
  2. 持续更新中……

7. 闭包

定义:两个函数,嵌套关系,内部函数还访问了外部函数的变量,形成了一个闭包
作用:私有化数据(全局访问不到,全局不可以修改),保护数据安全,持久化维持数据
闭包与斐波那契的应用
闭包的弊端:
内存泄漏
内存管理
技术,被指像的次数,指向这块内存的数量为0的时候释放
标记清除算法,从widnow开始找(好,函数里面开辟的空间是找不到的),没有的话就清除,函数里面的,函数调用结束就可以清除,
ret = null;清除闭包(闭包用完不用就释放掉)
复制代码

8. computed和watch的区别

computed:计算属性

计算属性是由data中的已知值,得到的一个新值。
这个新值只会根据已知值的变化而变化,其他不相关的数据的变化不会影响该新值。
计算属性不在data中,计算属性新值的相关已知值在data中。
别人变化影响我自己。
watch:监听数据的变化

监听data中数据的变化
监听的数据就是data中的已知值
我的变化影响别人

1.watch擅长处理的场景:一个数据影响多个数据

2.computed擅长处理的场景:一个数据受多个数据影响

复制代码

9. reduce(感谢 www.jianshu.com/p/e375ba1cf…

计算数组中每个元素出现的次数

let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];

let nameNum = names.reduce((pre,cur)=>{
  if(cur in pre){
    pre[cur]++
  }else{
    pre[cur] = 1 
  }
  return pre
},{})
console.log(nameNum); //{Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
复制代码

数组去重

let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre,cur)=>{
    if(!pre.includes(cur)){
      return pre.concat(cur)
    }else{
      return pre
    }
},[])
console.log(newArr);// [1, 2, 3, 4]
复制代码

将二维数组转化为一维

let arr = [[0, 1], [2, 3], [4, 5]]
let newArr = arr.reduce((pre,cur)=>{
    return pre.concat(cur)
},[])
console.log(newArr); // [0, 1, 2, 3, 4, 5]
复制代码

将多维数组转化为一维

let arr = [[0, 1], [2, 3], [4,[5,6,7]]]
const newArr = function(arr){
   return arr.reduce((pre,cur)=>pre.concat(Array.isArray(cur)?newArr(cur):cur),[])
}
console.log(newArr(arr)); //[0, 1, 2, 3, 4, 5, 6, 7]
复制代码

对象里的属性求和

var result = [
    {
        subject: 'math',
        score: 10
    },
    {
        subject: 'chinese',
        score: 20
    },
    {
        subject: 'english',
        score: 30
    }
];

var sum = result.reduce(function(prev, cur) {
    return cur.score + prev;
}, 0);
console.log(sum) //60
复制代码

10、e.target与e.currentTarget的区别

  • target指向被单击的对象
  • currentTarget指向当前事件活动的对象

先看个例子

<body>
    <div id="a">
        <div id="b">
            <div id="c">
                <div id="d"></div>
            </div>
        </div>
    </div>
    
    <script>
        document.getElementById('a').addEventListener('click', function ( e ) {
            console.log('target:' + e.target.id + '&currentTarget:' + e.currentTarget.id);
        });
        document.getElementById('b').addEventListener('click', function ( e ) {
            console.log('target:' + e.target.id + '&currentTarget:' + e.currentTarget.id);
        });
        document.getElementById('c').addEventListener('click', function ( e ) {
            console.log('target:' + e.target.id + '&currentTarget:' + e.currentTarget.id);
        });
        document.getElementById('d').addEventListener('click', function ( e ) {
            console.log('target:' + e.target.id + '&currentTarget:' + e.currentTarget.id);
        });
    </script>
</body>

复制代码

不必记什么时候e.currentTarget和e.target相等,什么时候不等,理解两者的究竟指向的是谁即可。

  • e.target 指向触发事件监听的对象。
  • e.currentTarget 指向添加监听事件的对象

给上面代码a注册事件,currentTarget始终指向了a,点击d的时候,target指向了d,current仍然指向a

参考链接1 参考链接2

11. let arr = ['aecb', 'dcbc', 'fcbg']返回最长的重复字符串cb

let arr = ['aecb', 'dcbc', 'fcbg']
function findStr(arr) {
  let str = ''
  for(let i = 0; i < arr[0].length; i++) {
    str += arr[0][i]
    let isTrue = arr.every(v => v.indexOf(str) > 0)
    str = isTrue ? str : str.slice(0, str.length - 2)
  }
  return str
}
findStr(arr)
复制代码

12 数字字符串补零

addZero: function (num) {
    // return num + parseInt(num) < 10 ? '0' : ''
    if (parseInt(num) < 10) {
      num = '0' + num;
    }
    return num;
  },
复制代码

13. 用最精炼的代码实现数组非零非负最小值index

例如:[10,21,0,-7,35,7,9,23,18] 输出5, 7最小

(() => {
    function getIndex(arr) {
      let index;
      let n = Infinity;
      for (let i = 0; i < arr.length; i++) {
        const item = arr[i];
        if (item > 0 && item < n) {
          n = item;
          index = i;
        }
      }

      return index;
    }
    let arr = [-1, 21, 0, -7, 35, 7, 9, 23, 18];
    console.log(getIndex(arr));
})();
复制代码

14. 求目标值是由数组元素的哪两个值之和的值、下标(算法)

const arr = [3, 4, 6, 39, 6, 42, 76]
const tag = 81
// 求值:
// 思路:
// 1、遍历数组arr
// 2、给arr的每一项都做一个标注为 true
// 3、如果 target - 数组的这一项 以前存在过也就是为true
// 那就得出这两个值
普通方法:
function getTotal(arr, tag) {
  return arr.filter(item => arr.includes(tag - item))
}

优化方法:
function getTotal(array, target) {
  var a = {}
  for (var i = 0; i < array.length; i++) {
    var temp = target - array[i]
    if (a[temp] != undefined) return [temp, array[i]]
    a[array[i]] = true
  }
}

// 求下标
// 求目标值是由数组元素的哪两个值之和的下标(算法)
普通方法:
function getIndex(arr, tag) {
  return arr.reduce((acc, cur, index) => {
    if (arr.includes(tag - cur)) {
      acc.push(index)
    }
    return acc
  }, [])
}
优化方法:
// 只要把原来优化方法里的的true改为i即可
function getTotal(array, target) {
  var a = {}
  for (var i = 0; i < array.length; i++) {
    var temp = target - array[i]
    if (a[temp] != undefined) return [a[temp], i]
    a[array[i]] = i
  }
}
复制代码

15. 手写一个Promise.all

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('我是p1')
  }, 2000)
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('我是p2')
  }, 1000)
})
复制代码
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))
    }
  })
}
Promise.myAll([p1, p2]).then((res) => {
  console.log(res)
})
复制代码

16. 手写一个Promise.race

Promise.myRace = function (promiseArr) {
  return new Promise((resolve, reject) => {
    promiseArr.forEach((p) => {
      // 如果不是Promise实例需要转化为Promise实例
      Promise.resolve(p).then(
        (val) => resolve(val),
        (err) => reject(err)
      )
    })
  })
}
Promise.myRace([p1, p2]).then((res) => {
  console.log(res)
})
复制代码
文章分类
前端
文章标签