当前位置: 首页 > news >正文

品牌自适应网站建设/百度服务中心电话

品牌自适应网站建设,百度服务中心电话,灵寿网站建设,wordpress _e写在前面 Vue构造函数的创建过程 一文中介绍了 Vue构造函数 的创建过程,其中第一个对 Vue构造函数 进行成员添加的就是 initMixin(Vue),该调用内创建了 _init 方法。可以说,Vue实例 的大门就是 _init 方法,因此,我们从…

写在前面

Vue构造函数的创建过程 一文中介绍了 Vue构造函数 的创建过程,其中第一个对 Vue构造函数 进行成员添加的就是 initMixin(Vue),该调用内创建了 _init 方法。可以说,Vue实例 的大门就是 _init 方法,因此,我们从这个方法入手,一步一步剖析 vm 是如何生成的。

/*
<div id="app"><div><h1>{{msg}}</h1><div id="extendUse"></div><child-component :msg="msg" /></div>
</div>
*/
// Vue.extend 手动挂载组件
const ExtendUse = Vue.extend({props: ['msg'],template: `<h2>ExtendUse said: {{msg}}</h2>`,
})
const ChildExtend = new ExtendUse({propsData: {msg: 'hello Extend'}
}).$mount('#extendUse')
// Vue.component 自动挂载组件
const ChildComponent = Vue.extend({props: ['msg'],template: `<h2>child's father said: {{msg}}</h2>`,
})
Vue.component('child-component', ChildComponent)
// Vue 实例
const vm = new Vue({el: '#app',data: {msg: 'hello Vue',}
})

按照惯例,我先将 _init 的源码简写一下,并且划分一下步骤:

Vue.prototype._init = function (options) {// 步骤 - 1const vm = thisvm._uid = uid++vm._isVue = true// 步骤 - 2if (options && options._isComponent) {initInternalComponent(vm, options)} else {vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm)}// 步骤 - 3if (process.env.NODE_ENV !== 'production') {initProxy(vm)} else {vm._renderProxy = vm}vm._self = vm// 步骤 - 4initLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, 'beforeCreate')initInjections(vm)initState(vm)initProvide(vm)callHook(vm, 'created')// 步骤 - 5if (vm.$options.el) {vm.$mount(vm.$options.el)}
}

步骤 - 1

在这段代码中,我们首要了解的是 vm 指向了谁?
从它的调用 function Vue(options) { this._init(options) } 中我们可以了解到,Vue 是一个构造函数,而 _init 方法又不是该构造函数的静态方法,因此 this 指向了实例,所以 const vm = this 这一步其实就是将实例本身赋值给了 vm。

再具体点?const app = new Vue(options) 这里的 app 就是 vm 的值。

接着,在实例身上添加上 _uid 和 _isVue 的标识,记录当前实例是第 (_uid + 1) 个 Vue 实例对象。

步骤 - 2

这段代码是一个条件判断,判断的是是否是一个组件。那怎样的存在算是一个组件呢?Vue.extend 手动挂载上去的算不算一个组件呢?

我就先兜个底,只有存在于 vm.options.components 中的才算组件,且能进入 if 判断之中。这就意味着,要么 Vue.component(id, definition) 定义的对象,要么 new Vue({ components: { id: definition } }) 定义的对象,其他方式都不具备 _isComponent 属性。因此,即使是 Vue.component 方法内同样也调用了的 Vue.extend 方法,在手动挂载时也不算作组件,不具备 _isComponent 属性。

那这段代码的意义是什么呢?简而言之,就是将 Vue构造函数 中的成员变量 options 和我们传入的 options 合并然后挂载到实例的 $options 属性上。

initInternalComponent

不知道大家想没想过,组件为什么会进到 _init 中来,哪儿定义了它也可以进来进行初始化操作?

如果你没有忘记 详解Vue.component和Vue.extend 一文中讲过,Vue.component 中会调用一次 Vue.extend,将生成的实例存入 vm.options.components 之中。而在 Vue.extend 中我们曾定义过一个 VueComponent构造函数,这个构造函数继承了 Vue构造函数,因此也有 _init 方法,在 VueComponent构造函数 的 constructor 中又恰好调用了 this._init(options),所以套了一层又一层,剥丝抽茧后你应该就能明白为什么组件可以进来了吧。

那么,这个方法内做了些什么?当你进入函数体你会发现,options 中哪来的这么多属性???我丢,见都没见过啊,一脸懵逼逐渐变成N脸懵逼。所以我们先按下不表,因为其中涉及到模板编译部分,扯得太远回不来就麻烦了。

resolveConstructorOptions

既然不讲组件的 options 合并,那总得讲讲 new (Vue.extend(extendOptions))(options) / new Vue(options) 的 options 合并啊,讲讲讲,这不就来了么。先声明一下,以下 vm 指的是 Vue构造函数 的实例,componentVM 指的是 Vue.extend 返回的构造函数的实例。

