自己用的I_Plan

159 阅读21分钟

以下复习内容分成多个模块,是我自己的用的,你们不要看,我还没写完

JS部分

JS基础

  1. js的基本数据类型/引用数据类型,作用是什么呢。
基本数据类型
1.String,基本字符串类型 比如:'word', '1'
2.Number,数字类型 12
3.Boolean,布尔值类型, truefalse
4.Null,空类型 typeof null === 'object'
5.Undefined,未定义类型, typeof undefined === 'undefined'
6.Symbol 唯一的值类型,typeof Symbol() === 'symbol'
7.BigInt 可以表示任意大的整数,大于2^53 - 1 typeof BigInt('10') === 'bigint'
引用数据类型/复杂数据类型
1.Object,{name: 'xxx'}
2.Array, ['str', 1]
3.Functionfunction(){}
  1. 说一下你对原型/原型链的理解。
从以下几点回答:
1. 开局一张图。
2. 理解几个名词:
构造函数(`function Person(){}`,Person叫做构造函数),
原型对象(Person.prototype),
实例(let f1 = new Person(), f1叫做实例)
3. 两个相等原则 Person.prototype.constructor == Person,f1.__proto__ == Person.prototype。
last answer: 实例,原型,构造函数都可以通__proto__关联起来,这种关系叫做原型链。
查找某个实例的属性可以通过原型链一级一级向上去找,直到Object.prototype =》null
  1. 判断数据类型的几种方法。
1. 基本数据类型可以通过typeof判断。
2. 也可以通过instanceof判断 [1,2] instanceof Array
3. Object.prototype.toString.call([1,2]).slice(8, -1)
4. [1,2].constructor
5. Array.isArray([])
  1. 变量提升,var/let/const的区别。
var存在变量提升,let/const不存在
let/const是块级作用域
const定义常量不能修改,在初始化的时候必须赋值
  1. this指向的问题。
1.this的指向,在普通函数中谁调用指向谁。
2.在箭头函数中,this的指向决定于外层函数。
3.call,apply,bind可以改变this的指向。
  1. 箭头函数和普通函数的区别。
1.箭头函数没有this指向的问题。
2.箭头函数不能作为构造函数。
3.箭头函数没有arguments
  1. js的隐式转换的方法。
toString() 返回表示该对象的字符串,当对象被期望已字符串形式处理的时候,自动调用。
valueOf() 返回对象的原始值。
== 类型转换之后进行比较
!= 类型转换之后比较
!! 取反在取反 返回布尔值类型
~~ 对于浮点数,~~value可以代替parseInt(value)
~ 类似 arr.indexOf(e) > -1  === ~arr.indexOf(e)
  1. null和undefined的区别。
null 是空类型。在内存里的表示就是,栈中的变量没有指向堆中的内存对象。
可以在变量使用完成之后手动设置null,释放内存。
undefined1)声明变量没有赋值。(2)访问了不存在的属性,或不存在的变量。(3)没有返回值的函数。
(4)使用void 处理表达式,void [] === undefined
  1. addEventLinstenr的第三个参数。
target.addEventListener(type, listener, options);
  • type 表示监听事件类型的字符串
  • listener 事件的回调参数
  • options 可选参数配置 capture 捕获还是委托,once 只调用一次, passive为true不会调用preventDefault()
  1. 如何阻止事件冒泡。
  1. js继承的几种理解和区别。
  • 原型链继承
function Person(){
    this.colors = ['red', 'orange']
}
function Student(){}
Student.prototype = new Person()
let s1 = new Student()
s1.colors.push('yellow')

let s2 = new Student()
console.log(s2.colors)
特点:多个实例对引用类型的操作会被篡改,无法传参
  • 构造函数继承
function Person(name){
    this.name = name
}
Person.prototype.sayName = function(){
    console.log(`我是:${this.name}`)
}
function Student(...arg){
    Person.apply(this, arg)
}
Student.prototype.getName = function(){
     console.log(`names:${this.name}`)
}
let s1 = new Student('李四')
s1.sayName() // Uncaught TypeError: s1.sayName is not a function
特点:无法调用父类构造函数上的原型方法,可以传参,父类的引用属性不会被共享。
  • 组合继承
function Person(name){
    this.name = name
    this.colors = ['red', 'orange']
}
Person.prototype.sayName = function(){
    console.log(`我是:${this.name}`)
}
function Student(){
    Person.apply(this, arguments)
}
Student.prototype = new Person()
Student.prototype.constructor = Student
let s1 = new Student('李四')
s1.colors.push('blue')
let s2 = new Student('王五')
console.log(s2.colors) // ['red', 'orange']
s2.sayName() // 我是:王五
特点:结合两者的优点,可以传参数,可以调用父类原型的方法,父类的引用属性不会被共享。
调用了两次父类方法,会在__proto__上存在相同的属性
  • 原型式继承
