2019-03-18

  1. 盒模型
    页面渲染时,dom 元素所采用的 布局模型。可通过box-sizing进行设置。根据计算宽高的区域可分为: border-box
  2. BFC
    块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。
  3. 居中布局

水平居中
行内元素: 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

  1. 选择器优先级

!important > 行内样式 > #id > .class > tag > * > 继承 > 默认
选择器 从右往左 解析

  1. 闭包

闭包属于一种特殊的作用域,称为 静态作用域。它的定义可以理解为: 父函数被销毁 的情况下,返回出的子函数的[[scope]]中仍然保留着父级的单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。

  1. 对象的拷贝

浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响
Object.assign
展开运算符(...)
深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响
JSON.parse(JSON.stringify(obj)): 性能最快
具有循环引用的对象时,报错
当值为函数、undefined、或symbol时,无法拷贝
递归进行逐一赋值

  1. new运算符的执行过程

新生成一个对象
链接到原型: obj.proto = Con.prototype
绑定this: apply
返回新对象(如果构造函数有自己 retrun 时,则返回该值)

  1. instanceof原理

能在实例的 原型对象链 中找到该构造函数的prototype属性所指向的 原型对象,就返回true。即:

  1. 继承

在 JS 中,继承通常指的便是 原型链继承,也就是通过指定原型,并可以通过原型链继承原型上的属性或者方法。

  1. 模块化

模块化开发在现代开发中已是必不可少的一部分,它大大提高了项目的可维护、可拓展和可协作性。通常,我们 在浏览器中使用 ES6 的模块化支持,在 Node 中使用 commonjs 的模块化支持。
分类:
es6: import / export
commonjs: require / module.exports / exports
amd: require / defined
require与import的区别
require支持 动态导入,import不支持,正在提案 (babel 下可支持)
require是 同步 导入,import属于 异步 导入
require是 值拷贝,导出值变化不会影响导入值;import指向 内存地址,导入值会随导出值而变化

  1. 防抖与节流

防抖与节流函数是一种最常用的 高频触发优化方式,能对性能有较大的帮助。

防抖 (debounce): 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。

节流(throttle): 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可。

  1. ES6/ES7

由于 Babel 的强大和普及,现在 ES6/ES7 基本上已经是现代化开发的必备了。通过新的语法糖,能让代码整体更为简洁和易读。

声明
let / const: 块级作用域、不存在变量提升、暂时性死区、不允许重复声明
const: 声明常量,无法修改
解构赋值
class / extend: 类声明与继承
Set / Map: 新的数据结构
异步解决方案:
Promise的使用与实现
generator:
yield: 暂停代码
next(): 继续执行代码

  1. babel编译原理

babylon 将 ES6/ES7 代码解析成 AST
babel-traverse 对 AST 进行遍历转译,得到新的 AST
新 AST 通过 babel-generator 转换成 ES5

  1. 数组(array)

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 为当前值(从第二项开始)
数组乱序:

  1. nextTick

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

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

  1. 生命周期

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更新
flushScheduleQueuewatcher.before: 触发beforeUpdate钩子 - watcher.run(): 执行watcher中的 notify,通知所有依赖项更新UI
触发updated钩子: 组件已更新
actived / deactivated(keep-alive): 不销毁,缓存,组件激活与失活
destroy:
beforeDestroy: 销毁开始
销毁自身且递归销毁子组件以及事件监听
remove(): 删除节点
watcher.teardown(): 清空依赖
vm.$off(): 解绑监听
destroyed: 完成后触发钩子


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')
}
  1. 数据响应(数据劫持)

看完生命周期后,里面的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')
 }
}

  1. virtual dom 原理实现

创建 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)
 }
 })
}
// 1、列表对比,根据 key查找对应的匹配项
// 2、对比出新旧列表的新增/删除/移动
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 }
}
  1. Proxy 相比于 defineProperty 的优势

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

  1. vue-router

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

  1. vuex

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容

  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 8,630评论 0 3
  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,037评论 0 21
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,351评论 0 5
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,037评论 1 32
  • 我叫小丽,现居住上海,我的职业是一名律师,工作中我雷厉风行,说一不二,工作外我孤高冷傲,别人都说我难以接近,包括我...
    云磬阅读 363评论 0 0