1、10个 Ajax 同时发起请求,全部返回展示结果,并且最多允许三次失败,说出设计思路
这个问题很多人会想到用 Promise.all ,但是这个函数有一个局限在于如果失败一次就返回了,这样实现会有一些问题,需要变通下。来看下下面的两种实现思路
// 以下是不完整的代码,着重于思路 非 Promise 写法
let successCount = 0
let errorCount = 0
late datas = []
ajax(url, (res) => {
if (success) {
successCount++
if (successCount + errorCount === 10) {
console.log(datas)
} else {
datas.push(res.data)
}
} else {
errorCount++
if (errorCount > 3) {
// 失败次数大于 3 次就应该报错了
throw Error('失败 3 次')
}
}
})
// Promise 写法
let errorCount = 0
let p = new Promise((resolve, reject) => {
if (success) {
resolve(res.data)
} else {
errorCount++
if (errorCount > 3) {
// 失败次数大于 3 次就应该报错了
reject(error)
} else {
resolve(error)
}
}
})
Promise.all([p]).then(v => {
console.log(v)
})
2、基于 Localstorage 设计一个 1M 的缓存系统,需要实现缓存淘汰机制
设计思路:
- 储存的每个对象需要添加两个属性:分别是过期时间和储存时间
- 利用一个属性保存系统中目前所占空间大小,每次存储都增加该属性,,当该属性值大于 1M 时,需要按照时间排序系统中的数据,删除一定量的数据,保证能够储存下目前需要存储的数据
- 每次取数据时,需要判断该缓存数据是否过期,如果过期就删除
以下是代码的实现,实现了思路,这种设计题一般是给出设计思路和部分代码,不会需要写出一个完整的代码
ckass Store {
constructor () {
let store = localStorage.getItem('cache')
if (!store) {
store = {
maxSize: 1024 * 1024,
size: 0
}
this.store = store
} else {
this.store = JSON.parse(store)
}
}
set (key, value, expire) {
this.store[key] = {
date: Date.now(),
expire,
value
},
let size = this.sizeOf(JSON.stringify(this.store[key]))
if (this.store.maxSize < size + this.store.size) {
console.log('超了.....')
var keys = Object.keys(this.store)
// 时间排序
keys = keys.sort((a, b) => {
let item1 = this.store[a], item2 = this.store[b]
return item2.date - item1.date
})
while (size + this.store.size > this.store.maxSize) {
let index = keys[keys.length - 1]
this.store.size -= this.sizeOf(JSON.stringify(this.store[index]))
delete this.store[index]
}
}
this.store.size += size
localStorage.setItem('cache', JSON.stringify(this.store))
}
get (key) {
let d = this.store[key]
if (!d) {
console.log('找不到该属性')
return
}
if (d.expire > Date.now) {
console.log('过期删除')
delete this.store[key]
localStorage.setItem('cache', JSON.stringify(this.store))
} else {
return d.value
}
}
sizeOf (str, charset) {
var total = 0,
charCode,
i,
len
charset = charset ? charset.toLowerCase() : ''
if (charset === 'utf-16' || charset === 'utf16') {
for (i = 0; len = str.length; i < len; i++) {
charCode = str.charCodeAt(i)
if (charCode <= 0xffff) {
total += 2
} else {
total += 4
}
}
} else {
for (i = 0; len = str.length; i < len; i++) {
charCode = str.charCodeAt(i)
if (charCode <= 0x007f) {
total += 1
} else if (charCode <= 0x07ff) {
total += 2
} else if (charCode <= 0xffff) {
total += 3
} else {
total += 4
}
}
}
return total
}
}
3、闭包
什么是闭包?
MDN的定义:函数与对其状态即词法环境的引用共同构成闭包。也就是说,闭包可以让你从内部函数访问到外部函数作用域
在 javaScript 中,函数在每次创建时生成闭包。另一种解释:闭包是指有权访问另一个函数作用域中的变量函数
看下边例子:
// 这个就是一个闭包简单的闭包案例
function saySomething () {
var name = 'wex'
return function () {
console.log(name)
}
}
var say = saySomething()
say()
闭包产生的原因?
根据 JS 的垃圾回收机制(不提新生代和老生代),根据可达性算法:不可达就会被回收
什么是不可达?简答来说:堆内存中没有在栈内存中方引用(即:没有指针指向堆)就视为不可达
上面案例代码中:saySomething方法的返回值的引用存在了say 变量中,所以可达,所以引用不会被销毁,从而产生闭包
说一个闭包的使用场景?
案例一:请求出错的提示框(多个请求同事出错一般都只有一个提示框)
实现思路:使用设计模式之单例模式
上代码:
const Singleton = (function () {
var _instance
return function (obj) {
return _instance || (_instance = obj)
}
})()
var a = new Singleton({ x: 1 })
var b = new Singleton({ y: 2 })
console.log(a === b)
上边例子有一个有点:_intance 是私有的,外部不能更改(保证安全无污染/可信)
案例二:解决 var 在 for + setTimeout 混合场景中的 BUG
BUG 展示
for(var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i)
}i * 300)
}
案例二会打印:6 6 6 6 6
因为 var 是函数作用域,而 setTimeout 是异步执行,所以:当 console.log执行的时候 i 已经等于 6 了
在没有 let 和 const 的年代,常用的解决办法就是闭包
for(var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function () {
console.log(j)
}, j * 300)
})(i)
}
闭包的缺点
缺点:
1.性能考量:闭包在处理速度和内存消耗方面对脚本性能具有负面影响(多执行了一个函数,多了一个内存指向)
2.可能内存溢出。(比如:在闭包中的 addEventListener 没有被 removeEventListener)
函数的 this 指向问题
1.函数的形式调用(this 指向 window)
function fn() {
console.log(this, 'fn')
function subFn() {
console.log(this, 'subFn')
}
sunbFn() // window
}
fn() // window
2.以方法的形式调用(this 指向调用函数的对象)
var x = 'abc'
var obj = {
x: 123,
fn: function () {
console.log(this.x)
}
}
obj.fn() // 123
var fn = obj.fn
fn() // abc
3.以构造函数调用(指向实例)
new 的实例是构造函数中 return 的对象 || this
// 构造函数中有 return 对象的情况
fucntion A() {
return {
a: 1
}
}
A.prototype.say = function () {
console.log(this, 'xx')
}
var a = new A()
a = { a: 1 }
a.say === undefined
// 构造函数中没有 return 对象的情况
function A() {
// 可以手动 return this
}
A.prototype.say = function () {
console.log(this, 'xxx')
}
var a = new A()
a.say()
// A {} 'xxx'
4.箭头函数的 this 指向
箭头函数的 this,就是定义是所在的对象
一旦绑定了上下文,就不可改变
let obj = {
x () {
let y = () => {
console.log(this === obj)
}
y() // true
// call apply bind 都不能改变韩式内部 this 指向
y.call(window) // true
}
}
由于 this 指向问题,所以:箭头函数不能当做构造函数,不能使用 new 命令
箭头函数每有 arguments,需要手动使用 …args 参数代替
箭头函数不能用作 Generator 函数
说一下尾递归
尾递归就是:函数最后 单纯 return 函数,尾递归来说,由于只存在一个调用记录,所以永远不会发生‘占溢出’错误
ES6 出现的尾递归,可以将复杂度O(n)的调用记录,换为复杂度O(1)d调用记录
测试:不使用尾递归
function Fibonacci (n) {
if (n <= 1) { return 1 }
// return 四则运算
return Fibonacci(n - 1) + Fibonacci(n - 2)
}
Fibonacci(10) // 89
Fibonacci(100) // 超时
测试:使用尾递归
function Fibonacci(n, ac1 = 1, ac2 = 1) {
if (n <= 1) { return ac2 }
return Fibonacci(n - 1, ac2, ac1 + ac2)
}
Fibonacci(100) // 573147844013817200000
Fibonacci(1000) // 7.0330367711422765e+208
Fibonacci(10000) // Infinity
蹦床函数(协程):解决递归栈溢出问题,将函数变成循环
function trampoline(f) {
while (f && f intanceof Function){
f = f()
}
return f
}
尾递归优化:
function tco(f) {
var value
var active = false
var accumulated = []
return function accoumulator () {
accumulated.push(arguments)
// 除了第一次执行,其他的执行都是为了传参
if (!active) { // 很重要,如果不使用 active 关闭后续进入,sum 函数超过会溢出
// 在第一次进入递归优化时激活,关闭后续进入
active = true
// 有参数就执行
while (accumulated.length) {
// 调用 f ,顺便清除参数
value = f.apply(this, accumulated.shift())
// 由于 while 中又调用 f,f 调用 sum,然后 sum 在执行时给 accumulated 塞了一个参数
// 所以 while 循环会在 sum 返回结果前一种执行,知道递归完成
}
active = false
return value
}
}
}
var sum = tco(function (x, y) {
if (y > 0) {
// 此时的 sum 是 accumulator
// 执行 sum 等于给 accumulator 传参
return sum(x + 1, y -1)
} else {
return x
}
})
sum(1, 10000)
手写一个防抖函数
防抖函数:
function debounce(fn, wait) {
let timer = null
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
}
使用场景:输入框校验
手写一个节流函数
节流函数:
function throttle(fn, wait = 300) {
let flag = true
return (...args) => {
if (!flag) return
flag = false
setTimeout(() => {
fn.apply(this, args)
flag - true
}, wait)
}
}
使用场景:
1.延迟防抖函数:onscroll 时触发事件
2.立即执行防抖函数:按钮点击事件(魔种情况下 once 函数更合适)