function create(O){
    function F(){}
    F.prototype = O
    return new F()
}
const Person = {
    friends: ['a', 'b']
}
consnt p1 = create(Person)
p1.friends.push('c')

  • 寄生式继承
function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}
const Person = {
    friends: ['a', 'b']
}
function createStudent(o){
    const p = object(o);
    p.sayName = function(){
        console.log(`我是:${this.name}`)
    }
    return p;
}
const s1 = createStudent(Person)
s1.friends.push('c')
const s2 = createStudent(Person)
console.log(s2.friends)
  • 寄生式组合继承
function create(o){
    function F(){}
    F.prototype = o
    return new F()
}
function insertPrototype(child,parent){
    const p = create(parent.prototype)
    p.constructor = child
    child.prototype = p
}
function Person(name){
    this.name = name
    this.friends = ['a', 'b']
}
function Student(...arg){
    Person.call(this,arg)
}
const s1 = new Student('张三')
s1.friends.push('c')
const s2 = nnew Student('李四')
console.log(s2.frindes)
特点:综合了以上的优点,多个实例之间不会影响属性方法,不会影响父类的引用数据类型,调用一次构造方法
  • Class继承
class Person {
    constructor(name){
        this.name = name
    }
    sayName(){
        console.log(`我是:${this.name}`)
    }
}
class Student extends Person{
    constructor(name, age){
        super(name)
        this.age = age
    }
    sayAge(){
        console.log(`我的年龄是:${this.age}`)
    }
}
const s1 = new Student('张三', 18)
  1. Map和Object的区别。
  • Map: 是一种类似对象的数据结构,但不局限于对象的key只能是字符串
const obj = Object.create({})
obj.name = 'zhangsan'
const map1 = new Map()
map1.set(obj,'张三')
map1.set(111,'张三')
console.log(map1)
console.log(map1.get(obj))
console.log(map1.has(obj))
console.log(map1.size)
console.log(map1.delete(111))
console.log(map1)
  • Object:是一种复杂数据类型,由key和value的键值对数据格式组成 MDN参考
  1. Set和Array的区别。
  • Set是一种类似数组的数据结构,和数组的区别是值具有唯一性。经常用于数组的去重。
const set1 = new Set()
console.log(set1)
set1.add(1)
set1.add('1')
console.log(set1)
set1.delete(1)
console.log(set1)
console.log(set1.has(1))
console.log(set1.size)
console.log(set1)
  • Array是数组结构。需要理解一下api的使用。 参考MDN
api很多,按照一定的标准分一下类
#### 返回iterator
- entries() Array Iterator对象,该对象包含数组中每个索引的键/值对。
- values() 方法返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值
- keys() 方法返回一个包含数组中每个索引键的Array Iterator对象。

#### 遍历查找满足条件
- every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值
- filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素
- find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
- findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。
- includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false。
- indexOf() 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
- lastIndexOf() 方法返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,
如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始
- some() 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值。

#### 更改数组的长度
- pop()
- push()
- shift()
- unshift()
- concat()
- splice()
- reverse()

