防抖和节流
浏览器的 resize、scroll、keypress、mousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能
为了优化体验,我们就可以采用throttle(节流)和debounce(防抖)的方式来减少调用频率
1. 防抖
n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
function debounce(fn, ms) {
let timer
return function (...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
}, ms);
}
}
const taskTunc = () => { console.log('run task') }
const debounceTask = debounce(taskTunc, 1000)
window.addEventListener('scroll', debounceTask)
一直滚着不触发console(一直在clearTimeout) 停止滚动1s后才会触发(fn.apply(this, args))
2. 节流
n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
function throttle(fn, ms) {
let canrun = true;
return function (...args) {
if (!canrun) {
return
}
canrun = false
setTimeout(() => {
fn.apply(this, args)
canrun = true
}, ms)
}
}
const throttleFunc = () => { console.log('throttle task') }
const throttleTask = throttle(throttleFunc, 1000)
window.addEventListener('scroll', throttleTask)
滚动的过程中 1s触发一次console, 停止滚动不触发。
相同点:
- 都可以通过使用
setTimeout实现 - 目的都是,降低回调执行频率。节省计算资源
不同点:
- 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次
- 应用场景
防抖在连续的事件,只需触发一次回调的场景有:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
- 窗口大小
resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
节流在间隔一段时间执行一次回调的场景有:
- 滚动加载,加载更多或滚到底部监听
- 搜索框,搜索联想功能
3. new
- 创建一个空对象,作为返回的对象实例
- 将空对象的_proto_指向构造函数的prototype
- 将构造函数的this指向当前的实例对象
- 执行构造函数的初始化代码
function newFunc(constructFn, ...args) {
const obj = {};
obj.__proto__ = constructFn.prototype
let result = constructFn.apply(obj, args)
return result instanceof Object ? result : obj;
//根据构造函数返回类型做判断,如果是原始值,则忽略。 若返回对象则正常处理
}
4. Object.create
- 创建一个新对象,使用现有对象作为新创建对象的原型
- Object.create是内部定义一个函数,并且让F.prototype对象 赋值为引进的对象/函数 o,并return出一个新的对象。
Object.create = function (o) {
const F = function () {}
F.prototype = o
return new F();
}
a2.prototype.constructor === base // true
new 与 Object.create() 的区别
var base = function () {
this.name = 'haha'
}
let a1 = new base();
let a2 = Object.create(base);
console.log(a1.name) // haha
console.log(a2.name) // undefined => Object.create 失去了对原构造函数属性的访问
5. bind 返回一个函数
Function.prototype.newBind = function () {
let _this = this;
let args = Array.prototype.slice.call(arguments);
let newThis = args.shift(); // newThis为第一个参数,也就是新的bind指向;args为去掉第一个参数的剩余数组
// console.log('newThis', newThis);
const newFunc = function () {
//arguments为bind返回方法调用传入的参数,也要划入bind的参数列表 详情可参考:用例A-Run2
const newArgs = args.concat(...arguments);
// console.log('newArgs---', newArgs)
if (this instanceof newFunc) {
// bind()返回的函数通过new调用 绑定this为实例对象
_this.apply(this, newArgs)
} else {
// 普通调用,绑定this为bind传进来的第一个参数对象
_this.apply(newThis, newArgs)
}
}
// 支持 new 调用方式: 用例B-Run3
// 这样c = new a.bind(b) c可以找到a原型上的属性和方法
// 即a.bind(b)返回的构造函数被new调用时候,
// 生成的实例查找属性时,找不到就到构造函数newFunc的原型找,因为原型指向了_this的原型也就是调用bind的方法a的原型,因为a.bind(b)一开始bind方法被的this指向的是a,所以可以在a的原型上找
// 总结所以c的属性可以在a的prototype上找
newFunc.prototype = Object.create(_this.prototype)// Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。
return newFunc
}
// 测试用例A
const me = { name: 'Jack' }
const other = { name: 'Jackson' }
function say(...args2) {
console.log('args2-----', args2)
console.log(`My name is ${this.name || 'default'}`);
}
// 用例A-Run1
say.newBind(me)(other) // newThis {name: 'Jack'} My name is Jack
// 用例A-Run2
say.newBind(me,1,2)(3,4,5) //args2----- (5) [1, 2, 3, 4, 5] My name is Jack
// 测试用例B
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
// 用例B-Run1
var bindFoo = bar.bind(foo, 'daisy')('18'); // 1 daisy 18
// 用例B-Run2
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18');// undefined daisy 18
console.log(obj.habit); // shopping
console.log(obj.friend); // kevin
// 用例B-Run1
var bindFoo = bar.newBind(foo, 'daisy')('18'); // 1 daisy 18
// 用例B-Run2
var bindFoo = bar.newBind(foo, 'daisy');
var obj = new bindFoo('18');// undefined daisy 18
console.log(obj.habit); // shopping
console.log(obj.friend); // undefined -> obj实例的原型跟bar的原型没有绑定在一起
// 用例B-Run3
var bindFoo = bar.newBind(foo, 'daisy');
var obj = new bindFoo('18');// undefined daisy 18
console.log(obj.habit); // shopping
console.log(obj.friend); // kevin -> obj实例的原型跟bar的原型绑定在一起了 obj.prototype = Object.create(bar.prototype)
// 所以obj.friend = bar.prototype.friend = 'kevin';
6. call
Function.prototype.newCall = function (context) {
// 1. 判断有没有传入要绑定的对象,没有默认为window;如果是基本类型的话通过Object()方法进行转换
context = Object(context) || window;
// 2. 为context添加一个fn属性,值为this a.call(b) 这里的this指向调用函数a
context.fn = this
// 3. 保存返回值
let result = ''
// 4. 取出传递的参数,第一个参数是this
// 截取除第一个参数外剩余参数的方法
const args = [...arguments].slice(1) // 剩余参数
// 5. 执行方法,传入参数
result = context.fn(...args)
// 6. 删除该属性
delete context.fn
// 7. 将结果返回
return result
}
// 测试用例 没有context传入的情况
const obj = {
value: 'hello'
}
function fn(name, age) {
return {
value: this.value,
name: name,
age: age
}
}
const res = fn.newCall(obj, 'name', 'age') // {value: 'hello', name: 'name', age: 'age'}
console.log(res)
7. apply
Function.prototype.newApply = function (context, args) {
context = Object(context) || window
context.fn = this // this为fn
let result = ''
if (!args) {
result = context.fn()
} else {
// 将args参数展开
result = context.fn(...args) // 由context调用fn也就是obj调用fn,所以改变了this指向了obj
}
delete context.fn
return result
}
let obj = {
value: 'hello'
}
const fn = function (name, age) {
return {
value: this.value,
name,
age
}
}
const res = fn.newApply(obj, ['name', 'age'])
console.log(res) // {value: 'hello', name: 'name', age: 'age'}
8. deepCopy
// const cloneObj = JSON.parse(JSON.stringify(obj))
function deepCopy(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== 'object') { return obj }
// 防止循环引用
if (cache.get(obj)) return cache.get(obj)
// 支持函数
if (obj instanceof Function) {
return function () {
return obj.apply(this, arguments)
}
}
// 支持日期
if (obj instanceof Date) return new Date(obj)
// 支持正则对象
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags)
// 还可以增加其他对象,比如:Map, Set等,根据情况判断增加即可,面试点到为止就可以了
// 数组是 key 为数字素银的特殊对象
const res = Array.isArray(obj) ? [] : {}
// 缓存 copy 的对象,用于处理循环引用的情况
cache.set(obj, res)
Object.keys(obj).forEach((key) => {
if (obj[key] instanceof Object) {
res[key] = deepCopy(obj[key], cache)
} else {
res[key] = obj[key]
}
});
return res
}
// 测试
const source = {
name: 'Jack',
meta: {
age: 12,
birth: new Date('1997-10-10'),
ary: [1, 2, { a: 1 }],
say() {
console.log('Hello');
}
}
}
source.source = source
const newObj = deepCopy(source)
console.log(newObj.meta.ary[2] === source.meta.ary[2]); // false
console.log(newObj.meta.birth === source.meta.birth); // false
// hasOwnProperty 语法
// hasOwnProperty表示是否有某自己的属性。这个方法会查找一个对象是否有某个属性,但是不会去查找它的原型链
var obj = {
a: 1,
fn: function () {
},
c: {
d: 5
}
};
console.log(obj.hasOwnProperty('a')); // true
console.log(obj.hasOwnProperty('fn')); // true
console.log(obj.hasOwnProperty('c')); // true
console.log(obj.c.hasOwnProperty('d')); // true
console.log(obj.hasOwnProperty('d')); // false, obj对象没有d属性
var str = new String();
// split方法是String这个对象的方法,str对象本身是没有这个split这个属性的
console.log(str.hasOwnProperty('split')); // false
console.log(String.prototype.hasOwnProperty('split')); // true
9. 事件总线 | 发布订阅模式
class EventEmitter {
constructor() {
this.cache = {}
}
on(name, fn) {
if(this.cache[name]) {
this.cache[name].push(fn)
} else {
this.cache[name] = [fn]
}
}
off(name, fn) {
const tasks = this.cache[name]
if(tasks) {
const index = tasks.findIndex(f => f === fn || f.callback === fn)
if(index >=0) {
tasks.splice(index, 1)
}
}
}
emit(name, once = false) {
if(this.cache[name]){
const tasks = this.cache[name].slice() // 创建副本
for(let i in tasks) {
tasks[i]()
}
if(once) {
delete this.cache[name]
}
}
}
}
const eventBus = new EventEmitter();
const task1 = () => { console.log('task1') }
const task2 = () => { console.log('task2') }
eventBus.on('task', task1)
eventBus.on('task', task2)
eventBus.off('task', task1)
setTimeout(() => {
eventBus.emit('task')
}, 1000)
10. 柯里化
// 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args)
} else {
return function (...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}
const sum = function (a, b, c) {
return a + b + c
}
const curriedsum = curry(sum);
console.log(curriedsum(1, 2, 3))
console.log(curriedsum(1)(2, 3))
console.log(curriedsum(1)(2)(3))
11. instanceof
// object instanceof constructor
function isInstanceOf (child, parent) {
let proto = Object.getPrototypeOf(child);
let prototype = parent.prototype;
while(true) {
if(proto === null) return false
if(proto === prototype) return true
proto = Object.getPrototypeOf(proto)
}
}
// 测试
class Parent {}
class Childs extends Parent {}
const childs = new Childs()
console.log(isInstanceOf(childs, Parent), isInstanceOf(child, Child), isInstanceOf(child, Array)) // true true false
12. 数组扁平化
// 方法一
function recursionFlat(ary = []) {
const res = [];
ary.forEach(item => {
if (Array.isArray(item)) {
res.push(...recursionFlat(item))
} else {
res.push(item)
}
})
return res
}
// 方法二
function reduceFlat(ary = []) {
return ary.reduce((res, item) => res.concat(Array.isArray(item) ? reduceFlat(item) : item), [])
}
const source = [1, 2, [3, 4], [5, 6, [7, 8]], 9]
console.log(recursionFlat(source)) // [1,2,3,4,5,6,7,8,9]
console.log(reduceFlat(source)) // [1,2,3,4,5,6,7,8,9]
13. 对象扁平化
function objectFlat(obj = {}) {
const res = {};
function flat(item, prekey = '') {
Object.entries(item).forEach(([key, val]) => {
const newkey = prekey ? `${prekey}.${key}` : key
if (val && typeof val === 'object') {
flat(val, newkey)
} else {
res[newkey] = val
}
})
}
flat(obj)
return res
}
const source = { a: { b: { c: 'c', d: 'd' }, e: 'e' }, f: { g: 'g' } }
console.log(objectFlat(source)) // {a.b.c: 'c', a.b.d: 'd', a.e: 'e', f.g: 'g'}
14. 图片懒加载
// <img src="default.png" data-src="https://xxxx/real.png">
function isVisible(el) {
const position = el.getBoundingClientRect()
const windowHeight = document.documentElement.clientHeight
// 顶部边缘可见
const topVisible = position.top > 0 && position.top < windowHeight;
// 底部边缘可见
const bottomVisible = position.bottom < windowHeight && position.bottom > 0;
return topVisible || bottomVisible;
}
function imageLazyLoad() {
const images = document.querySelectorAll('img')
for (let img of images) {
const realSrc = img.dataset.src
if (!realSrc) continue
if (isVisible(img)) {
img.src = realSrc
img.dataset.src = ''
}
}
}
// 测试
window.addEventListener('load', imageLazyLoad)
window.addEventListener('scroll', imageLazyLoad)
// or
window.addEventListener('scroll', throttle(imageLazyLoad, 1000))
15. 类数组转化为数组的4种方式
// 类数组转化为数组
const arrayLike = {
0: '前端胖头鱼',
1: 100,
length: 2
}
Array.prototype.slice.call(arrayLike)
Array.prototype.slice.apply(arrayLike)
Array.prototype.splice.call(arrayLike, 0)
Array.prototype.concat.apply([], arrayLike)
Array.from(arrayLike)
16. sleep
// 实现一个函数,n秒后执行函数func
const sleep = function (func, ms) {
return new Promise ((resolve) => {
setTimeout(()=> {
resolve(func())
}, ms)
})
}
const consoleStr = (str) => {
return function () {
console.log(str)
return str
}
}
const doFns = async () => {
const name = await sleep(consoleStr('name'), 1000)
const sex = await sleep(consoleStr('sex'), 1000)
const age = await sleep(consoleStr('age'), 1000)
console.log(name, sex, age)
}
doFns()
17. sum函数
// 实现一个函数sum函数满足以下规律
// sum(1, 2, 3).valueOf() // 6
// sum(2, 3)(2).valueOf() // 7
// sum(1)(2)(3)(4).valueOf() // 10
// sum(2)(4, 1)(2).valueOf() // 9
// 1.sum函数可以传递一个或者多个参数
// 2.sum函数调用后返回的是一个新的函数且参数可传递一个或者多个
// 3.调用.valueOf时完成最后计算
const sum = function (...args) {
const add = (...args2) => {
args = [...args, ...args2]
return add
}
add.valueOf = () => args.reduce((sum, item) => sum + item, 0)
return add
}
console.log(sum(1, 2, 3).valueOf()) // 6
console.log(sum(2, 3)(2).valueOf()) // 7
console.log(sum(1)(2)(3)(4).valueOf()) // 10
console.log(sum(2)(4, 1)(2).valueOf()) // 9
18. 数组排序 - 排序
// 1. sort 排序
// a.数字排序
const arr = [3, 2, 4, 1, 5]
arr.sort((a, b) => a - b)
console.log(arr) // [1, 2, 3, 4, 5]
// b.字母排序
const arr = ['a', 'c', 'd', 'e', 'b', 'k']
arr.sort()
console.log(arr)// ['a', 'b', 'c', 'd', 'e', 'k']
// 2. 冒泡排序
function bubbleSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
console.log(i)
for (let j = 0; j < arr.length - 1 - i; j++) {
console.log(j)
if(arr[j] > arr[j+1]) {
let num = arr[j]
arr[j] = arr[j+1]
arr[j+1] = num
}
}
}
return arr;
}
console.log(bubbleSort([1,6,3,7,2,0,5,4])) // [0, 1, 2, 3, 4, 5, 6, 7]
19. 数组去重
// 19. 数组去重/合并/展开/是否为数组
//a.1 Set去重
let arr = [1, 2, 4, 3, 4, 2, 5, 6, 5, 6]
const newArr1 = [...new Set(arr)];
const newArr2 = Array.from(new Set(arr))
console.log(newArr1) // [1, 2, 4, 3, 5, 6]
console.log(newArr2) // [1, 2, 4, 3, 5, 6]
// a.2 indexOf去重
const newArr = arr.filter((item, index) => arr.indexOf(item) === index) // filter的第一个参数是item
console.log(newArr) // [1, 2, 4, 3, 5, 6]
// a.3 循环去重
let arr = [1, 2, 3, 2, 33, 55, 66, 3, 55];
const newArr = [];
for (let i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) == -1) {
newArr.push(arr[i])
}
}
console.log(newArr) // [1, 2, 3, 33, 55, 66]
// a.4 对象项去重
let arr1 = [
{id: 1, name: '汤小梦'},
{id: 2, name: '石小明'},
{id: 3, name: '前端开发'},
{id: 1, name: 'web前端'}
];
const unique = function (arr, key) {
return [...new Map(arr.map(item => [item[key], item])).values()]
}
console.log(unique(arr1, 'id'))
// 结果
// [
// {id: 1, name: "web前端"},
// {id: 2, name: "石小明"},
// {id: 3, name: "前端开发"}
// ]
// b. 合并
let arr3 = ['a', 'b']
let arr4 = ['c', 'd']
let arr5 = arr3.concat(arr4);
console.log(arr5);
let arr6 = [...arr3, ...arr4]
console.log(arr6); // ['a', 'b', 'c', 'd']
// c. 展平
// c.1 展平 | flat
let arr7 = [1, 2, [3, 4], [5, 6, [7, 8, 9]]];
let arrNew = arr7.flat(Infinity);
console.log(arrNew) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// c.2 展平 .join().split(',')
let arrNew = arr7.join().split(',').map(Number)
// c.3 展平 .toString().split(',')
let arrNew = arr7.toString().split(',').map(Number)
// c.4 展平 forEach
let flatArray = function (arr) {
let arrResult = []
arr.forEach((item) =>{
if(Array.isArray(item)) {
arrResult.push(...flatArray(item))
} else {
arrResult.push(item)
}
})
return arrResult;
}
console.log(flatArray(arr7))
// d. 是否为数组
let arr = []
console.log(arr instanceof Array)
console.log(arr.constructor === Array)
console.log(Object.prototype.toString.call(arr) === '[object Array]')
console.log(Array.isArray(arr))
20. 继承
原型链继承
function Game() {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
// LOL类
function LOL() {}
// LOL继承Game类
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game1 = new LOL();
const game2 = new LOL();
game1.skin.push('ss');
此时game1.skin与game2.skin相同,都是['s','ss']
-
- 父属赋子类原型属性,此属于子共享属性,因此会互相影响。
-
- 实例化子类时,不能向父类传参
构造函数继承
在子类构造函数内部调用父类构造函数
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
this.arg = arg;
}
Game.prototype.getName = function() {
return this.name;
}
// LOL类
function LOL(arg) {
Game.call(this, arg);
}
// LOL继承Game类
const game3 = new LOL(1);
const game4 = new LOL(2);
console.log(game3.arg, game4.arg);
game3.skin.push('1', '2');
console.log(game3.skin,game4.skin)
game3.getName();
//输出结果
1 2
['s', '1', '2'] ['s']
game3.getName is not a function
// 解决了共享属性问题&传参问题, 但是调用原型链上的方法时,显示not a function
原型链上的共享方法无法被读取继承,如何解决?
组合继承
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
this.arg = arg;
}
Game.prototype.getName = function() {
return this.name;
}
// LOL类
function LOL(arg) {
Game.call(this, arg);
}
// LOL继承Game类
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game3 = new LOL();
组合继承就没有缺点么? 问题就在于:无论何种场景,都会调用两次父类构造函数。
-
- 初始化子类原型时
-
- 子类构造函数内部call父类的时候
寄生组合继承
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
// LOL类
function LOL(arg) {
Game.call(this, arg);
}
// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
LOL.prototype = Object.create(Game.prototype);
LOL.prototype.constructor = LOL;
// LOL继承Game类
const game3 = new LOL();
提高:看起来完美解决了继承。js实现多重继承?
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
function Store() {
this.shop = 'steam';
}
Store.prototype.getPlatform = function() {
return this.shop;
}
// LOL类
function LOL(arg) {
Game.call(this, arg);
Store.call(this, arg);
}
LOL.prototype = Object.create(Game.prototype);
// LOL.prototype = Object.create(Store.prototype);
Object.assign(LOL.prototype, Store.prototype);
LOL.prototype.constructor = LOL;
// LOL继承Game类
const game3 = new LOL();
21. Promise
const PENDING = 'pending';
const FULFULLED = 'fulfilled';
const REJECTED = 'rejected';
class MPromise {
constructor(fn) {
this.status = PENDING;
this.value = '';
this.reason = '';
this.resolveMicroQueueTaskList = [];
this.rejectMicroQueueTaskList = [];
fn(this.resolve.bind(this), this.reject.bind(this));
}
resolve(value) {
if (this.status === PENDING) {
this.value = value;
this.status = FULFULLED;
}
}
reject(reason) {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
}
get status() {
return this._status;
}
set status(newStatus) {
this._status = newStatus;
if (newStatus === FULFULLED) {
this.resolveMicroQueueTaskList.forEach(cb => {
cb()
});
} else if (newStatus === REJECTED) {
this.rejectMicroQueueTaskList.forEach(cb => {
cb()
});
}
}
then(resolve, reject) {
const resolveFunction = resolve ? resolve : (value) => value;
const rejectFunction = reject ? reject : (reason) => reason;
const nextPromse = new MPromise((resolve, reject) => {
const resolveMicroQueueTask = () => {
queueMicrotask(() => {
const x = resolveFunction(this.value);
this.resolveNextPromise(x, resolve);
})
}
const rejectMicroQueueTask = () => {
queueMicrotask(() => {
const y = rejectFunction(this.reason)
this.resolveNextPromise(y, resolve);
})
}
switch (this.status) {
case PENDING: {
this.resolveMicroQueueTaskList.push(resolveMicroQueueTask);
this.rejectMicroQueueTaskList.push(rejectMicroQueueTask);
break;
}
case FULFULLED: {
resolveMicroQueueTask();
break;
}
case REJECTED: {
rejectMicroQueueTask();
}
}
})
return nextPromse;
}
catch(reject) {
this.then(null, reject);
}
resolveNextPromise(x, resolve) {
resolve(x);
}
static resolve(value) {
if(value instanceof MPromise) {
return value;
}
return new MPromise((resolve, reject) => {
resolve(value);
})
}
static reject (reason) {
if(reason instanceof MPromise) {
return reason
}
return new MPromise((resolve, reject) => {
reject(reason)
})
}
static race (promiseList) {
let promiseListLen = promiseList.length;
return new MPromise((resolve, reject) => {
if(promiseListLen === 0) {
resolve()
}
for(var i = 0; i< promiseList.length; i++){
MPromise.resolve(promiseList[i]).then(res=> {
resolve(res)
}).catch(err => {
reject(err)
})
}
})
}
static all (promiseList) {
let promiseListLen = promiseList.length;
let j = 0;
let promiseValList = [];
return new MPromise((resolve, reject) => {
if(promiseListLen === 0) {
resolve()
}
for(var i = 0; i< promiseList.length; i++){
MPromise.resolve(promiseList[i]).then(res=> {
j++
promiseValList.push(res);
if(promiseListLen === j) {
resolve(promiseValList)
}
}).catch(err => {
reject(err)
})
}
})
}
}
22. 异步并发数限制 | 下载图片显示 -> 图片同时下载的链接数量不可以超过3个。
var urls = [
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];
function spliting(str) {
if (str.indexOf('painting') > -1) {
return str.split('painting')[1]
} else if (str.indexOf('bpmn') > -1) {
return str.split('bpmn')[1]
}
}
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function () {
console.log("图片 " + spliting(url) + " load完成"); // 5. 加载完成
// 图片 1.png load完成
// 图片 2.png load完成
// 图片 5.png load完成
// 图片 4.png load完成
// 图片 7.png load完成
// 图片 6.pngload完成
// 图片 8.pngload完成
// 图片 3.pngload完成
resolve(img)
}
img.onerror = function () {
reject('load fail' + url)
}
img.src = url;
})
}
function limitLoad(urls, handler, limit) {
let sequence = [].concat(urls)
let promises = sequence.splice(0, limit).map((url, index) => { // 1. 先splice url 返回加载函数
console.log('sequence ' + spliting(url) + ' index' + index)
// sequence 1.png index0
// sequence 2.png index1
// sequence 3.png index2
return handler(url).then(() => { // 4. 开始加载
// 返回下标是为了知道数组中是哪一项最先完成 a!
console.log('下标index为' + index + '的图片下载加载完成') // 6. 下标打印
// 下标index为0的图片下载加载完成
// 下标index为1的图片下载加载完成
// 下标index为2的图片下载加载完成
return index
})
})
// 注意这里要将整个变量过程返回,这样得到的就是一个Promise,可以在外面链式调用
// sequence 为[4.png, 5.png, 6.png, 7.png, 8.png]
return sequence.
reduce((initialPromise, url) => { // 2. 累加后续要加载的urls
console.log('reduce url ' + spliting(url))
// reduce url 4.png
// reduce url 5.png
// reduce url 6.png
// reduce url 7.png
// reduce url 8.png
let result = initialPromise
.then(() => {
return Promise.race(promises) // 3. 开始加载1.png 2.png 3.png
})
.then(fastestIndex => { // 7. 获取到已经完成的下标 对应a 然后将"容器"内已经完成的那一项替换
promises[fastestIndex] = handler(url).then(() => {
console.log('下标 ' + fastestIndex + ' 替换的url ' + spliting(url) + ' 也load完成')
// 已经完成的下标 fastestIndex1替换url5.png
// 已经完成的下标 fastestIndex0替换url4.png
// 已经完成的下标 fastestIndex0替换url7.png
// 已经完成的下标 fastestIndex1替换url6.png
// 已经完成的下标 fastestIndex0替换url8.png
return fastestIndex
})
})
console.log(result) // a new promise
return result
}, Promise.resolve())// 初始化传入
.then(() => {
return Promise.all(promises)
})
}
limitLoad(urls, loadImg, 3).then(res => {
console.log("图片全部加载完毕");
console.log(res)
}).catch(err => {
console.log(err)
})
23. 异步串行 | 异步并行
// 串行就像队列,一个执行完,下一个再执行,如js同步执行
// 并行:是指系统拥同时处理多个任务的能力
// a. 异步串行
var a = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('a')
}, 3000)
})
}
var b = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('b')
}, 2000)
})
}
// 1. promise.then 实现异步串行
console.time('test')
a().then(aa => {
console.log(aa) //a
return b()
}).then(bb => {
console.log(bb) // b
console.timeEnd('test') // test: 5004.426025390625 ms
})
// 2. async await 实现异步串行
(async () => {
console.time('test')
var aa = await a()
var bb = await b()
console.log(aa, bb) // a b
console.timeEnd('test') // test: 5004.216064453125 ms
})()
// 3. reduce 实现异步串行 => 当我们有多个异步,比如2000个该如何
var createpromises = function (i) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('timein ' + i)
resolve('a')
}, i * 1000)
})
}
var promisesList = [createpromises(10), createpromises(3), createpromises(2), createpromises(5), createpromises(4), createpromises(6)]
function reducePromise(promiseslist) {
promiseslist.reduce((pre, item) => {
return pre.then(item)
}, Promise.resolve())
}
reducePromise(promisesList)
// timein 2
// timein 3
// timein 4
// timein 5
// timein 6
// timein 10 第10秒
// b. 异步并行
// 1. promise.all实现异步并行
var a = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('a')
}, 3000)
})
var b = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('b')
}, 4000)
})
console.time('test')
Promise.all([a, b]).then(data => {
console.timeEnd('test') // test: 4004.47216796875 ms
console.log('data', data) // data (2) ['a', 'b']
}).catch(err => {
console.log('err', err)
})
// 2. async await 实现异步并行
var createpromises = function (i) {
return new Promise((resolve, reject) => {
setTimeout(()=> {
console.log('timein' + i)
resolve(i)
}, i * 1000)
})
}
var promisesList = [
createpromises(6),
createpromises(5),
createpromises(4),
createpromises(10),
createpromises(1),
createpromises(2),
createpromises(3)
]
var fn = async (promiseslist) => {
let awaitArr = []
for (var i = 0; i < promiseslist.length; i++){
let awaitpromise = await promiseslist[i]
awaitArr.push(awaitpromise)
}
return awaitArr
}
fn(promisesList).then(res=> {
console.log('res', res)
})
// timein1
// timein2
// timein3
// timein4
// timein5
// timein6
// timein10
// res (7) [6, 5, 4, 10, 1, 2, 3]
LRU缓存函数
jquery的链式调用是怎么实现的?
jQuery 可以链式调用,比如:
$("div").eq(0).css("width", "200px").show();
链式调用的核心就在于调用完的方法将自身实例返回。
// 定义一个对象
class listFunc {
// 初始化
constructor(val) {
this.arr = [...val];
return this;
}
// 打印这个数组
get() {
console.log(this.arr);
return this;
}
// 向数组尾部添加数据
push(val) {
console.log(this.arr);
this.arr.push(val);
return this;
}
// 删除尾部数据
pop() {
console.log(this.arr);
this.arr.pop();
return this;
}
}
const list = new listFunc([1, 2, 3]);
list.get().pop().push('ldq')
如何使用js计算一个html页面有多少种标签?
- 如何获取所有DOM节点
- 伪数组如何转为数组
- 去重
解答:
- 获取所有的DOM节点。
document.querySelectorAll('*')
此时得到的是一个NodeList集合,我们需要将其转化为数组,然后对其筛选。
- 转化为数组
[...document.querySelectorAll('*')]
- 获取数组每个元素的标签名
[...document.querySelectorAll('*')].map(ele => ele.tagName)
使用一个map方法,将我们需要的结果映射到一个新数组。
- 去重
new Set([...document.querySelectorAll('*')].map(ele=> ele.tagName)).size
我们使用ES6中的Set对象,把数组作为构造函数的参数,就实现了去重,再使用Set对象的size方法就可以得到有多少种HTML元素了。
怎么使用 js 实现拖拽功能?
一个元素的拖拽过程,我们可以分为三个步骤:
- 第一步是鼠标按下目标元素
- 第二步是鼠标保持按下的状态移动鼠标
- 第三步是鼠标抬起,拖拽过程结束
这三步分别对应了三个事件,mousedown 事件,mousemove 事件和 mouseup 事件。 只有在鼠标按下的状态移动鼠标我们才会执行拖拽事件,因此我们需要在 mousedown 事件中设置一个状态来标识鼠标已经按下,然后在 mouseup 事件中再取消这个状态。在 mousedown 事件中我们首先应该判断,目标元素是否为拖拽元素,如果是拖拽元素,我们就设置状态并且保存这个时候鼠标的位置。然后在 mousemove 事件中,我们通过判断鼠标现在的位置和以前位置的相对移动,来确定拖拽元素在移动中的坐标。最后 mouseup 事件触发后,清除状态,结束拖拽事件。
怎么使用 setTimeout 实现 setInterval?
setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。 针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
// 思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果
function mySetInterval(fn, timeout) {
// 控制器,控制定时器是否继续执行
var timer = {
flag: true
};
// 设置递归函数,模拟定时器执行。
function interval() {
if (timer.flag) {
fn();
setTimeout(interval, timeout);
}
}
// 启动定时器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
js 中的倒计时,为什么会有时间偏差?
前端实现中我们一般通过 setTimeout 和 setInterval 方法来实现一个倒计时效果。但是使用这些方法会存在时间偏差的问题,这是由于 js 的程序执行机制造成的,setTimeout 和 setInterval 的作用是隔一段时间将回调事件加入到事件队列中,因此事件并不是立即执行的,它会等到当前执行栈为空的时候再取出事件执行,因此事件等待执行的时间就是造成误差的原因
js中数组是如何在内存中存储的?
数组不是以一组连续的区域存储在内存中,而是一种哈希映射的形式。它可以通过多种数据结构来实现,其中一种是链表。 js分为基本类型和引用类型:
- 基本类型是保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,通过按值访问;
- 引用类型是保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用
怎么实现一个扫描二维码登录PC网站的需求?
二维码登录本质
二维码登录本质上也是一种登录认证方式。既然是登录认证,要做的也就两件事情:
- 告诉系统我是谁
- 向系统证明我是谁
扫描二维码登录的一般步骤
- 扫码前,手机端应用是已登录状态,PC端显示一个二维码,等待扫描
- 手机端打开应用,扫描PC端的二维码,扫描后,会提示"已扫描,请在手机端点击确认"
- 用户在手机端点击确认,确认后PC端登录就成功了
具体流程
生成二维码
- PC端向服务端发起请求,告诉服务端,我要生成用户登录的二维码,并且把PC端设备信息也传递给服务端
- 服务端收到请求后,它生成二维码ID,并将二维码ID与PC端设备信息进行绑定
- 然后把二维码ID返回给PC端
- PC端收到二维码ID后,生成二维码(二维码中肯定包含了ID)
- 为了及时知道二维码的状态,客户端在展现二维码后,PC端不断的轮询服务端,比如每隔一秒就轮询一次,请求服务端告诉当前二维码的状态及相关信息,或者直接使用websocket,等待在服务端完成登录后进行通知
扫描二维码
- 用户用手机去扫描PC端的二维码,通过二维码内容取到其中的二维码ID
- 再调用服务端API将移动端的身份信息与二维码ID一起发送给服务端
- 服务端接收到后,它可以将身份信息与二维码ID进行绑定,生成临时token。然后返回给手机端
- 因为PC端一直在轮询二维码状态,所以这时候二维码状态发生了改变,它就可以在界面上把二维码状态更新为已扫描
状态确认
- 手机端在接收到临时token后会弹出确认登录界面,用户点击确认时,手机端携带临时token用来调用服务端的接口,告诉服务端,我已经确认
- 服务端收到确认后,根据二维码ID绑定的设备信息与账号信息,生成用户PC端登录的token
- 这时候PC端的轮询接口,它就可以得知二维码的状态已经变成了"已确认"。并且从服务端可以获取到用户登录的token
- 到这里,登录就成功了,后端PC端就可以用token去访问服务端的资源了