2023前端面试题整理

3 篇文章 0 订阅
2 篇文章 0 订阅

前端面试大全整理

算法

n维数组旋转 90 度算法

export const rotate = function (matrix: number[][]) {
    let n = matrix.length

    // matrix[x][y] => matrix[y][n - 1 - x]
    const changeItem = (num: number, x: number, y: number, rodateTime: number, isOnce?: boolean) => {
        // 终止条件
        rodateTime++
        if (num === matrix[x][y] && !isOnce && rodateTime > 4) return

        let space = matrix[y][n - 1 - x]

        matrix[y][n - 1 - x] = num
        changeItem(space, y, n - 1 - x, rodateTime)
    }

    for (let j = 0; j < Math.floor(n / 2); j++) {
        let rodateTime = 0;
        for (let i = j; i < n - 1 - j; i++) {
            changeItem(matrix[j][i], j, i, rodateTime, true)
            rodateTime = 0
        }
    }

    return matrix
};

((())) 暴力解法找到所有的夸号组合(以下有解 搜索生成括号)

  • 待定,貌似不行,应该用动态规划的方式
  • 可以实现的思路:通过 f(1,2,3) = f(1,2) 拼接 f(3)
export const findkhByQuery = (num: number = 3) => {
    /**
     * arr.push() 
     * 怎样组合呢? (())() 
     * (  + )()()
     * 思路:一个夸号去移动
     * 
     * ()()()  取出第1个 从 0 移动到 最后一位
     * let str = '(' 
     * )()() 发现 不管第一个如何移动 只有第一次才行 移动到右边后发现有首位是 右夸号结束移动
     * 
     * (()()  取出第2个 ')' 从 0 移动到 最后一位
     * let str = ')'
     * 
     * (()()  取出第3个 ')' 从 0 移动到 最后一位
     * let str = '('
     * 
     * 
     * value
     */
    let initStr = '';
    let arr = []
    for (let i = 0; i < num; i++) {
        initStr += "()"
    }
    let n = initStr.length
    let map: any = {}

    const isValid = (str: string): boolean => {
        if (!str || str[0] === ')') {
            return false
        }

        let stack = []
        for (let i = 0; i < str.length; i++) {
            if (str[i] === '(') {
                stack.push('(')
            }
            if (str[i] === ')') {
                let x = stack.pop()
                if (!x) {
                    return false
                }
            }
        }

        if (stack.length == 0) {
            return true
        }

        return false
    }

    // 可以再套一个循环 用于决定要 去几个字符串 
    for (let k = 0; k < num * 2; k++) {

        for (let i = 0; i < num * 2; i++) {
            // 从 0 移动到最后一位
            // let val = initStr[i]
            // 取出需要拿出来的字符串
            let val = initStr.slice(0, k + 1)
            console.log(val, 'val');

            // 取到切割剩下的
            // 'abcdef' abc cde 
            let newStr = initStr.slice(k + 1, n)

            for (let j = 0; j < num * 2; j++) {
                let moveRes = newStr.slice(0, j) + `${val}` + newStr.slice(j, n)
                // debugger

                // debugger
                if (!map[moveRes]
                    && isValid(moveRes)
                ) {
                    map[moveRes] = moveRes
                    arr.push(moveRes)
                }
                // arr.push(moveRes)
            }
        }
    }
    console.log(arr, 'arr');
}


// console.log(findkhByQuery(3));

回文字串

  • 思路:列出所有的字串
var partition = function (s: string) {
    let arr: string[][] = []
    arr.push(s.split(''))

    for (let i = 0; i < s.length; i++) {
        let subitem: string[] = []
        for (let j = i + 1; j < s.length; j++) {

            let val = s.substring(i, j + 1)

            if (val.split('').reverse().join('') === val) {
                /* 
                如何做到自动补全的功能? abcde bcd => a,bcd,e
                * abacd_aba => ,aba,c,d,_,aba,
                * abacd_abaDDC => ,aba,c,d,_,aba,D,D,C
                * 切掉空的
                */
                let ns = s.replace(val, `,${val},`)
                // 去掉首尾的,号
                if (ns.startsWith(',')) {
                    ns = ns.substring(1, ns.length)
                } else if (ns.endsWith(',')) {
                    ns = ns.substring(0, ns.length - 1)
                }
                let resItem: any[] = []

                // aba,cd_,aba => aba,c,d,_,aba
                ns.split(',').forEach(item => {
                    if (item !== val) {
                        resItem = [...resItem, ...item.split('')]
                    } else {
                        resItem.push(item)
                    }
                })
                subitem = resItem

            }
        }
        if (subitem.length) {
            arr.push(subitem)
        }
    }


    return arr
};

console.log(partition("fff"));

模板字符串替换

const template = `<div>{{ name }}<span>{{ age }}</span><div>`;
let obj = { name: 'glack', age: '23' }

const func = (template: string, obj: any) => {
    Object.keys(obj).forEach(item => {
        let key = '{{ ' + item + ' }}'
        template = template.replace(key, ' ' + obj[item] + ' ')
    })

    return template
}

console.log(func(template, obj), 'xxx');

实现全排列

/*
    [1]
    [12, 21]
    [312, 132, 123, 321, 231, 213]
    
    [0,-1,1]
    [1]

    f( [1, 2, 3] ) = f([1, 2]) 和 3 的排列
    f( [1, 2]) = f([1]) 和 2 的排列
    f( [1] ) = [1]
    
    思路:f(12345) = f(1234) 和 5 组合起来的数组
*/
const qpl = (nums: number[]): any[] => {
    let time = 0

    const dfs = (a: number[]): any[] => {
        let n = a.length
        if (n == 1) {
            return [a]
        }

        let res: any[] = []
        let fontArr = a.slice(0, n - 1) // [1, -1]
        let lastStr = a.slice(n - 1, n)[0] //  0

        dfs(fontArr)?.forEach(it1 => {
            // [1, -1] [-1, 1] 中 使用 0 来移动位置
            for (let i = 0; i < it1.length + 1; i++) {
                res.push([...it1.slice(0, i), lastStr, ...it1.slice(i, it1.length)])
            }
        })

        return res
    }

    return dfs(nums)
}

console.log(qpl([-1, 2, 3]));

有序数组中找大于等于 n 最左边的数

给一个数组:[1,2,3,4,4,4,5,6,7,7,8,11,345,567] 找到大于 4最左边的数字;

  • 使用二分法
export const erfen = (arr: number[], n: number) => {
    let len = arr.length
    if (!arr || len < 2 || typeof n !== 'number') return arr

    let curValue = 0, midIndex, midValue
    let leftNums = [1]
    let rightNums = []
    let handleArr = JSON.parse(JSON.stringify(arr))

    while (leftNums.length + rightNums.length > 0) {
        midIndex = Math.floor(handleArr.length / 2)
        midValue = handleArr[midIndex]
        leftNums = handleArr.slice(0, midIndex - 1)
        rightNums = handleArr.slice(midIndex + 1, handleArr.length)

        if (midValue >= n) {
            curValue = midValue
            handleArr = leftNums
        } else {
            handleArr =  
        }
    }
    return curValue
}
优化简洁版

思路:如果传递的 n 大于等于 midValue 那就将右边的下标往中间挪动。这样就可以开始从 0 到 midIndex 之前找最小的值。如果小于,midIndex 到 len - 1 找小的最左边的值。

// 有序数组中找大于等于 n 最左边的数
export const erfen = (arr: number[], n: number) => {
    let len = arr.length
    if (!arr || len < 2 || typeof n !== 'number') return -1

    let leftIndex = 0
    let rightIndex = len - 1
    let ans = -1

    while(leftIndex <= rightIndex){
        let midIndex = Math.floor((leftIndex + rightIndex)/ 2)

        if (arr[midIndex] >= n) {
            ans = midIndex
            rightIndex = midIndex - 1
        }else{
            leftIndex = midIndex + 1
        }
    }

    return arr[ans]
}
console.log(erfen([1, 2, 3, 3, 3, 3, 4, 5, 5, 7, 8, 10, 11, 13], 3));

反转链表

  1. 保存gnext数据
  2. gPrev赋值给当前 head 节点的 next 节点
  3. 移动 gPrev 到下一步 (head 赋值给 gPrev)
  4. gPrev 接收搭配报错的gNext数据
interface INode {
    value: number,
    next?: INode,
    prev?: INode
}
export const linkReverseFunc = (head: INode) => {
    let prev: INode | undefined = undefined
    let next: INode | undefined = undefined
    while (head.next) {
        // 保存next数据
        next = head.next
        // 将当前节点的下一个指向prev 如果是第一次 则为空 如果第二次 在第一步prev已经赋值给了当前head节点
        head.next = prev
        // 当前节点给下一次的prev使用
        prev = head
        // 下一个节点变成开始全局记录的next 
        head = next

        if (!prev.next) {
            delete prev.next
        }
    }

    if(!head.next){
        console.log(head,'head');
        head.next = prev
        prev = head
    }

    return prev
}
let nodeinit: INode = {
    value: 100,
    next: {
        value: 200,
        next: {
            value: 300,
            next: {
                value: 400,
                next: {
                    value: 500,
                }
            }
        }
    }
}
console.log(linkReverseFunc(nodeinit), 'test');

并查集(待定)

如果 多个用户 a 或者 b 或者 c 的字段值一样 就合并
返回合并之后的用户加上count字段

思路:声明多个集合,如果,没出现过,就set到map里面,如果出现过,就count+1
下次再来。。。太难了,有思路再来。

