Vue中,我们访问的数据都是直接通过App.dataName,而不是App.data.dataName,因此我们需要对参数进行代理
// 对data 进行代理
function agency(vm) {
let keys = Object.keys( vm.$data )
for( let i = 0; i < keys.length; i++ ) {
let key = keys[i]
Object.defineProperty(vm, key, {
configurable: true,
enumerable: true,
get() {
return vm.$data[key]
},
set( newVal ) {
vm.$data[key] = newVal
}
})
}
}
发布订阅模式
采取用一种类似预购的模式
- 商家提供一个数据
- 根据需求订购所需商品
- 等待,做其他的事
- 当货到了,查看数据,通知取货
我们在代码中需要使用事件模型:
需要一个 event对象
- on 类似预定货物
- off 类似取消预定
- emit 类似通知取货
let event = (function(){
options = []
return {
on: function( type, fn ) {
options[type] = (options[type] || [])
options[type].push(fn)
},
off:function( type, fn ) {
if( arguments.length === 0 ) {
options = []
}else if( arguments.length === 1) {
if( options[type] ) indexof(options[type]) = []
else return
}else {
for( let i = 0; i < options[type].length ; i++ ) {
if( options[type][i] === fn ) {
options[type].splice( i, 1)
}
}
}
},
emit: function( type ) {
let datas = Array.prototype.splice.call( arguments, 1 )
console.log(datas)
for( let i = 0; i < options[type].length ; i++ ) {
options[type][i].apply( this, datas )
}
}
}
}())
Vue中实现的逻辑
DEP
let depId = 0
class Dep {
constructor() {
this.id = depId++
this.sub = []
}
addSub( target ) {
this.sub.push( target )
}
depend() {
if( Dep.target ) {
this.addSub( Dep.target )
Dep.target.addDep( this ) //将Dep 和 渲染watcher 关联起来
}
}
removeSub( target ) {
if( this.sub.length ) {
let index = this.sub.indexOf(target)
if( index > -1 ) {
this.sub.splice( index, 1 )
}
}
}
notify() {
let deps = this.sub
let that = this
deps.forEach( function(watcher) {
watcher.update()
that.removeSub(watcher)
});
}
}
Dep.target = null
let targetStack = []
function enterStack( target ) {
targetStack.unshift( Dep.target )
Dep.target = target
}
function popStack() {
targetStack.shift(Dep.target)
}
watcher
/**
* 观察者
*/
let watcherId = 0
class Watcher {
/**
* @param {object} vm PFVue实例
* @param {string|function} expOrFun 如果是渲染watcher 传入 function 如果是计算watcher 传入路径
*/
constructor( vm, expOrFun ) {
this.vm = vm
this.getter = expOrFun
this.id = watcherId++
this.deps = [] // 依赖项
this.depIds = {} // 是一个 Set 类型, 用以保护 依赖项的唯一性
this.get()
}
get() {
enterStack( this )
console.log( typeof this.getter )
if( typeof this.getter === 'function' ) {
this.getter.call( this.vm, this.vm )
}
popStack()
}
run() {
this.get()
}
update() {
this.run()
}
/**将 当前dep 和 watcher 关联 */
addDep(dep) {
this.deps.push( dep )
}
}
其他部分,这一部分前面已经写过了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root">
<div class="aaa">{{name}}</div>
<div>{{age}}</div>
<div>{{gender}}</div>
<div>
<li></li>
<li></li>
<li></li>
</div>
<button onclick="add()">增加</button>
</div>
<script src="./js/Dep.js"></script>
<script src="./js/watcher.js"></script>
<script>
/** 创建Vue类 */
class pfVue {
constructor(obj) {
this.$el = document.querySelector(obj.el)
this.$parent = this.$el.parentNode
this.$data = obj.data
this.mount()
agency(this)
}
}
// Vue的初始化方法
pfVue.prototype.mount = function () {
this.render = this.renderCreater()
this.mountComponent()
}
// 初始化组件
pfVue.prototype.mountComponent = function () {
let mount = function () {// Vue 中使用 watcher 来监控变化
console.log( 'updates')
this.update(this.render())
}
new Watcher( this, mount )
}
/**
* 通过柯里化来存储 Vnode
*/
pfVue.prototype.renderCreater = function () {
let vnode = readNode(this.$el)
return function () {
return complier(vnode, this.$data)
}
}
/** 将虚拟DOM 渲染到页面中: diff算法比较其差别*/
pfVue.prototype.update = function (newVnode) {
observe(this.$data, this)
mountNode.call(this, newVnode)
}
/**
* 对虚拟DOM进行渲染
*/
function complierNode(v) {
let $nodeType = v.nodeType
let node = {}
if ($nodeType === 1) {
node = document.createElement(v.nodeName)
node.nodeType = $nodeType
for (let item in v.data) {
node.setAttribute(item, v.data[item])
}
if (v.childNodes.length > 0) {
let _children = v.childNodes //这里曾少写过一个Let导致递归错误
for (let i = 0; i < _children.length; i++) {
let child = complierNode(_children[i])
node.appendChild(child)
}
}
}
else if ($nodeType === 3) {
node = document.createTextNode(v.nodeValue)
}
return node
}
// 挂载DOM
function mountNode(newVnode) {
let $compliedDOM = complierNode(newVnode)
this.$parent.replaceChild($compliedDOM, this.$el)
this.$el = $compliedDOM
}
// 创建VNode类
// nodeName data content nodeType children
class VNode {
constructor(nodeName, data, nodeValue, nodeType) {
this.nodeName = nodeName && nodeName.toLowerCase();
this.data = data;
this.nodeValue = nodeValue;
this.nodeType = nodeType;
this.childNodes = [];
}
appendChildren(vnode) {
this.childNodes.push(vnode)
}
}
/**
* 把节点转换成虚拟节点
*/
function readNode(node) {
let comNode;
const _nodeType = node.nodeType
if (_nodeType === 1) {
// 元素节点
const _nodeAttrs = node.attributes
const _nodeName = node.nodeName
let data = {}
for (let i = 0; i < _nodeAttrs.length; i++) {
data[_nodeAttrs[i].nodeName] = _nodeAttrs[i].nodeValue
}
comNode = new VNode(_nodeName, data, undefined, _nodeType)
let childNodes = node.childNodes
for (let i = 0; i < childNodes.length; i++) {
comNode.appendChildren((readNode(childNodes[i])))
}
} else if (_nodeType === 3) {
// 文本节点
comNode = new VNode(undefined, undefined, node.nodeValue, _nodeType)
}
return comNode
}
//对存在的Dom进行替换
function complier(template, data) {
// 用于替换{{}}中数据的正则
const ruleForSupport = /\{\{(.+?)\}\}/g
let children = template.childNodes
let _vnode = null
let type = template.nodeType
//对文本标签中的{{}}中的数据替换
if (type == 3) {
let value = template.nodeValue
value = value.replace(ruleForSupport, (_, g) => {
let arr = g.trim().split('.')
let getValueByKeli = createGetValueByPath(arr)
let arrValue = getValueByKeli(data)
return arrValue
})
_vnode = new VNode(template.nodeName, template.data, value, template.nodeType)
} else {
_vnode = new VNode(template.nodeName, template.data, template.nodeValue, template.nodeType)
// 对于非文本标签进行遍历
for (let i = 0; i < children.length; i++) {
_vnode.appendChildren(complier(children[i], data))
}
}
return _vnode
}
// 利用函数的柯里化
function createGetValueByPath(path) {
let res = path
return function (value) {
let prop
while (prop = res.shift()) {
value = value[prop]
}
return value
}
}
function defineReactive(obj, key, value, enumerable) {
let that = this
let dep = new Dep();
//通过传入的value实习闭包 这样set和get方法就无需创建其他的中间变量
Object.defineProperty(obj, key, {
configurable: true,
enumerable: !!enumerable,
set(newValue) {
value = newValue
observe( obj, that )
dep.notify()
typeof that.mountComponent === 'function' && that.mountComponent()
},
get() {//在其中进行通知
console.log(1111)
dep.depend()
return value
}
})
}
/**
* 将对象进行响应式处理
*/
function observe(obj, vm) {
if (Array.isArray(obj)) {
console.log( 1 )
obj.__proto__ = array_methods
for (let i = 0; i < obj.length; i++) {
observe(obj[i], vm)
}
} else {
console.log(typeof obj)
console.log( 2 )
let keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
let prop = keys[i]
defineReactive.call(vm, obj, prop, obj[prop], true)
if( typeof obj[prop] === 'object' )
observe( obj[prop], vm )
}
}
}
// 对data 进行代理
function agency(vm) {
let keys = Object.keys(vm.$data)
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
proxy(vm, '$data', key)
}
}
function proxy(o, prop, key) {
Object.defineProperty(o, key, {
configurable: true,
enumerable: true,
get() {
return o[prop][key]
},
set(newVal) {
o[prop][key] = newVal
}
})
}
// 定义需要重写的方法
let ARRAY_METHODS = [
'push',
'pop',
'splice',
'shift',
'unshift',
'sort',
'reverse'
]
// 对array_methods中方法进行重写赋值
let array_methods = Object.create(Array.prototype)
ARRAY_METHODS.forEach(method => {
array_methods[method] = function () {
for (let i = 0; i < arguments.length; i++) {
observe(arguments[i])
}
let res = Array.prototype[method].apply(this, arguments)
return res
}
})
let app = new pfVue({
el: '#root',
data: {
name: 'pf',
age: 13,
gender: 'man',
father: '11'
},
})
function add() {
app.$data.age = app.$data.age + 1
}
</script>
</body>
</html>
推荐一个发布订阅者模式的分析
https://segmentfault.com/a/1190000016208088