前端面试卷九

940 阅读5分钟

打印出 1 - 10000 之间的所有对称数

//所谓对称数就是指从左到右遍历与从右到左遍历的结果一样,如121,不能是个位数
function duiCheng()(
    Arrar(10000).keys().filter(index)=>{    //创建10000个元素的数组,keys就是0~9999的数组
        return index.toString().length > 1 &&    //排除个位数
               index.toString().split('').reverse().join('') == index
    })
)

周一算法题之「移动零」

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非
零元素的相对顺序。如输入: [0,1,0,3,12] 输出: [1,3,12,0,0]复制代码说明: 必须
在原数组上操作,不能拷贝额外的数组。 尽量减少操作次数

function moveZero(arr){
   var len = arr.length    //保存数组长度,每次将0移到最后,len减1,减少循环
   for(var i=0;i<len;i++){
       if(!arr[i]){
           arr.push(arr.splice(i,1))    //删除该元素,也就是0,放到数组末尾
           i--    //删除后i要减1才不会跳过下一个元素
           len--    //每次移动后,len减1,减少循环,末尾的0不需要遍历
       }
   }
   return arr
}

var、let 和 const 区别的实现原理是什么

var 和 let 用以声明变量,const 用于声明只读的常量;
var 声明的变量,不存在块级作用域,在全局范围内都有效,
let 和 const声明的,只在它所在的代码块内有效;
let 和 const 不存在像 var 那样的 “变量提升” 现象,所以 var 定义变
量可以先使用,后声明,而 let 和 const 只可先声明,后使用;
let 声明的变量存在暂时性死区,即只要块级作用域中存在 let,那么它

所声明的变量就绑定了这个区域,不再受外部的影响。
let 不允许在相同作用域内,重复声明同一个变量;
const 在声明时必须初始化赋值,一旦声明,其声明的值就不允许改变,
更不允许重复声明;如 const 声明了一个复合类型的常量,其存储的是一个引
用地址,不允许改变的是这个地址,而对象本身是可变的。**
变量与内存之间的关系,主要由三个部分组成:变量名 内存地址 内存空间**

JS 引擎在读取变量时,先找到变量绑定的内存地址,然后找到地址所指向的内

存空间,最后读取其中的内容。当变量改变时,JS 引擎不会用新值覆盖之前旧
值的内存空间(虽然从写代码的角度来看,确实像是被覆盖掉了),而是重新
分配一个新的内存空间来存储新值,并将新的内存地址与变量进行绑定,JS 引
擎会在合适的时机进行 GC,回收旧的内存空间。const 定义变量(常量)后,
变量名与内存地址之间建立了一种不可变的绑定关系,阻隔变量地址被改变,
当 const 定义的变量进行重新赋值时,根据前面的论述,JS 引擎会尝试重新分
配新的内存空间,所以会被拒绝,便会抛出异常。

请实现一个 add 函数,满足以下功能

add(1); // 1
add(1)(2); // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6

//要加工的函数:const add = (...args) => args.reduce((a, b) => a + b);
//分析:柯里化操作(闭包收集参数),根据参数达没达到总的参数个数决定是收集还是调用

//实现方法1:数组收集参数
function curry(fn,length){    //fn=add函数,length=3
    var argArr = []    //收集参数
    return function result(...args){    //返回一个函数
        if(args.length+argArr.length>=length){    //如果参数收集完毕就执行
            return fn(...argArr,...args)    //执行函数
        }
        else{    //参数数量不够,小于3    如:add(1,2)
            argArr.push(...args)    //收集参数
            return result    //递归收集(3)
        }
    }
}


//实现方法2:利用bind具备收集参数的机制来实现参数收集
//bind源码:
Function.prototype.bind = function (context) {
    var self = this;    //调用的函数
    // 第 1 个参数是指定的 this,截取保存第 1 个之后的参数
    var args = Array.prototype.slice.call(arguments, 1); //(this,1,2)
    return function () {    //返回一个新函数
        // 此时的 arguments 是指 bind 返回的函数调用时接收的参数,(3):arguments就是3
      	// 类数组转成数组
        var bindArgs = Array.prototype.slice.call(arguments);
        //绑定this到context,这里参数是concat,就变成了[1,2,3]
        return self.apply( context, args.concat(bindArgs) );
    }
}
function curry(fn,length){    //fn=add函数,length初始为空,代表剩余参数个数
    length = length || fn.length    //length初始化为总的参数个数3
    return function(...args){    //返回一个函数
        if(args.length>=length){    //如果参数收集完毕就执行
            //fn经过bind之后把之前的参数已经收集,这里不需要cancat
            return fn.apply(this,args)    //执行函数
        }
        else{    //参数数量不够,小于3    如:add(1,2)
            //bind过的函数会收集参数
            curry(fn.bind(this,...args),length-args.length)    //递归调用,传递剩余参数个数
        }
    }
}


//实现方法3 直观写法,不返回
function curry = fn => judge = (...args)=>{    包装fn函数为judge函数
    if(args.length > fn.length){    //如果参数收集完毕就执行
        return fn(...args)    //注意这里不需要return
    }
    else (...arg)=>judge(...args,arg)    //多阶函数,收集参数递归调用
}