// users合并
/* [
        { a: 1, b: 2, c: 3},
        { a: 1, b: 3, c: 4},
        { a: 8, b: 9, c: 10},
        { a: 10, b: 9, c: 8}
    ]
    思路:三个集合 aMap bMap cMap
    默认为空 最后会变成 aMap:  { a: 
                                { a: 1, b: 2, c: 3, count: 2}
                                { a: 8, b: 9, c: 10} }
                                { a: 10, b: 9, c: 8}
                            }, 

                     bMap: { a: { a: 2, b: 3, c: 9, count: 3}}
                     cMap: { a: { a: 3, b: 4, c: 10, count: 4}}
*/
export const mergeUser = (users: IUserInfo[]) => {
    let aMap = new Map()
    let bMap = new Map()
    let cMap = new Map()
    users.forEach(item => {
        // 如果三个集合都没有这个item 那就放在
        if (!aMap.has(item.a) && !bMap.has(item.b) && !cMap.has(item.c)) {
            aMap.set(item.a, { ...item, count: 1 })
            bMap.set(item.b, { ...item, count: 1 })
            cMap.set(item.c, { ...item, count: 1 })
            // return
        }
        if (aMap.has(item.a)) {
            let aItem = aMap.get(item.a)
            aItem.count++
            // return
        }
        if (bMap.has(item.b)) {
            let bItem = bMap.get(item.b)
            bItem.count++
            // return
        }
        if (cMap.has(item.c)) {
            let cItem = cMap.get(item.c)
            cItem.count++
            // return
        }
    })
    console.log(aMap, bMap, cMap, 'abc');
}
mergeUser([{ a: 1, b: 2, c: 3 }, { a: 1, b: 3, c: 4 }, { a: 8, b: 9, c: 10 }, { a: 10, b: 9, c: 8 }])

覆盖绳子最多的点数

// 盖住最大值的绳子上的点
export const ropeCoveringPoint = (arr: number[], n: number) => {
    let left = 0, right = 0, max = 0, len = arr.length

    while (left < len) {
        while (left < len && arr[right] - arr[left] <= n) {
            right++
        }
        // debugger
        max = Math.max(max, right - (left++))

    }

    return max
}

console.log(ropeCoveringPoint([1, 3, 6, 8, 10, 12, 13, 17, 19, 24, 28, 31], 16));

字符串只有 ”G“和”B“ 如果要把G放最左边,B放最右边,求需要移动几部才能让所有g在左边,b在右边。

export const moveChar = (str: string) => {
    let len = str.length
    let count = 0
    let gCount = 0
    for (let i = 0; i < len; i++) {
        if (str[i] === 'G') {
            gCount ++
            let res = (i - gCount) < 0 ? 0 : (i - gCount)
            // debugger
            count = count + res + 1
        }
    }

    return count
}
console.log(moveChar('GGBBGGGBBBGGGGBBBB'));

生成括号

思路:f(n) 的值永远都是 f(n-1) 的结果 里面每一项push ()

export function generateParenthesis(initn: number): string[] {
    const func = (n: number): string[] => {
        if (n == 1) {
            return ['()']
        }

        let res = func(n - 1)
        
        let pjRes = []
        for (let i = 0; i < res.length; i++) {
            let str = res[i]
            let sL = str.length
            for (let j = 0; j < sL; j++) {
                pjRes.push(`${str.slice(0, j)}()${str.slice(j, sL)}`)
            }
        }
        return pjRes
    }

    return Array.from(new Set(func(initn)))
}

console.log(generateParenthesis(3), 'na');

实现深拷贝包括 map set 类型

  1. map
  2. set
  3. 函数
  4. 循环引用
  5. 深层数据拷贝
export function cloneDeep(obj: any, map = new WeakMap()): any {
    if (typeof obj !== 'object' || obj == null) return obj

    // 避免循环引用
    const objFromMap = map.get(obj)
    if (objFromMap) return objFromMap

    let target: any = {}
    map.set(obj, target)

    // Map
    if (obj instanceof Map) {
        target = new Map()
        obj.forEach((v, k) => {
            const v1 = cloneDeep(v, map)
            const k1 = cloneDeep(k, map)
            target.set(k1, v1)
        })
    }

    // Set
    if (obj instanceof Set) {
        target = new Set()
        obj.forEach(v => {
            const v1 = cloneDeep(v, map)
            target.add(v1)
        })
    }

    // Array
    if (obj instanceof Array) {
        target = obj.map(item => cloneDeep(item, map))
    }

    // Object
    for (const key in obj) {
        const val = obj[key]
        const val1 = cloneDeep(val, map)
        target[key] = val1
    }

    return target
}

const a: any = {
    set: new Set([1, 2, 3, 4]),
    map: new Map([['x', 10], ['y', 20]]),
    info: {
        city: '北京'
    },
    fn: () => { },
}

a.self = a
console.log(cloneDeep(a), 'cloneDeep');

[‘1’, ‘2’, ‘3’].map(parseInt)

上面代码相当于

['1', '2', '3'].map((item, index) => {
    parseInt(item, index)
    // parseInt 
    // 转换的数字
    // raIndex 第二个参数是 进制 单位 (2-32)
    /**
     * parInt('1', 0) // 0 的时候 说明没传 是2进制
     * parInt('2', 1) // 1 的时候 没有 1 进制 所以返回 NAN
     * parInt('3', 2) // 2 的时候 3 不属于二进制的内容(011101010101010这种只有 0 和 1 才属于二进制) 
     */
})

所以最终返回 [1, NaN, NaN]

函数的参数传递就是赋值传递

const fn = (x, y) => {
    // 这里就相当于 x = num; y = obj
}
let num = 'aaa'
let obj = { name: 'glack' }
fn(num, obj)

数组转为树节点(针对有序的二维数组排列树节点)

const arr1 = [
    { id: 1, name: '部门A', parent_id: 0 },
    { id: 2, name: '部门B', parent_id: 1 },
    { id: 3, name: '部门C', parent_id: 1 },
    { id: 4, name: '部门D', parent_id: 2 },
    { id: 5, name: '部门E', parent_id: 4 },
    { id: 6, name: '部门F', parent_id: 4 },
];
/**
 * 针对有序的二维数组排列树节点
 * @param arr arr
 */
interface ITreeNode {
    id: number,
    name: string,
    children?: ITreeNode[]
}
interface ITreeItem {
    id: number,
    name: string,
    parent_id: number
}
export const treeTransform = (arr: ITreeItem[]): ITreeNode | null => {
    // 用于id 和 treeNode 的映射
    let treeMap: Map<number, ITreeNode> = new Map()

    let root: ITreeNode | null = null

    // 定义tree node 并加入 map
    arr.forEach(({ id, name }) => {
        const treeNode: ITreeNode = { id, name }
        treeMap.set(id, treeNode)
    })

    arr.forEach(item => {
        const { id, parent_id } = item

        const treeNode = treeMap.get(id)

        // 找到 parentNode 并加入到它的 children
        const parentNode = treeMap.get(parent_id)
        if (parentNode && treeNode) {
            if (parentNode.children == null) parentNode.children = []
            parentNode.children.push(treeNode)
        }

        // console.log(parentNode, 'parentNode');
        // console.log(treeNode, 'treeNode');

        // 找到根节点
        if (parent_id === 0) {
            // @ts-ignore
            root = treeNode
        }
    })

    return root
}
let result = treeTransform(arr1)
console.log(result);
怎么证明 root 的子元素一直在变?
  • 在 map 外部修改map里面的children 元素 里面的元素会跟着修改
let map = new Map();
map.set('arr', []);
let arr = map.get('arr');
arr.push('a');
map.get('arr') // ['a']
树转为数组
let res: any[] = []
let treeTransformArray = (root: any[], parent_id: number = 0) => {
    let arr = Array.isArray(root) ? root : [root]

    // 遍历所有元素
    arr.forEach(item => {
        if (item.children) {
            treeTransformArray(item.children, item.id)
        }

        res.push({ id: item.id, name: item.name, parent_id })
    })
}
// @ts-ignore 这里的result 是上面的结果
treeTransformArray(result)
console.log(res, 'zz');

原型链面试题

function Foo(){
    Foo.a = function name() {
        console.log(1);
    }
    this.a = function () {
        console.log(2);
    }
}

Foo.prototype.a = function(){ console.log(3); }

Foo.a = function(){ console.log(4) }

Foo.a()
let obj = new Foo()
obj.a()
Foo.a()

// 4 2 1

promise和事件循环的算法

Promise.resolve().then(() => {
    console.log(1)
}).then(() => {
    console.log(2)
}).then(() => {
    console.log(3)
}).then(() => {
    console.log(4)
}).then(() => {
    console.log(5)
}).then(() => {
    console.log(6)
})

Promise.resolve().then(() => {
    console.log(10)
}).then(() => {
    console.log(20)
}).then(() => {
    console.log(30)
}).then(() => {
    console.log(40)
}).then(() => {
    console.log(50)
}).then(() => {
    console.log(60)
})
// 1 10 2 20 3 30 4 40 5 50 6 60

多个promise 已完成的状态同时执行 .then 的链式调用
所以 .then会交替执行

Promise.resolve().then(() => {
    console.log(1)
    return Promise.resolve(100)
}).then((res) => {
    console.log(res)
}).then(() => {
    console.log(3)
}).then(() => {
    console.log(4)
}).then(() => {
    console.log(5)
}).then(() => {
    console.log(6)
})

Promise.resolve().then(() => {
    console.log(10)
}).then(() => {
    console.log(20)
}).then(() => {
    console.log(30)
}).then(() => {
    console.log(40)
}).then(() => {
    console.log(50)
}).then(() => {
    console.log(60)
})
// 1 10 20 30 100 40 3 50 4 60 5 6

.then 中返回promise 实例会慢两拍(三步) 因为需要时间把自己 penging 变成 fulfilled状态

setState 同步异步问题

didMount(){
    // 假设默认值是 0 
    this.setState({
        val: this.state.val + 1
    })
    console.log(this.state.val) // 异步更新 0 
    this.setState({
        val: this.state.val + 1
    })
    console.log(this.state.val) // 异步更新 0 

    setTimeout(() => {
        this.setState({
            val: this.state.val + 1
        })
        console.log(this.state.val) // 同步更新 2
        this.setState({
            val: this.state.val + 1
        })
        console.log(this.state.val) // 同步更新 3
    })
}
setState 会合并

如果同时多个setState 都要修改val 那么会优先使用 下面的那个setState语句

不合并的情况

传入函数: this.setState((prev, props) => { return …})

