前端|每天5道面试题(4)

3,210 阅读4分钟

每天背5道,务必背熟记住,希望能对找工作的小前端有点帮助

1、为什么 JavaScript 是单线程

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

2、浅拷贝、深拷贝

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。基本类型不存在这个问题。

简单来说,有两个对象 A 和 B,B = A,当你修改 A的属性或者方法 时,B 的值也跟着发生了变化,这时候就叫浅拷贝。如果不发生变化,就叫深拷贝。在引用数据类型中,会产生浅拷贝的问题。

如何实现深拷贝?

(1)使用递归的方式实现深拷贝

(2)用**JSON.parse( JSON.stringify(obj) )**来完成深拷贝,但是该方法不能解决属性为函数,undefined,循环引用的的情况

(3)用Object.assign()来完成深拷贝,newObj = Object.assign({}, obj),这种方法对于一层对象来说是没有问题的,但是如果对象的属性对应的还是对象或者数组时,就是浅拷贝。

var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); // wade 原对象也改变了,是浅拷贝

一层的情况

let obj = {
   username: 'kobe'
};
let obj2 = Object.assign({},obj);
obj2.username = 'wade';
console.log(obj);//{username: "kobe"} 原对象没有被改变,是深拷贝

3、数组去重

方法1:定义一个新数组,并存放原数组的第一个元素,然后将元素组一一和新数组的元素对比,若不同则存放在新数组中

方法2:先将原数组排序,在与相邻的进行比较,如果不同则存入新数组。

方法3:利用对象属性存在的特性,如果没有该属性则存入新数组。

Array.prototype.unique = function () {
    var arr = this, obj = {}, result = [];
    for (var i = 0; i < arr.length; i++) {
        if (!obj[arr[i]]) { //如果能查找到,证明数组元素重复了
            obj[arr[i]] = 1;
            result.push(arr[i]);
        }
    }
    return result;
};
var a = [1, 2, 3, 1, 2, 3];
var b = a.unique();
console.log(b); //打印结果:(3) [1, 2, 3]

方法4(最常用):使用es6 set,Set数据结构,它类似于数组,其成员的值都是唯一的

let arr= [1, 2, 3, 3, 5, 7, 2, 6, 8];
console.log([...new Set(arr)]);

方法5:使用filter过滤函数去重。

var arr = [1, 2, 3, 1, 2, 3];
console.log(arr.filter((v, i, arr) => arr.indexOf(v) === i))//打印结果:(3) [1, 2, 3]

4、防抖、节流

防抖:触发高频函数事件后,n秒内函数只能执行一次,如果在n秒内这个事件再次被触发的话,那么会重新计算时间。通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。
节流:所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可

//防抖
function debounce(func, wait){
  let timeout;
  return function(){
    if(timeout){
      clearTimeout(timeout)
    }
    timeout = setTimeout(() => {
      func.apply(this, arguments)
    }, wait)
  }}
//节流
function throttle(func, wait){
  let timeout;
  return function(){
    if(!timeout){
      timeout = setTimeout(() => {
        timeout = null;
        func.apply(this, arguments)
      }, wait)
    }
  }}

5、数组操作

  • map【常用】: 遍历数组,返回回调返回值组成的新数组

  • forEach【常用】: 无法break,可以用try/catchthrow new Error来停止

  • filter【常用】: 过滤

  • some: 有一项返回true,则整体为true

  • every: 有一项返回false,则整体为false

  • join【常用】: 通过指定连接符生成字符串

  • push / pop: 末尾推入和弹出,改变原数组, push 返回数组长度, pop 返回原数组最后一项;

  • unshift / shift: 头部推入和弹出,改变原数组,unshift 返回数组长度,shift 返回原数组第一项 ;

  • sort(fn) / reverse【常用】: 排序与反转,改变原数组

  • concat【常用】: 连接数组,不影响原数组, 浅拷贝

  • slice(start, end): 返回截断后的新数组,不改变原数组

  • splice(start, number, value...)【常用】: 返回删除元素组成的数组,value 为插入项,改变原数组

  • indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标

  • reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值

    • 当传入 defaultPrev 时,从第一项开始;

    • 当未传入时,则为第二项

本着能为找工作中的前端帮一点小忙的初衷,借鉴了掘金里面很多大佬的文章,如有侵权请告知