美文网首页Web前端之路JavaScript 进阶营
前端常见面试题(七)@郝晨光

前端常见面试题(七)@郝晨光

作者: 郝晨光 | 来源:发表于2019-07-10 23:16 被阅读199次

简述同步和异步的区别

众所周知,javascript是单线程的语言,所谓的单线程,就是从上至下,依次执行,当然这里的依次执行要抛开javascript的预解析机制。
这样做的原因是因为javascript最初是为了操作DOM,运行在浏览器环境下的,而操作DOM的时候,不能是异步的,不然的话两个异步任务同时修改DOM结构的话,会导致浏览器不知道该执行哪一个。
但是这样做也有缺点,当遇到一个响应时间特别长的任务时,容易导致页面加载错误或者浏览器未响应的情况。

同步就是所有的任务都处在同一队列中,不可以插队,一个任务执行完接着开始执行下一个,相对于浏览器而言,同步的效率过低,一些耗费时间比较长的任务应该用异步来执行。

异步就是将一个任务放入到异步队列中,当这个任务执行完成之后,再从异步队列中提取出来,插队到同步队列中,拿到异步任务的结果,可以提升代码执行的效率,不需要因为一个耗费时长的代码而一直等待。

javascript异步的几种实现方式
  1. 回调函数 callback
    最简单的回调函数的实现方式,利用setTimeout会进入计时器队列,而不是正常队列的特性,来实现回调函数
    计时器队列与正常队列同时运行,不影响正常队列的运行,在正常队列空闲的时候,并且计时器队列的任务倒计时已经结束,就将计时器队列的任务提取到正常队列中获得最终结果。

    • function syncMethod(callback) {
        setTimeout(function() {
            callback(count);
            console.log('第一次调用回调函数!');
            setTimeout(function() {
                callback(count);
                console.log('第二次调用回调函数!');
            })
            for(let i=0;i<100;i++) {
                count++;
            }
        }, 0);
        let count = 1;
        for(let i=0;i<100;i++) {
            count++;
        }
      }
      syncMethod(function(count) {
          console.log(count); // 调用两次,第一次返回101,第二次返回201
      })
      
      callback示例返回值
  2. 事件驱动

    • javascript是基于事件驱动的语言,当我们给元素绑定事件的时候,并不会立即执行,而是当触发指定的事件时,才会执行对应的方法
    •  const btn = document.querySelector('button');
       btn.addEventlistener('click', function() {
           console.log('clickBtn')
       })
      
  3. 发布者订阅者模式(观察者模式)

    • 发布者订阅者模式是通过保存事件,然后在需要使用的时候直接发布事件,就可以触发保存的回调
    •   function Watcher() {
            this.events = {}; // 定义事件对象,用来保存需要触发的事件
        };
        // $on方法,用来监听事件,订阅者
        Watcher.prototype.$on = function(event, handler) {
            this.events[event] = this.events[event] || []; 
            // 如果当前事件已经被监听,则在原基础上进行添加新的事件,如果没有的话新建一个数组,用来存储事件
            this.events[event].push({
                handler: handler
            })
            // 将事件存储到对应的事件模型中
        };
        // $emit方法,用来发布事件,发布者
        Watcher.prototype.$emit = function(event,...arg) {
           // 如果没有订阅当前事件的话,直接返回
            if(!this.events[event]) {
                return
            }
            // 循环遍历对应事件,将对应的事件根据填入的先后顺序触发
            for(let i = 0; i< this.events[event].length; i++) {
                this.events[event][i].handler(...arg)
            }
        };
        // 关闭监听事件
        Watcher.prototype.$close = function(event) {
            // 如果没有订阅当前事件的话,直接返回
            if(!this.events[event]) {
                return
            }
            // 删除订阅事件
            delete this.events[event];
        };
        // 实例化一个观察者
        let watcher= new Watcher();
        // 监听事件,并传入事件需要触发的函数
        watcher.$on('handler1', function(name, age) {
            console.log(name, age, '我是第一个函数')
        });
        // 监听不同的事件
        watcher.$on('handler2', function(sex) {
            console.log(sex)
        });
        // 监听同一个事件,触发不同的函数
        watcher.$on('handler1', function(name, age) {
            console.log(name, age, '我是第二个函数')
        });
        // 发布事件,并传参
        watcher.$emit('handler2', "男");
        watcher.$emit('handler1', '张三',20);
        // 关闭事件
        watcher.$close('handler1');
        // 事件不会触发,因为已经关闭了对应事件的监听
        watcher.$emit('handler1', '张三',20);
      
      Image 3.jpg
  4. promise 异步编程的解决方案

    • Promise是ES6新增的异步编程的解决方案,它有三种状态分别是pending进行中、resolved已完成、rejected已失败。
    • Promise可以链式调用,避免层层嵌套,异步操作更加容易方便,提升代码可读性。
    • Promise一旦创建就不可以取消,一旦在resolvedrejected中确立一种状态,就不可以再发生改变
    •  function asynMethod(flag) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    if(flag) {
                        resolve('郝晨光精心写作,如有错误请指出,谢谢!');
                    }else {
                        reject('错误信息!')
                    }
                }, 3000)
            })
        }
        asynMethod(true).then(data => {
            console.log(data);
            return asynMethod(true)
        }).then(data => {
            console.log(data);
            return asynMethod(false)
        }).catch(err =>{
            console.log(err);
        })
      