同步更新

setState 同步更新:在setTimeout setInterval promise.then
自定义的 dom 事件
ajax 回调
例子:

document.getElementById('id').addEventlistener('click', () => {
    this.setState({
        val: this.state.val + 1
    })
    console.log(this.state.val) // 同步更新
})

setState是同步 不过只不过是当做异步处理

React 18 中不一样了

可以在setTimeout 中 异步更新了
Auto Batch
需要将 ReactDOM.render 替换为 ReactDOM.createRoot

a.x 比赋值的优先级高

let a = { n: 1 }
let b = a
a.x = a = { n: 2 }

console.log(a.x) // undefined
console.log(b.x) //  n: 2

字符串对象的key 只能是字符串或者 symbol 类型

let a = {}, b = '123', c = 123
a[b] = 'b'
a[c] = 'c'

// log => a[b] c


let a = {}, b = Symbol('123'), c = Symbol('123')
a[b] = 'b'
a[c] = 'c'

// log => a[b] b

设计一个前端统计 SDK

  • 统计的范围:性能 错误 pv 自定义事件
  • 发送数据使用 img 的 src 来发送get请求
  • 报错统计结合 Vue React 报错

sourcemap 的作用

  • 线上代码压缩报错,sourcemap 可解决这个问题
  • 可以给压缩后的文件识别报错的行数,找到错误的地方
如何设置?
  • webpack 通过 devtool 配置sourcemap
    • eval - js 在 eval(…) 中 不生产sourcemap
    • source-map 生成单独的 map 文件, 并在js 最后指定
    • eval-source-map,js在eval(…) 中,sourcemap内嵌
    • inline-source-map - sourcemap 内嵌到 js 中
    • cheap-source-map - sourcemap 中只有行信息,没有列
    • eval-cheap-source-map 同上,只有行没有列
      非开源项目:不要泄漏sourcemap ! 否则容易被反解

SPA 和 MPA 怎么选择

  • SPA 特点

    • 功能较多,一个页面展示不完
    • 以操作为主,非展示为主
    • 适合一个综合Web应用
  • MPA

    • 功能较少,展示为主,例如:分享页,新闻详情页,微信公众号发出的页面

多种排序算法

快速排序
// slice
export const quickSort1 = (arr: number[]): number[] => {
    let length = arr.length

    if (length === 0) return []

    let left = [], right = []
    let midIndex = Math.floor(arr.length / 2)
    let midValue = arr.slice(midIndex, midIndex + 1)[0]
    
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] < midValue) {
            left.push(arr[i])
        } else if (arr[i] > midValue) {
            right.push(arr[i])
        }
    }
    
    return quickSort1(left).concat([midValue], quickSort1(right))
}
// splice
export const quickSort2 = (arr: number[]): number[] => {
    let length = arr.length

    if (length === 0) return []

    let left = [], right = []
    let midIndex = Math.floor(arr.length / 2)
    let midValue = arr.splice(midIndex, 1)[0]
    
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] < midValue) {
            left.push(arr[i])
        }
        if (arr[i] > midValue) {
            right.push(arr[i])
        }
    }

    return quickSort2(left).concat([midValue], quickSort2(right))
}

const arr = [4,5,7,1,2,3,6,3,15,5,123,8,9,1,9,8]
console.log(quickSort2(arr), 'arr')
选择排序
// 选择排序一
export function sort1(arr: number[]): number[] {
    let len = arr.length
    if (arr == null || arr.length < 2) return []

    let ti = 0

    while (ti < len) {
        // 找出最小值
        let num: number | null = null
        let idx: number = 0
        for (let i = ti; i < len; i++) {
            if (num == null || arr[i] < num) {
                num = arr[i]
                idx = i
            }
        }

        // 最小下标和 第 ti 项换位置
        let n = arr[ti]
        arr[ti] = arr[idx]
        arr[idx] = n

        ti++
    }

    return arr
}
// 选择排序二
export function sort2(arr: number[]): number[] {
    let length = arr.length

    for (let i = 0; i < length; i++) {
        let minValueIndex = i
        for (let j = i + 1; j < length; j++) {
            minValueIndex = arr[j] < arr[minValueIndex] ? j : minValueIndex
        }
        // 交换位置
        let n = arr[i]
        arr[i] = arr[minValueIndex]
        arr[minValueIndex] = n
    }

    return arr
}
冒泡排序

思路:
[ 3, 2, 1, 6, 5, 3, 7]
0 1 2 3 4 5 6
0 1 对比 大的往右
1 2 对比 大的往右
2 3

以此类推
最大的在最右边

export function sort3(arr: number[]): number[] {
    let length = arr.length
    if (arr.length < 2) return []
    let time = 0

    for (let i = 0; i < length; i++) {
        for (let j = 0; j < length - i; j++) {
            time++
            console.log(time);

            // 如果 i 大于 j 交换
            if (arr[j] > arr[j + 1]) {
                let n = arr[j + 1]
                arr[j + 1] = arr[j]
                arr[j] = n
            }
        }
    }

    return arr
}

插入排序
 /**
 * 
 * [2, 4, 1, 3, 8, 6, 7]
 * 思路:
 * 0 - 1 arr: [2, 4, 1, 3, 8, 6, 7] 排序
 * 0 - 2 arr: [1, 2, 4, 3, 8, 6, 7] 排序
 * 0 - 3 arr: [1, 2, 3, 4, 8, 6, 7] 排序
 * 0 - len - 1 排序 ...
 */
// 真·插入排序
function sort6(arr: number[]): number[] {
    let length = arr.length
    if (!arr || length < 2) return arr

    for (let i = 1; i < length; i++) {
        let endIndex = i
        while (endIndex - 1 >= 0 && arr[endIndex] < arr[endIndex - 1]) {
            // 交换位置
            let n = arr[endIndex]
            arr[endIndex] = arr[endIndex - 1]
            arr[endIndex - 1] = n
            endIndex --
        }
    }

    return arr
}
// 真·插入排序优化
function sort7(arr: number[]): number[] {
    let length = arr.length
    if (arr == null || length < 2) return arr

    for (let i = 1; i < length; i++) {
        for (let endI = i; endI - 1 >= 0 && arr[endI] < arr[endI - 1]; endI--) {
            let n = arr[endI]
            arr[endI] = arr[endI - 1]
            arr[endI - 1] = n
        }
    }

    return arr
}

数组扁平化

  • 扁平一层数组
    • 效果 [1, 2, [3, 4, [5]]] -> [1, 2, [3, 4, 5]]
export const flatten1 = (arr: any[]): any[] => {
    let res: any[] = []

    for (let i = 0; i < arr.length; i++) {
        let item = arr[i]
        if (Array.isArray(item)) {
            for (let j = 0; j < item.length; j++) {
                res.push(item[j])
            }
        } else {
            res.push(item)
        }
    }

    return res
}

export const flatten2 = (arr: any[]): any[] => {
    let res: any[] = []
    for (let i = 0; i < arr.length; i++) {
        res = res.concat(arr[i])
    }

    return res
}

const initarr = [1, [2, [3], 4], 5]
console.log(flatten1(initarr), '1');
console.log(flatten2(initarr), '2');
  • 扁平多层数组
    concat的方式
export const flatten3 = (arr: any[]): any[] => {
    let res: any[] = []

    for (let i = 0; i < arr.length; i++) {
        let item = arr[i]
        
        if (Array.isArray(item)) {
            let nArr = flatten3(item)            
            res = res.concat(nArr)
        } else {
            res.push(item)
        }
    }

    return res
}

push的方式

export const flatten3 = (arr: any[]): any[] => {
    let res: any[] = []

    for (let i = 0; i < arr.length; i++) {
        let item = arr[i]
        
        if (Array.isArray(item)) {
            let nArr = flatten3(item)
            // 解释:不管如何走,flatten3 返回的永远是拍平的数组,因为他不是数组的话会再次走进里面去。
            // 至于为什么需要在contact 或者 forEach 因为返回的是一层的数组。
            res = nArr.forEach(n => res.push(n))
        } else {
            res.push(item)
        }
    }

    return res
}

toString方式

const arr = [1, [2, [3], 4], 5]
arr.toString() // 1,2,3,4,5
// 这样的方法不健壮

常见的类型判断 引用数据类型和基本数据类型的区别

引用类型 (Object,Array) 基本类型 (number, string, boolean, symbol, null, undefined)

export const getType = (x: any) => {
    let type = Object.prototype.toString.call(x)
    let spaceIndex = type.indexOf(' ')
    let res = type.slice(spaceIndex + 1, -1)
    return res.toLowerCase()
}

new 一个对象发生了什么

class Foo {
    this.name = 'glack'
    this.age = 'age'

    getName () {
        return 'xxx'
    }
}

上面转换成了es5 的语法

function Foo {
    this.name = 'glack'
    this.age = 'age'
}
Foo.prototype.getName = function() { 
    return 'xxx'
}
手写一个new 方法
/**
 * 
 * @param consturctor 
 * @param args 
 */
export function customNew<T>(consturctor: Function, ...args: any[]): T {
    // 1. 创建一个空对象,集成 constructor 的原型
    const obj = Object.create(consturctor.prototype)
    // 2. 将 obj 作为this,执行 constructor ,传入参数
    consturctor.apply(obj, args)
    // 3. 返回 obj
    return obj
}

class Foo {
    // 属性
    name: string
    age: number

    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }

    getName() {
        return this.name
    }
}

// const f = new Foo('glack', 23)
const f = customNew<Foo>(Foo, 'glack', 23)
console.log(f, 'f');
console.log(f.age);
console.log(f.getName());

遍历一个 dom 树

// 遍历子节点
export function visitNode(n: Node) {
    if (n instanceof Comment) {
        console.log(n, 'n');
    }
    if (n instanceof Text) {
        console.log('Text node ---', n.textContent?.trim());
    }
    if (n instanceof HTMLElement) {
        console.log('HTML node ---', `<${n.tagName.toLowerCase()}>`);
    }
}