#### 循环数组做更多处理
- map()
- forEach()
- reduce()
- reduceRight()
  1. js中任务执行的的几种方法。异步任务都有哪些?说说你的理解。
  • 同步执行:比如去火车站买票,只有一个窗口,这样只能同时一个人去购买,接下来的人等待,也就是阻塞其它任务。
  • 异步执行:比如还在火车站买票,有人在买着,你可以先去做别的事情,等到你了你在执行,不会阻塞任务,可以通过回调函数的方式进入执行队列。
  • 异步任务有如下几个:
    1. 定时任务 setTimeout,setInterval
    2. 网络请求 ajax
    3. 事件监听器 addEventListener
  • 对网络请求的异步任务我们有如下几种实现方式
    1. 回调函数
    function request({url, method, data, success, error}){
        let xhr = new XMLHttpRequest()
        xhr.open(method, url)
        xhr.send(data)
        xhr.onreadystatechange = function (){
           if(xhr.readystate === 4){
            if(xhr.status>=200 && xhr.state<=300){
                success(JSON.parse(xhr.responseText))
            } eles {
                error(xhr.statusText)
            }
           }
        }
    }
    request({
        url: `http://localhost:3001/api/getList?id=${id = 1}`,
        method: 'get',
        data: {id: 1},
        success: (res) => {
            console.log(res)
            // 如果使用回调函数
            request({
            url: `http://localhost:3001/api/getList?id=${id = 2}`,
            method: 'get',
            data: {id: 2},
            success: (res) => {
                console.log(res)
                // ...依次嵌套形成了回调地狱
            }
        })
        }
    })
    
    1. promise
    const promise = new Promise((resolve, reject) => {
        resolve('success')
    })
    promise.then(data=> {
        return data + '1'
    }).then(data=> {
        return data + '2'
    })
    具体详情的作用可以看下面的api实现中
    
    1. generator
    1. 通过带*的函数创建方式一样创建 generator函数。比如:
    function *getList(){
        yiled 1
        yiled 2
    }
    2. GeneratorES6提出的一种异步编程的方案。因为手动创建一个iterator十分麻烦,
    因此ES6推出了generator,用于更方便的创建iterator。
    也就是说,Generator就是一个返回值为iterator对象的函数
    3. 可以使用 for of来遍历iterator对象比如:
    const arr = [1,2,3]
    const iterator = arr.entries()
    for(let value of iterator){
        // value: [0,1]
        // value: [1,2]
        // value: [2,3]
    }
    原生具备iterator接口的数据结构如下:
    Array/String/Map/Set/arguments/NodeList
    这些数据结构都有一个Symbol.iterator属性,可以直接通过这个属性来直接创建一个迭代器
    const arr1 = [1,2,3]
    const arrIterator = arr1[Symbol.iterator]()
    console.log(arrIterator.next())
    4. generaotr函数返回的就是一个Iterator对象。
    使用要点
    - 不能在箭头函数中使用yield
    - yield表达式只能用在 Generator函数里面
    - 不能在return后使用yield语句
    作用:用同步函数的写法实现异步
    
    1. async/await
    async/awaitGenerator函数的语法糖,就是一种封装好的简介写法。
    本质上还是用Promise来实现的。
    好处:
    - 自带执行器,不用想Generator函数一样需要手动调用next()
    - 更加清楚,代码简洁
    - await后跟的不一定是Promise
    async function getList() {
        const data = await xhrHttpRequest({
            url: `http://localhost:3001/list/?id=2&num=${id}`,
            method: 'get'
        })
        const childId = (JSON.parse(data))?.['data']?.childId
        const res = await xhrHttpRequest({
            url: `http://localhost:3001/detail/?id=${childId}`,
            method: 'get'
        })
        console.log(res)
    }
    

详解JS的四种异步解决方案

多线程/单线程, 同步/异步,阻塞/非阻塞

  1. for/for in /for of/ map/ forEach 有什么区别。
  • for循环,最基本的遍历数据的方式
let str = '';
for (let i = 0; i < 9; i++) {
  str = str + i;
}
  • for...in 语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性。
注意点:
一般是用来遍历对象
不建议用来遍历数组,但是可以遍历
const arr1 = [1,2,3]
arr1.prototype.a = 4
for(let i in arr1){
    console.log(arr1[i])
}
不能遍历对象
以上的意思通俗讲就是可以遍历Iterator对象
for(let value of new Array(1,2,3) || new Map([1,2,3]) || new Set([1,2,3])){
    console.log(value)
}
  • map是数组的api,用来循环处理数组中的元素,返回一个新的数组
const arr1 = [1,2,3].map(el => {
    return el * 2
})
  • forEach,数组的api,和map不同的是,他每次调用返回的都是undefined,并且不会影响原数组。
特点如下:
1. 除了抛出异常以外,没有办法中止或跳出forEach()循环
2. 每次都是返回undefined
  1. 位运算符的理解。 MDN文档-表达式与运算符
  • & 位与运算符
  • 位或运算符
  • 位非运算符
  • ^ 位异或
  • << 位右移
  • >> 位左移

JS部分API的理解

  1. Array的一些常用api
  • slice和splice的区别
- slice不会改变原数组
- slice常用于数组的浅拷贝
const arr1 = [1,2,3]
console.log(arr1.slice(0, 3))
slice(start, end)
- splice会改变原数组
- 经常用来删除数组中的指定元素,返回删除的数组
const arr2 = [1,2,3]
console.log(arr2.splice(1,2))
  • 创建数组的方式都有哪几个
- new Array(1,2,3)
- [1,2,3]
- Array.from(NodeList)
  • 遍历数组的方法
- map() 返回新数组,不改变原数组
- forEach() 返回undefined,不改变原数组
  • 数组断言的方法
- some()
- every()
- filter()
  • 查找数组中某些元素的方法
- includes()
- indexOf()
- lastIndexOf()

掘金数组分类

  1. Object的属性判断/遍历
  2. new Map()的遍历,取值等api
  3. new Set()
  4. Promise/all/race/allSetlled
  5. indexOf/includes的区别
  6. 数组的交并差

