VUE
简介
vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
vue是一套用于构建用户界面的渐进式框架【即主张:每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。Vue则不强求你一次性接受并使用它的全部功能特性】。
比如:Angular,它两个版本都是强主张的,必须接受以下东西:
- 必须使用它的模块机制
- 必须使用它的依赖注入
- 必须使用它的特殊形式定义组件
核心
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统:
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
我们已经成功创建了一个 Vue 应用!看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。
el:
Vue实例挂载的元素节点,值可以是 CSS 选择符,或实际 HTML 元素,或返回 HTML 元素的函数
实例化构造函数【new vue】(将vue的实例挂载到dom对象上从而运用数据驱动的方式来扩展我们的代码)
目录:node_module/vue/src/core/instance/index
options - data / methods
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
在js中一切皆函数,其实Vue就是一个函数,在初始化的时候执行原型链上的_init方法
_init
initMixin(Vue)
去掉Performance后:
let uid = 0;
export function initMixin(Vue: Class<Component>) {
Vue.prototype._init = function(options?: Object) {
const vm: Component = this;
vm._uid = uid++; // 当前实例的 _uid 加 1
// a flag to avoid this being observed
// 用 _isVue 来标识当前实例是 Vue 实例, 这样做是为了后续被 observed
vm._isVue = true;
// merge options 合并options
if (options && options._isComponent) { // _isComponent 标识当前为 内部Component
// 内部Component 的 options 初始化
initInternalComponent(vm, options);
}
else { // 非内部Component的 options 初始化
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
// 在render中将this指向vm._renderProxy
if (process.env.NODE_ENV !== 'production') {
initProxy(vm);
}
else {
vm._renderProxy = vm;
}
// expose real self
vm._self = vm;
initLifecycle(vm); // 初始化生命周期
initEvents(vm); // 初始化事件
initRender(vm); // 初始化渲染函数
callHook(vm, 'beforeCreate'); // 回调 beforeCreate 钩子函数
initInjections(vm); // resolve injections before data/props
// 初始化 vm 的状态
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created'); // vm 已经创建好 回调 created 钩子函数
if (vm.$options.el) { // 挂载实例
vm.$mount(vm.$options.el);
}
};
}
在初始化方法中主要干了以下几件事
- 初始化 options 参数
- 无论是 jQuery.js 还是 Vue.js,都是在 js 的基础上再次封装的库,都需要创建对应的实例来封装对应的操作。如通过 $('div') 获得一个 jQuery 的 div元素 实例,也称为 jQuery 对象,jQuery 对象包含了对选中的 div元素 的各种操作API,因此 jQuery 实例封装的是对选中元素的各种操作。
- 而 Vue.js 在此基础上更近一步,封装了对视图的所有操作,包括数据的读写、数据变化的监听、DOM元素的更新等等,通过 new Vue(options) 来创建出一个 Vue实例 ,也称为 Vue对象 ,该 Vue实例 封装了操作元素视图的所有操作,可通过 Vue实例 来轻松操作对应区域的视图。
- options 对象的具体可选属性有很多,具体可分为五大类 : 数据 、DOM 、生命周期钩子 、 资源 、组合
- 将 _renderProxy 设置为 vm
initProxy
if (hasProxy) {
// ...
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm;
}
不管开发环境还是生产环境都是为了给实例对象增添一个_renderProxy属性
vm._renderProxy = process.env.NODE_ENV !== 'production' && hasProxy ? new Proxy(vm, handlers) : vm;
- 为vm挂载一个_renderProxy属性
- 若是开发环境且支持原生Proxy接口,_renderProxy属性挂一个Proxy对象
- 若是生产环境或不支持原生Proxy,直接挂vm
renderProxy 渲染代理
Proxy对象用于拦截针对目标对象的所有操作,因此,对_renderProxy属性的操作将被拦截
具体的拦截方式 : handlers对象
const options = vm.$options
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
/*用户传入的render方法*/
options.render
_withStripped存在与否决定了对_renderProxy的拦截方式的不同
const getHandler = {
get (target, key) {
if (typeof key === 'string' && !(key in target)) {
warnNonPresent(target, key)
}
return target[key]
}
}
1 .拦截对_renderProxy的指读操作
2 .若访问的属性不存在就报警
3 .不改变默认返回值
const warnNonPresent = (target, key) => {
warn(
`Property or method "${key}" is not defined on the instance but ` +
'referenced during render. Make sure that this property is reactive, ' +
'either in the data option, or for class-based components, by ' +
'initializing the property. ' +
'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',
target
)
}
在渲染过程中调用的属性或方法在实例对象上不存在
实例渲染时,渲染函数会从vm._renderProxy对象上读取所需要的数据,一般来说他指向的是实例对象,也就是说直接从实例对象上读取数据用于渲染。但在开发环境,且支持Proxy的情况下,程序会给这个操作增加一层校验:渲染所需数据不存在时报警
校验render函数是否引用了vm上不存在数据或非特许的数据
校验自定义的快捷键名是否和Vue内置的快捷键修饰符重名
- 初始化生命周期
Vue的生命周期分为三个阶段,分别为: 初始化,运行中, 销毁,一共8个钩子函数
vue中的生命周期指的是组从创建到销毁的一个过程,在这个过程中,我们在每一个特定的阶段会触发一些方法,我们给这些方法起了个名字叫做生命周期钩子函数/ 组件钩子
因为我们想在生命周期钩子中实现项目功能,那么我们必须知道每一个钩子函数的具体用途:
比如 :页面初始化、数据进出顺序、页面状态控制
- 初始化 Render
renderMixin
export function renderMixin (Vue: Class<Component>) {
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode {
const vm: Component = this
// vm.$options.render & vm.$options._parentVnode
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
}
vm.$vnode = _parentVnode
let vnode
try {
// 执行 vue 实例的 render 方法
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
if (process.env.NODE_ENV !== 'production') {
if (vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
}
// 返回空vnode避免render方法报错退出
if (!(vnode instanceof VNode)) {
vnode = createEmptyVNode()
}
// 父级Vnode
vnode.parent = _parentVnode
return vnode
}
}
源码执行了 installRenderHelpers 方法,然后定义了 Vue 的 $nextTick 和 _render 方法
installRenderHelpers :
export function installRenderHelpers (target: any) {
target._o = markOnce
target._n = toNumber // 数字
target._s = toString // 字符串
target._l = renderList // 列表
target._t = renderSlot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
}
用在Vue生成的渲染函数中
在 $nextTick 函数中执行了 nextTick 函数
nextTick:
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
if (useMacroTask) {
macroTimerFunc()
} else {
microTimerFunc()
}
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
_render 方法,关键在这个 try...catch 方法中,执行了Vue实例中的 render 方法生成一个vnode。如果生成失败,会试着生成 renderError 方法。如果vnode为空,则为vnode传一个空的VNode,最后返回vnode对象。
initRender
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// 将 createElement 方法绑定到这个实例,这样我们就可以在其中得到适当的 render context。
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// 规范化一直应用于公共版本,用于用户编写的 render 函数。
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// 父级组件数据
const parentData = parentVnode && parentVnode.data
// 监听事件
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
- 初始化 vm的状态,prop/data/computed/method/watch都在这里完成初始化
- 挂载实例
vue没有把所有的方法都写在函数内部,这样从代码上来说,每次实例化的时候不会生成重复的代码
主要还是代码结构更清晰,利用mixin的概念,把每个模块都抽离开,这样代码在结构和扩展性都有很大提高,这里的每个mixin先不说,先看以一下整体结构,这里定义完还要被core里的index.js再次包装调用initGlobalAPI(Vue)来初始化全局的api方法,在web下runtime文件夹下引用再次封装,vue是分为运行时可编译和只运行的版本,所以如果需要编译,在Vue原型上添加了$mount方法,先来看一下initGlobalAPI,在instance中都是在原型链上扩展方法,在这里是直接在Vue上扩展静态方法
现在数据和 DOM 已经被建立了关联,所有东西都是响应式的。
注意我们不再和 HTML 直接交互了。一个 Vue 应用会将其挂载到一个 DOM 元素上 (对于这个例子是 #app) 然后对其进行完全控制。那个 HTML 是我们的入口,但其余都会发生在新创建的 Vue 实例内部。
网友评论