JS代码题

191 阅读10分钟

防抖和节流

浏览器的 resizescrollkeypressmousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能 为了优化体验,我们就可以采用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

  1. 创建一个空对象,作为返回的对象实例
  2. 将空对象的_proto_指向构造函数的prototype
  3. 将构造函数的this指向当前的实例对象
  4. 执行构造函数的初始化代码
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

  1. 创建一个新对象,使用现有对象作为新创建对象的原型
  2. 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']

    1. 父属赋子类原型属性,此属于子共享属性,因此会互相影响。
    1. 实例化子类时,不能向父类传参

构造函数继承

在子类构造函数内部调用父类构造函数

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();

组合继承就没有缺点么? 问题就在于:无论何种场景,都会调用两次父类构造函数。

    1. 初始化子类原型时
    1. 子类构造函数内部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 实现拖拽功能?

一个元素的拖拽过程,我们可以分为三个步骤:

  1. 第一步是鼠标按下目标元素
  2. 第二步是鼠标保持按下的状态移动鼠标
  3. 第三步是鼠标抬起,拖拽过程结束

这三步分别对应了三个事件,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去访问服务端的资源了