zoukankan      html  css  js  c++  java
  • 前端关于这些问题你都会了吗?(二)

    第 1 题:(携程)算法手写题

    已知如下数组:

    var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];

    编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

        var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
        //第一种实现方法
        //1.数组扁平化
        //2.数组去重
        //3.数组 排序
        function BPH(arr){
            let temp = [];
            let temp2 = [];
            for(let i=0;i<arr.length;i++){
                if(Array.isArray(arr[i])){
                    temp = [...arr[i]];
                    temp2 = temp2.concat(temp)
                }else{
                    temp2 = temp2.concat(arr[i])
                }
            }
            if(temp.length == 0){
                return  arr;
            }else{
                return BPH(temp2)
            }
        }
        let aa = BPH(arr);
        let cc = [...new Set(aa)];
        function arrSort(a,b){
            if(a<b){
                return -1;
            }else{
                return 1
            }
        }
        let dd = cc.sort(arrSort);
     //第二种实现方法
      [...new Set(arr.flat(Infinity))].sort((a,b)=>{return a-b;})
    讲解一点关于flat方法
    Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维数组。该方法返回一个新数组,对原数据没有影响。flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。
    如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。

    第 2 题:(滴滴、挖财、微医、海康)JS 异步解决方案的发展历程以及优缺点。

    1. 回调函数(callback)
    setTimeout(() => {
        // callback 函数体
    }, 1000)
    缺点:回调地狱,不能用 try catch 捕获错误,不能 return
    
    回调地狱的根本问题在于:
    
    缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符
    嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转)
    嵌套函数过多的多话,很难处理错误
    ajax('XXX1', () => {
        // callback 函数体
        ajax('XXX2', () => {
            // callback 函数体
            ajax('XXX3', () => {
                // callback 函数体
            })
        })
    })
    优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)
    
    2. Promise
    Promise就是为了解决callback的问题而产生的。
    
    Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装
    
    优点:解决了回调地狱的问题
    
    ajax('XXX1')
      .then(res => {
          // 操作逻辑
          return ajax('XXX2')
      }).then(res => {
          // 操作逻辑
          return ajax('XXX3')
      }).then(res => {
          // 操作逻辑
      })
    缺点:无法取消 Promise ,错误需要通过回调函数来捕获
    
    3. Generator
    特点:可以控制函数的执行,可以配合 co 函数库使用
    
    function *fetch() {
        yield ajax('XXX1', () => {})
        yield ajax('XXX2', () => {})
        yield ajax('XXX3', () => {})
    }
    let it = fetch()
    let result1 = it.next()
    let result2 = it.next()
    let result3 = it.next()
    4. Async/await
    async、await 是异步的终极解决方案
    
    优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
    
    缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
    
    async function test() {
      // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
      // 如果有依赖性的话,其实就是解决回调地狱的例子了
      await fetch('XXX1')
      await fetch('XXX2')
      await fetch('XXX3')
    }
    下面来看一个使用 await 的例子:
    
    let a = 0
    let b = async () => {
      a = a + await 10
      console.log('2', a) // -> '2' 10
    }
    b()
    a++
    console.log('1', a) // -> '1' 1
    对于以上代码你可能会有疑惑,让我来解释下原因
    
    首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
    因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码
    同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10
    上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖

    第 3 题:(兑吧)情人节福利题,如何实现一个 new

     function P(name) {
                this.name =name;
            }
            function createNew(func){
                var newObj = Object.create(func.prototype);//创建一个新对象
                var key  = func.call(newObj);//this指向这个新对象,并且执行构造函数
                if(typeof(key) === "object"){//判断构造函数是否有返回一个对象,有则返回构造函数的返回值,
                                            //无则返回新对象
                    return key;
                }else{
                    return newObj
                }
            }
            var o6=createNew(P);

    第 4 题:(网易)简单讲解一下http2的多路复用

    在 HTTP/1 中,每次请求都会建立一次HTTP连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,即使开启了 Keep-Alive ,解决了多次连接的问题,但是依然有两个效率上的问题:
    
    第一个:串行的文件传输。当请求a文件时,b文件只能等待,等待a连接到服务器、服务器处理文件、服务器返回文件,这三个步骤。我们假设这三步用时都是1秒,那么a文件用时为3秒,b文件传输完成用时为6秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输)
    第二个:连接数过多。我们假设Apache设置了最大并发数为300,因为浏览器限制,浏览器发起的最大请求数为6,也就是服务器能承载的最高并发为50,当第51个人访问时,就需要等待前面某个请求处理完成。
    HTTP/2的多路复用就是为了解决上述的两个性能问题。
    在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。
    帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。
    多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。

    第 5 题:谈谈你对TCP三次握手和四次挥手的理解

    三次握手之所以是三次是保证client和server均让对方知道自己的接收和发送能力没问题而保证的最小次数。
    
    第一次client => server 只能server判断出client具备发送能力
    第二次 server => client client就可以判断出server具备发送和接受能力。此时client还需让server知道自己接收能力没问题于是就有了第三次
    第三次 client => server 双方均保证了自己的接收和发送能力没有问题
    
    其中,为了保证后续的握手是为了应答上一个握手,每次握手都会带一个标识 seq,后续的ACK都会对这个seq进行加一来进行确认。

    第 6 题:两个数组合并成一个数组

    //请把两个数组 ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'] 和 ['A', 'B', 'C', 'D'],
        //合并为 ['A1', 'A2', 'A', 'B1', 'B2', 'B', 'C1', 'C2', 'C', 'D1', 'D2', 'D']
        let arrOne = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'];
        let arrTwo = ['A', 'B', 'C', 'D'];
        let aa = arrTwo.map(item=>{
            return item+"3"
        })
        let cc = [...arrOne,...aa].sort().map(item => {
            if(item.indexOf("3") !== -1){
                item = item.split("")[0]
            }
            return item;
        })
        console.log(cc)

    第 7 题:Virtual DOM 真的比操作原生 DOM 快吗?谈谈你的想法。

    在比较性能的时候,要分清楚初始渲染、小量数据更新、大量数据更新这些不同的场合。Virtual DOM、脏检查 MVVM、数据收集 MVVM 在不同场合各有不同的表现和不同的优化需求。Virtual DOM 为了提升小量数据更新时的性能,也需要针对性的优化,比如 shouldComponentUpdate 或是 immutable data。
    
    初始渲染:Virtual DOM > 脏检查 >= 依赖收集
    小量数据更新:依赖收集 >> Virtual DOM + 优化 > 脏检查(无法优化) > Virtual DOM 无优化
    大量数据更新:脏检查 + 优化 >= 依赖收集 + 优化 > Virtual DOM(无法/无需优化)>> MVVM 无优化
    不要天真地以为 Virtual DOM 就是快,diff 不是免费的,batching 么 MVVM 也能做,而且最终 patch 的时候还不是要用原生 API。在我看来 Virtual DOM 真正的价值从来都不是性能,而是它 1) 为函数式的 UI 编程方式打开了大门;2) 可以渲染到 DOM 以外的 backend,比如 ReactNative。
    以上这些比较,更多的是对于框架开发研究者提供一些参考。主流的框架 + 合理的优化,足以应对绝大部分应用的性能需求。如果是对性能有极致需求的特殊情况,其实应该牺牲一些可维护性采取手动优化:比如 Atom 编辑器在文件渲染的实现上放弃了 React 而采用了自己实现的 tile-based rendering;又比如在移动端需要 DOM-pooling 的虚拟滚动,不需要考虑顺序变化,可以绕过框架的内置实现自己搞一个。
    附上尤大的回答链接
    链接:https://www.zhihu.com/question/31809713/answer/53544875

    第 8题:下面的代码打印什么内容,为什么?

    ƒ b() {
    b = 20;
    console.log(b)
    }
    原因:
    作用域:执行上下文中包含作用于链:
    在理解作用域链之前,先介绍一下作用域,作用域可以理解为执行上下文中申明的变量和作用的范围;包括块级作用域/函数作用域;
    特性:声明提前:一个声明在函数体内都是可见的,函数声明优先于变量声明;
    在非匿名自执行函数中,函数变量为只读状态无法修改;

    第 9 题:为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作?

    中文翻译可能有些偏差(不是我翻的)。区分 actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。
    
    事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行。异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux 里面就好像 reducer 必须同步返回下一个状态一样)。
    
    同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。其实我有个点子一直没时间做,那就是把记录下来的 mutations 做成类似 rx-marble 那样的时间线图,对于理解应用的异步状态变化很有帮助。
    
    #照搬过来,给更多人看到
    
    作者:尤雨溪
    链接:https://www.zhihu.com/question/48759748/answer/112823337
    来源:知乎

    第 10题:(京东)下面代码中 a 在什么情况下会打印 1?

    答案解析 因为==会进行隐式类型转换 所以我们重写toString方法就可以了
    
    var a = {
      i: 1,
      toString() {
        return a.i++;
      }
    }
    
    if( a == 1 && a == 2 && a == 3 ) {
      console.log(1);
    }
  • 相关阅读:
    5.3 员工管理系统之登录和过滤器
    5.2 员工管理系统之页面国际化
    5.1 员工管理系统之导入静态资源
    5.0 Thymeleaf表达式使用
    1.初识Hadoop
    左耳朵耗子谈直面焦虑和成长
    10.高性能JavaScript
    9.高可维护性的JavaScript
    springboot整合jsp踩坑
    springboot 上传图片与回显
  • 原文地址:https://www.cnblogs.com/advanceCabbage/p/12494571.html
Copyright © 2011-2022 走看看