中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)(二)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)(二)

中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)(一):https://developer.aliyun.com/article/1413861

21. 数组(array)

  • map: 遍历数组,返回回调返回值组成的新数组
  • forEach: 无法break,可以用try/catchthrow new Error来停止
  • filter: 过滤
  • some: 有一项返回true,则整体为true
  • every: 有一项返回false,则整体为false
  • join: 通过指定连接符生成字符串
  • push / pop: 末尾推入和弹出,改变原数组, push 返回数组长度, pop 返回原数组最后一项;
  • unshift / shift: 头部推入和弹出,改变原数组,unshift 返回数组长度,shift 返回原数组第一项 ;
  • sort(fn) / reverse: 排序与反转,改变原数组
  • concat: 连接数组,不影响原数组, 浅拷贝
  • slice(start, end): 返回截断后的新数组,不改变原数组
  • splice(start, number, value...): 返回删除元素组成的数组,value 为插入项,改变原数组
  • indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标
  • reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值
  • 当传入 defaultPrev 时,从第一项开始;
  • 当未传入时,则为第二项
  • 数组乱序:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
    return Math.random() - 0.5;
});
复制代码
  • 数组拆解: flat: [1,[2,3]] --> [1, 2, 3]
Array.prototype.flat = function() {
    return this.toString().split(',').map(item => +item )
}
复制代码

浏览器

1. 跨标签页通讯

不同标签页间的通讯,本质原理就是去运用一些可以 共享的中间介质,因此比较常用的有以下方法:

  • 通过父页面window.open()和子页面postMessage
  • 异步下,通过 window.open('about: blank')tab.location.href = '*'
  • 设置同域下共享的localStorage与监听window.onstorage
  • 重复写入相同的值无法触发
  • 会受到浏览器隐身模式等的限制
  • 设置共享cookie与不断轮询脏检查(setInterval)
  • 借助服务端或者中间层实现

2. 浏览器架构

  • 用户界面
  • 主进程
  • 内核
  • 渲染引擎
  • JS 引擎
  • 执行栈
  • 事件触发线程
  • 消息队列
  • 微任务
  • 宏任务
  • 网络异步线程
  • 定时器线程

3. 浏览器下事件循环(Event Loop)

事件循环是指: 执行一个宏任务,然后执行清空微任务列表,循环再执行宏任务,再清微任务列表

  • 微任务 microtask(jobs): promise / ajax / Object.observe(该方法已废弃)
  • 宏任务 macrotask(task): setTimout / script / IO / UI Rendering

4. 从输入 url 到展示的过程

  • DNS 解析
  • TCP 三次握手
  • 发送请求,分析 url,设置请求报文(头,主体)
  • 服务器返回请求的文件 (html)
  • 浏览器渲染
  • HTML parser --> DOM Tree
  • 标记化算法,进行元素状态的标记
  • dom 树构建
  • CSS parser --> Style Tree
  • 解析 css 代码,生成样式树
  • attachment --> Render Tree
  • 结合 dom树 与 style树,生成渲染树
  • layout: 布局
  • GPU painting: 像素绘制页面

5. 重绘与回流

当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。

  • 重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少
  • 回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:
  • 页面初次渲染
  • 浏览器窗口大小改变
  • 元素尺寸、位置、内容发生改变
  • 元素字体大小变化
  • 添加或者删除可见的 dom 元素
  • 激活 CSS 伪类(例如::hover)
  • 查询某些属性或调用某些方法
  • clientWidth、clientHeight、clientTop、clientLeft
  • offsetWidth、offsetHeight、offsetTop、offsetLeft
  • scrollWidth、scrollHeight、scrollTop、scrollLeft
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo()

回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。

最佳实践:

  • css
  • 避免使用table布局
  • 将动画效果应用到position属性为absolutefixed的元素上
  • javascript
  • 避免频繁操作样式,可汇总后统一 一次修改
  • 尽量使用class进行样式修改
  • 减少dom的增删次数,可使用 字符串 或者 documentFragment 一次性插入
  • 极限优化时,修改样式可将其display: none后修改
  • 避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用 变量存住

6. 存储