这个方法本质上是为了得到 Vue构造函数 身上的 options。

如果传入的 constructor 没有 super 属性,则说明当前实例是 vm,直接返回 vm.constructor.options。

如果传入的 constructor 拥有 super 属性,则说明当前实例是 componentVM,那么当前的 vm.constructor.options 就是 VueComponent.options。如果 VueComponent.superOptions 和 VueComponent.super.options 不相等时会将 VueComponent.superOptions 更新,得到最新的 VueComponent.options 然后返回,否则直接返回 VueComponent.options。

但是,明明 superOptions 和 super.options 都是指向的 Vue构造函数,什么时候会不相等呢?大家可以亲自调试一下以下代码:

const Score = Vue.extend({template: '<p>{{firstName}} {{lastName}}: {{score}}</p>'
})
Vue.mixin({data() {return {firstName: 'Walter',lastName: 'White'}}
})
Score.mixin({data: function () {return {score: '99'}}
})
new Score().$mount('#app')

mergeOptions

首先,我们需要了解这个方法做了什么。

function mergeOptions (parent, child, vm) {if (process.env.NODE_ENV !== 'production') {checkComponents(child)}if (typeof child === 'function') {child = child.options}normalizeProps(child, vm)normalizeInject(child, vm)normalizeDirectives(child)const extendsFrom = child.extendsif (extendsFrom) {parent = mergeOptions(parent, extendsFrom, vm)}if (child.mixins) {for (let i = 0, l = child.mixins.length; i < l; i++) {parent = mergeOptions(parent, child.mixins[i], vm)}}const options = {}let keyfor (key in parent) {mergeField(key)}for (key in child) {if (!hasOwn(parent, key)) {mergeField(key)}}function mergeField (key) {const strat = strats[key] || defaultStratoptions[key] = strat(parent[key], child[key], vm, key)}return options
}

checkComponents

判断 (vm | componentVM).options.components: { id: definition } 的 id 是否等于 slot / component 或其他原生 HTML 标签,如果是则报错。

normalizeProps

当 (vm | componentVM) 中传入的 options 中存在 props 时激活该方法,目的是为了格式化 props 中的属性。

如果 options.props 是一个数组,则将 options.props 的值统一成 { item: { type: null } }。

如果 options.props 是一个对象,则将 options.props 的值统一成 { key: { type: value } }。

当 key 是以短横线命名法( name-space )命名时将其转换成驼峰命名法( nameSpace )

// 如果 options.props 是一个数组
new Vue({props: ['msg', 'aeo-rus']
})
/*
props: {msg: {type: null},aeoRus: {type: null}
}
*/
// 如果 options.props 是一个对象
new Vue({props: {msg: {type: String,default: 'hello Vue'},'aeo-rus': {default: 'aeorus'},}
})
/*
props: {msg: {type: String,default: 'hello Vue'},aeoRus: {default: 'aeorus'}
}
*/

normalizeInject

当 (vm | componentVM) 中传入的 options 中存在 inject 时激活该方法,目的是为了格式化 inject 中的属性。

如果 options.inject 是一个数组,则将 options.inject 的值统一成 { item: { from: item } }。

如果 options.inject 是一个对象,则将 options.inject 的值统一成 { key: { from: value } } ( 当 value 也是对象时则统一成 { from: key, value.k: value.v } )。

new Vue({inject: ['onload', 'reload']
})
/*
inject: {onload: {from: 'onload'},reload: {from: 'reload'}
}
*/
new Vue({inject: {onload: {from: 'onlaunch',default: 'onload'},reload: 'reload'}
})
/*
inject: {onload: {from: 'onlaunch',default: 'onload'},reload: {from: 'reload'}
}
*/

normalizeDirectives

当 (vm | componentVM) 中传入的 options 中存在 directives 时激活该方法,目的是为了格式化 directives 中的指令。

将 options.directives 的值统一成 { key: { bind: value, update: value } } ( 有可能 value 是一个方法 ) 。

new Vue({directives: {focus: {inserted(el) {el.focus()}}}
})
/*
directives: {focus: {inserted: el => {el.focus()}}
}
*/
new Vue({directives: {focus: el => {el.focus()}}
})
/*
directives: {focus: {bind: el => {el.focus()},update: el => {el.focus()}}
}
*/

extends / mixins

当 (vm | componentVM) 中传入的 options 中存在 extends / mixins 时进入条件判断,递归 mergeOptions 方法,将 extends / mixins 的对象 ( 实质上就是 options ) 与 (vm | componentVM) 进行合并。

mergeField

