• 【壹题1-10】


    第 1 题:(滴滴、饿了么)写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

    在react和vue中,数据因用户行为发生改变时,会生成新的虚拟DOM,然后会比较新旧的虚拟DOM,通过diff算法找到区别,key的作用就是通过该唯一的标识,快速定位锁定变化,避免不必要的遍历查找,浪费性能。

    但是在vue中对于key的效率又有了分支,因为有时候在没有的key的情况下,diff算法是速度甚至会更快。在没有绑定key值的情况下,而且在遍历一些简单的模板的情况下,由于会发生节点的复用(就地复用),diff算法速度更快,在vue中被称为默认模式。而key存在时,就不存在节点复用了,而是增删节点,相对于简单的模板而言是比较费时的。

    但是这种没有key的默认模式存在副作用,例如:在通过表单输入值时,会发生状态错位(示例链接:https://www.jianshu.com/p/4bd5e745ce95

    总结:

    key是给每一个vnode的唯一id,可以依靠key,更准确,更快的拿到oldVnode中对应的vnode节点。

    1. 更准确

    因为带key就不是就地复用了,在sameNode函数 a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。

    2. 更快

    利用key的唯一性生成map对象来获取对应节点,比遍历方式更快。(这个观点,就是我最初的那个观点。从这个角度看,map会比遍历更快。)

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

    其实际执行的代码是:

    ['1', '2', '3'].map((item, index) => {
    	return parseInt(item, index)
    })

    返回的值是:

    parseInt('1', 0) // 1
    parseInt('2', 1) // NaN
    parseInt('3', 2) // NaN, 3 不是二进制

    原因:

    parseInt() 函数解析一个字符串参数,并返回一个指定基数的整数 (数学系统的基础)。

    const intValue = parseInt(string[, radix]);

    string: 要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。

    radix: 一个介于2和36之间的整数(数学系统的基础),表示上述字符串的基数。默认为10。

    返回值:返回一个整数或NaN

    第 3 题:(挖财)什么是防抖和节流?有什么区别?如何实现?

    防抖:触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间;

    思路:每次触发事件时都取消之前的延时调用方法

    function debounce(fn) {
    let timeout = null; // 创建一个标记用来存放定时器的返回值
    return function () {
    clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
    timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
    fn.apply(this, arguments);
    }, 1000);
    };
    }
    function sayHi() {
    console.log('防抖成功');
    }
    var inp = document.getElementById('button');
    button.addEventListener('click', debounce(sayHi)); // 防抖

    节流:高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。

    思路:每次触发事件时都判断当前是否有等待执行的延时函数。

    function throttle(fn) {
    let canRun = true; // 通过闭包保存一个标记
    return function () {
    if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
    canRun = false; // 立即设置为 false
    setTimeout(() => { // 将外部传入的函数的执行放在 setTimeout 中
    fn.apply(this, arguments);
    // 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉
    canRun = true;
    }, 1000);
    };
    }
    function sayHi() {
    console.log('节流成功');
    }
    button.addEventListener('click', throttle(sayHi));

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

    四者都是ES6提供的新的数据结构类型。

    Set:类似于数组,它的特点是成员唯一没有重复值。set本身是一个构造函数,用来生成数据结构。它可以接受一个数组作为参数(或者具有 iterable 接口的其他数据结构),用来初始化。

    const s=new Set([1,2,3,4,5,5,5]);
    console.log(s)//Set(5) {1, 2, 3, 4, 5}
    const s1=new Set("hello");
    console.log(s1);//Set(4) {"h", "e", "l", "o"}

    WeakSet:和set类似,也是不重复的值的集合。但是,它与 Set 有两个区别。首先,WeakSet 的成员只能是对象,而不能是其他类型的值。其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机

    制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

    Map:JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

    为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值

    对”的数据结构,Map 比 Object 更合适。

    const m=new Map([["name","lily"],["age","18"]]);
    console.log(m)//Map(2) {"name" => "lily", "age" => "18"}

    let a=new Map([[{d:'3',f:'6'},{e:'5'}]]);
    a.set({a:"1",b:"2"},{c:"4"});
    console.log(a)
    //0: {Object => Object} key: {d: "3", f: "6"} value: {e: "5"}
    //1: {Object => Object} key: {a: "1", b: "2"} value: {c: "4"}

    WeakMap:它和map的区别类似于set和WeakSet的区别。WeakMap的键名只接受对象,不接受其他类型。键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的;

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

    答案完全引用自:https://www.cnblogs.com/luoxiaoyi/p/10073154.html

    深度优先遍历:

    假设给定图G的初态是所有顶点均未曾访问过。在G中任选一顶点v为初始出发点(源点),则深度优先遍历可定义如下:首先访问出发点v,并将其标记为已访问过;然后依次从v出发搜索v的每个邻接点w。若w未曾访问过,则以w为新的出发点继续进行深度优先遍历,直至图中所有和源点v有路径相通的顶点(亦称为从源点可达的顶点)均已被访问为止。若此时图中仍有未访问的顶点,则另选一个尚未访问的顶点作为新的源点重复上述过程,直至图中所有顶点均已被访问为止。

    图的深度优先遍历类似于树的前序遍历。采用的搜索方法的特点是尽可能先对纵深方向进行搜索。这种搜索方法称为深度优先搜索(Depth-First Search)。相应地,用此方法遍历图就很自然地称之为图的深度优先遍历。

    说白了深度优先遍历就是一种不撞南墙不会头的算法,他会把一条路走完之后再回溯到有分叉的节点继续遍历。

    如图:

    1. 首先标记点0,然后按字典序寻找未标记的相邻点进行遍历(点1)。
    2. 标记点1,按字典序寻找未标记的相邻点继续遍历(点4)。
    3. 同步骤2,直到遍历到点3,因为与他相邻的点(点0,点6)都被标记过,所以回溯到点6,继续寻找点6的未标记的相邻点继续遍历(点7)。
    4. 标记点7,同步骤3,回溯点6。
    5. 这时点6的所有相邻点都被标记,回溯点4。
    6. 同步骤5,继续回溯到点1。
    7. 按字典序寻找点1的未标记的相邻点继续遍历(点2)。
    8. 同步骤2,遍历点5。
    9. 同步骤5,回溯到点0,此时整个图的点都被遍历过,结束。

    广度优先遍历:

    1. 从图中某个顶点V0出发,并访问此顶点;
    2. 从V0出发,访问V0的各个未曾访问的邻接点W1,W2,…,Wk;然后,依次从W1,W2,…,Wk出发访问各自未被访问的邻接点;
    3. 重复步骤2,直到全部顶点都被访问为止。

    这是一种层层递进的算法,与树的层序遍历类似。

    在广度优先搜索时,会从起点开始“一层一层”扩展的方法来遍历,扩展时每发现一个点就将这个点加入到队列,直到整张图都被遍历过位置。

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

    (暂无答案)

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

    ES5:通过把被继承构造函数的实例赋值给要继承的构造函数的原型对象,是通过原型或构造函数机制来实现的,其实质上是先创建子类的实例对象,然后再将父类的方法添加到this上。

    ES6:通过class类来替代了构造函数的写法,其包含有构造方法,类和类之间通过extends来实现继承。()ES6继承机制不同于ES5,它是通过先创建父类的实例对象this,然后再在子类中修改this,所以,子类必须在constructor方法中调用super方法,因为子类没有自己的this对象,而是继承了父类的this对象。

    总结:

    ES5和ES6继承最大的区别就是在于:

    1.ES5先创建子类,在实例化父类并添加到子类this中

    2.ES6先创建父类,在实例化子集中通过调用super方法访问父级后,在通过修改this实现继承

    第 8 题:setTimeout、Promise、Async/Await 的区别

    js主线程会不断的循环往复的从任务列表中读取任务,执行任务,这种运行机制称为事件循环(event loop)。

    执行异步任务有两种类型,分别是microtasks和macrotasks。microtasks的优先级要高于macrotasks。

    每一个event loop都有一个microtasks queue;每一个event loop都有一个或多个macrotasks queue(也称task queue)。

    每一次event loop,会先执行microtasks queue,执行完毕之后,会提取macrostaks queue中的一个任务加入到microtasks queue中,继续执行microtasks queue,依次执行下去直到结束。

    microtasks 包含以下api:process.nextTick、promise、Object.observe (废弃)、MutationObserver。

    macrostaks包含以下api:setTimeout、setImmerdiate、setInterval、I/O、UI 渲染。

    由此可见在执行顺序上promise的优先级要高于setTimeout。promise的性能要优于setTimeout,后者会发生两次UI重渲染。

    promise本身是同步的立即执行函数,当执行到resolve和reject的时候,此时是异步的操作,会放入到任务队列中。(如下)

    console.log('script start')
    let 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是建立在promise之上的,其返回的也是一个promise对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。(如下:)

    async function async1(){
    console.log('async1 start');
    await async2();
    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/await相对于promise的优点如下:

    1. 使用async/await可以使代码简洁优雅,不需要书写.then(),不需要新建一个匿名函数处理响应,也不需要再把数据赋值给一个我们其实并不需要的变量。

    2.在错误处理上,错误会出现在promise内部,由此需要多嵌套一个try/catch。

    3.业务如果需要先获取一个数据,根据该数据来决定是否获取更多的数据。如果使用promise,只能通过嵌套来完成,如果这种业务迭代很深,那么嵌套也会很深(闻到了回调地狱的味道)。

    const makeRequest = () => {
    return getJSON()
    .then(data => {
    if (data.needsAnotherRequest) {
    return makeAnotherRequest(data)
    .then(moreData => {
    console.log(moreData)
    return moreData
    })
    }
    else {
    console.log(data)
    return data
    }
    })
    }

    如果用async/await就简单很多,只需要在获取条件数据的方法前面使用await即可。

    const makeRequest = async () => {
    const data = await getJSON()
    if (data.needsAnotherRequest) {
    const moreData = await makeAnotherRequest(data);
    console.log(moreData)
    return moreData
    }
    else {
    console.log(data)
    return data
    }
    }

    第 9 题:(头条、微医)Async/Await 如何通过同步的方式实现异步

    (该题欢迎指正)

    async本质也是返回了一个promise对象,而promise对象本身是一个同步立即执行的函数,当执行到.then()时,才会放入到异步队列中,而async当执行到await时,会执行其后面的方法,然后跳出当前async函数体,等待下次被提取。

    第 10 题:(头条)异步笔试题

    请写出下面代码的运行结果

    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

  • 相关阅读:
    如何用互联网的思维开一家有逼格的客栈?
    create和grant配合使用,对Mysql进行创建用户和对用户授权
    Nginx 403 forbidden原因及故障模拟重现(转载)
    企业级缓存系统varnish应用
    实现基于Haproxy+Keepalived负载均衡高可用架构
    企业级监控zabbix基础
    实现基于Keepalived主从高可用集群网站架构
    实现基于LVS负载均衡集群的电商网站架构
    实现基于lnmp的电子商务网站
    CentOS6编译LAMP基于FPM模式的应用wordpress
  • 原文地址:https://www.cnblogs.com/lcr-smg/p/11238452.html
Copyright © 2020-2023  润新知