我们经常需要对业务中的一些数据进行存储,通常可以分为 短暂性存储 和 持久性储存。

  • 短暂性的时候,我们只需要将数据存在内存中,只在运行时可用
  • 持久性存储,可以分为 浏览器端 与 服务器端
  • 浏览器:
  • cookie: 通常用于存储用户身份,登录状态等
  • http 中自动携带, 体积上限为 4K, 可自行设置过期时间
  • localStorage / sessionStorage: 长久储存/窗口关闭删除, 体积限制为 4~5M
  • indexDB
  • 服务器:
  • 分布式缓存 redis
  • 数据库

7. Web Worker

现代浏览器为JavaScript创造的 多线程环境。可以新建并将部分任务分配到worker线程并行运行,两个线程可 独立运行,互不干扰,可通过自带的 消息机制 相互通信。

基本用法:

// 创建 worker
const worker = new Worker('work.js');
// 向 worker 线程推送消息
worker.postMessage('Hello World');
// 监听 worker 线程发送过来的消息
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
}
复制代码

限制:

  • 同源限制
  • 无法使用 document / window / alert / confirm
  • 无法加载本地资源

8. V8垃圾回收机制

垃圾回收: 将内存中不再使用的数据进行清理,释放出内存空间。V8 将内存分成 新生代空间老生代空间

  • 新生代空间: 用于存活较短的对象
  • 又分成两个空间: from 空间 与 to 空间
  • Scavenge GC算法: 当 from 空间被占满时,启动 GC 算法
  • 存活的对象从 from space 转移到 to space
  • 清空 from space
  • from space 与 to space 互换
  • 完成一次新生代GC
  • 老生代空间: 用于存活时间较长的对象
  • 从 新生代空间 转移到 老生代空间 的条件
  • 经历过一次以上 Scavenge GC 的对象
  • 当 to space 体积超过25%
  • 标记清除算法: 标记存活的对象,未被标记的则被释放
  • 增量标记: 小模块标记,在代码执行间隙执,GC 会影响性能
  • 并发标记(最新技术): 不阻塞 js 执行
  • 压缩算法: 将内存中清除后导致的碎片化对象往内存堆的一端移动,解决 内存的碎片化

9. 内存泄露

  • 意外的全局变量: 无法被回收
  • 定时器: 未被正确关闭,导致所引用的外部变量无法被释放
  • 事件监听: 没有正确销毁 (低版本浏览器可能出现)
  • 闭包: 会导致父级中的变量无法被释放
  • dom 引用: dom 元素被删除时,内存中的引用未被正确清空

可用 chrome 中的 timeline 进行内存标记,可视化查看内存的变化情况,找出异常点。

服务端与网络

1. http/https 协议

  • 1.0 协议缺陷:
  • 无法复用链接,完成即断开,重新慢启动和 TCP 3次握手
  • head of line blocking: 线头阻塞,导致请求之间互相影响
  • 1.1 改进:
  • 长连接(默认 keep-alive),复用
  • host 字段指定对应的虚拟站点
  • 新增功能:
  • 断点续传
  • 身份认证
  • 状态管理
  • cache 缓存
  • Cache-Control
  • Expires
  • Last-Modified
  • Etag
  • 2.0:
  • 多路复用
  • 二进制分帧层: 应用层和传输层之间
  • 首部压缩
  • 服务端推送
  • https: 较为安全的网络传输协议
  • 证书(公钥)
  • SSL 加密
  • 端口 443
  • TCP:
  • 三次握手
  • 四次挥手
  • 滑动窗口: 流量控制
  • 拥塞处理
  • 慢开始
  • 拥塞避免
  • 快速重传
  • 快速恢复
  • 缓存策略: 可分为 强缓存协商缓存
  • Cache-Control/Expires: 浏览器判断缓存是否过期,未过期时,直接使用强缓存,Cache-Control的 max-age 优先级高于 Expires
  • 当缓存已经过期时,使用协商缓存
  • 唯一标识方案: Etag(response 携带) & If-None-Match(request携带,上一次返回的 Etag): 服务器判断资源是否被修改,
  • 最后一次修改时间: Last-Modified(response) & If-Modified-Since (request,上一次返回的Last-Modified)
  • 如果一致,则直接返回 304 通知浏览器使用缓存
  • 如不一致,则服务端返回新的资源
  • Last-Modified 缺点:
  • 周期性修改,但内容未变时,会导致缓存失效
  • 最小粒度只到 s, s 以内的改动无法检测到
  • Etag 的优先级高于 Last-Modified

