zoukankan      html  css  js  c++  java
  • 面试十题(1)

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

    • 1)更准确
    • 2)更快
    • key是为了给vnode一个唯一的id, 可以依靠key能够更快更准确的拿到old vnode对应的vnode的节点
    • map映射,没有的话就是遍历。map映射比遍历快
    • diff算法来对比新旧虚拟节点

    2、 ['1', '2', '3'].map(parseInt) 结果和原因?

    • map 参数 两个。

    • 一个是执行函数,另一个可选对象

    • 【执行函数】有三个参数,当前元素(必须)、当前元素索引index、当前数组arr

    • 【可选对象】传递给函数,用作 "this" 的值。省略了 thisValue,或者传入 null、undefined,那么回调函数的 this 为全局对象。

    • parseInt 有两个参数,一个需要被解析的字符串(必须),另一个要解析的进制位数(可选,默认10进制,该值介于 2 ~ 36 之间。)

    • 所以题目解析为

    parseInt('1',0); // 1。 parseInt() 会根据十进制来解析,所以结果为 1parseInt('2',1); // NaN超出区间范围,所以结果为 NaNparseInt('3',2); // NaN用2进制来解析,应以 0 和 1 开头,所以结果为 NaN
    • 小知识点:parseInt方法解析的运算过程
    parseInt('101.55',10); //以十进制解析,运算过程:向上取整数(不做四舍五入,省略小数),结果为 101parseInt('101',2); //以二进制解析,运算过程:1*2的2次方+0*2的1次方+1*2的0次方=4+0+1=5,结果为 5parseInt('101',8); //以八进制解析,运算过程:1*8的2次方+0*8的1次方+1*8的0次方=64+0+1=65,结果为 65parseInt('101',16); //以十六进制解析,运算过程:1*16的2次方+0*16的1次方+1*16的0次方=256+0+1=257,结果为 257

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

    • 防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
    • 节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

    【区别】防抖动和节流本质是不一样的。防抖是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。

    • 防抖
    防抖主要利用定时器实现
    // 防抖
    function debounce(fn, wait) {
        var timeout = null;
        return function() {
            clearTimeout(timeout)
            timeout = setTimeout(() => fn.apply(this, arguments), wait)
        }
    }
    
    // 处理函数
    function handle() {
        console.log('防抖')
    }
    
    // 滚动事件
    window.addEventListener('scroll', debounce(handle, 1000))
    
    • 节流
    1. 时间戳实现
    // 节流
    function throttle(fn, delay) {
        var prev = +Date.now()
        return function() {
            var now = +Date.now()
            if (now - prev >= delay) {
                fn.apply(this, arguments)
                prev = now
            }
        }
    }
    
    // 处理函数
    function handle() {
        console.log('节流')
    }
    
    // 滚动事件
    window.addEventListener('scroll', throttle(handle, 1000))
    
    ---------------------------------------------------
    2. 定时器实现
    // 节流
    function throttle(fn, delay) {
        var timer = null
        return function() {
        var context = this
        var args = arguments
            if (!timer) {
                timer = setTimeout(function() {
                    fn.apply(context, args)
                    timer = null
                }, delay)
            }
        }
    }
    
    // 处理函数
    function handle() {
        console.log('节流2 定时器节流')
    }
    
    // 滚动事件
    window.addEventListener('scroll', throttle(handle, 2000))
    

    4、介绍下 Set、Map、WeakSet 和 WeakMap 的区别?

    • 主要介绍Set和Map
    • 【区别】WeakSet和WeakMap 中的对象都是弱引用
    • WeakSet 的成员只能是【对象】,而不能是其他类型的值。
    • WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的
    1. Set类似于数组,但【成员都是唯一的】。方法主要有add()、delete()、has()、clear()。属性主要有size
    • 主要应用: ①数组去重
    // 方法1
    var a = [1,2,3,4,5,5]
    var b = Array.from(new Set(a))
    //方法2
    var a = [1,2,3,4,5,5]
    var b = [...new Set(a)]
    

    ②实现两个数组的并集、交集和差集(详见阮一峰es6)

    • 遍历操作:keys()、values()、entries()、forEach()
    1. Map和普通对象相比,可以用各种类型数据当做键名,不限于字符串。
    • 属性size 方法主要有 set()、get()、has()、delete()、clear()。 遍历和Set一样

    5、介绍下深度优先遍历和广度优先遍历,如何实现?

    • 主要应用: 深度拷贝
    • 深度优先DFS(Depth-First-Search)就是自上而下的遍历搜索,广度优先BFS(Breadth-First-Search)则是逐层遍历
    • 【区别】算法区别:①深度优先不需要记住所有的节点, 所以占用空间小,而广度优先需要先记录所有的节点占用空间大②深度优先有回溯的操作(没有路走了需要回头)所以相对而言时间会长一点
    • 深度优先采用的栈的结构形式, 即先进后出
    • 广度优先则采用的是队列的形式, 即先进先出

    代码实现

    const data = [
        {
            name:'a',
            children: [
                { name:'b', children: [{ name:'e', children: [{name: 'm'}] }] },
                { name:'c', children: [{ name:'f' }] },
                { name:'d', children: [{ name:'g' }] },
            ],
        },
        {
            name:'a2',
            children: [
                { name:'b2', children: [{ name:'e2', children: [{name: 'm2'}] }] },
                { name:'c2', children: [{ name:'f2' }] },
                { name:'d2', children: [{ name:'g2' }] },
            ],
        }
    ]
    // 深度优先遍历  使用递归
    function depthFirstSearch(data) {
        const result = [];
        data.forEach(item => {
            const map = data => {
                result.push(data.name);
                data.children && data.children.forEach(child => map(child));
            }
            map(item);
        })
        return result.join(',');
    }
    // 广度遍历, 创建一个执行队列,当队列为空的时候则结束
    function breadthFirstSearch(data) {
        let result = [];
        let queue = data;
        while (queue.length > 0) {
            [...queue].forEach(child => {
                queue.shift();
                result.push(child.name);
                child.children && (queue.push(...child.children));
            });
        }
        return result.join(',');
    }
    console.log(depthFirstSearch(data))
    console.log(breadthFirstSearch(data))
    

    6.请分别用深度优先思想和广度优先思想实现一个拷贝 函数?

    // 工具函数
    function getEmpty (origin) {
        if(Object.prototype.toString.call(origin) === '[object Object]'){
            return {};
        }
        if(Object.prototype.toString.call(origin) === '[object Array]'){
            return [];
        }
        return origin;
    };
    // 广度优先拷贝
    function deepCopyBFS(origin){
        let queue = []; //用队列 先进先出
        let map = new Map(); //用于记录出现过的对象,用于处理环状数据
        let target = getEmpty(origin);
        if(target !== origin){
            queue.push([origin,target]);
            map.set(origin,target);
        }
    
        while (queue.length) {
            let [ori,tar] = queue.shift(); //对象赋值,是指针引用
            for(let key in ori){
                //处理环状数据
                if(map.get(ori[key])){
                    tar[key] = map.get(tar[key]);
                    continue;
                }
        
                tar[key] = getEmpty(ori[key]);
                if(tar[key] !== ori[key]){
                     queue.push([ori[key],tar[key]]);
                     map.set(ori[key],tar[key]);
                }
            }
        }
        return target;
    }
    //深度优先拷贝
    function deepCopyDFS(origin){
        let stack = [];//用堆 先进后出
        let map = new Map(); //用于记录出现过的对象,用于处理环
        let target = getEmpty(origin);
        if(target !== origin){
            stack.push([origin,target]);
            map.set(origin,target);
        }
    
        while (stack.length) {
            let [ori,tar] = stack.pop(); //对象赋值,是指针引用
            for(let key in ori){
                //处理环状
                if(map.get(ori[key])){
                    tar[key] = map.get(tar[key]);
                    continue;
                }
    
                tar[key] = getEmpty(ori[key]);
                if(tar[key] !== ori[key]){
                    stack.push([ori[key],tar[key]]);
                    map.set(ori[key],tar[key]);
                }
            }
        }
        return target;
    }
    

    7、ES5/ES6 的继承除了写法以外还有什么区别?

    1. class 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 let、const 声明变量。
    const bar = new Bar(); // it's ok
    function Bar() {
      this.bar = 42;
    }
    
    const foo = new Foo(); // ReferenceError: Foo is not defined
    class Foo {
      constructor() {
        this.foo = 42;
      }
    }
    

    2.class 声明内部会启用严格模式。

    // 引用一个未声明的变量
    function Bar() {
      baz = 42; // it's ok
    }
    const bar = new Bar();
    
    class Foo {
      constructor() {
        fol = 42; // ReferenceError: fol is not defined
      }
    }
    const foo = new Foo();
    
    1. class 的所有方法(包括静态方法和实例方法)都是不可枚举的。
    // 引用一个未声明的变量
    function Bar() {
      this.bar = 42;
    }
    Bar.answer = function() {
      return 42;
    };
    Bar.prototype.print = function() {
      console.log(this.bar);
    };
    const barKeys = Object.keys(Bar); // ['answer']
    const barProtoKeys = Object.keys(Bar.prototype); // ['print']
    
    class Foo {
      constructor() {
        this.foo = 42;
      }
      static answer() {
        return 42;
      }
      print() {
        console.log(this.foo);
      }
    }
    const fooKeys = Object.keys(Foo); // []
    const fooProtoKeys = Object.keys(Foo.prototype); // []
    
    1. class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]],不能使用 new 来调用。
    function Bar() {
      this.bar = 42;
    }
    Bar.prototype.print = function() {
      console.log(this.bar);
    };
    
    const bar = new Bar();
    const barPrint = new bar.print(); // it's ok
    
    class Foo {
      constructor() {
        this.foo = 42;
      }
      print() {
        console.log(this.foo);
      }
    }
    const foo = new Foo();
    const fooPrint = new foo.print(); // TypeError: foo.print is not a constructor
    
    1. 必须使用 new 调用 class。
    function Bar() {
      this.bar = 42;
    }
    const bar = Bar(); // it's ok
    
    class Foo {
      constructor() {
        this.foo = 42;
      }
    }
    const foo = Foo(); // TypeError: Class constructor Foo cannot be invoked without 'new'
    
    1. class 内部无法重写类名。
    function Bar() {
      Bar = 'Baz'; // it's ok
      this.bar = 42;
    }
    const bar = new Bar();
    // Bar: 'Baz'
    // bar: Bar {bar: 42}  
    
    class Foo {
      constructor() {
        this.foo = 42;
        Foo = 'Fol'; // TypeError: Assignment to constant variable
      }
    }
    const foo = new Foo();
    Foo = 'Fol'; // it's ok
    
    1. ES5 和 ES6 子类 this 生成顺序不同。ES5 的继承先生成了子类实例,再调用父类的构造函数修饰子类实例,ES6 的继承先生成父类实例,再调用子类的构造函数修饰父类实例。这个差别使得 ES6 可以继承内置对象。
    function MyES5Array() {
      Array.call(this, arguments);
    }
    
    // it's useless
    const arrayES5 = new MyES5Array(3); // arrayES5: MyES5Array {}
    
    class MyES6Array extends Array {}
    
    // it's ok
    const arrayES6 = new MyES6Array(3); // arrayES6: MyES6Array(3) [empty*3]
    
    • ES5 的子类和父类一样,都是先创建好,再实现继承的,所以它们的指向都是 [Function] 。
    • ES6 则得到不一样的结果,它指向父类,那么我们应该能推算出来,它的子类是通过 super 来改造的。

    8、setTimeout、Promise、Async/Await 的区别

    • setTImeout

    注意setTimeout是异步执行函数 , 当js主线程运行到此函数时,不会等待setTimeout中的回调函数 ,会直接进行setTimeout下面的语句(尽管setTimeout的延迟时间为0时) 当执行完当前事件循环的时候,setTimeout中的回调会在下次事件循环中被执行

    console.log('setTimeout start');
     
    setTimeout(function(){
        console.log('setTimeout execute');
    })
     
    console.log('setTimeout end ');
    
    // setTimeout start => setTimeout end => setTimeout execute
    
    • Promise

    Promise 本身是同步的立即执行函数,当在执行体中执行resolve()或者reject()的时候,此时是异步操作,等主栈完成后,才会去执行resolve()/reject()中的方法

    console.log('script start');
    var promise1 = new Promise(function (resolve) {
        console.log('promise1');
        resolve();
        console.log('promise1 end');
    }).then(function () {
        console.log('promise2');
    })
    
    setTimeout(function () {
        console.log('setTimeout');
    })
    
    console.log('script end');
    
    // script start => promise1 => promise1 end =>script end =>promise2 => setTimeout
    
    • async/await

    async函数返回一个promise对象,当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成(await的函数),在执行函数体后面的语句,可以理解为,async让出了线程,跳出了async函数体,因此await函数后的语句相当于在then回调中执行.await的含义为等待,也就是async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。

    async function async1(){
        console.log('async1 start');
        await async2(); 
        //等待 async2()返回之后 再执行下面的语句 ,
        // 相当于将 console.log('async1 end')异步化了 相当于 console.log('async1 end')then之后执行了
        console.log('async1 end')
    }
    async function async2(){
        console.log('async2')
    }
    console.log('script start');
    async1();
    console.log('script end')
    
    // script start->async1 start->async2->script end->async1 end
    
    • 综合题
    async function async1() {
    	console.log('async1 start');
    	await async2();
    	console.log('asnyc1 end');
    }
    async function async2() {
    	console.log('async2');
    }
    console.log('script start');
    setTimeout(() => {
    	console.log('setTimeout');
    }, 0);
    async1();
    new Promise(function (reslove) {
    	console.log('promise1');
    	reslove();
    }).then(function () {
    	console.log('promise2');
    })
    console.log('script end');
    
    // 结果
    script start
    async1 start
    async2
    promise1
    script end
    asnyc1 end
    promise2
    setTimeout
    
    • js事件循环机制EventLoop: ①宏任务:包括整体代码script,setTimeout,setInterval ②微任务:Promise.then(非new Promise),process.nextTick(node中) ③事件的执行顺序:是先执行宏任务,然后执行微任务,这个是基础,任务可以有同步任务和异步任务,同步的进入主线程,异步的进入Event Table并注册函数,异步事件完成后,会将回调函数放入Event Queue中(宏任务和微任务是不同的Event Queue),同步任务执行完成后,会从Event Queue中读取事件放入主线程执行,回调函数中可能还会包含不同的任务,因此会循环执行上述操作。

    • setTimeout并不是直接的把你的回掉函数放进上述的异步队列中去,而是在定时器的时间到了之后,把回掉函数放到执行异步队列中去。如果此时这个队列已经有很多任务了,那就排在他们的后面。这也就解释了为什么setTimeout为什么不能精准的执行的问题了。setTimeout执行需要满足两个条件:

    1. 主进程必须是空闲的状态,如果到时间了,主进程不空闲也不会执行你的回掉函数
    2. 这个回掉函数需要等到插入异步队列时前面的异步函数都执行完了,才会执行
    • 执行顺序:宏任务 => 微任务的Event Queue => 宏任务的Event Queue

    9. async/await 如何通过同步的方式实现异步

    • async/await是参照 Generator 封装的一套异步处理方案,可以理解为 Generator的语法糖
    • async 输出的是一个 Promise 对象
    function requestA() {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve({ age: 20 });
            }, 1000 * 2);
        });
    }
    
    async function getData() {
        console.log('数据加载第一步');
        let result = await requestA();
        console.log('数据加载第二步');
    
        return result;
    }
    
    getData().then(res => {
        console.log('数据请求完毕', res);
    });
    
    // 数据加载第一步
    // 2秒后
    // 数据加载第二步
    // 数据请求完毕 { age: 20 }
    

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

    var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
    
    • 多维数组 变成 一维数组
    1. 数组flat方法
    let newArr = arr.flat(Infinity)
    
    1. 正则
    let strArr = JSON.stringify(arr);
    let newArr = strArr.replace(/([]))/g, '').split(',');
    
    1. 递归
    let newArr = [];
    let fn = function(arr) {
        for(let i = 0; i < arr.length; i++) }{
            let item = arr[i];
            if (Array.isArray(arr[i])){
                fn(item);
            } else {
                newArr.push(item);
            }
        }
    }
    
    1. reduce
    function faltten(arr) {
        return arr.reduce((accumulator, current) => {
            return accumulator.concat(
                Array.isArray(current) ? 
                faltten(current) : 
                current
                );
        }, []);
    }
    let newArr = flatten(arr)
    console.log(flatten(arr))
    
    1. 扩展运算符
    while(arr.some(Array.isArray)){
        arr=[].concat(...arr)
    }
    console.log(arr);
    
    • 去重
    [...new Set(newArr)]
    Array.from(new Set(newArr))
    
    • 排序
    .sort((a, b) => a - b)
  • 相关阅读:
    Hibernate核心接口和类
    Struts 2基础知识
    博客开篇
    程序员的幽默笑话(深意爆笑)
    追MM的各种算法
    全局css(慕课)
    全局css
    目录的分层
    class 设置多个css属性
    Python爬虫学习:四、headers和data的获取
  • 原文地址:https://www.cnblogs.com/jialuchun/p/15115113.html
Copyright © 2011-2022 走看看