// 深度优先遍历
function deepFindNode(root: Node) {
    visitNode(root)

    const childNodes = root.childNodes
    if (childNodes.length) {
        childNodes.forEach(item => {
            deepFindNode(item)
        })
    }
}

// 广度优先遍历(队列)
function breadFirstTraverse(root: Node) {
    const quene: Node[] = []

    // 根节点入队
    quene.unshift(root)

    while(quene.length > 0){
        const curNode = quene.pop()
        if(curNode == null) break

        visitNode(curNode)

        // 子节点入队
        const childNodes = curNode.childNodes
        if (childNodes.length > 0) {
            childNodes.forEach(n => quene.unshift(n))
        }
    }
}

setTimeout(() => {
    const box = document.getElementById('box')
    if (!box) throw Error('box is null')

    breadFirstTraverse(box)
}, 1000);

递归

问题:计算 1 - 100 的和。

  1. 一般是先假装该函数是存在的,通过数学中的找规律的方法,找到方法的递归体
func(n) = func(n-1) + n
  1. 找到临界值,比如,1 - 100 的和,就是 func(n-1) + 100, 临界值是 n = 1 reurn 1
if (n === 1) return 1
  1. 写入函数体
function comput1(num: number): number {
    if (num === 1) return 1
    return comput1(num - 1) + num
}

一些基础题:

// 求 2,4,6,8,10... 第n项与前n项之和
function sum1(num: number): number {
    if (num === 1) return 2
    return sum1(num - 1) + 2 * num
}

// 求1 3 5 7 9 ... 第n项与第n项之和
function sum2(num: number): number {
    if (num === 1) return 1
    return sum2(num - 1) + (2 * num - 1)
}
console.log(sum2(5));

模板字符串替换

export function render(temp: string, person: { name: string, age: number, sex: boolean }): string {
    let reg = /\{\{(\w+)\}\}/
    
    if (reg.test(temp)) {
        // 找到模板字符串 
        // @ts-ignore
        const name = reg.exec(temp)[1]
        
        // @ts-ignore
        temp = temp.replace(reg, person[name])
        return render(temp, person)
    }
    return temp
}


let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
    name: '布兰',
    age: 12,
    sex: true
}
console.log(render(template, person));

reduce 实现 map 和 filter 方法

export function reduceCustom(arr: any[]): any[] {
    return arr.reduce((filterRes, item) => {
        if (item > 20) {
            filterRes.push(item * 2)
        }
        return filterRes
    }, [])
}
const arr = reduceCustom([10, 20, 30, 40, 50])
console.log(arr, 'arr');

reduce 实现 map 方法

export function zMap(arr: any[], fn: Function) {
    return arr.reduce((initArr: any[], item: number) => {
        return initArr.concat(fn(item))
    }, [])
}
const arr = zMap([10, 20, 30, 40, 50], (x: any) => x * 2)
console.log(arr, 'arr'); // 20 40 60 80 100

二维数组累加

const arr = [
    { id: 1, name: 'glack1', count: 1 },
    { id: 2, name: 'glack2', count: 2 },
    { id: 3, name: 'glack3', count: 3 },
    { id: 4, name: 'glack4', count: 4 },
    { id: 1, name: 'glack5', count: 2 },
    { id: 3, name: 'glack6', count: 0 },
    { id: 2, name: 'glack7', count: 1 },
    { id: 4, name: 'glack8', count: 2 },
]

export function arrayAdd(arr: any[]) {
    let map: any = {}
    return arr.reduce((initArr, item) => {
        let key = item.id
        if (map[key]) {
            let num = map[key].count + item.count
            map[key].count = num
            initArr = initArr.map((it1: any) => {
                if (it1.id == key) {
                    return {
                        ...it1,
                        count: num
                    }
                }
                return it1
            })
        } else {
            map[key] = {
                count: item.count
            }
            initArr.push(item)
        }
        return initArr
    }, [])
}

console.log(arrayAdd(arr), 'xxx');

一维数组递归变成树节点。

interface Node {
    id: number,
    parent_id: number | string,
    children?: any[]
}

export function treeTransform(arr1: Node[], id: number | string = ''): any {
    let nArr: Node[] = arr1.filter(item => item.parent_id == id)
    return nArr.map(item => ({
        ...item,
        children: treeTransform(arr1, item.id)
    }))
}

const arr1 = [
    { id: 1, parent_id: '' },
    { id: 2, parent_id: 1 },
    { id: 3, parent_id: 1 },
    { id: 4, parent_id: 2 },
    { id: 5, parent_id: 4 }
];
console.log(treeTransform(arr1));

手写 LazyMan

  • 支持sleep
  • 支持链式调用

思路:

  1. 首先初始化一个任务队列
  2. 每碰到eat 或者 sleep 都先注册一个函数,把函数放进任务队列中
  3. 返回this,让它支持链式调用
  4. 最后等到 eat 和 sleep 创建完成了任务队列之后
  5. 再调用第一个next方法(shift)
export class LazyMan {
    private tasks: Function[] = []
    private name: string

    constructor (name: string){
        this.name = name

        setTimeout(() => {
            // 等到初始化执行完毕再执行next
            console.log(this.tasks.length);
            
            // 也就是等到 eat(a) eat(b) eat(c) sleep(xx) 才会触发next()
            this.next()
        });
    }

    private next(){
        const task = this.tasks.shift()
        if (task) task()
    }

    eat(food: string){
        const task = () => {
            console.log(`${this.name} eat ${food}`);
            this.next()
        }
        this.tasks.push(task)
        return this
    }

    sleep(seconds: number){
        const task = () => {
            setTimeout(() => {
                console.log(`${this.name}睡了${seconds}s 开始执行下一个任务`);
                this.next()
            }, seconds * 1000);
        }
        this.tasks.push(task)

        return this
    }
}

const me = new LazyMan('glack')
me.eat('apple').eat('bannan').sleep(3).eat('pear')

手写一个 curry 函数 把其他函数柯里化(函数式编程,不常用,但是会考)

思路:
curry 最终返回一个函数
执行fn 中间状态返回函数,add(1) 或者 add(1)(2)
最后情况执行函数 add(1)(2)(3) 执行

export function curry(fn: Function) {
    // 这里的fnArgsLength 就是指 fn 的参数的长度
    const fnArgsLength = fn.length
    let args: any[] = []

    function calc(this: any, ...newArgs: any[]) {
        args = [
            ...args,
            ...newArgs
        ]
        if (args.length < fnArgsLength) {
            // 如果当前拼接的长度小于整个方法应传递的长度 返回当前函数 给继续调用 add(20)
            return calc
        }else{
            // 如果长度是拼接的三个参数 那就直接执行传递的方法
            return fn.apply(this, args.slice(0, fnArgsLength))
        }
    }
    return calc
}

function add(a: number, b: number, c:number): number{
    return a + b + c
}

const curryAdd = curry(add)
console.log(curryAdd(10)(20)(30));
// 这里如果传递方式有误的话 curryAdd(10)(20)(30) 会导致length 判断出错 没有返回函数 导致fn()() 报错

intanceof 原理是什么 用代码表示

export const myIntanceof = (intance: any, origin: any) => {
    if (intance == null) return false

    let type = typeof intance

    if (type !== 'object' && type !== 'function') {
        // 值类型 直接返回false
        return false
    }
    
    let tempIntance = intance
    while(tempIntance){
        if (tempIntance.__proto__ == origin.prototype) {
            return true
        }
        // 向上查找 如果为 null 就不进入循环
        tempIntance = tempIntance.__proto__
    }

    return false
}

console.log(myIntanceof({}, Object)); // true
console.log(myIntanceof([], Object)); // true
console.log(myIntanceof([], Array)); // true
console.log(myIntanceof({}, Array)); // false
console.log(myIntanceof('123', String)); // false
console.log(myIntanceof(123, Number)); // false
console.log(myIntanceof(true, Boolean));  // false
console.log(myIntanceof(()=>{}, Function)); // true

手写bind

分析:

  1. 返回function
  2. 重命名传递的this
  3. 定义调用者的this
  4. 拼接参数
// @ts-ignore
Function.prototype.customBind = function (context: any, ...bindArgs: any[]) {
    // context 是 bind 传入的this,bindArgs 是bind 传入的各个参数
    const self = this

    return function (...args: any[]) {
        const newArgs = bindArgs.concat(args)
        return self.apply(context, newArgs)
    }
}

function fn(this: any, ...args: any[]) {
    console.log(this, ...args);
}

// @ts-ignore
const fn1 = fn.customBind({ x: 100 }, 10, 20, 30)
fn1(40, 50)
bind call apply 区别

不同点:
bind 返回一个函数(不执行)
call 和 apply 会立即执行
传递方式不同:

call(this, 10,20,30)
apply(this, [10,20,30])

相同:
都是用于绑定this
都可以传入执行参数

手写call
// @ts-ignore
Function.prototype.customCall = function (context: any, ...args: any[]) {
    if (context == null) context = globalThis
    // 值类型 需要转换成对象类型
    if (typeof context !== 'object') context = new Object(context)

    const fnKey = Symbol() // 防止出现属性名称覆盖
    context[fnKey] = this // this 就是当前函数

    const res = context[fnKey](...args) // 绑定 this

    delete context[fnKey] // 清理fn,防止污染

    return res
}

function fn(this: any, a: any, b: any, c: any) {
    console.log(JSON.stringify(this), a, b, c);

}

// @ts-ignore
fn.customCall({ x: 100 }, 10, 20, 30)
// @ts-ignore
fn.customCall(100, 10, 20, 30)
手写apply
// @ts-ignore
Function.prototype.customApply = function (context: any, args: any[] = []) {
    if (context == null) context = globalThis
    // 值类型 需要转换成对象类型
    if (typeof context !== 'object') context = new Object(context)

    const fnKey = Symbol() // 防止出现属性名称覆盖
    context[fnKey] = this // this 就是当前函数

    const res = context[fnKey](...args) // 绑定 this

    delete context[fnKey] // 清理fn,防止污染

    return res
}


function fn(this: any, a: any, b: any, c: any) {
    console.log(JSON.stringify(this), a, b, c);

}