2. 常见状态码

  • 1xx: 接受,继续处理
  • 200: 成功,并返回数据
  • 201: 已创建
  • 202: 已接受
  • 203: 成为,但未授权
  • 204: 成功,无内容
  • 205: 成功,重置内容
  • 206: 成功,部分内容
  • 301: 永久移动,重定向
  • 302: 临时移动,可使用原有URI
  • 304: 资源未修改,可使用缓存
  • 305: 需代理访问
  • 400: 请求语法错误
  • 401: 要求身份认证
  • 403: 拒绝请求
  • 404: 资源不存在
  • 500: 服务器错误

3. get / post

  • get: 缓存、请求长度受限、会被历史保存记录
  • 无副作用(不修改资源),幂等(请求次数与资源无关)的场景
  • post: 安全、大数据、更多编码类型

两者详细对比如下图:

总结给大家一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

 

4. Websocket

Websocket 是一个 持久化的协议, 基于 http , 服务端可以 主动 push

  • 兼容:
  • FLASH Socket
  • 长轮询: 定时发送 ajax
  • long poll: 发送 --> 有消息时再 response
  • new WebSocket(url)
  • ws.onerror = fn
  • ws.onclose = fn
  • ws.onopen = fn
  • ws.onmessage = fn
  • ws.send()

5. TCP三次握手

建立连接前,客户端和服务端需要通过握手来确认对方:

  • 客户端发送 syn(同步序列编号) 请求,进入 syn_send 状态,等待确认
  • 服务端接收并确认 syn 包后发送 syn+ack 包,进入 syn_recv 状态
  • 客户端接收 syn+ack 包后,发送 ack 包,双方进入 established 状态

6. TCP四次挥手

  • 客户端 -- FIN --> 服务端, FIN—WAIT
  • 服务端 -- ACK --> 客户端, CLOSE-WAIT
  • 服务端 -- ACK,FIN --> 客户端, LAST-ACK
  • 客户端 -- ACK --> 服务端,CLOSED

7. Node 的 Event Loop: 6个阶段

  • timer 阶段: 执行到期的setTimeout / setInterval队列回调
  • I/O 阶段: 执行上轮循环残流的callback
  • idle, prepare
  • poll: 等待回调
  1. 执行回调
  2. 执行定时器
  • 如有到期的setTimeout / setInterval, 则返回 timer 阶段
  • 如有setImmediate,则前往 check 阶段
  • check
  • 执行setImmediate
  • close callbacks

跨域

  • JSONP: 利用<script>标签不受跨域限制的特点,缺点是只能支持 get 请求
function jsonp(url, jsonpCallback, success) {
  const script = document.createElement('script')
  script.src = url
  script.async = true
  script.type = 'text/javascript'
  window[jsonpCallback] = function(data) {
    success && success(data)
  }
  document.body.appendChild(script)
}
复制代码
  • 设置 CORS: Access-Control-Allow-Origin:*
  • postMessage

安全

  • XSS攻击: 注入恶意代码
  • cookie 设置 httpOnly
  • 转义页面上的输入内容和输出内容
  • CSRF: 跨站请求伪造,防护:
  • get 不修改数据
  • 不被第三方网站访问到用户的 cookie
  • 设置白名单,不被第三方网站请求
  • 请求校验

框架:Vue

1. nextTick

在下次dom更新循环结束之后执行延迟回调,可用于获取更新后的dom状态

  • 新版本中默认是microtasks, v-on中会使用macrotasks
  • macrotasks任务的实现:
  • setImmediate / MessageChannel / setTimeout