const options = {}
let key
for (key in parent) {mergeField(key)
}
for (key in child) {if (!hasOwn(parent, key)) {mergeField(key)}
}
function mergeField (key) { const strat = strats[key] || defaultStratoptions[key] = strat(parent[key], child[key], vm, key)
}

这个方法极其复杂,本质上就是合并了构造函数身上的属性和 options 上的属性。根据 key 的不同判断是返回 options 上的属性抑或以构造函数身上的属性的值为原型创造的新的拥有 options 上的属性的值的对象。

// 比如当 key 为 components 时返回
options = {components: {ChildComponent,__proto__: {components: {KeepAlive,Transition,TransitionGroup,},},},
}

步骤 - 3

我们可以看到在这段代码中多了一个新的属性 _renderProxy,从字面意义上来看应该称之为渲染代理对象,事实上它确实参与了渲染流程,在调用 render 方法获取 vnode 时会将 render 方法的内部指针指向它。

但随之而来产生了两个问题: 1.为什么要有渲染代理对象?2.为什么开发环境和生产环境要用不同的渲染代理对象?

其实并不复杂,我们通过以下代码来进行解答:

/*
目录: core/instance/render.js
vnode = render.call(vm._renderProxy, vm.$createElement)
*/
render(h) {h('div', 'hello vue')
}

我们通过以上代码可以发现,h函数 其实就是 vm.$createElement,但是因为调用的时候是直接使用 h() 来调用的,因此它内部的指针应该指向 window 而不是当前实例,所以我们需要通过 call 的方式将内部指针指向当前实例,如此就可以使用 this 获取到该实例身上 options 中其他的属性了。

那么开发环境和生产环境为什么要用不同的渲染代理对象呢?我们可以发现在开发环境中其实是调用了 initProxy 方法创建的渲染代理对象,其中判断了是否存在 Proxy 对象,通过 Proxy 对象拦截 this.xxx 中的 xxx 是否是一个非法的属性,有利于我们开发时的操作不谨慎。但是生产环境时就没必要了,毕竟我们不会将错误保留到线上。

步骤 - 4

这又是一连串的调用,像极了 core/global-api/index.js 中的操作,但是请不要忽略入参,core/global-api/index.js 中传入的是 Vue,而这里传入的是 vm。这意味着,core/global-api/index.js 中是对 Vue构造函数 进行成员的添加,而这里是对 vm 进行属性的添加。

initLifecycle

初始化一系列属性,如果当前实例是组件则将 $parent / $root 绑定上组件所归属的 vm。

vm = {__proto__: {...Vue,},_uid,_isVue,$options,_renderProxy,_self,/* new add start */$parent,$root,$children: [],$refs: {},_watcher: null,_inactive: null,_directInactive: false,_isMounted: false,_isDestroyed: false,_isBeingDestroyed: false,/* new add end */
}

initEvents

初始化事件中心,如果当前实例是组件则会对父组件的事件监听进行重新绑定。

vm = {__proto__: {...Vue,},_uid,_isVue,$options,_renderProxy,_self,$parent,$root,$children: [],$refs: {},_watcher: null,_inactive: null,_directInactive: false,_isMounted: false,_isDestroyed: false,_isBeingDestroyed: false,/* new add start */_events: {},_hasHookEvent: {},/* new add end */
}

initRender

初始化存放和生成 虚拟DOM 的属性和方法。

如果当前实例不是组件,则将 $attrs / $listeners 设置为空对象添加到实例上。

如果当前实例是组件,则会将父组件 虚拟DOM 上的 attrs 添加响应式后放到自身实例的 $attrs 属性上;再将父组件的事件监听添加响应式后放到自身实例的 $listeners 属性上。

vm = {__proto__: {...Vue,},_uid,_isVue,$options,_renderProxy,_self,$parent,$root,$children: [],$refs: {},_watcher: null,_inactive: null,_directInactive: false,_isMounted: false,_isDestroyed: false,_isBeingDestroyed: false,_events: {},_hasHookEvent: {},/* new add start */$vnode: null,_vnode: null,$slots,$scopedSlots,_c() {},$createElement() {},$attrs,$listeners,/* new add end */
}

beforeCreate

调用 beforeCreate 生命周期。

initInjections

由于 步骤 - 2 -> resolveConstructorOptions -> mergeOptions -> normalizeInject 这一过程中在实例的 $options 中挂载了 inject 这个属性的缘故,这个方法中就不需要再进行添加,只是单纯地为 inject 中的对象添加了响应式。

vm = {__proto__: {...Vue,},_uid,_isVue,$options: {/* update start */inject: {}/* update end */},_renderProxy,_self,$parent,$root,$children: [],$refs: {},_watcher: null,_inactive: null,_directInactive: false,_isMounted: false,_isDestroyed: false,_isBeingDestroyed: false,_events: {},_hasHookEvent: {},$vnode: null,_vnode: null,$slots,$scopedSlots,_c() {},$createElement() {},$attrs,$listeners,
}