// @ts-ignore
fn.customApply({ x: 100 }, [10, 20, 30])
// @ts-ignore
fn.customApply(100, [10, 20, 30])

手写EventBus 自定义事件

实现on once emit方法

方法一:

// 绑定成一个类似这种的数组
{
   'key1': [
    {fn1, isOnce: false},
    {fn2, isOnce: false},
    {fn3, isOnce: true},
   ]
   'key2': ...
}

具体代码:

export class EventBus {
    private events: {
        [key: string]: Array<{ fn: Function, isOnce: boolean }>
    }

    constructor() {
        this.events = {}
    }

    on(type: string, fn: Function, isOnce: boolean = false) {
        const events = this.events
        if (events[type] == null) {
            events[type] = [] // 初始化key 的 fn 数组
        }

        events[type].push({ fn, isOnce })
    }

    once(type: string, fn: Function) {
        this.on(type, fn, true)
    }

    off(type: string, fn?: Function) {
        if (!fn) {
            // 删除所有type的函数
            this.events[type] = []
        } else {
            // 解绑单个事件
            const fnList = this.events[type]
            if (fnList) {
                this.events[type] = fnList.filter(item => item.fn !== fn)
            }
        }
    }

    emit(type: string, ...args: any[]) {
        const fnList = this.events[type]
        if (fnList == null) return

        // filter 可以做到return 删除元素,并且还能遍历
        this.events[type] = fnList.filter(item => {
            const { fn, isOnce } = item
            fn(...args)

            if(!isOnce) return true

            return false
        })
    }
}

方法二:

export class EventBus {
    private events: {
        [key: string]: Array<Function>
    }
    private onceEvents: {
        [key: string]: Array<Function>
    }

    constructor() {
        this.events = {}
        this.onceEvents = {}
    }

    on(type: string, fn: Function) {
        const events = this.events
        if (events[type] == null) events[type] = [] // 初始化key 的 fn 数组

        events[type].push(fn)
    }

    once(type: string, fn: Function) {
        const onceEvents = this.onceEvents
        if (onceEvents[type] == null) onceEvents[type] = [] // 初始化key 的 fn 数组

        onceEvents[type].push(fn)
    }

    off(type: string, fn?: Function) {
        if (!fn) {
            // 删除所有type的函数
            this.events[type] = []
            this.onceEvents[type] = []
        } else {
            // 解绑单个事件
            const fnList = this.events[type]
            const onceFnList = this.onceEvents[type]

            if (fnList) {
                this.events[type] = fnList.filter(curFn => curFn !== fn)
            }
            if (onceFnList) {
                this.onceEvents[type] = onceFnList.filter(curFn => curFn !== fn)
            }
        }
    }

    emit(type: string, ...args: any[]) {
        const fnList = this.events[type]
        const onceFnList = this.onceEvents[type]

        if (fnList) {
            fnList.forEach(f => f(...args))
        }
        if(onceFnList){
            onceFnList.forEach(f => f(...args))

            // once 执行一次就删除
            this.onceEvents[type] = []
        }
    }
}

效果演示代码

const event = new EventBus()
event.on('key1', function (...args: any[]) {
    console.log('fn1', ...args);
})
event.on('key1', function () {
    console.log('fn2');
})
event.emit('key1', 10, 20, 30)

event.once('key2', () => {
    console.log('zzz');
})
event.emit('key2', () => {
    console.log('yyy');
})
event.emit('key2', () => {
    console.log('vvv');
})

LRUCache

Map 实现一个LRU 缓存类
/**
 * LRU cache
 */
export class LRUCache {
    private length: number
    private data: Map<any, any> = new Map()

    constructor(length: number) {
        if (length < 1) throw new Error('请输入大于 1 的数字')
        this.length = length
    }

    set(key: string, value: any) {
        const data = this.data
        if (data.has(key)) {
            data.delete(key)
        }
        data.set(key, value)

        // 如果长度大于传递的长度,需要删掉最后一个
        if (data.size > this.length) {
            let delKey = data.keys().next().value
            data.delete(delKey)
        }
    }

    get(key: string) {
        const data = this.data

        if(!data.has(key)) return null

        let value = data.get(key)
        data.delete(key)

        data.set(key, value)
        return value
    }

    getList(){
        return this.data
    }
}

const lruCache = new LRUCache(2)

lruCache.set('xx', 'xx')
lruCache.set('yy', 'yy')
lruCache.set('zz', 'zz')

console.log(lruCache.get('xx'));
console.log(lruCache.getList());
console.log('----------------');
console.log(lruCache.get('yy'));
console.log(lruCache.getList());
console.log('----------------');

生成符合条件的括号

思路:f(n) 的值永远都是 f(n-1) 的结果 里面每一项push ()

export function generateParenthesis(initn: number): string[] {
    const func = (n: number): string[] => {
        if (n == 1) {
            return ['()']
        }

        let res = func(n - 1)
        
        let pjRes = []
        for (let i = 0; i < res.length; i++) {
            let str = res[i]
            let sL = str.length
            for (let j = 0; j < sL; j++) {
                pjRes.push(`${str.slice(0, j)}()${str.slice(j, sL)}`)
            }
        }
        return pjRes
    }

    return Array.from(new Set(func(initn)))
}

console.log(generateParenthesis(3), 'na');

H5页面如何做首屏渲染

  • 路由懒加载
  • 服务端渲染 ssr
  • App 预取
  • 分页
  • 图片懒加载
  • hybrid 混合

后端返回 10w 条数据,前端如何优化

  • node 中间层里面去改
  • 虚拟列表
  • 插件
    • vue-virtual-scroll-list
    • React-virtualiszed

观察者模式和发布订阅者模式的区别

按钮点击事件
发布订阅是一个订阅事件 一个触发事件
emit on

观察者模式 发布者和订阅事件可以直接调用,比如addEventListener click
发布订阅模式 发布和监听者互相隔离,需要中间媒介来触发。

使用 Vue 的时候遇到那些坑

  • 内存泄漏
  • Vue 新增属性需要用 Vue.Set
  • Vue 删除属性需要用 Vue.delete
  • 无法直接修改数据 arr[index]= value
  • 从列表页切换至详情页,再返回列表页,会自动scroll到顶部。
    解决方案1:
    1. 在列表页数据缓存下来。 在返回的时候,列表数据渲染上去。
    2. 通过 MPA(多屏渲染) 的方式。eg:多个 WebView

实际工作中,React 做过什么优化

  • 修改css 模拟 v-show
  • 使用fragment 减少层级
  • render函数中减少定义,由于render 会频繁执行,所以尽量较少在dom节点新建逻辑。
  • SCU shouldComponentUpdate 来优化渲染
  • pureComponent React.memo
react的坑

js 关键字冲突
setState 异步更新的 需要在回调中用
对于深层数据的state遍历不是很友好,得通过浅拷贝的方式来手动赋值。

错误监听

  • window.error 监听全局组件报错,可以监听异步报错
  • 事件报错 try catch 报错 catch 抛出异常,让上级组件监听到
  • js 中 Promise 的 catch 报错,可以使用 onUnHandledrejection 监听。
  • 异步错误没法监听,需要结合window.error
  • 报错统计(埋点,上报,统计)
  • Vue
    • errorHandle 捕获下级组件的报错 但是没法捕获异步的报错
  • React
    • ErrorBoundary 组件 (核心就是componentDidCatch)

      • 统一监听下级组件内部报错
      • 降级展示UI(可以有友好提示)
      • 监听渲染报错
      • 不监听DOM事件,异步错误
      • Prod环境生效,dev环境抛出错误
      • 代码演示:
      import React from 'react';
      import ErrorTemplate from './template'
      
      class ErrorBoundary extends React.Component {
          constructor(props) {
              super(props);
              this.state = { error: null, errorInfo: null };
          }
      
          componentDidCatch(error, errorInfo) {
              this.setState({
                  error: error,
                  errorInfo: errorInfo
              })
          }
      
          render() {
              if (this.state.errorInfo) {
                  return <ErrorTemplate title="error" tip="糟糕,页面出错了!"/>
              }
              return this.props.children;
          }
      }
      export default ErrorBoundary
      
      • 生产环境验证
        yarn build
        http-server -p 8881 通过本地路径打开文件

如果一个H5 很慢,你该如何排查性能问题?

是哪里慢,怎么体现?
可以从很多个方面沟通。重点:沟通!沟通!沟通!

性能指标:

  • FP(First Paint)第一次加载的情况

  • FCP(First Contentful Paint)有内容的情况

  • FMP(First Meaningful Paint)第一次有意义的渲染(没有一个标准,已弃用)

  • DCL(DomContentLoaded)原生dom内容下载完成

  • LCP(Largest ContentFul Paint)重要的数据已经渲染完

  • L (Load)加载完毕

  • devtool 中的 【性能监控】 开启。可以监控加载慢的请求;network 看监控时间;

  • Lighthouse 测试报告

    • 第三方性能评测工具
    • 支持移动端和pc端
    • 优化建议:webp avif 格式的图片;有限的压缩方法,图片尺寸,http2;等等
    • 安装:npm i lighthouse -g
    • 使用:lighthouse https://www.imooc.com/–view --preset=desktop
  • 网页加载慢?

    • 优化服务器硬件配置,使用CDN
    • 路由懒加载,大组件异步加载 - 减少主包的体积
    • 优化HTTP 换成策略
  • 网页渲染慢?

    • 优化服务端接口(ajax返回时间优化)
    • 优化组件内部业务逻辑
    • 服务端渲染 SSR
  • 持续跟进

    • 性能优化是持续的过程
    • 持续跟进,统计结果,
    • 第三方统计服务:阿里云ARMS、百度统计
  • 二分法减少问题范围逐步更近

工作经历中,遇到什么项目难点,如何解决?

  • redux中深浅拷贝,深浅比较问题。层次比较深的时候,每一层都需要解构出来,代码容器不好看,而且要是碰到层级非常深导致set的代码很长。