2. 生命周期

  • _init_
  • initLifecycle/Event,往vm上挂载各种属性
  • callHook: beforeCreated: 实例刚创建
  • initInjection/initState: 初始化注入和 data 响应性
  • created: 创建完成,属性已经绑定, 但还未生成真实dom
  • 进行元素的挂载: $el / vm.$mount()
  • 是否有template: 解析成render function
  • *.vue文件: vue-loader会将<template>编译成render function
  • beforeMount: 模板编译/挂载之前
  • 执行render function,生成真实的dom,并替换到dom tree
  • mounted: 组件已挂载
  • update:
  • 执行diff算法,比对改变是否需要触发UI更新
  • flushScheduleQueue
  • watcher.before: 触发beforeUpdate钩子 - watcher.run(): 执行watcher中的 notify,通知所有依赖项更新UI
  • 触发updated钩子: 组件已更新
  • actived / deactivated(keep-alive): 不销毁,缓存,组件激活与失活
  • destroy:
  • beforeDestroy: 销毁开始
  • 销毁自身且递归销毁子组件以及事件监听
  • remove(): 删除节点
  • watcher.teardown(): 清空依赖
  • vm.$off(): 解绑监听
  • destroyed: 完成后触发钩子

上面是vue的声明周期的简单梳理,接下来我们直接以代码的形式来完成vue的初始化

new Vue({})
// 初始化Vue实例
function _init() {
   // 挂载属性
    initLifeCycle(vm) 
    // 初始化事件系统,钩子函数等
    initEvent(vm) 
    // 编译slot、vnode
    initRender(vm) 
    // 触发钩子
    callHook(vm, 'beforeCreate')
    // 添加inject功能
    initInjection(vm)
    // 完成数据响应性 props/data/watch/computed/methods
    initState(vm)
    // 添加 provide 功能
    initProvide(vm)
    // 触发钩子
    callHook(vm, 'created')
   // 挂载节点
    if (vm.$options.el) {
        vm.$mount(vm.$options.el)
    }
}
// 挂载节点实现
function mountComponent(vm) {
   // 获取 render function
    if (!this.options.render) {
        // template to render
        // Vue.compile = compileToFunctions
        let { render } = compileToFunctions() 
        this.options.render = render
    }
    // 触发钩子
    callHook('beforeMounte')
    // 初始化观察者
    // render 渲染 vdom, 
    vdom = vm.render()
    // update: 根据 diff 出的 patchs 挂载成真实的 dom 
    vm._update(vdom)
    // 触发钩子  
    callHook(vm, 'mounted')
}
// 更新节点实现
funtion queueWatcher(watcher) {
  nextTick(flushScheduleQueue)
}
// 清空队列
function flushScheduleQueue() {
   // 遍历队列中所有修改
    for(){
      // beforeUpdate
        watcher.before()
        // 依赖局部更新节点
        watcher.update() 
        callHook('updated')
    }
}
// 销毁实例实现
Vue.prototype.$destory = function() {
   // 触发钩子
    callHook(vm, 'beforeDestory')
    // 自身及子节点
    remove() 
    // 删除依赖
    watcher.teardown() 
    // 删除监听
    vm.$off() 
    // 触发钩子
    callHook(vm, 'destoryed')
}
复制代码

3. 数据响应(数据劫持)

