介绍下如何实现 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 运行时替换更新中的模块,如果确定这些模块无法更新,则触发整个页面刷新。