前端面试卷一

541 阅读8分钟

react,vue项目中为什么在列表组件中要写key,作用是啥?

react,vue2以上都是使用diff算法比较新旧vnode,而key是给每一个vnode的唯一id,根据key可以快速拿到odlvnode中对应的vnode节点,减少diff算法的时间复杂度。

[1,2,3].map(parseInt)的结果,why?

第一步:[1,2,3].map(parseInt(item,index,arr)=>{}),item依次为1,2,3,index依次为0,1,2
第二步:parseInt(数值,进制),0代表10进制,parseInt(1,0),parseInt(2,1),parseInt(3,2)
第三步:返回[1,NaN,NaN]

防抖和节流

参考链接:www.jianshu.com/p/c8b86b09d…

所谓防抖,就是指触发事件后在 n 秒后函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。防抖函数分为非立即执行版和立即执行版。

非立即执行版:

function debonce(func,wait){    //func代表要加工的函数,wait代表时间,该时间后事件func执行一次
    let timeout //计时器
    return function(){    //返回加工后的函数
        let context = this    //始终是在当前上下文执行
        let args = arguments    //保存返回的加工函数接受的参数
        if(timeout) clearTimeout(timeout)    //如果在wait时间内再次触发就重新计时
        timeout = setTimeout(()=>{
            func.apply(context,args)
        },wait)
    }
}

立即执行版:

function debonce(func,wait){    //func代表要加工的函数,wait代表时间,该时间内事件func执行一次
    let timeout //计时器
    return function(){    //返回加工后的函数
        let context = this    //始终是在当前上下文执行
        let args = arguments    //保存返回的加工函数接受的参数
        if(timeout) clearTimeout(timeout)    //如果在wait时间内再次触发就重新计时
        let ifCallNow = !timeout //只要还在计时就不能执行
        timeout = setTimeout(()=>{
            timeout = null //只有时间到了才允许执行
        },wait)
        if(ifCallNow) func.apply(context,args)
    }
}

双剑合璧版:immediate代表是否需要立即执行

function debonce(func,wait,immediate){    //func代表要加工的函数,wait代表时间,该时间内事件func执行一次
    let timeout //计时器
    return function(){    //返回加工后的函数
        let context = this    //始终是在当前上下文执行
        let args = arguments    //保存返回的加工函数接受的参数
        if(timeout) clearTimeout(timeout)    //如果在wait时间内再次触发就重新计时
        if(immediate){
            let ifCallNow = !timeout //只要还在计时就不能执行
            timeout = setTimeout(()=>{
                timeout = null //只有时间到了才允许执行
            },wait)
            if(ifCallNow) func.apply(context,args)
        }
        else{
            timeout = setTimeout(()=>{
                func.apply(context,args)
            },wait)
        }
    }
}

所谓节流,就是指连续触发事件但是在 n 秒内只执行一次函数。节流会稀释函数的执行频率。对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。

时间戳版:立即执行

function throttle(func,wait){     //func要加工的函数,wait时间内只会执行一次func
    let previous = 0    //初始化上次执行时间
    return function(){
        let now = Date.now()
        if(now - previous > wait){ //时间到了就执行函数
            func.apply(this,arguments)
            previous = now    //更新上次执行时间
        }
    }
}

定时器版:非立即执行

function throttle(func,wait){     //func要加工的函数,wait时间内只会执行一次func
    let timeout
    return function(){
        if(!timeout) {    //如果没有计时器就创建,第一次进来以及函数执行一次后,这里的timeout也可以换成一个标志
            timeout = setTimeout(()=>{
                func.apply(this,arguments)
                timeout = null
            },wait)
        }
    }
}

Set,Map,WeakSet,WeakMap?

参考链接:https://www.jianshu.com/p/80bf2e6139dc

Set是ES6新的数据结构,类似数组,但成员的值是唯一的,没有重复的值。

let s = new Set()
s.add(1)
s.add(2)
s.add(2)
s.add(3)
for(let item of s){
  console.log(item) //1,2,3
}

let ss=new Set([1,2,3,3])
[...ss] //1,2,3

//去重
[...new Set(array)],Array.from(new Set(array))

Weakset和Set结构类似,也是不重复的值的集合,但WeakSet的成员只能是对象,不可遍历,没有size属性(因为WeakSet的成员都是弱引用,随时可能消失,成员是不稳定的),垃圾回收机制只在乎强引用。

作用:使用ws储存DOM节点,就不用担心节点从文档移除时,会引发内存泄漏(即在被移除的节点上绑定的click等事件)

let array=[[1,2],[3,4]]
let ws=new WeakSet(array)  //{[1,2],[3,4]}

let array=[1,2,3,4]
let ws=new WeakSet(array) //报错

map本质上是键值对的集合,可以是任意的数据类型,方法有size,set,get,delete,has,clear
WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。WeakMap的键名所指向的对象是弱引用,不计入垃圾回收机制。无法被遍历,因为没有size。无法被清空,因为没有clear(),跟WeakSet相似

let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();

myWeakmap.set(myElement, {timesClicked: 0});