initState

这一步大家肯定熟,只要在网上看过 Vue 源码解析啊响应式原理啊之类视频的应该都了解,这里就是网传的 Vue 的 constructor 中的内容。

即初始化 props / methods / data / computed / watch。

vm = {__proto__: {...Vue,},_uid,_isVue,$options: {inject: {}},_renderProxy,_self,$parent,$root,$children: [],$refs: {},_watcher: null,_inactive: null,_directInactive: false,_isMounted: false,_isDestroyed: false,_isBeingDestroyed: false,_events: {},_hasHookEvent: {},$vnode: null,_vnode: null,$slots,$scopedSlots,_c() {},$createElement() {},$attrs,$listeners,/* new add start */_watchers,_props, // 如果 options 上有 props...options.methods, // 如果 options 上有 methods...options.data, // 如果 options 上有 data_data: {...options.data}, // 如果 options 上有 data...options.computed, // 如果 options 上有 computed/* new add end */
}

initProvide

vm = {__proto__: {...Vue,},_uid,_isVue,$options: {inject: {}},_renderProxy,_self,$parent,$root,$children: [],$refs: {},_watcher: null,_inactive: null,_directInactive: false,_isMounted: false,_isDestroyed: false,_isBeingDestroyed: false,_events: {},_hasHookEvent: {},$vnode: null,_vnode: null,$slots,$scopedSlots,_c() {},$createElement() {},$attrs,$listeners,_watchers: [], // 如果 options 上有 watch 则会存在 Watcher 的实例_props, // 如果 options 上有 props...options.methods, // 如果 options 上有 methods...options.data, // 如果 options 上有 data_data: {...options.data}, // 如果 options 上有 data...options.computed, // 如果 options 上有 computed/* new add start */_provided, // 如果 options 上有 provide/* new add end */
}

created

调用 created 生命周期。

步骤 - 5

到目前为止,我们都只是初始化的工作,大部分都是挂载某某属性,要说真的做了什么业务相关的事情,那大概就是 initState 这部分了,其中为数据添加了响应式,当有计算属性和侦听器时还顺便做了依赖收集。

但是,我们是否还没看到对于 DOM 的处理?对了,这就是为什么在 beforeCreate 和 created 生命周期里无法获取 this.$refs 的原因,我们还没有进入模板编译,那么 DOM 自然就还没有生成,没有生成的东西怎么可能在这些钩子里被获取呢?

自此,_init 就告一段落了,接下来我们即将进入下一个流程 ———— 模板编译 -> ゲットスタート ( get start )。

http://www.lbrq.cn/news/774271.html

相关文章:

  • 网站建设咨询话术技巧/军事新闻头条
  • 网站中怎么做下载链接/网络推广需要多少费用
  • 怎样给网站做一张背景/乔拓云智能建站
  • 怎么入侵网站后台管理/百度竞价推广开户联系方式
  • 受欢迎的天津网站建设/超级seo外链
  • 网站开发需要的编程软件/网站外链推广平台
  • 哈尔滨营销型网站建设/发广告平台有哪些免费
  • 如何做病毒视频网站/济南做网站公司
  • 做网站与不做网站的区别/建站推广
  • 有做门窗找活的网站吗/关键词优化的作用
  • 网页游戏排行大全/seo推广一个月见效
  • 网站建设的话术/东莞好的网站国外站建设价格
  • 叙述网站的设计制作流程/十大搜索引擎地址
  • 如何进行网站分析/google广告投放技巧
  • 企业解决方案业务是什么/西安网站seo费用
  • 做网站的中标公司/百度提交入口网址是什么
  • 大气宽屏网站模板企业源码带后台/最近刚发生的新闻
  • 做电子商务网站需要什么软件/seo排名赚app官网
  • 定制建设网站/seo营销服务
  • 月子会所网站建设方案/百度下载并安装到桌面
  • 公众号排版设计/重庆网站seo服务
  • 如何做自己的业务网站/网站测试
  • 私有云可以做网站/域名查询注册信息查询
  • 百度商桥在网站/云速seo百度点击
  • 自己做视频网站能赚钱/seo快速排名系统
  • wordpress找不到分类页面/企业seo关键词优化
  • 网站建设图片怎么做/seo日常工作内容
  • 可以做没有水印的视频网站/互联网宣传方式有哪些
  • 行业网站策划方案/成都百度推广排名优化
  • 网站源码爬取工具/百度竞价账户
  • UE5.3 C++ 动态多播实战总结
  • 游戏中角色持枪:玩家操控角色,角色转向时枪也要转向
  • Mybatis学习之逆向工程(十)
  • 查看 php 可用版本
  • CST MATLAB 联合仿真超材料开口谐振环单元
  • 搭建本地 Git 服务器