我是靠谱客的博主 英勇舞蹈,最近开发中收集的这篇文章主要介绍vue原理简单实现(一),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

书接上文点这里

  • 首先来说vue是个类,当实例化这个类的时候会传入一个对象例如:
new Vue({
el:"#app",
data(){
return {
a:1
}
}
})

这种传参的方式叫做options api

  • 在真实的vue中由于考虑到方便类的扩展问题,所以vue采用的是用构造函数的方式来写的
function Vue(options){
this._init(options)// 入口方法,用来做数据初始化的
}
// 做成公共方法
Vue.prototype._init = funciton (options){
}
export Vue

在vue中首先vue会对传过来的数据做一次初始化操作,调用init方法,因为后期的组件操作中要用到这个方法,所以把这个方法做成一个公共的方法

  • 为了方便扩展和解耦vue采用插件的模式为构造函数扩展方法
  • vue会先拿到实例然后定义一个$options变量,然后把参数(用户传进来的options)赋值给这个变量
export function initMixin(Vue){
Vue.prototype._init = funciton (options){
const vm = this // 把实例赋值给变量vm
vm.$options = options // 通过$options存储options
}
}

接下来就是vue核心部分:响应式数据原理

  • 这里先说一下MVVM框架,首先来讲vue并不是MVVM框架,文档中应该也有写到,为什么不是呢?
  • MVVM指的是数据更新视图,不会操作dom而vue中会有dom的操作($ref会操作dom)
  • 拿到vm实例后先通过initState方法初始化一下数据
export function initMixin(Vue){
Vue.prototype._init = funciton (options){
const vm = this // 把实例赋值给变量vm
vm.$options = options // 通过$options存储options
initState(vm)
}
}
  • initState中包括了很多需要初始化的数据(版本源码2.6.12)如下:
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}

可以看到vue中根据不同的数据调用了不同的数据初始化的方法,而其中比较重要的就是数据初始化的过程

function initData(vm){
let data = vm.$options.data
data = vm._data = typeof data == 'funciton' ? data.call(vm) : data
observe(data)
}

其中的call是为了让data函数的this指向vm实例,否则会指向window,这样在vue实例化后的其他方法及对象中就能通过this拿到data中的数据了。
而vm._data是为了把data中的数据挂载vm上,否则vm上是没有data函数中的数据的

  • 接下来data交到了observe中然后来看看observe做了什么
    observe把data分成了数组和对象,当data是对象的时候把data交给Observe类
function isObject(obj){
return obj !== null && typeof obj === 'object'
}
export function observe(data){
// vue中数组和对象是分开处理
if(!isObject(data)) return;
new Observe(data)
}
  • vue中初始化数据的时候会对初始化的数进行一个劫持,方便之后能够监听到数据的变化并根据变化来更新视图,而Observe类主要做的就是数据劫持的工作
class Observe{
constructor(value){
this.walk(value)
}
walk(obj){
Object.keys(obj).forEach(key=>{
defineReactive(obj,key);
})
}
}

可以看到Observe类中用到了一个方法,这个方法用来劫持每个key,让我们来实现他(是个公共方法)

export function defineReactive(obj,key){
// 这里初始化的时候数据默认都没有getter
// 递归观测data中的数据是否为对象
observe(obj)
let val = obj[key]
Object.defineProperty(obj,key,{
get(){
return val
}
set(newValue){
if(val === newValue) return;
// 递归检测设置的值是否为对象
observe(newValue)
val = newValue
}
})
}

在defineReactive中两次调用observe,第一次为了防止obj是个对象套对象
第二次调用是因为当给一个被监听的值赋值的时候只走了set方法,如果直接赋值的话被设置的值如果是个对象则对象是无法被监听到的,所以要做一层监听
其中observe函数的主要作用就是给数据添加监听(也就是get和set)

  • 根据源码来看第一次进入observe的时候入参data是个对象,Array.isArray(data)为false,只有当深度遍历的时候才会出现有数组的情况才需要做区分
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}
// export function isPlainObject (obj: any): boolean {
//
return _toString.call(obj) === '[object Object]'
// }
//
如果data不是个函数的话会报警告
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
  • 到这里对象的监听方法先告一段落,接下来说一下数组的监听方法,对于数组的监听方法肯定就不能直接走walk了,要进行区分,因此要更改Observe类中的方法
class Observe{
constructor(value){
Object.definePropety(value,'__ob__',{
enumerable:fase,// 防止重复设置导致溢出
configurable:false,// 防止被改写
value:this
})
if(Array.isArray(value)){
value.__proto__ = arrayMethods
observeArray(value)
} else {
this.walk(value)
}
}
}
  • __ob__有两个作用,1.可以标识value是否被监听,2.挂载了整个儿observe的实例;
  • 其中的arrayMethods是自定义的继承自Array的原型,让我们来简单的实现以下
let arrayProto = Array.prototype// 复制原型
let arrayMethods = Object.create(arrayProto)// 添加原型链
let methodsToPatch = [//用来存放能够改变数组的方法
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverce",
]
// 将方法挂到arrayMethods上并进行重写
methodsToPatch.froEach(method => {
arrayMethods[method] = function(...args){
let result = arrayProto[method].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)
return result
}
})
  • 上面这段代码考虑当向数组中添加值的时候如果添加的是个对象的情况这个对象无法被监控,所以拿到添加的数组,并重新监控,那observeArray做了什么呢?只是简单的遍历并添加监控
observeArray(value){
value.forEach(item =>{
observe(item)
})
}
  • 当data上有__ob__的时候就不要在监听了
export function observe(data){
// vue中数组和对象是分开处理
if(!isObject(data)) return;
if(data.__ob__) return;
new Observe(data)
}

最后

以上就是英勇舞蹈为你收集整理的vue原理简单实现(一)的全部内容,希望文章能够帮你解决vue原理简单实现(一)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(49)

评论列表共有 0 条评论

立即
投稿
返回
顶部