react-router 里的link标签和a标签有什么区别

如何禁掉 标签默认事件,禁掉之后如何实现跳转。

Link 做了 3 件事情:
1.有 onclick 那就执行 onclick: if (_this.props.onClick) _this.props.onClick(event);
2.click 的时候阻止 a 标签默认事件(这样子点击就不会跳转和刷面:
event.preventDefault();
3.再取得跳转 href(即是 to),用 history(前端路由两种方式之一,history
& hash)跳转,此时只是链接变了,并没有刷新页面

周一算法题之「两数之和」

给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。你可以假设每个输入
只对应一种答案,且同样的元素不能被重复利用。
示例:给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

function answer(arr,target){    //target代表目标值9
    var map = {}    //用map保存遍历的数以及下表,我们只需要在剩余项中去找target-当前值
    for(var i=0;i<arr.length;i++){
        if(map[target-arr[i]]){    //遍历到7时,发现map[2]存在,直接返回结果即可
            return [map[target-arr[i]],i]
        }
        else{
            map[arr[i]] = i    //保存{2:0}到map中
        }
    }
}

在输入框中如何判断输入的是一个正确的网址。

function isUrl(url){
    const a = document.createElement('a')    //创建a标签
    a.href= url
    return [
        /^(http|https):$/.test(a.protocol),    //必须是http或者https协议
        a.host,    //必须要有主机名
        a.pathName!=url,
        a.pathName!=`/${url}`
    ].find(x=>!x) == undefined    //去找不满足条件的,发现找不到,即全部满足证明url有效
}

实现 convert 方法,把原始 list 转换成树形结构,要求尽可能降低时间复杂度

以下数据结构中,id 代表部门编号,name 是部门名称,parentId 是父部门编号,为 0 代表
一级部门,现在要求实现一个 convert 方法,把原始 list 转换成树形结构,parentId 为多少
挂载在该 id 的属性 children 数组下,结构如下:

// 原始 list 如下 let list =[ 
    {id:1,name:'部门 A',parentId:0}, 
    {id:2,name:'部门 B',parentId:0}, 
    {id:3,name:'部门 C',parentId:1}, 
    {id:4,name:'部门 D',parentId:1}, 
    {id:5,name:'部门 E',parentId:2}, 
    {id:6,name:'部门 F',parentId:3}, 
    {id:7,name:'部门 G',parentId:2}, 
    {id:8,name:'部门 H',parentId:4}
]; 
const result = convert(list, ...);
// 转换后的结果如下 let result = [ 
    { id: 1, name: '部门 A', parentId: 0, children: [ 
            { id: 3, name: '部门 C', parentId: 1, children: [ 
                    { id: 6, name: '部门 F', parentId: 3 }, 
                    {id: 16, name: '部门 L', parentId: 3 } 
                ] 
            },
            { id: 4, name: '部门 D', parentId: 1, children: [ 
                    { id: 8, name: '部门 H', parentId: 4 } 
                ] 
            }
        ] 
    }, 
    ···
];

实现代码:

function convert(list){
   var res = []
   var map = list.forEach(item=>map[item.id] = item)    //构造map,key是id,value是整个对象
   for(var i=0;i<list.length;i++){
        var item = list[i]
        if(!item.parentId) {
            res.push(item)    //最上层的
            continue;
        }r
        if(map[item.parentId]){    //如果有父亲,就放入父亲的children中
            var parent = map[item.parentId]
            parent.children = parent.children||[]
            parent.children.push(item)
        }
   }
   return res
}

设计并实现 Peromise.race()

//参数全部都是promse的情况
Promise.prototype.race = function(promises){
    new Promise((resolve,reject)=>{    
        promises.forEach(promise=>promise.then(resolve,reject))    //谁先执行完成谁就先返回
    })
}

//参数实现了iterator接口的情况
Promise._myrace = function(iterator){    //实现了iterator接口的集合
    return new Promise((resolve,reject)=>{
      try{
        let it = iterator[Symbol.iterator]()    //获取迭代器对象
        while(true){    //死循环,只要有一个执行完毕就结束循环
            let res = it.next()    //获取每一项
            if(res.done) break    //如果执行完毕
            if(res.value instanceof Promise)    //如果本身就是promise
                res.value.then(resolve,reject)
            else 
                resolve(res.value)    //done会置为true
        }
      }
      catch(err){
        reject(err)
      }
    })
}

实现模糊搜索结果的关键词高亮显示

//v-html绑定计算属性,只要输入框输入内容变化就会替换v-html
<template lang="">
  <span v-html="ruleTitle" ></span>
</template>

<script>
export default {
  props: {
    item: '',
    searchVal: '',
  },
  computed: {
    ruleTitle() {
      let titleString = this.item;
      if (!titleString) {
        return '未知';
      }
      if (this.searchVal && this.searchVal.length > 0) {
            // 匹配关键字正则
        const replaceReg = new RegExp(this.searchVal, 'g');
            // 高亮替换v-html值
        const replaceString = `<span class="red">${this.searchVal}</span>`;
            // 开始替换
        titleString = titleString.replace(replaceReg, replaceString);
      }
      return titleString;
    },
  },
};
</script>