看完生命周期后,里面的watcher等内容其实是数据响应中的一部分。数据响应的实现由两部分构成: 观察者( watcher )依赖收集器( Dep ),其核心是 defineProperty这个方法,它可以 重写属性的 get 与 set 方法,从而完成监听数据的改变。

  • Observe (观察者)观察 props 与 state
  • 遍历 props 与 state,对每个属性创建独立的监听器( watcher )
  • 使用 defineProperty 重写每个属性的 get/set(defineReactive
  • get: 收集依赖
  • Dep.depend()
  • watcher.addDep()
  • set: 派发更新
  • Dep.notify()
  • watcher.update()
  • queenWatcher()
  • nextTick
  • flushScheduleQueue
  • watcher.run()
  • updateComponent()

大家可以先看下面的数据相应的代码实现后,理解后就比较容易看懂上面的简单脉络了。

let data = {a: 1}
// 数据响应性
observe(data)
// 初始化观察者
new Watcher(data, 'name', updateComponent)
data.a = 2
// 简单表示用于数据更新后的操作
function updateComponent() {
    vm._update() // patchs
}
// 监视对象
function observe(obj) {
   // 遍历对象,使用 get/set 重新定义对象的每个属性值
    Object.keys(obj).map(key => {
        defineReactive(obj, key, obj[key])
    })
}
function defineReactive(obj, k, v) {
    // 递归子属性
    if (type(v) == 'object') observe(v)
    // 新建依赖收集器
    let dep = new Dep()
    // 定义get/set
    Object.defineProperty(obj, k, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            // 当有获取该属性时,证明依赖于该对象,因此被添加进收集器中
            if (Dep.target) {
                dep.addSub(Dep.target)
            }
            return v
        },
        // 重新设置值时,触发收集器的通知机制
        set: function reactiveSetter(nV) {
            v = nV
            dep.nofify()
        },
    })
}
// 依赖收集器
class Dep {
    constructor() {
        this.subs = []
    }
    addSub(sub) {
        this.subs.push(sub)
    }
    notify() {
        this.subs.map(sub => {
            sub.update()
        })
    }
}
Dep.target = null
// 观察者
class Watcher {
    constructor(obj, key, cb) {
        Dep.target = this
        this.cb = cb
        this.obj = obj
        this.key = key
        this.value = obj[key]
        Dep.target = null
    }
    addDep(Dep) {
        Dep.addSub(this)
    }
    update() {
        this.value = this.obj[this.key]
        this.cb(this.value)
    }
    before() {
        callHook('beforeUpdate')
    }
}
复制代码

4. virtual dom 原理实现

  • 创建 dom 树
  • 树的diff,同层对比,输出patchs(listDiff/diffChildren/diffProps)
  • 没有新的节点,返回
  • 新的节点tagNamekey不变, 对比props,继续递归遍历子树
  • 对比属性(对比新旧属性列表):
  • 旧属性是否存在与新属性列表中
  • 都存在的是否有变化
  • 是否出现旧列表中没有的新属性
  • tagNamekey值变化了,则直接替换成新节点
  • 渲染差异
  • 遍历patchs, 把需要更改的节点取出来
  • 局部更新dom
// diff算法的实现
function diff(oldTree, newTree) {
   // 差异收集
    let pathchs = {}
    dfs(oldTree, newTree, 0, pathchs)
    return pathchs
}
function dfs(oldNode, newNode, index, pathchs) {
    let curPathchs = []
    if (newNode) {
        // 当新旧节点的 tagName 和 key 值完全一致时
        if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
            // 继续比对属性差异
            let props = diffProps(oldNode.props, newNode.props)
            curPathchs.push({ type: 'changeProps', props })
            // 递归进入下一层级的比较
            diffChildrens(oldNode.children, newNode.children, index, pathchs)
        } else {
            // 当 tagName 或者 key 修改了后,表示已经是全新节点,无需再比
            curPathchs.push({ type: 'replaceNode', node: newNode })
        }
    }
   // 构建出整颗差异树
    if (curPathchs.length) {
        if(pathchs[index]){
          pathchs[index] = pathchs[index].concat(curPathchs)
        } else {
          pathchs[index] = curPathchs
        }
    }
}
// 属性对比实现
function diffProps(oldProps, newProps) {
    let propsPathchs = []
    // 遍历新旧属性列表
    // 查找删除项
    // 查找修改项
    // 查找新增项
    forin(olaProps, (k, v) => {
        if (!newProps.hasOwnProperty(k)) {
            propsPathchs.push({ type: 'remove', prop: k })
        } else {
            if (v !== newProps[k]) {
                propsPathchs.push({ type: 'change', prop: k , value: newProps[k] })
            }
        }
    })
    forin(newProps, (k, v) => {
        if (!oldProps.hasOwnProperty(k)) {
            propsPathchs.push({ type: 'add', prop: k, value: v })
        }
    })
    return propsPathchs
}
// 对比子级差异
function diffChildrens(oldChild, newChild, index, pathchs) {
    // 标记子级的删除/新增/移动
    let { change, list } = diffList(oldChild, newChild, index, pathchs)
    if (change.length) {
        if (pathchs[index]) {
            pathchs[index] = pathchs[index].concat(change)
        } else {
            pathchs[index] = change
        }
    }
   // 根据 key 获取原本匹配的节点,进一步递归从头开始对比
    oldChild.map((item, i) => {
        let keyIndex = list.indexOf(item.key)
        if (keyIndex) {
            let node = newChild[keyIndex]
            // 进一步递归对比
            dfs(item, node, index, pathchs)
        }
    })
}
// 列表对比,主要也是根据 key 值查找匹配项
// 对比出新旧列表的新增/删除/移动
function diffList(oldList, newList, index, pathchs) {
    let change = []
    let list = []
    const newKeys = getKey(newList)
    oldList.map(v => {
        if (newKeys.indexOf(v.key) > -1) {
            list.push(v.key)
        } else {
            list.push(null)
        }
    })
    // 标记删除
    for (let i = list.length - 1; i>= 0; i--) {
        if (!list[i]) {
            list.splice(i, 1)
            change.push({ type: 'remove', index: i })
        }
    }
    // 标记新增和移动
    newList.map((item, i) => {
        const key = item.key
        const index = list.indexOf(key)
        if (index === -1 || key == null) {
            // 新增
            change.push({ type: 'add', node: item, index: i })
            list.splice(i, 0, key)
        } else {
            // 移动
            if (index !== i) {
                change.push({
                    type: 'move',
                    form: index,
                    to: i,
                })
                move(list, index, i)
            }
        }
    })
    return { change, list }
}
复制代码