JS手写

  1. 几种继承的手写实现。
  • 原型链继承
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.fruit = ['apple', 'banana', 'orange'];
}
Person.prototype.sayName = function () {
  console.log(this.name);
}
function Student(name, age, grade) {
  this.name = name
  this.age = age
  this.grade = grade;
}
Student.prototype = new Person();
let s1 = new Student('张三', 18, '一年级');
s1.fruit.push('pear');
let s2 = new Student('李四', 19, '二年级');
console.log(s2.fruit)
  • 构造函数继承
function Person(name){
    this.name = name
}
Person.prototype.sayName = function(){
    console.log(`我是:${this.name}`)
}
function Student(...arg){
    Person.apply(this, arg)
}
Student.prototype.getName = function(){
    console.log(`names:${this.name}`)
}
let s1 = new Student('李四')
s1.sayName()
  • 组合继承
function Person(name){
    this.name = name
    this.colors = ['red', 'orange']
}
Person.prototype.sayName = function(){
    console.log(`我是:${this.name}`)
}
function Student(){
    Person.apply(this, arguments)
}
Student.prototype = new Person()
Student.prototype.constructor = Student
let s1 = new Student('李四')
s1.colors.push('blue')
let s2 = new Student('王五')
console.log(s2.__proto__)
s2.sayName()
  • 原型式继承
function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}
const Person = {
    friends: ['a', 'b']
}
const p1 = object(Person)
p1.friends.push('c')
const p2 = object(Person)
console.log(p2.friends)
  • 寄生式继承
function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}
const Person = {
    friends: ['a', 'b']
}
function createStudent(o){
    const p = object(o);
    p.sayName = function(){
        console.log(`我是:${this.name}`)
    }
    return p;
}
const s1 = createStudent(Person)
s1.friends.push('c')
const s2 = createStudent(Person)
console.log(s2.friends)
  • 寄生组合式继承
function create(o){
    function F(){}
    F.prototype = o;
    return new F();
}
function insertPrototype(subType, superType){
    const p = create(superType.prototype)
    p.constructor = subType
    subType.prototype = p;
}
function Person(name){
    this.name = name
    this.friends = ['a', 'b']
}
function Student(){
    Person.call(this, [...arguments])
}
insertPrototype(Student, Person)

const s1 = new Student('张三')
s1.friends.push('c')
const s2 = new Student('李四')
console.log(s2.friends)
  • Class继承
class Person {
    constructor(name){
        this.name = name
    }
    sayName(){
        console.log(`我是:${this.name}`)
    }
}
class Student extends Person{
    constructor(name, age){
        super(name)
        this.age = age
    }
    sayAge(){
        console.log(`我的年龄是:${this.age}`)
    }
}
const s1 = new Student('张三', 18)
  1. call/apply/bind的手写实现。
  • call
Function.prototype._call = function (context = window) {
    let fn =  Symbol()
    const arg = [...arguments].slice(1)
    context[fn] = this
    const result = context[fn](...arg)
    delete context[fn]
    return result
}
  • apply
Function.prototype._apply = function (context = window, arg) {
    if(!Array.isArray(arg)) throw new Error('参数必须是array')
    let fn =  Symbol()
    context[fn] = this
    const result = context[fn](arg)
    delete context[fn]
    return result
}
  • bind
Function.prototype._bind = function (context = window){
    const fn = function (){}
    const arg = [...arguments].slice(1)
    let _this = this
    let result = function (){
        let arg1 = [...arguments]
        return _this.apply(this instanceof fn ? this : context, arg.concat(arg1))
    }
    fn.prototype = this.prototype
    result.prototype = new fn()
    return result
}
  1. new的作用及手写。
  • 在new一个对象的时候都干了什么事情。
  • 创建一个空对象
  • 把空对象的__proto__绑定到构造函数的prototype
  • 绑定this
  • 返回结果:判断返回是在对象的原型上否
const _new = (fn,...arg) => {
  if(typeof fn !== 'function') return false
  let obj = {}
  obj.__proto__ = fn.prototype
  let result = fn.apply(obj, arg)
  return result instanceof Object ? result : obj
}
  1. instanceof的作用以及手写。
const _instanceof = (value, type) => {
        let left = value.__proto__;
        let right = type.prototype;
        while (true){
            if(left === null){
                return false
            }
            if(left === right){
                return true
            }
            left = left.__proto__
        }
 }
  1. 防抖和节流的区别以及实现。
### 使用场景

- 浏览器的重绘和回流都会消耗一些性能,如DOM节点频繁的改变会造成卡顿等状态,浏览器窗口的缩小放大。
- 频繁发起相同网络请求也会造成服务器性能开销,比如做输入框的联想搜索
- 其实还有很多其它场景不在一一举例。

### 概念的理解