myElement.addEventListener('click', function() {
  let logoData = myWeakmap.get(myElement);
  logoData.timesClicked++;
}, false);
上面代码中,myelement是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是myelement。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

深度优先遍历,广度优先遍历以及实现

链接:https://segmentfault.com/a/1190000018706578

深度优先:该方法是以纵向的维度对dom树进行遍历,从一个dom节点开始,一直遍历其子节点,直到它的所有子节点都被遍历完毕之后在遍历它的兄弟节点。

访问顺序如下:

递归写法:

funtion deepFirstSearch(node,nodeList){    //node代表当前节点,nodeList是要返回的结果
    if(node){
        nodeList.push(node)    //存入当前节点
        if(node.children){    //遍历孩子
            node.children.forEach(child => {
                deepFirstSearch(chile,nodeList)
            })
        }
    }
    return nodeList
}

非递归写法:

funtion deepFirstSearch(node){    //node代表当前节点
    let nodes = []    //nodes是要返回的结果
    if(node){
        var stack = []    //需要一个stack,以便访问完了所有的chilren之后再访问兄弟
        stack.push(node)    //存入当前节点
        while(stack.length){    //只要栈不为空就取出栈顶元素
           let item = stack.pop()    //栈顶元素
           nodes.push(item)    
           for(let i = item.children.length-1;i >= 0; i--){    //注意是stack,后进先出
               stack.push(item.children[i])                     
           }
        }
    }
    return nodos 
}

广度优先:该方法是以横向的维度对dom树进行遍历,从该节点的第一个子节点开始,遍历其所有的兄弟节点,再遍历第一个节点的子节点,完成该遍历之后,暂时不深入,开始遍历其兄弟节点的子节点。

访问顺序如下:

递归版本

function breadthFirstSearch(node,nodeList) {    //node代表当前节点,nodeList是要返回的结果
    let i = 0
    if(node) {
        nodeList.push(node)
        breadthFirstSearch(node.nextElementSlibing)    //先递归访问该节点的所有兄弟节点
        node = nodeList[i++]    //取出当前节点
        breadthFirstSearch(node.firstElementChild)    //然后再递归访问该节点的子节点

    }
}

非递归版本:

function breadthFirstSearch(node) {  
    var nodes = [];  
    if (node) {  
        var queue = [];  
        queue.unshift(node);  
        while (queue.length) {  
            var item = queue.shift();  
            nodes.push(item);  
            var children = item.children;  
            for (var i = 0; i < children.length; i++)  
                queue.push(children[i]);  
        }  
    }  
    return nodes;  
}

深度优先遍历,广度优先遍历实现拷贝函数

//判断object类型的一个工具函数,拷贝的时候要用
let _toString  = Objecct.prototype.toString
let map = {
    array:"Array",
    object:"Object",
    function:"Function",
    string:"String",
    null:"Null",
    undefined:"Undefined",
    boolean::"Boolean",
    number:"Number"
}
getType(obj){
    return _toString.call(obj).slice(8,-1)    //[object array] => array -1代表倒数第一
}
isTypeOf(obj,type){
    return map[type] && map[type] === getType(obj)
}

//判断数组或者对象
function getEmpty(o){
    if(Object.prototype.toString.call(o) === '[object Object]'){
        return {};
    }
    if(Object.prototype.toString.call(o) === '[object Array]'){
	return [];
    }
    return o;
}

深度优先遍历拷贝:

let dfsDeepClone(obj,visitedArr){    //obj 原始对象 visitedArr代表已经拷贝的对象,防止循环拷贝    防止a:{b:a}
    let _obj = {}     //要返回的拷贝后的对象
    if(isTypeOf(obj,"array")||isTypeOf(obj,"object"){    //如果是对象或者数组就要递归
        let index = visitedArr.findIndex(obj)
        if(index != -1){
            _obj = visitedArr[index]
        }
        else{
            _obj = isTypeOf(obj,"array")?[]:{}
            for(let item in obj){
               _obj[item] = dfsDeepClone(obj[item])
            }
        }
    }
    else if(isTypeOf(obj,"function")){
        _obj = eval('('+obj.toString()+')')    //如果是函数就返回函数字符串
    }
    else _obj = obj
    return _obj
}

广度优先遍历拷贝:

function deepCopyBFS(origin){    //origin代表原始对象
    let queue = [];
    let map = new Map(); // 记录出现过的对象,用于处理环
    let target = getEmpty(origin);    //getEmpty函数判断是否是数组或者对象,不相等就是
    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(ori[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;
}

ES6和ES5继承的区别

1.ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到子类this上(Parent.apply(this))

2.ES6的继承机制完全不同,实质上是先创建父类的实例对象this(必须先调用super()方法),让然后再用子类的构造函数修改this

3.ES5的继承通过原型或者构造函数机制来实现

4.ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承

5.子类必须在constructor中调用super方法,因为子类没有this,而是继承了父类的this,如果不调用super方法,子类得不到this对象

setTimeout和promise,anync/await的区别

链接:blog.csdn.net/yun_hou/art…

anync/await如何以同步的方式实现异步

Async/Await就是一个自执行的generate函数,async function代替了function*,await代替了yield ,且不需要next

异步笔试题

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