this.state = {fourValue: { a: 'a', b: { c: { d: 'd', e: 'ee' } } }}
// 要想修改e = 'ff'
必须层层解构
let b = this.state.threeValue.b
this.setState({
  threeValue: { 
    ...this.state.threeValue,
    b: { 
      ...b, 
      c: 'xxx' 
    } 
  }
})

数组里面这么做:

import React, { Component } from 'react'

export default class About extends Component {
  state = {
    isSubmitting: false,
    inputs: {
      username: {
        touched: false,
        value: 'some_initial_value'
      },
      users: [
        { id: 1, name: 'glack1', count: 1 },
        { id: 2, name: 'glack2', count: 2 },
        { id: 3, name: 'glack3', count: 3 },
        { id: 4, name: 'glack4', count: 4 },
        { id: 5, name: 'glack5', count: 5 },
      ]
    }
  }

  twochange() {
    let s = this.state
    s.inputs.users[0].count = 100
    this.setState({
      inputs: {
        ...s.inputs,
        users: [...s.inputs.users]
      }
    })
  }

  render() {
    return (
      <>
        <button onClick={() => this.twochange()}>改变array中的count</button>
        {this.state.inputs.users.map(item => {
          return <>
            <div>name:{item.name}</div>
            <div>id:{item.id}</div>
            <div>count:{item.count}</div>
          </>
        })}
      </>
    )
  }
}

  • 歌词播放器时间播放和歌词滚动配合。
  • 改造之前小程序的代码
    • 群聊模块轮询的方式改造成wxWebscoket
    • 优化首次加载是app.js 还未执行完毕的情况,通过app中的一个回调。
  • 模板:描述问题:背景 + 现象 + 造成的影响
  • 解决:分析 + 解决
  • 学到了什么?如何避免?
    可以整个文档,遇到的难点都归纳起来。

时间复杂度

  • O(1) 一次就够(数量级)
    ex: 只走了一次处理逻辑

    const func = (a) => {
    	return a
    }
    
  • O(n) 和传输的数据量一样
    ex:时间复杂度和当前arr的长度一致

    const func = (arr) => {
    	for(let i = 0; i < arr.length; i++){
    		console.log(arr[i])
    	}
    }
    
  • O(n^2) 数据量的平方
    ex

    const func = (arr) => {
    	for(let i = 0; i < arr.length; i++){
    		for(let j = 0; j < arr.length; j++){
    			console.log(arr[j])
    		}
    	}
    }
    
  • O(logn) 数据量的对数(数量级) 100 => 10
    ex: 二分算法,每次把数据砍掉二分之一。
    [1,2,3,4,5,6,7,8,…] 找到6,可以通过砍掉中间的来比对

  • O(nlogn)

空间复杂度

  • O(1) 一次就够(数量级)
    ex: 像这样,不管数组的长度是多少,数组的内存大小是相对固定的
    const func = (arr: []) => {
    	arr[1] = 1
    	arr[2] = 2
    	arr[3] = 3
    	arr[4] = 4
    	return a
    }
    
  • O(n)
    ex:这里定义了一个新的数组,并且对相关的数组进行了赋值
    const func = (arr) => {
    	let arr2 = []
    	for(let i = 0; i < arr.length; i++){
    		arr2[i] = arr[i]
    	}
    }
    

单元测试

jest
判断数组是否相等 toEquel
判断bool是否相等 toBe

二分查找

/**
 * desc: 给一个有序的数组,[10, 20, 50, 70, 90] 查到50下标
 * 算法:二分法查找
 */

// 循环的方式
export const binarySearch1 = (arr: number[], target: number) => {
    let startIndex: number = 0
    let endIndex: number = arr.length - 1

    while (startIndex < endIndex) {
        let mdIndex = Math.floor((endIndex + startIndex) / 2)
        let value = arr[mdIndex]

        // 如果正好是中间的数字
        if (value === target) return mdIndex

        // 在左边
        if (target < value) {
            endIndex = mdIndex - 1
        }

        // 在右边
        if (target > value) {
            startIndex = mdIndex - 1
        }

    }

    return -1
}

// 递归的方式
const binarySearch2 = (arr: number[], target: number, startIndex?: number, endIndex?: number): number => {
    if(arr.length <= 0) return -1
    if(startIndex == null) startIndex = 0
    if(endIndex == null) endIndex = arr.length - 1
    
    if(startIndex > endIndex) return -1

    // 找到中间下标和value
    let midIndex = Math.floor((startIndex + endIndex) / 2)
    let midValue = arr[midIndex]

    if (midValue > target) {
        return binarySearch2(arr, target, startIndex, midIndex - 1)
    }else if(midValue < target){
        return binarySearch2(arr, target, midIndex + 1, endIndex)
    }else{
        return midIndex
    }
}

const arr = [10, 20, 40, 50, 70, 90, 120]
let res = binarySearch2(arr, 90)
console.log(res, 'res');

这里写了两种方式,递归和循环都可以完成问题,时间复杂度为O(logn),但是如果一定得使用最好的方式,循环更好,因为递归调用方法也会耗时。

将一个数组旋转k步

方案一 unshift pop
时间复杂度:O(n^2)
unshift 也是一个O(n) 的结构

方案二 contact
时间复杂度:O(1)

/**
 * descripbe: 
 * 1. 完成两个算法,k位数往数组的前面加
 * 2. 测试时间 log time endTime
 */


// 通过unshift的方法 [1,2,3,4,5,6,7] 3 => [5,6,7,1,2,3,4]
export const rodate1 = (arr: number[], len: number): number[] => {
    // 去k的绝对值
    let k = Math.abs(len % arr.length)
    for (let i = 0; i < k; i++) {
        let chu = arr.pop()
        if (chu) {
            arr.unshift(chu)
        }
    }

    return arr
}

// 通过contact的方法
export const rodate2 = (arr: number[], len: number): number[] => {
    let k = Math.abs(len % arr.length)
    len = k
    let l = arr.length

    let left = arr.slice(0, l - len)
    let right = arr.slice(l - len, l)

    return [...right, ...left]
}

let arrHuge = []
for (let i = 0; i < 100000; i++) {
    arrHuge.push(i)
}
let step = 8 * 10000

// 这个过程执行了 4000ms 也就是4s 时间复杂度 O(n^2)
console.time()
rodate1(arrHuge, step)
console.timeEnd()

// 这个过程仅仅2.7毫秒 时间复杂度 O(1)
console.time()
rodate2(arrHuge, step)
console.timeEnd()

数据结构

栈的应用

判断是否匹配 夸号类型

/**
 * descripbe: 算法 {a[b(c)d]e}
 * 判断是否匹配 夸号类型
 */
const isMatch = (top: string, s: string): boolean => {
    if (top === "{" && s === '}') return true
    if (top === "(" && s === ')') return true
    if (top === "[" && s === ']') return true
    return false
}

export const matchFunc = (str: string) => {
    let length = str.length
    if (length <= 0) return false
    let leftSyb = '{[('
    let rightSyb = '}])'
    let stack = []

    for (let i = 0; i < length; i++) {
        let s = str[i]
        if (leftSyb.includes(s)) {
        	// 碰到左边的字符串先入栈
            stack.push(s)
        } else if (rightSyb.includes(s)) {
        	// 碰到右边的字符串先判断是否和顶层的匹配
            let top = stack[stack.length - 1]

            if (isMatch(top, s)) {      
            	// 匹配的话,就删除顶层
                stack.pop()
            } else {
                return false
            }
        }
    }

    return stack.length === 0
}

