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);
深度优先遍历,广度优先遍历以及实现
链接: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的区别
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