- 防抖--在事件触发n秒后执行回调,如果n秒内再次触发则重新计时。
- 节流--在规定时间内事件只会触发一次,如果多次触发只有一次生效。

## 相同和不同

- 相同:都是降低了高频事件的执行。
- 不同:防抖是重新计时,节流是不在执行

## 防抖的实现和应用

联想搜索,浏览器窗口的缩放

计时器版本
const debounce = (fn, time, immediate) => {
    let timer = null
    return (...arg) => {
        if(timer) clearTimeout(timer)
        if( immediate&& !timer){
            timer = setTimeout(null, time)
            fn.apply(this, arg)
        } else {
            timer = setTimeout(()=> {
                fn.apply(this, arg)
            }, time)
        }
        
    }
}
## 节流的实现和应用
浏览器的滚动,按钮的避免频繁点击
const throttle = (fn, time, immediate) => {
    let timer = null, start = 0;
    return (...arg) => {
       if(immediate){
           let now = new Date()
           if(now - start > time){
               fn.apply(this, arg)
               start = now
           }
       } else {
           if(!timer){
               timer = setTimeout(() => {
                   timer = null
                   fn.apply(this, arg)
               }, time)
           }
       }
    }
}
  1. 函数的柯里化的应用场景以及实现。
#### 柯里化的理解
1. 概念是将一个有多个参数的函数转化为单个参数的函数。
2. 柯里化函数返回的是一个函数。
2. 柯里化本质上是降低通用性,提高适用性。
#### 柯里化的应用
1. 参数的复用
  • ES6通用实现
const curry = fn => {
    const next = (...arg) => {
        if(fn.length === arg.length) return fn(...arg)
        return (...arg1) => {
            return next(...arg, ...arg1)
        }
    }
    return next
}
  • Demo
const checkTelNumber = (reg , str) => {
    return reg.test(str)
}
const mobileReg = /^1\d{10}$/
const _check = curry(checkTelNumber)
const _checkMobile = _check(mobileReg)
console.log(_checkMobile(11581239162))
  1. Promise的理解以及和async的区别,以及几个常用api的手写。
- 在使用new Promise()的时候传入的是一个执行器executor,executor接受两个参数
  resolve,reject,并且立即执行。
- 我们都知道的Promise有三个状态
  pending
  fulfilled
  rejected
  状态只能从 pending =》fulfilled
         pending =》rejected

- Promise有一个then方法,接受两个参数,成功的回调(onFulfilled),失败的回调(onRejected)
  如果onFulfilled不是函数则忽略,如果onRejected不是函数忽略
  如果onFulfilled是函数,则在状态由pending转变为fulfilled的时候执行一次,第一个参数是执行的结果传入。
  如果onRejected是函数,则第一个参数是失败的原因,函数执行一次。