5. Proxy 相比于 defineProperty 的优势

  • 数组变化也能监听到
  • 不需要深度遍历监听
let data = { a: 1 }
let reactiveData = new Proxy(data, {
  get: function(target, name){
    // ...
  },
  // ...
})
复制代码

6. vue-router

  • mode
  • hash
  • history
  • 跳转
  • this.$router.push()
  • <router-link to=""></router-link>
  • 占位
  • <router-view></router-view>

7. vuex

  • state: 状态中心
  • mutations: 更改状态
  • actions: 异步更改状态
  • getters: 获取状态
  • modules: 将state分成多个modules,便于管理

算法

其实算法方面在前端的实际项目中涉及得并不多,但还是需要精通一些基础性的算法,一些公司还是会有这方面的需求和考核,建议大家还是需要稍微准备下,这属于加分题。

1. 五大算法

  • 贪心算法: 局部最优解法
  • 分治算法: 分成多个小模块,与原问题性质相同
  • 动态规划: 每个状态都是过去历史的一个总结
  • 回溯法: 发现原先选择不优时,退回重新选择
  • 分支限界法

2. 基础排序算法

  • 冒泡排序: 两两比较
  function bubleSort(arr) {
      var len = arr.length;
      for (let outer = len ; outer >= 2; outer--) {
          for(let inner = 0; inner <=outer - 1; inner++) {
              if(arr[inner] > arr[inner + 1]) {
                  [arr[inner],arr[inner+1]] = [arr[inner+1],arr[inner]]
              }
          }
      }
      return arr;
  }
复制代码
  • 选择排序: 遍历自身以后的元素,最小的元素跟自己调换位置
function selectSort(arr) {
    var len = arr.length;
    for(let i = 0 ;i < len - 1; i++) {
        for(let j = i ; j<len; j++) {
            if(arr[j] < arr[i]) {
                [arr[i],arr[j]] = [arr[j],arr[i]];
            }
        }
    }
    return arr
}
复制代码
  • 插入排序: 即将元素插入到已排序好的数组中
function insertSort(arr) {
    for(let i = 1; i < arr.length; i++) {  //外循环从1开始,默认arr[0]是有序段
        for(let j = i; j > 0; j--) {  //j = i,将arr[j]依次插入有序段中
            if(arr[j] < arr[j-1]) {
                [arr[j],arr[j-1]] = [arr[j-1],arr[j]];
            } else {
                break;
            }
        }
    }
    return arr;
}
复制代码

3. 高级排序算法

  • 快速排序
  • 选择基准值(base),原数组长度减一(基准值),使用 splice
  • 循环原数组,小的放左边(left数组),大的放右边(right数组);
  • concat(left, base, right)
  • 递归继续排序 left 与 right
