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 阅读一【待完结】的更多相关文章
- Vue小说阅读器(仿追书神器)
一个vue阅读器项目,目前已升级到2.0,阅读器支持横向分页并滑动翻页(没有动画,需要动画的可以自己设置,增加transitionDuration即可) 技术栈 vue全家桶+mint-ui gith ...
- 2.VUE前端框架学习记录二
VUE前端框架学习记录二:Vue核心基础2(完结)文字信息没办法描述清楚,主要看编码实战里面,有附带有一个完整可用的Html页面,有需要的同学到脑图里面自取.脑图地址http://naotu.baid ...
- 使用 Vue 和 epub.js 制作电子书阅读器
ePub 简介 ePub 是一种电子书的标准格式,平时我看的电子书大部分是这种格式.在手机上我一般用"多看"阅读 ePub 电子书,在 Windows 上找不到用起来比较顺心的软件 ...
- 【一套代码小程序&Native&Web阶段总结篇】可以这样阅读Vue源码
前言 前面我们对微信小程序进行了研究:[微信小程序项目实践总结]30分钟从陌生到熟悉 在实际代码过程中我们发现,我们可能又要做H5站又要做小程序同时还要做个APP,这里会造成很大的资源浪费,如果设定一 ...
- 用Vue.js搭建一个小说阅读网站
目录 1.简介 2.如何使用vue.js 3.部署api服务器 4.vue.js路由配置 5.实现页面加载数据 6.测试vue项目 7.在正式环境部署 8.Vue前端代码下载 1.简介 这是一个使用v ...
- vue 源码阅读记录
0.webpack默认引入的是vue.runtime.common.js,并不是vue.js,功能有略微差别,不影响使用 1.阅读由ts编译后的js: 入口>构造函数 >定义各类方法 &g ...
- 跟我一起做一个vue的小项目(APPvue2.5完结篇)
先放一下这个完结项目的整体效果 下面跟我我一起进行下面项目的进行吧~~~ 接下来我们进行的是实现header的渐隐渐显效果,并且点击返回要回到首页 我们先看效果 在处理详情页向下移动过程中,heade ...
- VUE实现Studio管理后台(完结):标签式输入、名值对输入、对话框(modal dialog)
一周的时间,几乎每天都要工作十几个小时,敲代码+写作文,界面原型算是完成了,下一步是写内核的HTML处理引擎,纯JS实现.本次实战展示告一段落,等RXEditor下一个版本完成,再继续分享吧.剩下的功 ...
- 阅读vue源码-----内置组件篇(keep-alive)
1.前言: <keep-alive>是vue实现的一个内置组件,也就是说vue源码不仅实现了一套组件化的机制,也实现了一些内置组件. <keep-alive>官网介绍如下:&l ...
随机推荐
- IDC 知识库
http://www.51idc.com/help/supportHelp.html Host key verification failed解决IIS错误信息--另一个程序正在使用此文件,进程无法访 ...
- GNU与Linux
GNU是自由软件之父 Richard Stallman在1984年组织开发的一个完全基于自由软件的软件体系,与此相应的有一分通用公共许可证(General Public License,简称GPL). ...
- webapp 里主要的 mate 用途
一.meta 标签分两大部分:HTTP 标题信息(http-equiv)和页面描述信息(name). 1.http-equiv 属性的 Content-Type 值(显示字符集的设定) 说明:设定页面 ...
- info.plist
更新了Xcode8 以及 iOS10,App访问用户的相机.相册.麦克风.通讯录的权限都需要重新进行相关的配置,不然在Xcode8中打开编译的话会直接crash. 需要在info.plist中添加Ap ...
- 公钥私钥与SSL的握手协议(转)
一,公钥私钥1,公钥和私钥成对出现2,公开的密钥叫公钥,只有自己知道的叫私钥3,用公钥加密的数据只有对应的私钥可以解密4,用私钥加密的数据只有对应的公钥可以解密5,如果可以用公钥解密,则必然是对应的私 ...
- 老男孩Linux.shell.RHCE运维初中高级50G附解压密码
学习Linux,好的教程.使学习事半功倍! 老男孩Linux.shell.RHCE运维初中高级 下载地址: http://pan.baidu.com/s/1hsQOb2W 密码: h4hs 解压密码: ...
- 初学者的React全家桶完整实例
概述 该项目还有些功能在开发过程中,如果您有什么需求,欢迎您与我联系.我希望能够通过这个项目对React初学者,或者Babel/webpack初学者都有一定的帮助.我在此再强调一下,在我写的这些文章末 ...
- 非IT人士的云栖酱油之行 (程序猿迷妹的云栖之行)
摘要: 熟悉我的人都知道,我是一个贪玩儿且不学无术的姑娘,对于互联网我也是知之甚少:这次去到杭州参加阿里巴巴集团主办的为期4天的科技大会也是很例外:但是不得不说这次的会议真是让我很震惊.今天我就和大家 ...
- spring注解 di 和 ioc 注解
注解: 1.注解就是为了说明java中的某一个部分的作用(Type) 2.注解都可以用于哪个部门是@Target注解起的作用 3.注解可以标注在ElementType枚举类所指定的位置上 4. @Do ...
- 批量修改Java类文件中引入的package包路径
http://libeey.blogbus.com/logs/101848958.html当复制其他工程中的包到新工程的目录中时,由于包路径不同,出现红叉,下面的类要一个一个修改包路径,类文件太多的话 ...