class _Promise {
  constructor(executor) {
    // 定义初始的状态
    this.status = "pending";
    this.value = undefined; // 成功的值
    this.reason = undefined; // 失败的值
    // 支持异步回调,使用一个成功的队列和失败的队列把回调函数存起来
    this.fulfilledList = [];
    this.rejectedList = [];
    // 在执行的时候循环这个队列
    let resolve = (value) => {
      // resolve的方法
      if (this.status === "pending") {
        this.status = "fulfilled";
        this.value = value;
        this.fulfilledList.map((fn) => fn());
      }
    };
    let reject = (reason) => {
      // reject的方法
      if (this.status === "pending") {
        this.status = "rejected";
        this.reason = reason;
        this.rejectedList.map((fn) => fn());
      }
    };
    try {
      executor(resolve, reject); // 直接执行 执行器函数
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
    // 判断第一个参数是function否
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : null;
    onRejected = typeof onRejected === "function" ? onRejected : null;
    return new _Promise((resolve, reject) => {
      if (this.status === "pending") {
        this.fulfilledList.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
            } catch (e) {
              reject(e);
            }
          });
        });
        this.rejectedList.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
            } catch (e) {
              reject(e);
            }
          });
        });
      }
      // 如果是function,且状态为fulfilled,执行成功回调
      if (this.status === "fulfilled") {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
          } catch (e) {
            reject(e);
          }
        });
      }
      // 如果是function,且状态是rejected,执行失败回调
      if (this.status === "rejected") {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
          } catch (e) {
            reject(e);
          }
        });
      }
    });
  }
  static resolve(value) {
    if (value instanceof _Promise) {
      // 判断返回的是在 promise的原型链上
      return value;
    } else {
      // 如果不在执行resolve
      this.status = "fulfilled";
      return new _Promise((resolve) => resolve(value));
    }
  }
  static reject(value) {
    this.status = "rejected";
    return new _Promise((resolve, reject) => {
      reject(value);
    });
  }
  catch(onRejected) {
    this.then(null, onRejected);
  }
  finally(callback) {
    return new _Promise(() => {}).then(
      (res) => {
        return _Promise.resolve(callback()).then(() => res);
      },
      (err) => {
        return _Promise.resolve(callback()).then(() => err);
      }
    );
  }
  // 传入一组,全部成功返回成功的数组,如果有任何一个失败返回这个失败的结果
  static all(promises) {
    let result = [];
    let count = 0;
    if (promises.length === 0) return Promise.resolve([]);
    return new Promise((resolve, reject) => {
      promises.forEach((p, i) => {
        Promise.resolve(p)
          .then((item) => {
            count += 1;
            result[i] = item;
            if (promises.length === count) {
              resolve(result);
            }
          })
          .catch(reject);
      });
    });
  }
  // 全部执行,返回所有成功和失败的结果数组
  static allSettled(promises) {
      let result = [],count = 0;
      return new _Promise((resolve, reject)=> {
          promises.forEach((p, i)=> {
            _Promise.resolve(p).then(item=> {
              count +=1
              result.push({
                status: 'fulfilled',
                value: item
              })
              if(promises.length === count){
                resolve(result)
              }
            }, err=> {
              count +=1
              result.push({
                status: 'rejected',
                reason: err
              })
              if(promises.length === count){
                resolve(result)
              }
            })
          })
      })
  }
  // 返回最先执行完的那个成功或者失败。
  static race(promises) {
    return new _Promise((resolve, reject) => {
      promises.forEach(p => {
        _Promise.resolve(p).then(resolve).catch(reject)
      })
    })
  }
  // 返回第一个成功的promise或者所有失败的 AggregateError集合
  static any(promises){
    let count = 0
    return new _Promise((resolve, reject) => {
      promises.forEach((p, i) => {
        _Promise.resolve(p).then(res => {
          resolve(res)
        }, err => {
          count +=1
          if(count === promises.length){
            return new AggregateError('all promise are rejected')
          }
        })
      })
    })
  }
}
  • 测试
const p1 = new _Promise((resolve, reject) => {
  resolve(1);
});
const p2 = new _Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(2);
  }, 1000);
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("err");
  }, 4000);
});
_Promise.all([p1, p2, p3]).then((item) => {
  console.log(item);
});
p1.then((item) => {
  console.log(item);
});
p2.then((item) => {
  console.log(item);
});
  1. 实现一个发布订阅模式
class EventEmiter{
    constructor(){
        this.taskList = {}
    }
    emit(name, ...arg){
        if(this.taskList[name]){
            const task = this.taskList[name].slice()
            for(let fn of task){
                fn(...arg)
            }
        }
    }
    on(name, fn){
        if(this.taskList[name]){
            this.taskList[name].push(fn)
        } else {
            this.taskList[name] = [fn]
        }
    }
    off(name, fn){
        const task = this.taskList[name].slice()
        if(task){
            const index = task.findIndexof(f=> f===fn || f.callback ===fn)
            if(index > -1){
               task.splice(index, 1)
            }
        }
    }
}
  1. 实现一个LRU算法。
  • LRU算法是全称least recently used
class LRUCatch{
    constructor(capacity){
        this.capacity = capacity
        this.maps = new Map()
    }
    get(key){
        if(this.maps.has(key)){
            const value = this.maps.get(key)
            this.maps.delete(key)
            this.maps.set(key, value)
            return value
        }
    }
    put(key, value){
        if(this.maps.has(key)){
            this.maps.delete(key)
        }
        if(this.maps.size >= this.capicity){
            this.maps.delete(this.maps.keys().next().value)
        }
        this.maps.set(key, value)
    }
}
  1. 实现数组去重。
1. 使用new Set()去重
const uniqueArr = arr => {
    return [...new Set(arr)]
}
2. 使用for循环/ indexOf/includes
const uniqueArr1 = arr => {
    const result = []
    for(let value of arr){
        if(!result.includes(value)){
            result.push(value)
        }
    }
    return result
}
3. 使用对象的属性唯一
const uniqueArr2 = arr => {
    const obj = arr.reduce((pre, cur) => {
    pre[cur + typeof cur] = cur
    return pre
    }, {})
    return Object.values(obj)
}
  1. 实现图片懒加载(至少用两种方法)。
  2. 实现js的深拷贝/浅拷贝。
