vue 阅读一【待完结】

vue

初步方案:从第一个commit开始到到最近的commit,从代码的大纲开始到细节,利用思维导图。

注意: 源码阅读是一件比较枯燥的事情,要有别的东西一起做,源码只是偶尔看看,经常发呆的话,非常浪费时间。

写在前面: 阅读源码并不能产生什么实际的价值,而阅读的源码的过程中,你学到的思路,分析方法,总结,才是你花大时间阅读源码所能产生的实际价值。

阅读源码还是缺乏一点思考,没有结合到实际项目中源码是怎么产生作用的!!!

阅读源码的疑问:

  • definePrototype是如何生效的
  • 双向绑定的通知机制是如何做的
  • 底层源码个组件之间的通信
  • 观察者模式的流程大概弄懂了,但是细节部分是怎么驱动生效的呢?

可能的答案:

关键可能是dep,每个待观察的对象都有一个Observer实例,实例都具有一个dep,每个dep都有一个notify;set方法的时候会通知notify,notify直接调用待观察对象的update方法。这个逻辑链路是符合观察者模式的设计模式的。

观察者模式:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化。

dev init a879ec0 第一版本

内部结构图

一、vue的构造器概览

  constructor (options) {

this.$options = options

this._data = options.data

const el = this._el = document.querySelector(options.el)

const render = compile(getOuterHTML(el)) //编译vue模板

this._el.innerHTML = ''

Object.keys(options.data).forEach(key => this._proxy(key)) //利用Observer.defineProperty();重定义了属性成get和set

if (options.methods) {

Object.keys(options.methods).forEach(key => {

this[key] = options.methods[key].bind(this) //把methods 作用域绑定到this,也就是vue实例上面

})

}

this._ob = observe(options.data) // 将数据转化为观察者对象

this._watchers = []

this._watcher = new Watcher(this, render, this._update) // 解析表达式,收集依赖,当值变化的时候,通知回调

this._update(this._watcher.value)

}

源码编写挺遵守规范的,类的首字母大写啊,观察者模式啊等等。

二、observe 详解

数据的观察者,每个data属性上面都会有一个观察者

/**

* Attempt to create an observer instance for a value,

* returns the new observer if successfully observed,

* or the existing observer if the value already has one.

*

* @param {*} value

* @param {Vue} [vm]

* @return {Observer|undefined}

* @static

*/

export function observe (value, vm) {// 此处的value 是options.data,一般来说是个json

if (!value || typeof value !== 'object') {

return

}

var ob

if (

hasOwn(value, '__ob__') && //hasOwn = hasOwnproperty ,检查是否具有`__ob__`属性

value.__ob__ instanceof Observer //`__ob__` 是否是Observer的实例

) {

ob = value.__ob__ //已经存在`__ob__`,则赋原值

} else if (

shouldConvert &&

(isArray(value) || isPlainObject(value)) && //判断是否是原生的数组或者是对象

Object.isExtensible(value) && //判断一个对象是否可扩展,添加属性

!value._isVue

) {

ob = new Observer(value)

}

if (ob && vm) {

ob.addVm(vm) //??添加vm实例,代理keys

}

return ob

}

/**

* Add an owner vm, so that when $set/$delete mutations

* happen we can notify owner vms to proxy the keys and

* digest the watchers. This is only called when the object

* is observed as an instance's root $data.

*

* @param {Vue} vm

*/

Observer.prototype.addVm = function (vm) {

(this.vms || (this.vms = [])).push(vm)

}

/**

* Observer class that are attached to each observed

* object. Once attached, the observer converts target

* object's property keys into getter/setters that

* collect dependencies and dispatches updates.

*

* @param {Array|Object} value

* @constructor

*/

export function Observer (value) {

this.value = value

this.dep = new Dep()

def(value, '__ob__', this) //??将value作为obj,观察者的关键

if (isArray(value)) {

var augment = hasProto

? protoAugment

: copyAugment

augment(value, arrayMethods, arrayKeys) //如果是数组,则拦截变异方法,通知更新

this.observeArray(value) // 遍历value ,observer实例化每个value的值。

} else {

this.walk(value)

}

}

/**

* A dep is an observable that can have multiple

* directives subscribing to it.

* dep 就是一个可以订购多个指令的观察者

* @constructor

*/