let str1 = '{1[2(3)4]5}' // 匹配
let str2 = '{1[2(34]5}19)' // 顺序不一样
console.log(matchFunc(str1),matchFunc(str2), // true, false
找数组中两个字符串相加为10的值

O(n^2)的方式两个循环如下:

/**
 * desc: 找数组中两个字符串相加为10的一个值
 */
export const twoNumberAdd = (arr: number[], total: number): number[] => {
    if(arr.length === 0) return []
    if(total === 0) return []
    let length = arr.length
    const res: number[] = []
    let flag = false

    for (let i = 0; i < length - 1; i++) {
        for (let j = i; j < length - 1; j++) {
            if (arr[i] + arr[j] == total) {
                res.push(arr[i])
                res.push(arr[j])
                flag = true
                break;
            }
        }
        if (flag) break // for循环中,break可以停止循环。
    }
    return res
}

const arrlog = [1,2,3,4,5,6,8]
const res = twoNumberAdd(arrlog, 10)
console.log(res, 'res');

二分理念去查,时间复杂度 O(N)

export const twoNumberAdd2 = (arr: number[], total: number): number[] => {
    if(arr.length === 0) return []
    if(total === 0) return []
    const res: number[] = []
    let i = 0
    let j = arr.length - 1

    while (i < j) {
        let a = arr[i]
        let b = arr[j]
        let t = a + b

        if (t > total) {
            j--
        } else if (t < total) {
            i++
        } else {
            res.push(a)
            res.push(b)
            break
        }
    }

    return res
}


const arrlog = [1, 2, 3, 4, 5, 6, 8]
const res = twoNumberAdd2(arrlog, 10)
console.log(res, 'res');

时间复杂度对比,可以看出,二分法的时间复杂度更低。选第二个方法。

const arrlog = [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 4, 5, 6, 8]
console.time()
for (let i = 0; i < 100 * 10000; i++) {
    twoNumberAdd(arrlog, 10)
}
console.timeEnd()


console.time()
for (let i = 0; i < 100 * 10000; i++) {
    twoNumberAdd2(arrlog, 10)
}
console.timeEnd()

// default: 288.071044921875 ms
// default: 14.84619140625 ms
链表

js实现单向链表结构


// 实现一个单项链表功能
// 变成 {value: '100', next: { value: "200", next: { value: '300', next: { value: '400' } } }}
const arrInit = [100, 200, 300, 400, 500]
const func = (arr) => {
  let length = arr.length
  if (length <= 0) return [] 
  
  let curNode = {
    value: arr[length-1],
  }
  for (let i = length-2; i >= 0; i--) {
    console.log(i, 'i');
    curNode = {
      value: arr[i],
      next: curNode
    }
  }
  return curNode
}

console.log(func(arrInit), 'arr')

实现反转单向链表算法

/**
 * {value: 100, next: { value: 200, next: { value: 300, next: { value: 400, next: { value: 500 } } } }}
 * 处理成为下面结构
 * {value: 500, next: { value: 400, next: { value: 300, next: { value: 200, next: { value: 100 } } } }}
 * @param arr 反转单向链表结构结构
 */
interface INode {
    value: number,
    next?: INode
}
export const reverseLinkNode = (linkNode: INode) => {
    // 定义三个指针用于接收
    let prevNode: INode | undefined = undefined
    let curNode: INode | undefined = undefined
    let nextNode: INode | undefined = linkNode
    
    // 循环赋值引用
    while(nextNode){
        console.log('1');
        
        // 第一种情况
        if (curNode && !prevNode) {
            delete curNode.next
        }

        // 第二种和倒数第二种情况
        if (curNode && prevNode) {
            curNode.next = prevNode
        }

        prevNode = curNode
        curNode = nextNode
        // @ts-ignore
        nextNode = nextNode.next
    }

    
    curNode!.next = prevNode

    
    return curNode
}

const list = {value: 100, next: { value: 200, next: { value: 300, next: { value: 400, next: { value: 500 } } } }}
let list2 = reverseLinkNode(list)
console.log(list2, 'list2');

链表中,查询慢删除快
数组中,查询快删除慢。

队列

用两个栈实现队列(数组方式)

/**
 * 两个栈实现队列
 */
export class MyQuene {
    constructor(stack1: number[]){
        this.stack1 = stack1
    }
    stack1: number[] = []
    stack2: number[] = []

    add(n: number) {
        this.stack1.push(n)
    }
    delete(): number | null {
        let stack1 = this.stack1
        let stack2 = this.stack2

        // 1. 压栈处理成stack2
        while(stack1.length){
            let n = stack1.pop()
            if (n) stack2.push(n)
        }
        
        // 2. stack2.pop
        let deleteItem = stack2.pop()
        // 3. stack2 压栈成stack1
        while(stack2.length){
            let n = stack2.pop()
            if(n) stack1.push(n)
        }
        
        return deleteItem as number
    }
    get length() {
        return this.stack1.length
    }
}

const q = new MyQuene([1,2,3,4,5])
let dItem = q.delete()
console.log(dItem, q.stack1); // 1, [2,3,4,5]
q.add(6)						
console.log('add', q.stack1); // add, [2,3,4,5,6]
console.log(q.length);        // 5

大致思路如下:
首先有两个栈,一个栈用于存初始值。一个用于转换的时候用,stack1压栈成stack2,顺序改变,A在首部变成尾部,直接pop删除最后一个元素,再压栈成stack1 得到的就是 删掉了A的 BCDE。

一些数据量庞大的场景中,由于数组unshiftshift消耗的性能比较大时间复杂度(O(n^2)),使用栈的概念,可以通过 两个栈+队列 的方式完成数组的unshift操作。

用链表实现队列

/**
 * 链表实现队列
 */
interface INode {
    value: number,
    next: INode | null
}
export class MyQuene {
    head: INode | null = null
    tail: INode | null = null
    len: number = 0

    add(n: number) {
        let newNode: INode = {
            value: n,
            next: null
        }

        // 处理head
        if (this.head == null) {
            this.head = newNode
        }

        // // 处理tail
        let tailNode = this.tail
        if (tailNode) {
            // 第二次进入已经新add了一个newNode,可以指定next为新节点
            tailNode.next = newNode
        }
        this.tail = newNode

        this.len++
    }

    delete(): number | null {
        const headNode = this.head
        if (this.len == 0) return null
        if (this.head == null) return null

        let value = headNode!.value
        
        // 处理head
        this.head = headNode!.next

        this.len--
        
        return value

    }
    get length(): number {
        return this.len
    }
}
数组和链表哪个更快?
  • 数组是连续存储,push很快,shift很慢
  • 链表是非连续存储,add和delete都很快(查找很慢)
  • 所以链表实现队列更快
二叉树结构
题目:给一个有序的二叉树结构,找到第k位的树
/**
 * 二叉搜索树中的第k位
 */
interface INode {
    value: number,
    left: INode | null,
    right: INode | null
}
const bst: INode = {
    value: 5,
    left: {
        left: {
            value: 2,
            left: null,
            right: null
        },
        right: {
            value: 4,
            left: null,
            right: null
        },
        value: 3
    },
    right: {
        left: {
            value: 6,
            left: null,
            right: null
        },
        right: {
            value: 8,
            left: null,
            right: null
        },
        value: 7
    },
}


let arr: number[] = []

/**
 * 前序遍历
 * Preorder traversal
 */
const preorderTraversal = (node: INode | null) => {
    if (!node) return
    if (!node) return
    arr.push(node.value)
    preorderTraversal(node.left)
    preorderTraversal(node.right)
}

/**
 * 中序遍历
 * Preorder traversal
 */
const inorderTraversal = (node: INode | null) => {
    if (node === null) return
    inorderTraversal(node.left)
    arr.push(node.value)
    inorderTraversal(node.right)
}

/**
 * 后序遍历
 * Preorder traversal
 */
const postorderTraversal = (node: INode | null) => {
    if (!node) return
    if (!node) return
    postorderTraversal(node.left)
    postorderTraversal(node.right)
    arr.push(node.value)
}


export const biranyTreeSearch1 = (node: INode, k: number): number | null=> {
    inorderTraversal(node)

    return arr[k - 1] || null
}

console.log(biranyTreeSearch1(bst, 3));

单元测试
一般react-create-app有集成jest包,可以直接通过 yarn test 运行
describe:描述内容
it: 提示内容
expect: 期望的方法
toBe: 期望得到某个数字
toBeNull: 期望得到null

import { INode, biranyTreeSearch1, bst } from './index'

describe('二叉搜索树求key位的值', () => {
    it('普通情况', () => {
        expect(biranyTreeSearch1(bst, 3)).toBe(4)
    });
    it('为0的情况', () => {
        expect(biranyTreeSearch1(bst, 0)).toBeNull()
    });
})

// Test Suites: 1 passed, 1 total
// Tests:       2 passed, 2 total

三种遍历

  • 前序遍历
  • 中序遍历
  • 后序遍历
重点:
  • 二叉树和三种遍历
  • 二叉搜索树的特点:left <= root;right >= root
  • 二分搜索树的价值:可使用二分法进行快速查找

算法三大规则

  • 贪心
  • 二分
  • 动态规划
斐波拉切数列 找第 N 位

递归方式
这种方式 时间复杂度(O(n^2))

export function fiboracheSequence1(n: number): number {
    if (n <= 0) return 0
    if (n === 1) return 1

    return fiboracheSequence1(n - 1) + fiboracheSequence1(n - 2)
}

// 测试
console.log(fiboracheSequence1(8)) // 21

循环的方式

export function fiboracheSequence2(n: number): number {
    if (n <= 0) return 0
    if (n === 1) return 1

    let n1 = 1
    let n2 = 0
    let res = 0

    for (let i = 2; i < n; i++) {
        res = n1 + n2

        n2 = n1
        n1 = res
    }

    return res 
}
移动数组中的0到末尾

O(n^2) 不好用

export const moveNumber1 = (arr: number[]) => {
    if (arr.length === 0) return []

    let length = arr.length
    let blLen = 0
    for (let i = 0; i < length - blLen; i++) {
        if (arr[i] === 0) {
            
            arr.push(0)
            arr.splice(i, 1)
            i--
            blLen ++
        }
    }

    return arr
}

let initArr = [1, 0, 1, 0, 1, 0, 234, 1, 34521, 0, 1, 90, 0]
moveNumber1(initArr)
console.log(initArr, 'arr')

O(n)时间复杂度

// O(n)的时间复杂度
export const moveNumber2 = (arr: number[]) => {
    let i, j = -1, length = arr.length

    for (i = 0; i < length; i++){
        if (arr[i] === 0) {
            if (j < 0) {
                j = i
            }
        }
        if(arr[i] !== 0 && arr[j] >= 0){
            // 交换
            const n = arr[i]
            arr[i] = arr[j]
            arr[j] = n
            
            j++
        }
    }
}

let initArr = [1, 0, 1, 0, 1, 0, 234, 1, 34521, 0, 1, 90, 0]
moveNumber2(initArr)
console.log(initArr, 'arr')
字符串中连续最多的字符,以及次数

O(n)

// abcdddddfffg
interface IRes {
    char: string,
    length: number
}
export const findStr = (str: string) => {
    let length = str.length
    let res: IRes = {
        char: '',
        length: 0
    }
    let temLength

    for (let i = 0; i < length; i++) { 
        console.log('---------------');
        
        temLength = 0
        for (let j = i; j < length; j++) {
            console.log(`i:${i}\nj:${j}\ntemp:${temLength}\nres.length:${res.length}\nres.char:${res.char}`);
            if (str[i] === str[j]) {
                temLength++
            }
            if (str[i] !== str[j] || j === length - 1) {
                if (temLength > res.length) {
                    res.length = temLength
                    res.char = str[i]
                }
                if (i < length - 1) {
                    i = j - 1
                }

                break;
            }
        }
    }

    return res

}

let str = 'abcdddefg'
console.log(findStr(str));

O(n)的时间复杂度

思路: 通过判断当前元素和上一个元素是否相等,来决定要不要累加。


export const findStr2 = (str: string): IRes => {
    let length = str.length
    let res: IRes = {
        char: '',
        length: 0
    }
    if (length === 0) return res

    let tempLength = 1
    for (let i = 0; i < length - 1; i++){
        if (str[i] === str[i+1]) {
            tempLength++
        }else if(str[i] !== str[i+1]){
            if (tempLength > res.length) {
                res = {
                    char: str[i],
                    length: tempLength
                }
                tempLength = 1
            }
        }
    }

    return res
}

let str = 'abcdddeeeeeeefffg'
console.log(findStr2(str)); // char: e, length: 7

双指针


export const findStr3 = (str: string): IRes => {
    let length = str.length
    let res: IRes = {
        char: '',
        length: 0
    }
    if (length === 0) return res

    let tempLength = 0
    let i = 0,j = 0
    for (; i < length; i++){
        if (str[i] === str[j]) {
            tempLength++
        }else if(str[i] !== str[j] || i === length - 1){
            if (tempLength > res.length) {
                res = {
                    char: str[j],
                    length: tempLength
                }
            }
            tempLength = 0
            if (i < length - 1){
                j = i
                i --
            }
        }
    }
    

    return res
}

单元测试

import { findStr1, findStr2 } from './index'

describe('寻找重复的字串第一种方法:跳步', () => {
    it('普通情况', () => {
        expect(findStr1('abcdddedff')).toEqual({char: 'd', length: 3})
    })
    it('都是连续字符', () => {
        expect(findStr1('dddeeeeddd')).toEqual({char: 'e', length: 4})
    })
    it('字符串为空', () => {
        expect(findStr1('')).toEqual({char: '', length: 0})
    })
    it('无连续字符', () => {
        expect(findStr1('abcdefghijk')).toEqual({char: 'a', length: 1})
    })
})


describe('寻找重复的字串第二种方法:一次循环判断是否为相同字符', () => {
    it('普通情况', () => {
        expect(findStr2('abcdddedff')).toEqual({char: 'd', length: 3})
    })
    it('都是连续字符', () => {
        expect(findStr2('dddeeeeddd')).toEqual({char: 'e', length: 4})
    })
    it('字符串为空', () => {
        expect(findStr2('')).toEqual({char: '', length: 0})
    })
    it('无连续字符', () => {
        expect(findStr2('abcdefghijk')).toEqual({char: 'a', length: 1})
    })
})


求回文数(aba 121 454)
// 求一个范围内的回文数 转字符串 转数字判断是否相等
export const getPalindromeNumberFunc1 = (max: number): number[] => {
    let res: number[] = []
    if (max <= 0) return []

    for (let i = 1; i <= max; i++) {
        let n = i.toString()
        if (n === n.split('').reverse().join('')) {
            res.push(i)
        }
    }

    return res
}

// 求一个范围内的回文数 依次判断首位和末尾是否一直相等
export const getPalindromeNumberFunc2 = (max: number): number[] => {
    let res: number[] = []
    if (max <= 0) return []

    for (let i = 1; i <= max; i++) {
        let n = i.toString()
        let startIndex = 0
        let endIndex = n.length - 1
        let flag = true

        while (startIndex < endIndex) {
            if (n[startIndex] === n[endIndex]) {
                startIndex++
                endIndex--
            } else {
                flag = false
                break
            }
        }

        if (flag) {
            res.push(i)
        }
    }

    return res
}

// 求一个范围内的回文数 反转数字
export const getPalindromeNumberFunc3 = (max: number): number[] => {
    let res: number[] = []
    if (max <= 0) return []

    for (let i = 1; i <= max; i++) {
        let n = i
        let rev = 0

        while (n > 0) {
            rev = rev * 10 + n % 10
            n = Math.floor(n / 10)
        }

        if (rev === i) res.push(i)
    }

    return res
}

console.log(getPalindromeNumberFunc2(500));

测试效果:方法1:400ms;方法二:50ms;方法三:42ms
时间复杂度分析:方法一最慢,因为数组转换需要时间

数字转成千分位的字符串

export const numberToStr = (num: number): string => {
    
    let res = ''
    let str = num.toString()
    let length = str.length
    let times = 0

    for (let i = length - 1; i >= 0; i--) {
        times ++
        
        if (times == 3 && i !== 0) {
            res = ',' + str[i] + res
            times = 0
        }else{
            res = str[i] + res
        }
    }
    return res
}

console.log(numberToStr(13880000000));

大小写转换
// 正则表达式大小写转换
export const toggleCase1 = (str: string): string => {
    let res = ''
    let length = str.length

    for (let i = 0;i < length; i++){
        
        let s = str[i]
        let reg1 = /[a-z]/
        let reg2 = /[A-Z]/
        
        if (reg1.test(s)) {
            res = res + s.toUpperCase()
        }else if(reg2.test(s)){
            res = res + s.toLowerCase()
        }else {
            res = res + s
        }
    }
    return res
}

// 通过ASCII编码
export const toggleCase2 = (str: string): string => {
    let res = ''
    let length = str.length

    for (let i = 0;i < length; i++){
        let s = str[i]
        let code = s.charCodeAt(0)

        if (code >= 65 && code <= 90) {
            res = res + s.toLowerCase()
        }else if(code >= 97 && code <= 122){
            res = res + s.toUpperCase()
        }else {
            res = res + s
        }
    }
    
    return res
}

console.log(toggleCase2('avheDF!DsadSFEWF'));
0.1 + 0.2 !== 0.3

整数转换二进制没有误差
有些小数可能是无法用二进制精准转化
各个计算机语言的通病
通过math.js 进行准确的计算

跨域问题的解决

  • 前端解决
    • Vue中可以修改proxy代理
    • React
      • 通过package.json修改proxy属性;
      • 通过下载http-proxy-middleware ,修改setUp.js文件
      • 通过npm run eject 暴露配置修改webpack配置,不推荐。
      • 普通的html页面使用jsonp也可以实现,需要和后端规定一些标准。
    • 后端通过修改跨域头解决
    • 小程序可以通过添加白名单解决

This指向问题

- 在function中,this的指向是指调用该方法的指针本身。
- 在箭头函数中,this指向是当前的作用域
- class的class component 指向一般都是指当前组件,有一些情况,class组件的方法没有bind this 指针,需要手动指定。如果不指定,可能会导致方法调用不到的问题。

原型作用域的相关问题

  • 学习链接戳我:
    • 原型
      • 在我们创建函数的时候,会自动生成一个prototype属性,我们函数需要的所有属性和方法都绑定在这个prototype上,这个属性指向一个对象,也就是原型对象。
      • 原型对象里面拥有constructor,这个对象指向的是他的构造函数。比如用new 出来的对象下的prototype下面的consturctor会与函数相等,他们都是一个构造函数。他们指向的都是该构造函数本身
    • 原型特点:
      • 实例中的属性和方法会共享。
      • new出来的实例。如果set了同名属性,将会覆盖原本原型链上的属性。如果在实例外获取不到,会在原型链上面查找。
      • 如果碰到需求,需要重构原型函数,需要重写该函数时,需要将constructor再次赋值给原型琏上的实例方法。否则会导致方法没法完全相等的问题。
      • 举例:
         function Person(){}
         var p = new Person();
         // 这里的重写就是通过修改propotype来重写
         Person.prototype = {
          name: 'tt',
           age: 18
         }
         Person.prototype.constructor === Person // false 
         p.name // undefined 
        
        优化:
        Person.prototype = {
           constructor: Person,
           name: 'tt',
           age: 18
        }
        
    • 原型链
      • js中的继承是通过原型链的方式来的,例如声明了一个新的对象或者函数,他们的原型prototype也是一个原型对象,在他之上也有他的原型对象,于是层层链入,形成了一个类似链表的结构,就被称之为原型链。
      • 所有原型对象的终点都是Object的prototype对象。
      • Object的原型对象是null,null之上没有原型。
        原型链图示
    • 原型链的问题
      • 引用问题
      • 原型链上面如果是一个引用类型(Object Array),多个实例同时修改原型属性的值,所有的实例都会收到影响二发生改变。
      • 例子;这里不管如何修改arr的数据,其他引用arr的地方都会改变。
      function Person(){}
      Person.prototype.arr = [1, 2, 3, 4];
      var person1 = new Person();
      var person2 = new Person();
      person1.arr.push(5) 
      person2.arr // [1, 2, 3, 4, 5]
      
    • class和原型链的关系:
      • 本质上class语法只是es6的语法糖,构建工具将class语法构建成了原型对象的写法。例如constructor中的代码就是函数内部方法,class中的方法也就是挂载到了prototype上去。
      • JavaScript是基于原型的
      • 图解
      • 在这里插入图片描述

宏任务和微任务

  • 干货
    • http://events.jianshu.io/p/fd15db94a034
      总结:
      常见的宏任务:script语句 setTimeout setInterval setImmedate promise
      微任务:Promise.[ then/catch/finally ] node中的process.nextTick()
    • 一个任务开始的时候先执行srcipt 第一个宏任务,其中碰到了其他宏任务(setTimeout),先放入宏任务队列。碰到了微任务(.then),放入微任务队列,以此类推。
    • 接着执行完宏任务的结尾,微任务开始按顺序执行,
    • 再回来执行第二个宏任务(setTimeout的回调)以此类推。

函数的节流和防抖

  • https://gitee.com/HeiGes/net-case-must/blob/master/clientNetCaseMusic/README.md#%E9%98%B2%E6%8A%96%E5%92%8C%E8%8A%82%E6%B5%81
    • 为了解决一些场景连续触发一个函数,或者请求,需要通过防抖节流将方法减少运行。

class生命周期

  • https://blog.csdn.net/qq_43013884/article/details/125421659
  • 初始化
    • constructor
    • getDeriviedStateFormProps
    • render
    • componentDidMount
  • 更新时
    • getDeriviedStateFormProps
    • shouComponentUpdate
    • render
    • getSnapShotBeforeUpdate
    • componentDidUpdate
  • 卸载时
    • componentWillUnMount

一个前端大佬的文章

https://blog.csdn.net/qq_34998786/article/details/118887109?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-118887109-blog-122952429.pc_relevant_multi_platform_whitelistv3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-118887109-blog-122952429.pc_relevant_multi_platform_whitelistv3&utm_relevant_index=2

浏览器对于小数单位是怎么计算的?
interface和type的区别是什么?你日常工作中是用interface还是type?
快速理解 TypeScript 的逆变、协变、双向协变、不变

https://zhuanlan.zhihu.com/p/500762226

能不能讲讲小程序的原理?
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值