前端面试卷七

313 阅读6分钟

介绍下如何实现 token 加密

jwt(Json web token):
链接:www.jianshu.com/p/576dbf44b…
1.需要一个 secret(随机数,也就是私钥,不能暴露,留在服务器端)
2.后端利用 secret 和加密算法(如:HMAC-SHA256)对 payload(如账号密码)加密
生成一个字符串(token),返回前端
3.前端每次 request 在 header 中带上token()
4.后端用同样的算法解密

组成:header(base64) + payload(base64,此处存放用户信息) +sign(secrect+SHA256
算法签名,防篡改)
请求时:加上Authorization:'Bear '+token。
如何防篡改:假设黑客篡改了我们的head或者payload,因为他没有私钥secrect,所以他得不到正确的签名,请求服务器时服务器也对传递过来的header和payload进行签名,发现与传递过来的签名不一致,认为被篡改,拒绝请求,关键点在于私钥在服务器端。

传统的cookie+session方式:
客户端每次都要带上cookie,cookie不能跨域,服务端每次都要在内存中保存一份session,
cookie容易被劫持,虽然可以进行签名。

redux 为什么要把 reducer 设计成纯函数

redux 的设计思想就是不产生副作用,数据更改的状态可回溯,可预测。

如何设计实现无缝轮播

简单来说,无缝轮播的核心是制造一个连续的效果。最简单的方法就是复制一
个轮播的元素,当复制元素将要滚到目标位置后,把原来的元素进行归位的操
作,以达到无缝的轮播效果。

本质:通过requestAmimationFrame(callback),每个16.7ms内只会执行一次callback
达到肉眼看上去不掉帧的动画效果。

useEffect(() => { 
    const requestAnimationFrame = window.requestAnimationFrame 
        || window.webkitRequestAnimationFrame 
        || window.mozRequestAnimationFrame 
    const cancelAnimationFrame = window.cancelAnimationFrame 
        || window.webkitCancelAnimationFrame 
        || window.mozCancelAnimationFrame
     const scrollNode = noticeContentEl.current
     const distance = scrollNode.clientWidth / 2 
     scrollNode.style.left = scrollNode.style.left || 0 
     window.__offset = window.__offset || 0 
     let requestId = null 
     const scrollLeft = () => { 
        const speed = 0.5 
        window.__offset = window.__offset + speed 
        scrollNode.style.left = -window.__offset + 'px' 
        // 关键行:当距离小于偏移量时,重置偏移量 
        if (distance <= window.__offset) window.__offset = 0 
        requestId = requestAnimationFrame(scrollLeft) 
     } 
     requestId = requestAnimationFrame(scrollLeft) 
     if (pause)  cancelAnimationFrame(requestId) 
     return () => cancelAnimationFrame(requestId) 
}, [notice, pause])

模拟实现一个 Promise.finally

promise源码:

//调用代码
// new Promise((resolve,reject)=>{
    // setTimeout(resolve,1000)
    // setTimeout(reject,1000) 
//}).then(()=>{})
//.catch(()=>{})

function Promise(exectutor) {  //exextutor是我们传进去的函数,回立即执行,并注入resolve,reject
    this.state = 'pedding'  //初始化状态  
    this.success = '' //保存正确的结果 
    this.error = '' //保存错误的结果  
    this.fullFilledCallbacks = [] //保存正确回调  
    this.rejectedCallbacks = [] //保存错误回调  

    //定义resolve函数  
    function resolve(value) {    
        if (this.state == 'pedding') {    //防止同时触发resolve,reject  
            this.state = 'fullfilled'      
            this.success = value      
            this.fullFilledCallbacks.forEach(fn => fn())    
        }  
    }

    //定义reject函数  
    function reject(value) {    
        if (this.state == 'pedding') {      //防止同时触发resolve,reject  
            this.state = 'rejected'      
            this.error = value      
            this.rejectedCallbacks.forEach(fn => fn())    
        }  
    }  

    //定义then原型方法,传递参数正确回掉和错误回掉  
    Promise.prototype.then = function (onFullFilled, onRejected) {    
        const promise = new Promise((resolve, reject) => {     //返回一个promise才能链式调用then 
            if (this.state == 'pedding') { //代表promise还没有执行完,即没有调用resolve或者reject        
                this.fullFilledCallbacks.push(() => {          
                    try {            
                        let res = onFullFilled(this.success)  //执行的结果作为下一个then的参数            
                        resolve(res)          
                    } catch (error) {            
                        reject(error)          
                    }        
                })        
                this.rejectedCallbacks.push(() => {          
                    try {            
                        let res = onRejected(this.error)  //执行的结果作为下一个then的参数,即使错误            
                        resolve(res)          
                    } catch (error) {            
                        reject(error)          
                    }        
                })      
            }      
            else if (this.state == 'fullfilled') {        
                try {          
                    let res = onFullFilled(this.success)  //执行的结果作为下一个then的参数          
                    resolve(res)        
                } catch (error) {          
                    reject(error)        
                }      
            }      
            else {       
                 try {          
                    let res = onRejected(this.error)  //执行的结果作为下一个then的参数,即使错误          
                    resolve(res)        
                 } 
                 catch (error) {          
                    reject(error)        
                 }      
            }    
        })    
        return promise  
    }

    //定义catch原型方法,其实就是then方法的一个语法糖  
    Promise.prototype.catch = function (onRejected) {    
        this.then(() => { }, callback)  
    }  

    //定义finally原型方法,注意onFinished接收不到参数,无论finally之前成功还是失败都会执行onFinished
    且会把上一次的结果val向下传递
    Promise.prototype.finally = function(onFinished) {
        return this.then(val => {
            onFinished()
            return val
        }).catch((err) => {
            onFinished()
            return err
        })
    }

    //定义all静态方法,传递一个参数[promise,1,2],数组中的每一项可以是promise,也可以是数字
    //只有每一项成功了,才会返回数组, 否则返回失败,而且promise是按顺序执行的  
    Promise.all = function (promises) {    
        var arr = []  //返回一个数组    
        var num = 0;    
        function precess(key, value) { //用于存放数据的一个方法      
            arr[key] = value      
            if (++num == callbacks.length) {   //代表所有的任务都执行完毕 promise是异步的    
                resolve(arr)     
            }    
        }    
        promises.forEach((item, i) => {     
             if (item.then && typeof item.then == 'function') {  //如果是一个promise        
                item.then(res => {      //执行,并把结果放入数组    
                    process(i, res)        
                }).catch(err){
                    reject(err)    //一旦出错就结束
                }           
             }      
             else {        
                process(i, item)      
             }    
        });    
    }  

    //赛跑,哪个先执行完,直接返回
    Promise.race= function (promises) {    
        promises.forEach((item, i) => {     
            item.then(val=>resolve(val),err=>reject(err))   
        });    
    }      

    exectutor(resolve, reject) //立即执行,并并注入resolve,reject两个函数
}