export default function Dep () {

this.id = uid++

this.subs = []

}

/**

* Notify all subscribers of a new value.

*/

Dep.prototype.notify = function () {

// stablize the subscriber list first

var subs = this.subs.slice()

for (var i = 0, l = subs.length; i < l; i++) {

subs[i].update()

}

}

/**

* Augment an target Object or Array by intercepting

* the prototype chain using __proto__

*

* @param {Object|Array} target

* @param {Object} src

*/

function protoAugment (target, src) {

/* eslint-disable no-proto */

target.__proto__ = src

/* eslint-enable no-proto */

}

/**

* Augment an target Object or Array by defining

* hidden properties.

*

* @param {Object|Array} target

* @param {Object} proto

*/

function copyAugment (target, src, keys) {

for (var i = 0, l = keys.length; i < l; i++) {

var key = keys[i]

def(target, key, src[key])

}

}

export const arrayMethods = Object.create(arrayProto)

/**

* Intercept mutating methods and emit events

*/

;[

'push',

'pop',

'shift',

'unshift',

'splice',

'sort',

'reverse'

]

.forEach(function (method) {

// cache original method

var original = arrayProto[method]

def(arrayMethods, method, function mutator () {

// avoid leaking arguments:

// http://jsperf.com/closure-with-arguments

var i = arguments.length

var args = new Array(i)

while (i--) {

args[i] = arguments[i]

}

var result = original.apply(this, args)

var ob = this.__ob__

var inserted

switch (method) {

case 'push':

inserted = args

break

case 'unshift':

inserted = args

break

case 'splice':

inserted = args.slice(2)

break

}

if (inserted) ob.observeArray(inserted)

// notify change

ob.dep.notify()

return result

})

})

/**

* Set a property on an object. Adds the new property and

* triggers change notification if the property doesn't

* already exist.

*

* @param {Object} obj

* @param {String} key

* @param {*} val

* @public

*/

export function set (obj, key, val) {

if (hasOwn(obj, key)) {

obj[key] = val

return

}

if (obj._isVue) {

set(obj._data, key, val)

return

}

var ob = obj.__ob__

if (!ob) {

obj[key] = val

return

}

ob.convert(key, val)

ob.dep.notify() //更新只有,调用通知更新。

if (ob.vms) {

var i = ob.vms.length

while (i--) {

var vm = ob.vms[i]

vm._proxy(key)

vm._digest()

}

}

return val

}

/**

* Walk through each property and convert them into

* getter/setters. This method should only be called when

* value type is Object.

*

* @param {Object} obj

*/

Observer.prototype.walk = function (obj) {

var keys = Object.keys(obj)

for (var i = 0, l = keys.length; i < l; i++) {

this.convert(keys[i], obj[keys[i]])

}

}

/**

* Define a reactive property on an Object.

*

* @param {Object} obj

* @param {String} key

* @param {*} val

*/

export function defineReactive (obj, key, val) {

var dep = new Dep()

var property = Object.getOwnPropertyDescriptor(obj, key)

if (property && property.configurable === false) {

return

}

// cater for pre-defined getter/setters

var getter = property && property.get

var setter = property && property.set

var childOb = observe(val)

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get: function reactiveGetter () {

var value = getter ? getter.call(obj) : val

if (Dep.target) {

dep.depend()

if (childOb) {

childOb.dep.depend()

}

if (isArray(value)) {

for (var e, i = 0, l = value.length; i < l; i++) {

e = value[i]

e && e.__ob__ && e.__ob__.dep.depend()

}

}

}

return value

},

set: function reactiveSetter (newVal) {

var value = getter ? getter.call(obj) : val

if (newVal === value) {

return

}

if (setter) {

setter.call(obj, newVal)

} else {

val = newVal

}

childOb = observe(newVal)

dep.notify()

}

})

}

三、compile 详解

主要是用来解析vue的字符串模板和vue内置的语法为可处理的对象

export function compile (html) {

html = html.trim()

const hit = cache[html]

return hit || (cache[html] = generate(parse(html))) //如果没有的话就生成一个

}

parse() 主要使用了一个第三方库,将html元素转化成了一个对象;可以方便后一步generate处理vue内置语法v-for,v-if等等
htmlparser.js

