vue-cli vuex vue2.6源码地址 vue-router 开课吧老村长
如何调试源码,以及找到入口文件
runtime:仅包含运行时,不包含编译器 common:cjs规范,用于webpack1 esm:es模块,用于webpack2+ umd:universal module definition,兼容cjs和amd,用于浏览器
在vue源码中修改package.json 添加了--sourcemap
"scripts": {
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap"
}
新增html文件 使用功能vue语法,打开浏览器和控制台,就可以给源码打断点了
<script src="../../dist/vue.js"></script>
源码入口文件 scripts/config.js里查找web-full-dev,于是如果js文件为 entry-runtime-with-compiler.js
ctrl+shift+p 输入文件名字 就可以找到该文件 很多时候会用到别名,想快速找到该文件可以复制路径和上面的方法一样 也可以找到 import { warn, cached } from 'core/util/index' 别名文件是alias.js 其中一个别名如下core: resolve('src/core'),
vue的初始化过程 | new Vue过程经历了什么,其中包含虚拟dom
如果挂载了 则执行下面
src\platforms\web\entry-runtime-with-compiler.js 1号
Vue.prototype.$mount = function (el, hydrating) {
// 说明render函数的优先级 比 template高
return mount.call(this, el, hydrating)
}
src\platforms\web\runtime\index.js 2号
import { mountComponent } from 'core/instance/lifecycle'
import { patch } from './patch' Vue.prototype.__patch__ = patch
Vue.prototype.$mount = function (el, hydrating) {
return mountComponent(this, query(el), hydrating)
}
mountComponent src\core\instance\index.js 3号
import { initGlobalAPI } from './global-api/index'
initGlobalAPI(Vue)
src\core\instance\index.js 4号
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
function Vue(options) { this._init(options) }
initMixin(Vue) //给vue添加_init方法
stateMixin(Vue) //$set $watch $delete
eventsMixin(Vue) // $emit $on $off $once
lifecycleMixin(Vue) // _update $forceupdate $destroy
renderMixin(Vue) // _render $nextTick
src\core\instance\init.js 4-1号
export function initMixin (Vue) {
Vue.prototype._init = function (options) {
const vm = this
if (options && options._isComponent) { // 这是组件初始化
initInternalComponent(vm, options)
} else { // 这是根组件初始化
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
initLifecycle(vm) //设置 $parent $root $children $refs
initEvents(vm) //对父组件传入事件监听和回调
initRender(vm) //生命$slot $createElement()
callHook(vm, 'beforeCreate')/* beforeCreate------------------------------------------------------ */
initInjections(vm) // 注入数据
initState(vm) //数据初始化 响应式
initProvide(vm) // 提供数据
// 为什么要先注入 再提供?
// 1、注入数据之后,再处理响应式数据时,都会做判重处理
// 2、上面注入的数据,再转换一下,有可能提供给后代
callHook(vm, 'created')/* created---------------------------------------------------------------- */
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
initLifecycle-17 initEvents-9 initRender-7
initInjections-14 initState-36 initProvide-5
src\core\instance\init.js 4-2号
export function stateMixin(Vue) {
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (expOrFn, cb, options) {
const vm = this
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn() {
watcher.teardown()
}
}
}
export function mountComponent(vm, el, hydrating) {
callHook(vm, 'beforeMount')
new Watcher(
vm,
() => { vm._update(vm._render(), hydrating) },//先render 再update
//_render renderMixin(Vue)中声明
//_update lifecycleMixin(Vue)中声明
noop,
{
before() {
callHook(vm, 'beforeUpdate')
}
},
true
)
callHook(vm, 'mounted')
return vm
}
- 虚拟dom:是对dom的js抽象表示,他们是js对象,能够描述dom结构和关系。应用的各种状态变化会作用于虚拟dom,最终映射到dom上,vue使用的是snibbdom包
- 虚拟dom优点:1、轻量,快速,当变化时,比较新旧虚拟dom得到最小dom操作量,从而提高性能 2、跨平台:将虚拟dom更新转换为不同运行时特殊操作实现跨平台 3、兼容性:还可以加入兼容性代码增强操作的兼容性 4、vue1.0有细粒度的数据变化侦听,它是不需要虚拟dom的,vue2.0采用中等粒度,一个组件一个watcher,当状态变化只能通知到组件,再引入虚拟dom去进行比对和渲染 总之,vue之所以引入虚拟dom,为了性能优化。
_render
Vue.prototype._render = function () {//renderMixin包裹的
const vm = this
vnode = render.call(vm._renderProxy, vm.$createElement)//在initRender中声明了createElement
return vnode
}
render生成-30 compileToFunctions createCompiler
_update
Vue.prototype._update = function (vnode, hydrating) {//lifecycleMixin包裹的
const vm = this
const prevVnode = vm._vnode //取出之前的虚拟dom,默认是空
vm._vnode = vnode //将最新的虚拟dom 记录下来
if (!prevVnode) {
vm.$el = vm.__patch__(vm.$el, vnode)//数据没有变 第一次执行时
} else {
vm.$el = vm.__patch__(prevVnode, vnode)//数据变化 走这里
}
}
Vue.prototype.__patch__ = patch
const patch = createPatchFunction({ nodeOps, modules })
function createPatchFunction(backend) {
return function patch(oldVnode, vnode, hydrating, removeOnly) {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) { //oldVnode 是虚拟dom
patchVnode(oldVnode, vnode)
} else {//oldVnode 是真实的dom 则
createElm(vnode)//将虚拟dom 创建为真实dom 且插入
}
return vnode.elm
}
}
patchVnode
function patchVnode(oldVnode, vnode) {
const elm = vnode.elm = oldVnode.elm
const data = vnode.data
const oldCh = oldVnode.children
const ch = vnode.children
if (isUndef(vnode.text)) { //没有文本就是 元素类型 也有可能 vnode.text === ''
if (isDef(oldCh) && isDef(ch)) {// 双方都有孩子
/* 比较孩子 */
if (oldCh !== ch) updateChildren(elm, oldCh, ch)
} else if (isDef(ch)) {//新有 老没有 给老的加孩子
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')//清空老节点文本
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)//创建孩子 追加
} else if (isDef(oldCh)) {//新没有 老有 给老的孩子删除
removeVnodes(oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) { //新节点文本 是空字符串
/* 老节点存在文本,清空 */
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) { //新是文本节点 且不能等于 假如老是文本节点的内容
nodeOps.setTextContent(elm, vnode.text)
}
}
updateChildren
/* 重排算法 */
function updateChildren(parentElm, oldCh, newCh) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
const canMove = !removeOnly
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {//开头相同 索引向后移动一位
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {//结尾相同
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { //老的开始 = 新的结尾
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { //老的结尾 = 新的开始
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {//四种猜想之后 没有找到合适的,则开始循环查找
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // 没有找到,则创建新元素
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {//找到,除了打补丁,还有移动到队首
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
//重排操作结束,整理工作,必定有数字还剩下的元素没有处理
if (oldStartIdx > oldEndIdx) {//新的还有 老的没有 追加
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)//批量创建
} else if (newStartIdx > newEndIdx) {//老的还有 新的没有 移除
removeVnodes(oldCh, oldStartIdx, oldEndIdx)//批量删除
}
}
模板编译
解析模板parse optimize优化 generate代码生成
<div id="app">
<div v-if="foo">{{ foo }}</div>
</div>//生成下面render函数
(function anonymous() {
with (this) { return _c('div', { attrs: { "id": "app" } }, [(foo) ? _c('div', [_v(_s(foo))]) : _e()]) }
})
<div id="app">
<div v-for="item in arr">{{item}}</div>
</div>//生成下面render函数
(function anonymous() {
with (this) { return _c('div', { attrs: { "id": "app" } }, _l((arr), function (item) { return _c('div', [_v(_s(item))]) }), 0) }
})
在$mount时,如果有template,则会把template转换为render函数
const { render, staticRenderFns } = compileToFunctions(template) //只有optimize之后,才会有staticRenderFns,且isStatic = true
const { compileToFunctions } = createCompiler(baseOptions)
const createCompiler = createCompilerCreator(function baseCompile(template, options) {
const ast = parse(template.trim(), options)//解析模板 为 AST对象
if (options.optimize !== false) { // 优化: 标记静态节点 diff时可以直接跳过
// 每次重新渲染,不需要为静态子树创建新节点
// 虚拟dom中patch时,可以跳过静态子树
/*
function patchVnode(){
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
}
*/
optimize(ast, options)
}
const code = generate(ast, options) // 代码生成 : ast转换为代码
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
function parse(template, options) {
parseHTML(template, {
start(tag, attrs, unary) {//遇到开始标签
processFor(element)
processIf(element)// 调试时可以打条件断点 右键断点处 edit breakpoint // tag === 'p'
}
})
}
function generate(ast, options) {
const state = new CodegenState(options)
const code = genElement(ast, state) // 生成代码
return {
render: `with(this){return ${code}}`,// 最终输出
staticRenderFns: state.staticRenderFns
}
}
function genElement(el, state) {
if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state) // 条件语句生成三元运算符
}
}
function genIf(el, state, altGen, altEmpty) {
el.ifProcessed = true
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions(conditions, state, altGen, altEmpty) {
const condition = conditions.shift()
if (condition.exp) {
return `(${condition.exp})?${genTernaryExp(condition.block)
}:${genIfConditions(conditions, state, altGen, altEmpty)
}`
} else {
return `${genTernaryExp(condition.block)}`
}
function genTernaryExp(el) {
return altGen ? altGen(el, state) : el.once
? genOnce(el, state)
: genElement(el, state)
}
}
v-if v-for 只是在编译阶段执行,就是解析模板时候,如果我们要在render函数处理条件或者循环中能使用if和for
Vue.component('comp',{
props:["foo"],
render(h){
if(this.foo){
return h('div','111')
}
return h('div','222')
}
})
vue组件化
// components方法的挂载
function initGlobalAPI(Vue) {
['component', 'directive', 'filter'].forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
Vue.options.components = null
initAssetRegisters(Vue)
}
function initAssetRegisters(Vue) {
['component', 'directive', 'filter'].forEach(type => {
// vue.component('com',{})
Vue[type] = function (id, definition) {//这里的this指向Vue
if (!definition) {
return this.options[type + 's'][id]
} else {
if (type === 'component') {
/*
Vue.options._base = Vue
Vue.extend = function (){}
*/
definition.name = definition.name || id
definition = this.options._base.extend(definition)// 创建组件构造函数
}
if (type === 'directive') {
definition = { bind: definition, update: definition }
}
// 注册 this.options.components["com"] = Ctor
this.options[type + 's'][id] = definition
return definition
}
}
})
}
// (function anonymous() {
// with (this) { return _c('div', { attrs: { "id": "app" } }, [_c('comp')], 1) }
// })
Vue.prototype._render = function () {
render.call(vm._renderProxy, vm.$createElement)
}
function initMixin(Vue) {
Vue.prototype._init = function () {
initRender(vm)
}
}
function initRender(vm) {
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}
function createElement(context) {
return _createElement(context)
}
function _createElement(context) { //在这里使用上面挂载的components
if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
return createComponent(Ctor, data, context, children, tag)
}
}
function createComponent() {
const vnode = new VNode(`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,)
return vnode// 针对<com/> 的虚拟是 {tag:"vue-component-1-comp"}
}
组件的编译为vnode后,后续patch 需要将vnode生成真实dom的,过程如下
Vue.prototype._update = function (vnode, hydrating) {
if (!prevVnode) {// 第一次执行 没有虚拟节点
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)
} else {
vm.$el = vm.__patch__(prevVnode, vnode)
}
}
Vue.prototype.__patch__ = patch
const patch = createPatchFunction({ nodeOps, modules })
function createPatchFunction(backend) {
return function patch(oldVnode, vnode, hydrating, removeOnly) {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
} else {
createElm(vnode) // 如果组件是comp 则对应的vnode的tag名是 vue-component-1-comp
}
}
}
function createElm(vnode) {
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {//如果是组件创建则
return
}
}
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data // 读取installComponentHooks生成的hooks
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {//存在init钩子,则执行 创建实例并挂载
i(vnode) // 创建组件实例
}
if (isDef(vnode.componentInstance)) {// 如果组价实例存在
initComponent(vnode, insertedVnodeQueue)//属性初始化
insert(parentElm, vnode.elm, refElm)//dom插入 插入并不能看到 当前只是一个组件
return true
}
}
}
收集dep依赖的id是怎样的?
initState(vm) //数据初始化 响应式
initData(vm)
observe(data)
function observe(value) {
if (value.__ob__) {
return value.__ob__
} else {
return new Observer(value)
}
}
class Observer {
constructor (value) {
this.dep = new Dep()
value.__ob__ = this
if (Array.isArray(value)) {
value.__proto__ = arrayMethods
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
} else {
// this.walk
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
defineReactive(value, keys[i])
}
}
}
}
function defineReactive (obj,key,val,customSetter, shallow) {
const dep = new Dep()//这个dep和key 一 一 对应的
const property = Object.getOwnPropertyDescriptor(obj, key)
const getter = property.get
const setter = property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val) //只要是引用类型就有值
Object.defineProperty(obj, key, {
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()//收集依赖
if (childOb) {//如果存在子ob 则子依赖也要收集
childOb.dep.depend()
if (Array.isArray(value)) {// 是数组还得收集每一项的依赖dep实例
dependArray(value)
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || newVal !== newVal && value !== value) {
return
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
function dependArray (value) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
ob.dep.notify()
return result
})
})
事件循环
vue如何批量异步更新
- 异步:只要监听到数据变化,vue将开启一个队列,并缓存在同一事件循环中发生的所有数据变更
- 批量:如果同一个watcher被多次触发,只会被推入到队列中一次。去重对于避免不必要的计算和dom操作非常重要,在下一个时间循环tick中,vue刷新队列执行实际工作
- 异步策略:vue在内部对异步队列尝试使用原生的Promise.then、mutationobserver和setImmdiate,如果执行环境不支持,则会采用setTimeout(f,0)代替
this.foo = '111'
this.foo = '222'
this.foo = '333'
触发foo的dep.notify()
notify() {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()//触发update方法 且执行三遍
}
}
class Watcher {
update() {
queueWatcher(this)
}
}
function queueWatcher(watcher) {
const id = watcher.id
if (has[id] == null) {//下次相同id 就不会进入
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue) //建立一个微任务 等当前的宏任务执行完再执行
}
}
}
function nextTick(cb, ctx) {
callbacks.push(() => { cb.call(ctx) })
if (!pending) {
pending = true
Promise.resolve().then(flushCallbacks)
}
}
function flushCallbacks() {
pending = false
const cbs = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < cbs.length; i++) {
cbs[i]()
}
}
function flushSchedulerQueue() {
queue.sort((a, b) => a.id - b.id)
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
watcher.run()
}
}
vscode代码格式化
在项目里面新建.vscode文件夹 然后在内新增settings.json文件,内容如下
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
写源码可能会用到的js操作
getElementById
getElementsByTagName getElementsByName getElementsByClassName
querySelector querySelectorAll
childNodes:包含文本节点 children
nodeType innerHTML innerText
爸爸.appendChild(候选儿子) 在最后面插入该儿子
爸爸.removeChild(儿子) 删除该儿子
爸爸.replaceChild(候选儿子,儿子) 将该儿子替换为候选儿子
爸爸.insertBefore(候选儿子,儿子) 在该儿子前面插入候选儿子
var dom = document.createElement('div')
dom.setAttribute('xxx','111')
dom.setAttribute('www',"222")
console.log(dom);// <div xxx="111" www="222"></div>
console.log(dom.attributes.xxx);//xxx="111"
console.log(dom.getAttribute('xxx'));//111
数据劫持 + 发布者-订阅者模式,递归Observer data数据,其实用Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调,Compile编译模板时,新增Watcher依赖,并触发相关属性的getter方法收集依赖,当我们让属性发生变化时,会notify发布消息给订阅者,触发依赖数据更新
vue响应式原理
index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.box{
width: 100px;
height: 100px;
background-color:lime;
}
</style>
<script src="./vue.js"></script>
</head>
<body>
<div id="app">
<div v-text="a.b.c"></div>
<div>{{a.b.c}}</div>
<div v-html="html"></div>
<div v-bind:class="a.b.c"></div>
<input type="text" v-model="msg"> {{msg}}
<button v-on:click="clickone">点击</button>
</div>
<script>
var vm = new Vue({
el:'#app',
data:{
a:{
b:{
c:'box'
}
},
html:"<h1>header</h1>",
msg:'zanlan'
},
methods:{
clickone(){
console.log(this.a.b.c)
}
}
})
</script>
</body>
</html>
vue.js文件
const utils = {
getValue(expr, vm) {
return expr.split('.').reduce((v, item, index) => {
return v[item]
}, vm.$data)
},
setValue(expr,vm,val){
expr.split(".").reduce((v,item)=>{
return v[item] = val
},vm.$data)
},
text(node, expr, vm, flag) {
if (flag) {
node.textContent = expr.replace(/\{\{(.+?)\}\}/g, (...arg) => {
new Watcher(vm, arg[1], () => {
node.textContent = expr.replace(/\{\{(.+?)\}\}/g, (...arg) => {
return this.getValue(arg[1], vm)
})
})
return this.getValue(arg[1], vm)
})
} else {
new Watcher(vm, expr, () => {
node.textContent = this.getValue(expr, vm)
})
node.textContent = this.getValue(expr, vm)
}
},
html(node, expr, vm) {
new Watcher(vm, expr, () => {
node.textContent = this.getValue(expr, vm)
})
node.innerHTML = this.getValue(expr, vm)
},
bind(node, expr, vm, bname){
new Watcher(vm, expr, (oldvalue) => {
node.setAttribute(bname,this.getValue(expr, vm))
})
node.setAttribute(bname,this.getValue(expr, vm))
},
model(node, expr, vm){
console.log(node,expr,vm)
new Watcher(vm, expr, () => {
node.value = this.getValue(expr, vm)
})
node.addEventListener('input',(e)=>{
this.setValue(expr,vm,e.target.value)
},false)
node.value = this.getValue(expr, vm)
},
on(node, expr, vm, bname){
console.log(node, expr,bname)
const fn = vm.$options.methods[expr]
console.log(vm.$options.methods)
node.addEventListener(bname,()=>{
fn.call(vm)
})
}
}
class CompileTemplate {
constructor(el, vm) {
var el = document.querySelector(el);
this.vm = vm;
let fragment = document.createDocumentFragment();
let firstChild;
while (firstChild = el.firstChild) {
fragment.appendChild(firstChild);
}
this._compile(fragment)
el.appendChild(fragment);
}
_compile(fragment) {
[...fragment.childNodes].forEach(child => {
if (child.nodeType == 1) {
[...child.attributes].forEach(attribute => {
const { name, value } = attribute
if(name.startsWith("v-")){
// v-text = "qwe.dfsa.dfads"
// v-bind:class = "c"
// v-model = "m"
console.log(name, value)
const d = name.split("-")[1]
const [dname,bname] = d.split(":")
/* text node msg */
utils[dname](child, value, this.vm,bname)
child.removeAttribute('v-' + d )
}
})
} else if (child.nodeType == 3) {
const textContent = child.textContent
if (/\{\{(.+?)\}\}/.test(textContent)) {
utils.text(child, textContent, this.vm, true)
}
}
if (child.childNodes && child.childNodes.length) {
this._compile(child)
}
})
}
}
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm
this.expr = expr
this.cb = cb
Dep.target = this
this.oldValue = utils.getValue(expr, vm)
Dep.target = null
}
update() {
const newValue = utils.getValue(this.expr, this.vm)
if(newValue !== this.oldValue){
this.cb(this.oldValue)
}
}
}
class Dep {
constructor() {
this.subs = []
}
add(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach(item => {
item.update()
})
}
}
class Vue {
constructor(options) {
this.$el = options.el
this.$data = options.data
this.$options = options
this.proxyData(this.$data)
this.observe(this.$data)
this.compileTemplate(this.$el, this)
}
proxyData(data) {
for (const key in data) {
if (Object.hasOwnProperty.call(data, key)) {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(newone) {
if (newone !== data[key]) {
this.observe(newone)
data[key] = newone
}
}
})
}
}
}
observe(obj) {
if (typeof obj == 'object') {
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
if (typeof obj[key] == 'object') {
this.observe(obj[key])
} else {
this.defineReactive(obj, key, obj[key])
}
}
}
}
}
defineReactive(obj, key, value) {
let dep = new Dep()
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.add(Dep.target)
return value
},
set:(newone) => {
if (newone !== value) {
value = newone
this.observe(value)
dep.notify()
}
}
})
}
compileTemplate(el, vm) {
new CompileTemplate(el, vm)
}
}
文档碎片
因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。 回流:浏览器重新处理和绘制部门页面或全部页面时,回流就会发生。
let fragment = document.createDocumentFragment();
vue常用的api原理
v-model input + value + $emit
v-html innerHTML
v-text innerText
v-clock html文件中用vue,给根元素加上v-clock,加样式[v-clock]{opacity: 0;},解决小胡子问题
v-pre 内部没有vue语法 就加上
v-for 数字 字符串 数组 对象
v-if可不渲染 v-show 都渲染
v-for 优先级高于 v-if
v-bind 对class和style做了特殊处理
v-for 绑定key 为了diff算法更好的识别出某一项 绑定index时有时有问题,
比如每一项都有checkbox,勾选其中一项,删除该项,发现下一项复用了checkbox
v-on:click.stop 给儿子绑定,点击儿子 爸爸不会触发事件
v-on:click.capture 给爸爸绑定,点击儿子,爸爸先触发 因为事件是先捕获再冒泡
v-on:click.prevent 给a标签添加后,会阻止a标签跳转
v-on:click.self 给爸爸绑定,点击儿子区域不会触发,点击爸爸的位置才会触发
v-on:click.once 只能触发一次,点击第{2,}次,不会触发
v-on:keyup.enter 给input绑定,当敲定回车时,会触发
v-model.lazy 修改完失焦触发
v-model.trim 去除左右空白
v-number.number
@www = 'fn' 默认传个事件对象
@www = 'fn(1,2,3)' 只有实参
@www = 'fn($event,1,2,3)' 事件对象 + 实参
计算属性computed 用法和methods类似,可以缓存之前执行过的值,也可以使用get和set单独写
一般只用到get,当计算属性被人为赋值,且让该值重新计算,则用到set
cName:{
get(){
return this.name.split('').reverse().join('-')
},
set(val){
this.name = val + 'qwer'
}
}
this.cName = 'qerererwewe'
计算属性使用场景 求 P = this.x + this.y + this.z
监听属性使用场景 求 P1 = this.x + 123 求 P2 = this.y + 321 求 P3 = this.z + 456
计算属性原理
npm show vue versions 查看vue的所有版本
npm show vue version 查看当前vue版本
npm show @vue/cli versions 查看所有脚手架版本
npm show @vue/cli version 查看当前脚手架版本
开发环境:npx vue-cli-service inspect --mode development >> webpack.config.development.js
生产环境:npx vue-cli-service inspect --mode production >> webpack.config.production.js
在产生的 js 文件开头,添加:module.exports = ,然后格式化即可查看
devServer:{
host:'0.0.0.0',
proxy:{
'/api':{
target:'http://dev.bip.com.cn',
pathRewrite:{''^/api : "" }
}
},
disableHostCheck:true
}
disableHostCheck当设置为真时,此选项绕过主机检查。不建议这样做,因为不检查主机的应用程序容易受到DNS重新绑定攻击
在网页浏览过程中,用户在地址栏中输入包含域名的网址。浏览器通过DNS服务器将域名解析为IP地址,然后向对应的IP地址请求资源,最后展现给用户。而对于域名所有者,他可以设置域名所对应的IP地址。当用户第一次访问,解析域名获取一个IP地址;然后,域名持有者修改对应的IP地址;用户再次请求该域名,就会获取一个新的IP地址。对于浏览器来说,整个过程访问的都是同一域名,所以认为是安全的。这就造成了DNS Rebinding攻击。
基于这个攻击原理,Kali Linux提供了对应的工具Rebind。该工具可以实现对用户所在网络路由器进行攻击。当用户访问Rebind监听域名,Rebind会自动实施DNS Rebinding攻击,通过用户的浏览器执行js脚本,建立socket连接。这样,Rebind可以像局域网内部用户一样,访问路由器,从而控制路由器。
nextTick--------------------------------------------------------------------------------------------------------------------------- renderMixin(Vue)
const callbacks = []
let pending = false
function flushCallbacks() {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
export function renderMixin (Vue) {
Vue.prototype.$nextTick = function (fn) {
return nextTick(fn, this)
}
}
let timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
function nextTick(cb, ctx) {
callbacks.push(() => { cb.call(ctx) })
if (!pending) {
pending = true
timerFunc()
}
}
vue.use---------------------------------------------------------------------------------------------------------------------------
插件vue文件
<template>
<div>
<input v-model="name">
</div>
</template>
<script>
export default {
props:["value"],
computed:{
name:{
get(){
return this.value
},
set(val){
this.$emit('input',val)
}
}
}
}
</script>
插件js文件导出
import myInput from './myInput.vue'
export default {
install(Vue) {
Vue.component('myInput', myInput)
}
}
main.js文件导入 且使用
import myInput from './plugins/index.js'
Vue.use(myInput)
<my-input v-model="val" />
vue.use的原理
export function initGlobalAPI (Vue) {
initUse(Vue)
}
export function initUse (Vue) {
Vue.use = function (plugin) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
vue.mixin的原理---------------------------------------------------------------------------------------------------------------------------
export function initGlobalAPI (Vue) {
initMixin(Vue)
}
export function initMixin (Vue) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
slot用法 ---------------------------------------------------------------------------------------------------------------------------
v-slot 指令自 Vue 2.6.0 起被引入,提供更好的支持 slot 和 slot-scope attribute 的 API 替代方案。v-slot 完整的由来参见这份 RFC。在接下来所有的 2.x 版本中 slot 和 slot-scope attribute 仍会被支持,但已经被官方废弃且不会出现在 Vue 3 中。
HelloWorld插件
<ul>
<li v-for="(item,key) in list" :key="key">
<header>
<slot :item="item" a="111"></slot>
</header>
<footer>
<slot name="footer" b = '222' ></slot>
</footer>
</li>
</ul>
使用HelloWorld插件
<HelloWorld :list="list" >
<div slot-scope="obj">{{obj}}</div> // 最原始的使用方法
<template v-slot="obj">{{obj}}</template>
<template #default="obj">{{obj}}</template>
<template #footer="obj2">{{obj2}}</template>
</HelloWorld>
list:[1,2,3,4,5,6]
keep-alive用法---------------------------------------------------------------------------------------------------------------------------
a b 是组件中写的name
<keep-alive include="a,b">
<keep-alive :include="/a|b/">
<keep-alive :include="['a', 'b']">
还可以书写 exclude max
keep-alive原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
export function initGlobalAPI (Vue) {
extend(Vue.options.components, builtInComponents)
}
import KeepAlive from './keep-alive'
export default {
KeepAlive
}
function getComponentName (opts) {
return opts && (opts.Ctor.options.name || opts.tag)
}
function matches (pattern, name) {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
return pattern.test(name)
}
return false
}
function pruneCache (keepAliveInstance, filter) {
const { cache, keys, _vnode } = keepAliveInstance
for (const key in cache) {
const entry = cache[key]
if (entry) {
const name = entry.name
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
function pruneCacheEntry (
cache,
key,
keys,
current
) {
const entry = cache[key]
if (entry && (!current || entry.tag !== current.tag)) {
entry.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
export default {
name: 'keep-alive',
abstract: true,
props: {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number]
},
methods: {
cacheVNode() {
const { cache, keys, vnodeToCache, keyToCache } = this
if (vnodeToCache) {
const { tag, componentInstance, componentOptions } = vnodeToCache
cache[keyToCache] = {
name: getComponentName(componentOptions),
tag,
componentInstance,
}
keys.push(keyToCache)
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
this.vnodeToCache = null
}
}
},
created () {
this.cache = Object.create(null)
this.keys = []
},
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
this.cacheVNode()
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
updated () {
this.cacheVNode()
},
render () {
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot)
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
const name= getComponentName(componentOptions)
const { include, exclude } = this
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
const key = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
this.vnodeToCache = vnode
this.keyToCache = key
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
vuex使用---------------------------------------------------------------------------------------------------------------------------
import Vue from 'vue'
import Vuex, { Store } from 'vuex'
Vue.use(Vuex)
export default new Store({
state: {
count: 0
},
mutations: {
add(state) {
state.count++
},
changeCount(state, option) {
state.count = state.count + option.n
},
},
actions: {
changeCount(store, options) {
setTimeout(() => {
store.commit('changeCount', options)
}, 1000);
}
},
getters: {
countType(state) {
return state.count % 2 ? '奇数' : '偶数'
}
}
})
new Vue({
render: h => h(App),
store
}).$mount('#app')
vuex原理
let _Vue = null
function install(Vue) {
_Vue = Vue
Vue.mixin({
beforeCreate() {
//为每个vue文件 都能访问到$store属性
if (this.$options.store) {
this.$store = this.$options.store
} else if (this.$parent && this.$parent.$store) {// 先父 后子 子没有就访问父的$store
this.$store = this.$parent.$store
}
}
})
}
class Store {
constructor(options) {
let vm = new _Vue({
data() {
return {
state: options.state
}
}
})
this.state = vm.state
this.mutations = {}
let mutations = options.mutations || {}
this.actions = {}
let actions = options.actions || {}
this.getters = {}
let getters = options.getters || {}
Object.keys(mutations).forEach(key => {
this.mutations[key] = (option) => {
mutations[key].call(this, this.state, option)
}
})
Object.keys(actions).forEach(key => {
this.actions[key] = (option) => {
actions[key].call(this, this, option)
}
})
Object.keys(getters).forEach(key => {
Object.defineProperty(this.getters, key, {
get: () => {
return getters[key](this.state)
}
})
})
}
commit(key, option) {
this.mutations[key](option)
}
dispatch(key, option) {
this.actions[key](option)
}
}
// ...mapState(['count']) 在computed下
function mapState(arr) {
let obj = {}
arr.forEach(key => {
obj[key] = function () {
return this.$store.state[key]
}
})
return obj
}
// ...mapActions(['changeCount']) 在methods下
function mapActions(arr) {
let obj = {}
arr.forEach(key => {
obj[key] = function (...arg) {
this.$store.dispatch(key, arg)
}
})
return obj
}
// ...mapGetters({ 'countType': 'zidingyi'}) 在computed下
function mapGetters(obj) {
Object.keys(obj).forEach(key => {
const value = obj[key]
obj[value] = this.getters[key]
})
return obj
}
// ...mapMutations(['addCount'])
function mapMutations(arr) {
let obj = {}
arr.forEach(key => {
obj[key] = function (...arg) {
return this.$store.commit(key, arg)
}
})
return obj
}
export default {
install,
Store
}
export { Store, mapActions, mapGetters, mapMutations, mapState }
let vm = new _Vue({
data() {
return {
$$state: options.state
}
}
})
get state(){
//return vm._data.$$state 两种都可以
return vm.$data.$$state
}
set state(){
console.error('不可以设置值哦')
}
外面直接设置 this.$store.state = {} //会上面的错误
vue-router的使用---------------------------------------------------------------------------------------------------------------------------
import Vue from 'vue'
import VueRouter from 'vue-router'
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
Vue.use(VueRouter)
export default new VueRouter({
routes:[
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
})
new Vue({
router,
render: h => h(App)
}).$mount('#app')
<router-link to="/foo">1111</router-link>
<router-link to="/bar">2222</router-link>
<router-view></router-view>
vue-router的原理
let _vue = null
function install(Vue) {
_vue = Vue
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
this.$router = this.$options.router
} else if (this.$parent && this.$parent.$router) {
this.$router = this.$parent.$router
}
}
})
// 运行时 只能使用render渲染 不能使用template
Vue.component('router-link', {
props: {
to:{
type:String,
required:true
}
},
render(h) {
return h('a', { class: "qqq", attrs: { href: '#' + this.to } }, this.$slots.default)
// returen <a href={'#'+this.to}> {this.$slots.default} </a>
}
})
Vue.component('router-view', {
render(h) {
return h(this.$router.routerMap[this.$router._route])
}
})
}
class VueRouter {
constructor(options) {
let routes = options.routes
this.routerMap = {}
routes.forEach(item => {
this.routerMap[item.path] = item.component
})
this._route = location.hash.slice(1)
window.onhashchange = () => {
this._route = location.hash.slice(1)
}
_vue.util.defineReactive(this, '_route', '/')
}
}
VueRouter.install = install
export default VueRouter