页面渲染时,
dom
元素所采用的 布局模型。可通过box-sizing
进行设置。根据计算宽高的区域可分为
content-box
(W3C
标准盒模型)border-box
(IE
盒模型)padding-box
margin-box
(浏览器未实现)块级格式化上下文,是一个独立的渲染区域,让处于
BFC
内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。
IE下为
Layout
,可通过zoom:1
触发
触发条件:
position: absolute/fixed
display: inline-block / table
float
元素ovevflow !== visible
规则:
BFC
的两个相邻 Box
垂直排列BFC
的两个相邻 Box
的 margin
会发生重叠BFC
中子元素的 margin box
的左边, 与包含块 (BFC) border box
的左边相接触 (子元素 absolute
除外)BFC
的区域不会与 float
的元素区域重叠BFC
的高度时,浮动子元素也参与计算应用:
margin
重叠div
都位于同一个 BFC
区域之中)元素提升为一个比较特殊的图层,在三维空间中 (z轴) 高出普通元素一等。
触发条件
html
)position
css3
属性
flex
transform
opacity
filter
will-change
webkit-overflow-scrolling
层叠等级:层叠上下文在z轴上的排序
z-index
的优先级最高水平居中
text-align: center
margin: 0 auto
absolute + transform
flex + justify-content: center
垂直居中
line-height: height
absolute + transform
flex + align-items: center
table
水平垂直居中
absolute + transform
flex + justify-content + align-items
!important
> 行内样式 > #id
> .class
> tag
> *
> 继承 > 默认:after /
: clear: both
BFC
link
功能较多,可以定义 RSS
,定义 Rel
等作用,而@import
只能用于加载 css
link
时,页面会同步加载所引的 css
,而@import
所引用的 css
会等到页面加载完才被加载@import
需要 IE5
以上才能使用link
可以使用 js
动态引入,@import
不行
CSS
预处理器的原理: 是将类CSS
语言通过Webpack
编译 转成浏览器可读的真正CSS
。在这层编译之上,便可以赋予CSS
更多更强大的功能,常用功能:
mixin
复用面试中一般不会重点考察该点,一般介绍下自己在实战项目中的经验即可~
transition: 过渡动画
transition-property
: 属性transition-duration
: 间隔transition-timing-function
: 曲线transition-delay
: 延迟transitionend
animation / keyframes
animation-name
: 动画名称,对应@keyframes
animation-duration
: 间隔animation-timing-function
: 曲线animation-delay
: 延迟animation-iteration-count
: 次数
infinite
: 循环动画animation-direction
: 方向
alternate
: 反向播放animation-fill-mode
: 静止模式
forwards
: 停止时,保留最后一帧backwards
: 停止时,回到第一帧both
: 同时运用 forwards / backwards
animationend
动画属性: 尽量使用动画属性进行动画,能拥有较好的性能表现
translate
scale
rotate
skew
opacity
color
prototype
): 一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox
和 Chrome
中,每个JavaScript
对象中都包含一个__proto__
(非标准)的属性指向它爹(该对象的原型),可obj.__proto__
进行访问。new
来 新建一个对象 的函数。new
创建出来的对象,便是实例。 实例通过__proto__
指向原型,通过constructor
指向构造函数。以
Object
为例,我们常用的Object
便是一个构造函数,因此我们可以通过它构建实例。
// 实例
const instance = new Object()
则此时, 实例为
instance
, 构造函数为Object
,我们知道,构造函数拥有一个prototype
的属性指向原型,因此原型为:
// 原型
const prototype = Object.prototype
这里我们可以来看出三者的关系:
实例.__proto__ === 原型
原型.constructor === 构造函数
构造函数.prototype === 原型
// 这条线其实是是基于原型进行获取的,可以理解成一条基于原型的映射线
// 例如:
// const o = new Object()
// o.constructor === Object --> true
// o.__proto__ = null;
// o.constructor === Object --> false
实例.constructor === 构造函数
原型链是由原型对象组成,每个对象都有
__proto__
属性,指向了创建该对象的构造函数的原型,__proto__
将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链
Object.prototype
,如还是没找到,则输出undefined
;b.prototype.x = 2
;但是这样会造成所有继承于该对象的实例的属性发生改变。执行上下文可以简单理解为一个对象:
它包含三个部分:
VO
)this
指向它的类型:
eval
执行上下文代码执行过程:
global EC
)caller
) 逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee
) 被push
到执行栈顶层active EC
, 开始执行函数中的代码,caller
被挂起callee
被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
AO
: 自身活动对象如此
[[scopr]]
包含[[scope]]
,便自上而下形成一条 链式作用域。
闭包属于一种特殊的作用域,称为 静态作用域。它的定义可以理解为: 父函数被销毁 的情况下,返回出的子函数的
[[scope]]
中仍然保留着父级的单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。
闭包会产生一个很经典的问题:
多个子函数的
[[scope]]
都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。
••解决:**
[[scope]]
向上查找setTimeout
包裹,通过第三个参数传入html
静态
引入js
动态插入
: 异步加载,元素解析完成后执行
: 异步加载,但执行时会阻塞元素渲染浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响
Object.assign
...
)深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响
JSON.parse(JSON.stringify(obj))
: 性能最快undefined
、或symbol
时,无法拷贝obj.__proto__ = Con.prototype
this: apply
retrun
时,则返回该值)能在实例的 原型对象链 中找到该构造函数的
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;
}
})();
使用
ES6
的语法糖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
的模块化支持。
分类:
es6: import / export
commonjs: require / module.exports / exports
amd: require / defined
require与import的区别
require
支持 动态导入,import
不支持,正在提案 (babel
下可支持)require
是 同步 导入,impor
t属于 异步 导入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)
}
}
resize
事件,通常每隔 100~500 ms
执行一次即可。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)
}
}
}
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如下图:
babylon
将 ES6/ES7
代码解析成 AST
babel-traverse
对 AST
进行遍历转译,得到新的 AST
babel-generator
转换成 ES5
在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用。
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;
});
数组拆解: flat: [1,[2,3]] --> [1, 2, 3]
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
)JS
引擎
事件循环是指: 执行一个宏任务,然后执行清空微任务列表,循环再执行宏任务,再清微任务列表
microtask(jobs): promise / ajax / Object.observe
(该方法已废弃)macrotask(task): setTimout / script / IO / UI Rendering
DNS
解析TCP
三次握手url
,设置请求报文(头,主体)html
)HTML parser
--> DOM Tree
dom
树构建CSS parser --> Style Tree
css
代码,生成样式树attachment
--> Render Tree
layout
: 布局GPU painting
: 像素绘制页面当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。
- 重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少
- 回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:
dom
元素CSS
伪类(例如::hover
)clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
getComputedStyle()
getBoundingClientRect()
scrollTo()
回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。
最佳实践:
css
table
布局position
属性为absolute
或fixed
的元素上javascript
class
进行样式修改dom
的增删次数,可使用 字符串 或者 documentFragment
一次性插入display: none
后修改我们经常需要对业务中的一些数据进行存储,通常可以分为 短暂性存储 和 持久性储存。
cookie
: 通常用于存储用户身份,登录状态等
http
中自动携带, 体积上限为 4K
, 可自行设置过期时间localStorage / sessionStorage
: 长久储存/窗口关闭删除, 体积限制为 4~5M
indexDB
redis
现代浏览器为
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
dom
引用: dom
元素被删除时,内存中的引用未被正确清空可用
chrome
中的timeline
进行内存标记,可视化查看内存的变化情况,找出异常点。
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
)
Last-Modified
缺点:
s
, s
以内的改动无法检测到Etag
的优先级高于Last-Modified
1xx
: 接受,继续处理200
: 成功,并返回数据201
: 已创建202
: 已接受203
: 成为,但未授权204
: 成功,无内容205
: 成功,重置内容206
: 成功,部分内容301
: 永久移动,重定向302
: 临时移动,可使用原有URI304
: 资源未修改,可使用缓存305
: 需代理访问400
: 请求语法错误401
: 要求身份认证403
: 拒绝请求404
: 资源不存在500
: 服务器错误get
: 缓存、请求长度受限、会被历史保存记录
post
: 安全、大数据、更多编码类型
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()
建立连接前,客户端和服务端需要通过握手来确认对方:
syn
(同步序列编号) 请求,进入 syn_send
状态,等待确认syn
包后发送 syn+ack
包,进入 syn_recv
状态syn+ack
包后,发送 ack
包,双方进入 established
状态timer
阶段: 执行到期的setTimeout / setInterval
队列回调I/O
阶段: 执行上轮循环残流的callback
idle
, prepare
poll
: 等待回调
setTimeout / setInterval
, 则返回 timer
阶段setImmediate
,则前往 check
阶段check
setImmediate
close callbacks
JSONP
: 利用
标签不受跨域限制的特点,缺点是只能支持 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
在下次
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
会将
编译成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')
}
let data = { a: 1 }
let reactiveData = new Proxy(data, {
get: function(target, name){
// ...
},
// ...
})
mode
hash
history
跳转
this.$router.push()
占位
state
: 状态中心mutations
: 更改状态actions
: 异步更改状态getters
: 获取状态modules
: 将state
分成多个modules
,便于管理