**

* Convert HTML string to AST

*

* @param {String} html

* @return {Object}

*/

export function parse (html) {

let root

let currentParent

let stack = []

HTMLParser(html, {

html5: true,

start (tag, attrs, unary) {

let element = {

tag,

attrs,

attrsMap: makeAttrsMap(attrs),

parent: currentParent,

children: []

}

if (!root) {

root = element

}

if (currentParent) {

currentParent.children.push(element)

}

if (!unary) {

currentParent = element

stack.push(element)

}

},

end () {

stack.length -= 1

currentParent = stack[stack.length - 1]

},

chars (text) {

text = currentParent.tag === 'pre'

? text

: text.trim() ? text : ' '

currentParent.children.push(text)

},

comment () {

// noop

}

})

return root

}

// src/compile/codepen.js

// generate 主要是解析vue内置的语法,生成dom的。

export function generate (ast) {

const code = genElement(ast)

return new Function (`with (this) { return ${code}}`)

}

function genElement (el, key) {

let exp

if (exp = getAttr(el, 'v-for')) {

return genFor(el, exp)

} else if (exp = getAttr(el, 'v-if')) {

return genIf(el, exp)

} else if (el.tag === 'template') {

return genChildren(el)

} else {

return `__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })`

}

}

四、watcher 详解

解析表达式收集依赖,当值变化的时候回调

**

* A watcher parses an expression, collects dependencies,

* and fires callback when the expression value changes.

* This is used for both the $watch() api and directives.

*

* @param {Vue} vm

* @param {String|Function} expOrFn

* @param {Function} cb

* @param {Object} options

* - {Array} filters

* - {Boolean} twoWay

* - {Boolean} deep

* - {Boolean} user

* - {Boolean} sync

* - {Boolean} lazy

* - {Function} [preProcess]

* - {Function} [postProcess]

* @constructor

*/

export default function Watcher (vm, expOrFn, cb, options) {

// mix in options

if (options) {

extend(this, options)

}

var isFn = typeof expOrFn === 'function'

this.vm = vm

vm._watchers.push(this)

this.expression = expOrFn

this.cb = cb

this.id = ++uid // uid for batching

this.active = true

this.dirty = this.lazy // for lazy watchers

this.deps = []

this.newDeps = []

this.depIds = Object.create(null)

this.newDepIds = null

this.prevError = null // for async error stacks

// parse expression for getter/setter

if (isFn) {

this.getter = expOrFn

this.setter = undefined

} else {

warn('vue-lite only supports watching functions.')

}

this.value = this.lazy

? undefined

: this.get()

// state for avoiding false triggers for deep and Array

// watchers during vm._digest()

this.queued = this.shallow = false

}

/**

* Prepare for dependency collection.

*/

Watcher.prototype.beforeGet = function () {

Dep.target = this

this.newDepIds = Object.create(null)

this.newDeps.length = 0

}

/**

* Add a dependency to this directive.

*

* @param {Dep} dep

*/

Watcher.prototype.addDep = function (dep) {

var id = dep.id

if (!this.newDepIds[id]) {

this.newDepIds[id] = true

this.newDeps.push(dep)

if (!this.depIds[id]) {

dep.addSub(this)

}

}

}

五、utils 工具集

这个简直就是js的工具箱宝库,什么工具类都有。判断IE,判断是否是数组,定义defineProperty,debounce 输入延迟触发;

六、惊艳的写法

其实阅读源码另外有一个很有意思的地方,就是有些语法你会,但你没看到别人这么用,你可能永远或者很长一段时间都不会这么地用,也就是惊艳!!!

将类型判断与定义放在一起

(this.vms || (this.vms = [])).push(vm) 

相当于

一个创建了一个prototype拥有所有Array.prototype的object。

const arrayProto = Array.prototype

export const arrayMethods = Object.create(arrayProto)

创建一个原型为空的对象

const cache = Object.create(null)

如果hit没有值,则返回gennerate之后的值。

const hit = cache[html]

return hit || (cache[html] = generate(parse(html)))

以上是 vue 阅读一【待完结】 的全部内容, 来源链接: utcz.com/z/376266.html

回到顶部