//导出
module.exports = Promise

a.b.c.d 和 a['b']['c']['d'],哪个性能更高?

应该是 a.b.c.d 比 a['b']['c']['d'] 性能高点,后者还要考虑 [ ] 中是变量的情况,
再者,从两种形式的AST语法结构来看,显然编译器解析前者要比后者容易些,自然也
就快一点。

ES6 代码转成 ES5 代码的实现思路是什么

ES6 转 ES5 目前行业标配是用 Babel,转换的大致流程如下:

1.解析:解析代码字符串,生成 AST;(babel-loader: ts/jsx=>es6=>es5)

2.转换:按一定的规则转换、修改 AST;(babel-core,babel-polyfill,babel-env)

3.生成:将修改后的 AST 转换成普通代码。

数组编程题

随机生成一个长度为 10 的整数类型的数组,例如 [2, 10, 3, 4, 5, 11, 10, 11,20],
将其排列成一个新数组,要求新数组形式如下,例如 [[2, 3, 4, 5], [10, 11],[20]]。

//分组方法
function fromArray(arr){
  var map = new Map()    //用于存放分组
  var sortedArr = Array.from(new Set(arr)).sort((a,b)=>return a-b)    //去重并排序
  sortedArr.forEach(item=>{
      var key = Math.foor(item/10)    //按包含几个10来分组
      var group = map.get(key)||[]
      group.push(item)
      map.set(key,group)
  })
  return [...map.values()]    //map.keys获取key的集合,map.values获取值的集合
}

//生成数据的方法
getNumArr(length){    //length代表数量
    return Array.from({length:length},()=>{
        return Math.floor(Math.random() * 100)    //生成length个100以内的数组
    })
}

//测试代码
var res = fromArray(getNumArr(10))
console.log(res)    //是一个二维数组

如何解决移动端 Retina 屏 1px 像素问题

Retina显示屏又叫做视网膜屏,Retina其实是一种显示技术的名称。这种技术把更多的像素点压缩至一块屏幕上,从而达到分辨率非常惊人的细腻屏幕。事实上,Retnia这个词更接近于一个营销名词而非技术名次。因为从某种意义上来说,这是苹果为宣传自己的产品所创造出的名词。苹果也的确为这个名词申请了专利。 虽然屏幕的分辨率一般都是以“像素数 x 像素数”的格式出现,但真正决定屏幕清晰度的其实是像素密度,也就是ppi,而不是像素数。另外,除了ppi之外,眼睛和屏幕之间的距离也决定了一块屏幕是否清晰到称为“Retina”。对于智能手机来说,326 的 ppi 才能够被称为 Retina 显示屏,对于平板来说则为 264 ppi,而对于笔记本电脑来说,220 ppi 就足够了。

解决:
1.伪元素 + transform scaleY(.5)
2.border-image
3.background-image
4.box-shadow

如何把一个字符串的大小写取反(大写变小写小写变大写),例如 ’AbC' 变成 'aBc' 。

function processString(str){
    if(!str) return ''
    var arr = str.split('')
    var newArr = arr.map(item=>{
       return item===item.toUpperCase()?item.toLowerCase():item.toUpperCase()
    })
    return newArr.join('')
}

介绍下 webpack 热更新原理,是如何做到在不刷新浏览器的前提下更新页面的

1.当修改了一个或多个文件;
2.文件系统接收更改并通知 webpack;
3.webpack 重新编译构建一个或多个模块,并通知 HMR 服务器进行更新;
4.HMR Server 使用 webSocket 通知 HMR runtime 需要更新,HMR 运行时通过 HTTP 请求更新 jsonp;
5.HMR 运行时替换更新中的模块,如果确定这些模块无法更新,则触发整个页面刷新。