数组去重(手写代码)

  1. 使用ES6的Set去重
    • Set是ES6新增的数据类型,Set 的成员具有唯一性
    • function distinct(arr) {
        return Array.from(new Set(arr));
      }      
      
  2. 使用ES6的Set去重(超级简化版)
    • [...new Set(arr)]   // [...new Set(需要去重的数组)]
      
  3. 使用splice配合两重for循环去重
    • function distinct(arr) {
        for(let i = 0; i < arr.length; i++) {
            for(let j = i + 1; j < arr.length; j++) {
                if(arr[i] === arr[j]) {
                    arr.splice(j, 1);
                    j--;
                }
            }
        }
        return arr;
      }
      
  4. 使用for循环配合indexOf去重
    •  function distinct(arr) {
           let newArr = [];
           for(let i = 0; i < arr.length; i++) {
               if(newArr.indexOf(arr[i]) === -1) {
                   newArr.push(arr[i]);
               }
           }
           return newArr;
      }
      
  5. 使用for循环配合sort排序去重
    • function distinct(arr) {
            arr = arr.sort();
            let newArr = [];
            for(let i = 0; i < arr.length; i++) {
                if(arr[i] !== arr[i-1]) {
                    newArr.push(arr[i]);
                }
            }
            return newArr;
        }
      
  6. 使用for循环配合includes去重
    •   function distinct(arr) {
            let newArr = [];
            for(let i = 0; i < arr.length; i++) {
                if(!newArr.includes(arr[i])) {
                    newArr.push(arr[i]);
                }
            }
            return newArr;
       }
      
  7. 使用filter配合indexOf去重
    • function distinct(arr) {
            return arr.filter((item,index, arr) => arr.indexOf(item) === index);
      }
      

在JavaScript中什么是伪数组?如何将伪数组转化为标准数组

javascript中的伪数组(类数组):不具有数组的push,pop等方法,但是具有length,以及可以利用for循环遍历等特性,例如函数的 arguments 参数集合,还有通过document.getElementsByTagName等方法获取的NodeList等都是类数组

如何将伪数组转化为标准数组

  1.   let nodeList = document.getElementsByTagName('li');
      let nodeArr = Array.from(nodeList);
    
  2.    let nodeList = document.getElementsByTagName('li');
       let nodeArr = Array.prototype.slice.call(nodeList);
    

当转化为标准数组以后,就可以调用数组实例上的方法了,如push、pop、splice、filter、map、forEach等等


SPA路由history模式,打包上线都遇到了哪些问题?你是如何解决的?

  1. 资源路径404问题
    • vue-cli3环境下,在根目录新建vue.config.js,在该文件中写入如下
    •  module.exports = {
           publicPath: './'
       }
      
  2. 页面刷新404问题

JavaScript中callee和caller的作用

  1. callee是函数arguments对象内的指针,它指向当前的函数,使得在函数内部递归调用当前函数时,不需要调用函数名称,减少函数内部对于函数名的依赖
    function test() {
        console.log(arguments.callee);
    }
    test();
    
  2. caller是函数的一个属性,它指向调用当前函数的函数,如果当前函数在其他函数内被调用,则返回调用它的那个函数,如果是在全局环境下被调用,则返回 null
    我们可以利用caller的特性跟踪函数的调用链
    function fn1() {
        console.log(fn1.caller);
        console.log(arguments.callee.caller);
    }
    function fn2() {
        fn1()
    }
    fn2();
    

结言
感谢您的查阅,代码冗余或者有错误的地方望不吝赐教;菜鸟一枚,请多关照

相关文章

网友评论

    本文标题:前端常见面试题(七)@郝晨光

    本文链接:https://www.haomeiwen.com/subject/cvfkkctx.html