原型(prototype)
: 一个简单的对象,用于实现对象的 属性继承。能够简单的理解成对象的爹。在 Firefox 和 Chrome 中,每一个JavaScript
对象中都包含一个__proto__
(非标准)的属性指向它爹(该对象的原型),可obj.__proto__
进行访问。javascript
构造函数: 能够经过new
来 新建一个对象 的函数。css
实例: 经过构造函数和new
建立出来的对象,即是实例。 实例经过__proto__
指向原型,经过constructor
指向构造函数。html
说了一大堆,你们可能有点懵逼,这里来举个栗子,以Object
为例,咱们经常使用的Object
即是一个构造函数,所以咱们能够经过它构建实例。前端
// 实例 const instance = new Object()
则此时, 实例为instance
, 构造函数为Object
,咱们知道,构造函数拥有一个prototype
的属性指向原型,所以原型为:vue
// 原型 const prototype = Object.prototype
这里咱们能够来看出三者的关系:java
实例.__proto__ === 原型 原型.constructor === 构造函数 构造函数.prototype === 原型 // 这条线实际上是是基于原型进行获取的,能够理解成一条基于原型的映射线 // 例如: // const o = new Object() // o.constructor === Object --> true // o.__proto__ = null; // o.constructor === Object --> false 实例.constructor === 构造函数
放大来看,我画了张图供你们完全理解:node
function Hero(name){this.name=name}; var h1 = new Hero('dingdang'); Hero.prototype.__proto__ === Object.prototype;//由于Hero的原型是一个对象,因此它的__proto__ 指向对象的构造函数(Object)的原型 Hero.__proto__ === Function.prototype //构造函数的原型 //每一个JavaScript对象中都包含一个__proto__ (非标准)的属性指向它爹(该对象的原型)
原型链是由原型对象组成,每一个对象都有 __proto__
属性,指向了建立该对象的构造函数的原型,__proto__
将对象链接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链。react
属性查找机制: 当查找对象的属性时,若是实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象Object.prototype
,如仍是没找到,则输出undefined
;webpack
属性修改机制: 只会修改实例对象自己的属性,若是不存在,则进行添加该属性,若是须要修改原型的属性时,则能够用: b.prototype.x = 2
;可是这样会形成全部继承于该对象的实例的属性发生改变。es6
执行上下文能够简单理解为一个对象:
它包含三个部分:
this
指向它的类型:
eval
执行上下文代码执行过程:
push
到执行栈顶层pop
移除出执行栈,控制权交还全局上下文 (caller),继续执行变量对象,是执行上下文中的一部分,能够抽象为一种 数据做用域,其实也能够理解为就是一个简单的对象,它存储着该执行上下文中的全部 变量和函数声明(不包含函数表达式)。
活动对象 (AO): 当变量对象所处的上下文为 active EC 时,称为活动对象。
执行上下文中还包含做用域链。理解做用域以前,先介绍下做用域。做用域其实可理解为该上下文中声明的 变量和声明的做用范围。可分为 块级做用域 和 函数做用域
特性:
let foo = function() { console.log(1) } (function foo() { foo = 10 // 因为foo在函数中只为可读,所以赋值无效 console.log(foo) }()) // 结果打印: ƒ foo() { foo = 10 ; console.log(foo) }
咱们知道,咱们能够在执行上下文中访问到父级甚至全局的变量,这即是做用域链的功劳。做用域链能够理解为一组对象列表,包含 父级和自身的变量对象,所以咱们便能经过做用域链访问到父级里声明的变量或者函数。
[[scope]]
属性: 指向父级变量对象和做用域链,也就是包含了父级的[[scope]]
和AO
如此 [[scopr]]
包含[[scope]]
,便自上而下造成一条 链式做用域。
闭包属于一种特殊的做用域,称为 静态做用域。它的定义能够理解为: 父函数被销毁 的状况下,返回出的子函数的[[scope]]
中仍然保留着父级的单变量对象和做用域链,所以能够继续访问到父级的变量对象,这样的函数称为闭包。
闭包会产生一个很经典的问题:
[[scope]]
都是同时指向父级,是彻底共享的。所以当父级的变量对象被修改时,全部子函数都受到影响。解决:
[[scope]]
向上查找setTimeout
包裹,经过第三个参数传入<script>
引入<script>
<script defer>
: 异步加载,元素解析完成后执行<script async>
: 异步加载,但执行时会阻塞元素渲染浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响
Object.assign
深拷贝: 彻底拷贝一个新对象,修改时原对象再也不受到任何影响
JSON.parse(JSON.stringify(obj))
: 性能最快
undefined
、或symbol
时,没法拷贝obj.__proto__ = Con.prototype
apply
能在实例的 原型对象链 中找到该构造函数的prototype
属性所指向的 原型对象,就返回true
。即:
// __proto__: 表明原型对象链 instance.[__proto__...] === instance.constructor.prototype // return true
当你发现任何代码开始写第二遍时,就要开始考虑如何复用。通常有如下的方式:
extend
mixin
apply/call
在 JS 中,继承一般指的即是 原型链继承,也就是经过指定原型,并能够经过原型链继承原型上的属性或者方法。
var inherit = (function(c,p){ var F = function(){}; return function(c,p){ F.prototype = p.prototype; c.prototype = new F(); c.uber = p.prototype; c.prototype.constructor = c; } })();
class / extends
你们都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则以下:
valueOf
-> toString
boolean/null
-> 数字undefined
-> NaN
[1].toString() === '1'
{}.toString() === '[object object]'
NaN
!== NaN
、+undefined 为 NaN
判断 Target 的类型,单单用 typeof 并没有法彻底知足,这其实并非 bug,本质缘由是 JS 的万物皆对象的理论。所以要真正完美判断时,咱们须要区分对待:
null
): 使用 String(null)
string / number / boolean / undefined
) + function
: 直接使用 typeof
便可Array / Date / RegExp Error
): 调用toString
后根据[object XXX]
进行判断很稳的判断封装:
let class2type = {} 'Array Date RegExp Object Error'.split(' ').forEach(e => class2type[ '[object ' + e + ']' ] = e.toLowerCase()) function type(obj) { if (obj == null) return String(obj) return typeof obj === 'object' ? class2type[ Object.prototype.toString.call(obj) ] || 'object' : typeof obj }
模块化开发在现代开发中已经是必不可少的一部分,它大大提升了项目的可维护、可拓展和可协做性。一般,咱们 在浏览器中使用 ES6 的模块化支持,在 Node 中使用 commonjs 的模块化支持。
分类:
import / export
require / module.exports / exports
require / defined
require
与import
的区别
require
支持 动态导入,import
不支持,正在提案 (babel 下可支持)require
是 同步 导入,import
属于 异步 导入require
是 值拷贝,导出值变化不会影响导入值;import
指向 内存地址,导入值会随导出值而变化防抖与节流函数是一种最经常使用的 高频触发优化方式,能对性能有较大的帮助。
function debounce(fn, wait, immediate) { let timer = null return function() { let args = arguments let context = this if (immediate && !timer) { fn.apply(context, args) } if (timer) clearTimeout(timer) timer = setTimeout(() => { fn.apply(context, args) }, wait) } }
function throttle(fn, wait, immediate) { let timer = null let callNow = immediate return function() { let context = this, args = arguments if (callNow) { fn.apply(context, args) callNow = false } if (!timer) { timer = setTimeout(() => { fn.apply(context, args) timer = null }, wait) } } }
因为 JS 的设计原理: 在函数中,能够引用运行环境中的变量。所以就须要一个机制来让咱们能够在函数体内部获取当前的运行环境,这即是this
。
所以要明白 this
指向,其实就是要搞清楚 函数的运行环境,说人话就是,谁调用了函数。例如:
obj.fn()
,即是 obj
调用了函数,既函数中的 this === obj
fn()
,这里能够当作 window.fn()
,所以 this === window
但这种机制并不彻底能知足咱们的业务需求,所以提供了三种方式能够手动修改 this
的指向:
call: fn.call(target, 1, 2)
apply: fn.apply(target, [1, 2])
bind: fn.bind(target)(1,2)
因为 Babel 的强大和普及,如今 ES6/ES7 基本上已是现代化开发的必备了。经过新的语法糖,能让代码总体更为简洁和易读。
声明
let / const
: 块级做用域、不存在变量提高、暂时性死区、不容许重复声明const
: 声明常量,没法修改解构赋值
class / extend
: 类声明与继承
Set / Map
: 新的数据结构
异步解决方案:
Promise
的使用与实现
generator
:
yield
: 暂停代码next()
: 继续执行代码function* helloWorld() { yield 'hello'; yield 'world'; return 'ending'; } const generator = helloWorld(); generator.next() // { value: 'hello', done: false } generator.next() // { value: 'world', done: false } generator.next() // { value: 'ending', done: true } generator.next() // { value: undefined, done: true }
await / async
: 是generator
的语法糖, babel中是基于promise
实现。async function getUserByAsync(){ let user = await fetchUser(); return user; } const user = await getUserByAsync() console.log(user)
抽象语法树 (Abstract Syntax Tree),是将代码逐字母解析成 树状对象 的形式。这是语言之间的转换、代码语法检查,代码风格检查,代码格式化,代码高亮,代码错误提示,代码自动补全等等的基础。例如:
function square(n){ return n * n }
经过解析转化成的AST
以下图:AST
在一个函数中,首先填充几个参数,而后再返回一个新的函数的技术,称为函数的柯里化。一般可用于在不侵入函数的前提下,为函数 预置通用参数,供屡次重复调用。
const add = function add(x) { return function (y) { return x + y } } const add1 = add(1) add1(2) === 3 add1(20) === 21
map
: 遍历数组,返回回调返回值组成的新数组
forEach
: 没法break
,能够用try/catch
中throw new Error
来中止
filter
: 过滤
some
: 有一项返回true
,则总体为true
every
: 有一项返回false
,则总体为false
join
: 经过指定链接符生成字符串
push / pop
: 末尾推入和弹出,改变原数组, 返回推入/弹出项
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 为当前值(从第二项开始)
数组乱序:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; arr.sort(function () { return Math.random() - 0.5; });
Array.prototype.flat = function() { this.toString().split(',').map(item => +item ) }
不一样标签页间的通信,本质原理就是去运用一些能够 共享的中间介质,所以比较经常使用的有如下方法:
经过父页面window.open()
和子页面postMessage
window.open('about: blank')
和 tab.location.href = '*'
设置同域下共享的localStorage
与监听window.onstorage
设置共享cookie
与不断轮询脏检查(setInterval
)
借助服务端或者中间层实现
事件循环是指: 执行一个宏任务,而后执行清空微任务列表,循环再执行宏任务,再清微任务列表
microtask(jobs)
: promise / ajax / Object.observe(该方法已废弃)
macrotask(task)
: setTimout / script / IO / UI Rendering
当元素的样式发生变化时,浏览器须要触发更新,从新绘制元素。这个过程当中,有两种类型的操做,即重绘与回流。
重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时因为只须要UI层面的从新像素绘制,所以 损耗较少
回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会从新渲染页面,称为回流。此时,浏览器须要从新通过计算,计算后还须要从新页面布局,所以是较重的操做。会触发回流的操做:
回流一定触发重绘,重绘不必定触发回流。重绘的开销较小,回流的代价较高。
css
table
布局position
属性为absolute
或fixed
的元素上javascript
class
进行样式修改dom
的增删次数,可以使用 字符串 或者 documentFragment
一次性插入display: none
后修改咱们常常须要对业务中的一些数据进行存储,一般能够分为 短暂性存储 和 持久性储存。
短暂性的时候,咱们只须要将数据存在内存中,只在运行时可用
持久性存储,能够分为 浏览器端 与 服务器端
cookie
: 一般用于存储用户身份,登陆状态等
localStorage / sessionStorage
: 长久储存/窗口关闭删除, 体积限制为 4~5MindexDB
现代浏览器为JavaScript
创造的 多线程环境。能够新建并将部分任务分配到worker
线程并行运行,两个线程可 独立运行,互不干扰,可经过自带的 消息机制 相互通讯。
基本用法:
// 建立 worker const worker = new Worker('work.js'); // 向主进程推送消息 worker.postMessage('Hello World'); // 监听主进程来的消息 worker.onmessage = function (event) { console.log('Received message ' + event.data); }
限制:
document
/ window
/ alert
/ confirm
垃圾回收: 将内存中再也不使用的数据进行清理,释放出内存空间。V8 将内存分红 新生代空间 和 老生代空间。
可用 chrome 中的 timeline 进行内存标记,可视化查看内存的变化状况,找出异常点。
1.0 协议缺陷:
1.1 改进:
2.0:
https: 较为安全的网络传输协议
TCP:
缓存策略: 可分为 强缓存 和 协商缓存
Cache-Control/Expires: 浏览器判断缓存是否过时,未过时时,直接使用强缓存,Cache-Control的 max-age 优先级高于 Expires
当缓存已通过期时,使用协商缓存
Last-Modified 缺点:
Etag 的优先级高于 Last-Modified
二者详细对好比下图:
Websocket 是一个 持久化的协议, 基于 http , 服务端能够 主动 push
兼容:
new WebSocket(url)
ws.onerror = fn
ws.onclose = fn
ws.onopen = fn
ws.onmessage = fn
ws.send()
创建链接前,客户端和服务端须要经过握手来确认对方:
setTimeout / setInterval
队列回调callback
setTimeout / setInterval
, 则返回 timer 阶段setImmediate
,则前往 check 阶段setImmediate
<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) }
在下次dom
更新循环结束以后执行延迟回调,可用于获取更新后的dom
状态
新版本中默认是mincrotasks
, v-on
中会使用macrotasks
macrotasks
任务的实现:
setImmediate / MessageChannel / setTimeout
_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
,通知全部依赖项更新UIupdated
钩子: 组件已更新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') }
看完生命周期后,里面的watcher
等内容实际上是数据响应中的一部分。数据响应的实现由两部分构成: 观察者( watcher ) 和 依赖收集器( Dep ),其核心是 defineProperty
这个方法,它能够 重写属性的 get 与 set 方法,从而完成监听数据的改变。
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') } }
建立 dom 树
树的diff
,同层对比,输出patchs(listDiff/diffChildren/diffProps)
tagName
与key
不变, 对比props
,继续递归遍历子树
tagName
和key
值变化了,则直接替换成新节点渲染差别
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 } }
let data = { a: 1 } let reactiveData = new Proxy(data, { get: function(target, name){ // ... }, // ... })
mode
hash
history
this.$router.push()
<router-link to=""></router-link>
<router-view></router-view>
state
: 状态中心mutations
: 更改状态actions
: 异步更改状态getters
: 获取状态modules
: 将state
分红多个modules
,便于管理其实算法方面在前端的实际项目中涉及得并很少,但仍是须要精通一些基础性的算法,一些公司仍是会有这方面的需求和考核,建议你们仍是须要稍微准备下,这属于加分题。
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; }
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,不稳定性会致使从新渲染;
初始在第一级,到第一级有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. 数据树
有n个硬币,其中1个为假币,假币重量较轻,你有一把天平,请问,至少须要称多少次能保证必定找到假币?
因为精力时间及篇幅有限,这篇就先写到这。你们慢慢来不急。。🤪。下篇打算准备如下内容,我也得补补课先:
在面试中,不少领域并无真正的答案,能回答到什么样的深度,仍是得靠本身真正的去使用和研究。知识面的广度与深度应该并行,尽可能的拓张本身的领域,至少都有些基础性的了解,在被问到的时候能够同面试官唠嗑两句,而后在本身喜欢的领域,又有着足够深刻的研究,让面试官以为你是这方面的专家。
转载出处: https://juejin.im/post/5c64d15d6fb9a049d37f9c20