深拷贝:
const deepClone = (data, maps = new Map()) => {
    const result = Array.isArray(data) ? [] : {}
    if(maps.get(data)){
        return data
    }
    maps.set(data, true)
    for(let i in data){
        if(data.hasOwnProperty(i)){
        result[i] = typeof data[i] === 'object' ? deepClone(data[i]) : data[i]
        }
    }
    return result
}
浅拷贝:
const shallowCopy = data => {
    const result = Array.isArray(data) ? [] : {}
    for(let i in data) {
        if(data.hasOwnProperty(i)){
            result[i] = data[i]
        }
    }
    return result
}
const obj = {
    color: ['red', 'green']
}
const copyObj = shallowCopy(obj)
obj.color.push('black')
console.log('obj', obj)
console.log('copyObj', copyObj)
  1. 实现一个深度优先遍历算法/广度优先遍历。
深度优先遍历:
// 非递归使用栈的特性
const dfs1 = (tree, callback) => {
    const stack = [...tree]
    while(stack.length){
        const node = stack.pop()
        if(node.children){
            stack.push(...node.children.reverse())
        }
    }
}
// 利用递归
const dfs2 = (tree, callback) => {
    tree.forEach(node => {
        callback(node)
        if(node.children){
            dfs2(node.children, callback)
        }
    })
}
广度优先遍历
const bfs = (tree, callback) => {
    const queen = [...tree]
    while(queen.length){
        const node = queen.shift()
        callback(node)
        if(node.children){
            queen.push(...node.children)
        }
    }
}
  1. list转化为tree,tree转化为list。
const tree = [{
	id: 0,
	name: 'root',
	children: [{
		id: 1,
		name: '父节点1',
		children: [{
			id: 2,
			name: '子节点1',
			children: [{
				id: 3,
				name: '孙子节点1',
			}]
		},
		{
			id: 5,
			name: '子节点2'
		}]
	},
	{
		id: 6,
		name: '父节点2',
		children: [{
			id: 7,
			name: '子节点3'
		}]
	}]
}];
const treeToList = (tree, result = []) => {
    tree.forEach(node => {
        result.push(node)
        if(node.children){
            treeToList(node.children, result)
        }
    })
}
const listToTree = list => {
    const result = []
    const map = {}
    for(let value of list){
        const temp = {...value, children: []}
        map[value.id] = temp
        if(value.pid === 0){
            result.push(temp)
        } else {
            map[value.pid].children.push(temp)
        }
    }
    return result
}
  1. 实现一个sleep函数。
const sleep = time => {
    return new Promise(resolve=> {
        setTimeout(resolve, time)
    })
}
使用:
const fn = async () => {
    console.log('run')
    await sleep(1000)
    console.log('1s后run')
    await sleep(2000)
    console.log('2s后run') 
}
fn()
  1. 实现模拟红灯3s后显示,绿灯2s后显示,黄灯1s后显示。
  2. 实现一个flat函数。
  3. 实现一个异步控制并行函数/串行函数,(并行demo:同时只能执行两个任务)。
  4. 封装一个JSONP
  5. 数组的api的手写实现
  • map
  • forEach
  • reduce
  • slice
  • ...
  1. 实现获取url中的参数(至少用两种方法)。
  2. 实现一个变量名称转驼峰 has_name => hasName
  3. 判断对象是否循环引用。
  4. 实现一个自记忆函数
  5. 实现一个tree shaking
作用:
- 去除打包之后没有使用到的代码,没有饮用的代码块,不执行的代码。
大致思路如下:
- 

框架部分

Vue常见问题

  1. 说一下vue和react的区别。
vue3的特点

1. 降低了模块之间的耦合度,可以单独使用某个模块
2. 使用tree-shaking,实现api按需引入,减少打包体积。
3. 允许自定义渲染器。

composition api(组合api) 对比React Hook的优点如下:
1. 不限制api的调用顺序,不用担心闭包变量的问题。
2. Vue的响应式系统会自动帮我们收集依赖,不需要手动收集。
3. 不需要手动缓存回调函数来避免不必要的组件更新,vue能够保证组件在必要时才会做出更新。

官方参考

  1. 简单的讲一下MVVM原理。
  1. vue2.x和vue3.x的区别。
1. vue3使用了pnpm,workspace来实现monorepo环境。
2. vue2使用defineProperty来实现数据的劫持,当新增删除属性的时候无法监测到。
3. vue2对数组的处理是重写了数组的api

  1. vue2.x的响应式原理的实现。
  2. vue3的响应式原理。
### reactive的注意事项
- vue3.0的中,reactive收集依赖的时候利用栈的特征去收集当前activeEffect,有些浪费性能。
- vue3.2的时候在当前effect上增加一个parent属性进行标记。
- 使用WeakMap弱引用来收集依赖,当前activeEffect存放在Set中,同时要先判断一下Set有没有当前的activeEffect,这样性能会好一点,虽然可以利用Set的特性去重。
- 当前的activeEffect也要记录是谁收集了它,为了清理的时候方便。
- 在trigger的时候还要注意当前的effect和activeEffect是否相等避免死循环
  • 3.0的时候用的是一个栈来保存当前的activeEffect image.png
  • 3.0之后