function quickSort(arr) {
    if(arr.length <= 1) {
        return arr;  //递归出口
    }
    var left = [],
        right = [],
        current = arr.splice(0,1); 
    for(let i = 0; i < arr.length; i++) {
        if(arr[i] < current) {
            left.push(arr[i])  //放在左边
        } else {
            right.push(arr[i]) //放在右边
        }
    }
    return quickSort(left).concat(current,quickSort(right));
}
复制代码
  • 希尔排序:不定步数的插入排序,插入排序
  • 口诀: 插冒归基稳定,快选堆希不稳定

 

稳定性: 同大小情况下是否可能会被交换位置, 虚拟dom的diff,不稳定性会导致重新渲染;

4. 递归运用(斐波那契数列): 爬楼梯问题

初始在第一级,到第一级有1种方法(s(1) = 1),到第二级也只有一种方法(s(2) = 1), 第三级(s(3) = s(1) + s(2))

function cStairs(n) {
    if(n === 1 || n === 2) {
        return 1;
    } else {
        return cStairs(n-1) + cStairs(n-2)
    }
}
复制代码

5. 数据树

  • 二叉树: 最多只有两个子节点
  • 完全二叉树
  • 满二叉树
  • 深度为 h, 有 n 个节点,且满足 n = 2^h - 1
  • 二叉查找树: 是一种特殊的二叉树,能有效地提高查找效率
  • 小值在左,大值在右
  • 节点 n 的所有左子树值小于 n,所有右子树值大于 n

 

  • 遍历节点
  • 前序遍历
  1. 根节点
  2. 访问左子节点,回到 1
  3. 访问右子节点,回到 1
  • 中序遍历
  1. 先访问到最左的子节点
  2. 访问该节点的父节点
  3. 访问该父节点的右子节点, 回到 1
  • 后序遍历
  1. 先访问到最左的子节点
  2. 访问相邻的右节点
  3. 访问父节点, 回到 1
  • 插入与删除节点

6. 天平找次品

有n个硬币,其中1个为假币,假币重量较轻,你有一把天平,请问,至少需要称多少次能保证一定找到假币?

  • 三等分算法:
  1. 将硬币分成3组,随便取其中两组天平称量
  • 平衡,假币在未上称的一组,取其回到 1 继续循环
  • 不平衡,假币在天平上较轻的一组, 取其回到 1 继续循环

结语

由于精力时间及篇幅有限,这篇就先写到这。大家慢慢来不急。。🤪。下篇打算准备以下内容,我也得补补课先:

  • Webpack相关
  • 原理
  • Loader
  • Plugin
  • 项目性能优化
  • 首屏渲染优化
  • 用户体验优化
  • webpack 性能优化
  • Hybrid 与 Webview
  • webview 加载过程
  • bridge 原理
  • hybrid app 经验
  • 框架: React

由于精力时间及篇幅有限,这篇就先写到这。大家慢慢来不急。。🤪。调整下心态,继续

总结给大家一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
1月前
|
缓存 前端开发 中间件
[go 面试] 前端请求到后端API的中间件流程解析
[go 面试] 前端请求到后端API的中间件流程解析
|
29天前
|
存储 XML 移动开发
前端大厂面试真题
前端大厂面试真题
|
25天前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
【8月更文挑战第18天】
25 2
|
25天前
|
存储 JavaScript 前端开发
2022年前端js面试题
2022年前端js面试题
20 0
|
26天前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
44 个 React 前端面试问题
|
1月前
|
存储 JavaScript 前端开发
|
1月前
|
Web App开发 存储 缓存
|
24天前
|
前端开发 应用服务中间件 API
"揭秘!面试官必问:你是如何巧妙绕过跨域难题的?前端代理VS服务器端CORS,哪个才是你的秘密武器?"
【8月更文挑战第21天】在软件开发中,尤其前后端分离架构下,跨域资源共享(CORS)是常见的挑战。主要解决方案有两种:一是服务器端配置CORS策略,通过设置响应头控制跨域访问权限,无需改动前端代码,增强安全性;二是前端代理转发,如使用Nginx或Webpack DevServer在开发环境中转发请求绕过同源策略,简化开发流程但不适用于生产环境。生产环境下应采用服务器端CORS策略以确保安全稳定。
25 0
|
24天前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
24天前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。