image.png

### 流程如下
1)设置一个响应式对象 new Proxy(), 在get中执行track,在set中执行trigger
2)设置一个副作用函数effect,使用一个变量activeEffect把effect与reactive关联起来。数据变化更新,把正在执行的effect函数作为全局变量,渲染取值的时候在get中进行依赖收集。
3)收集过程中使用了WeakMap => {对象: Map(属性:new Set(effect))
4) 数据变化的时候,通过对象的属性拿到所有的effect集合然后执行。

image.png

image.png

image.png 6. vue2.x中如何对数组的变化监听到。 7. computed和watch的区别。

vue3中的computed
effect加缓存,加一个收集依赖的功能。
watch
1. 使用ReactiveEffect

(1) ref的简单理解。

  1. data为什么是一个函数返回。
  2. vue生命周期的理解。
  3. vue ssr的生命周期有哪些。
  4. 简单讲一下EventLoop,nextTick()
  5. v-modal的原理是什么
  6. vue如何实现事件监听的。
  7. vue模版编译的原理。
  8. 讲一下diff算法的过程,vue2.x和vue3中有什么区别呢。
  9. vue中虚拟domkey的作用。
  10. v-if/v-show的使用。
  11. keep-alive的理解。
  12. vue的组件通信。
  13. vue ssr框架的问题,nuxt的使用。
  14. vue的优化从哪几方面做。
  15. vuex的理解,mutation,action的理解。
  16. 实现vuex的数据刷新页面还保存。

Vue3

  1. setup的理解。

React

  1. setState是同步还是异步的。
  2. hooks的理解。
  3. 多了我也不知道了。

TS

  1. type和interface有什么区别?
  2. any,void,unknown,never有什么区别。 参考

Webpack/Vite部分

原理

  1. webpack的作用是什么?
  2. 使用的基本流程请简单描述一下。
  3. 你多做过哪些webpack的优化

plugin/loader

  1. 如何实现一个plugin/loader

浏览器

  1. 进程和线程的区别。
  2. 浏览器的主要进程都有哪些。
  3. 输入一个url的的过程。
  4. TCP的三次握手和四次挥手。
  5. 浏览器的重绘和回流。
  6. 浏览器的存储。
  7. Web Work的作用以及原理。
  8. 浏览器的垃圾回收机制的理解。

跨域

  1. 跨域的几种解决办法。

请求状态

  1. 浏览器的请求状态都有哪几种。

安全

  1. 前端安全的理解。
  2. csrf的攻击。

算法

10大基础算法

  1. 冒泡排序。
  2. 选择排序。
  3. 插入排序。
  4. 快速排序。
  5. 归并排序。
  6. 堆排序。
  7. 希尔排序。
  8. 桶排序。
  9. 计数排序。
  10. 基数排序。

leetcode 不同类型经典题解

  1. 链表。

CSS/HTML

CSS3/HTML5的新特性

  1. 如何实现的网页主题。
  2. css3的一些新属性比如:filter ...
  3. 实现垂直水平居中。
  4. 请简单讲一下BFC。
  5. link和@import的区别。
  6. opacity和hidden和display的区别。
  7. css权重的问题。
  8. 移动端一像素的线如何实现。
  9. flex。
  10. Grid的属性。
  11. position的所有属性。
  12. css动画。
  13. canvas的了解,属性/常用。

less/sass

  1. 简单讲一下如何在项目中使用,比如在vue中如何全局使用less,结合项目说一下。

Git的常用操作知识

  1. 基础命令。
  2. 进阶命令。
  3. 高级命令。

Node的一些使用及常见问题

node常见的web框架

  1. node web框架的选择

node中间件

  1. 如何封装一个中间件。 参考链接

一些常见的面试题目

promise相关

参考链接

  1. 使用Promise实现每隔1秒输出1,2,3。
const arr = [1,2,3];
arr.reduce((prev, next) => {
  return prev.then(() => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log(next);
        resolve();
      }, 1000);
    });
  });
}, Promise.resolve());
  1. 实现红绿灯效果: 红灯3秒亮一次,黄灯2秒亮一次,绿灯1秒亮一次;如何让三个灯不断交替重复亮灯。
function red() {
  console.log('red');
}
function green() {
  console.log('green');
}
function yellow() {
  console.log('yellow');
}
function light(cb, time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      cb();
      resolve();
    }, time);
  });
}
(function run() {
   light(red, 3000).then(() => {
     return light(yellow, 2000);
     }).then(() => {
       return light(green, 1000);
     }).then(() => {
       run();
     });
}());
  1. list转化为tree结构