项目结构:
kvue01.js
//数组响应式
//替换数组中原型中的七个方法
const orginalProto = Array.prototype;
//Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
const arrayProto = Object.create(orginalProto); //这里是备份一份数组的原型
// console.log(arrayProto);//arrayProto 的__proto__ 是Array.prototype,且arrayProto 是一个对象
['push', 'pop', 'shift', 'unshift'].forEach(method => {
arrayProto[method] = function() {
//原始操作
//方法覆盖
orginalProto[method].apply(this, arguments);
console.log(method)
}
});
//属性拦截:defineProperty()
//闭包:
// function makeFunc() {
// var name = "Mozilla";
// function displayName() {
// alert(name);
// }
// return displayName;
// }
// var myFunc = makeFunc();
// myFunc();
// 第一眼看上去,也许不能直观地看出这段代码能够正常运行。
// 在一些编程语言中,一个函数中的局部变量仅存在于此函数的执行期间。
// 一旦 makeFunc() 执行完毕,你可能会认为 name 变量将不能再被访问。
// 然而,因为代码仍按预期运行,所以在 JavaScript 中情况显然与此不同。
// 原因在于,JavaScript中的函数会形成了闭包。
// 闭包是由函数以及声明该函数的词法环境组合而成的。
// 该环境包含了这个闭包创建时作用域内的任何局部变量。
// 在本例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用。
// displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。
// 因此,当 myFunc 被调用时,变量 name 仍然可用,其值 Mozilla 就被传递到alert中。
function defineReactive(obj, key, val) {
//递归,看key是否是一个对象,如果是一个对象,会接着调用defineReactive,
// 如果不是直接return,调用defineReactive,再次看当前key是否是一个对象
observe(val);
//这里用了闭包
//val通过get方法和set方法暴露出去
Object.defineProperty(obj, key, {
get() {
console.log('get', key)
return val;
},
set(newval) {
console.log('set', key)
if (newval !== val) {
//这里是说boj本身有的属性,是对象,
// 然后更新这个对象的值,值变成其他值,这个值也是对象,
// 在这里进行响应式处理,对这个新的值
observe(newval);
val = newval;
// update();
}
}
})
}
function set(obj, key, val) {
defineReactive(obj, key, val)
}
let obj = {
'foo': 'foo',
'bar': 'bar',
'a': {
'n': 1
},
arr: []
};
// defineReactive(obj,'foo','foo1');
// obj.foo = 'foo02';
//实现new Vue({data{}})的响应式数据处理
function observe(obj) {
if (typeof obj !== 'object') {
return;
}
if (Array.isArray(obj)) {
//覆盖原型,替换7个变更操作
obj.__proto__ = arrayProto;
Object.keys(obj).forEach(item => {
observe(item);
})
} else {
//Object.keys() 返回一个由一个给定对象的自身可枚举属性组成的数组
//forEach() 为每个数组元素执行一次提供的函数
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
})
}
}
observe(obj);
function update() {
console.log(foo) //这里直接写 foo 可以获取到 id 为 foo 的 dom 元素
app.innerText = obj.foo;
}
// setInterval(function(){
// obj.foo = new Date().toLocaleTimeString();
// },1000)
// defineReactive(obj,'bar','bar1');
// obj.foo;
// obj.foo = 'foo02';
// obj.a.n;
// obj.a = {'s':2};//重新对 a 赋值,且对这个新值进行响应式处理
// obj.a.s;
//对于新定义的值
//不能直接 obj.b = { 'w':3 } 这样定义
//这样会逃脱初始化的响应式处理
//所以需要像vue里的$set这样进行新加值
// set(obj,'b',{'w':3});
// obj.b;
// obj.b.w;
// obj.arr.push(4);
// set(obj,'arr1',[4]);
// obj.arr1;
// console.log(typeof [1,2,3]);
// typeof 返回的值:undefined,boolean,string,number,object(null算object),function
//如果把对象进行响应式处理对话
//需要覆盖扩展这七个改变数组的方法
//push 尾部插入
//pop 头部插入
//shift 头部删除
//unshift 尾部删除
//splice(index,howmany,item1,item2,...) 添加/删除项目
//sort 排序
//reverse 翻转数组
//node运行js文件
//在该文件下打开终端
//保证安装了node后
//输入 node 文件名
//可以带后缀名,也可以不带后缀名
kvue02.js
function defineReactive(obj, key, val) {
//拦截的是Kvue data里的属性
//递归,看key是否是一个对象,如果是一个对象,会接着调用defineReactive,
// 如果不是直接return,调用defineReactive,再次看当前key是否是一个对象
observe(val);
//创建dep实例
//因为这里是闭包环境,所以每一个key被拦截到,就会创建一个dep实例,key和dep实例是一一对应的
//Kvue data里的每一个数据有一个dep对象
//在下面,当要set数据时,可以直接拿相对应当dep实例去执行更新函数
const dep = new Dep();
//这里用了闭包
//val通过get方法和set方法暴露出去
Object.defineProperty(obj, key, {
get() {
console.log('get', key);
//当创建一个watcher实例时,保存一个Dep.target
//Dep.target 保存的是watcher实例
//下面读取key时,触发了这里的get
//判断这里的target存不存在,如果存在
//把当前的watcher实例add到当前的Dep中
Dep.target && dep.addDep(Dep.target);
return val;
},
set(newval) {
console.log('set', key)
if (newval !== val) {
//这里是说boj本身有的属性,是对象,
// 然后更新这个对象的值,值变成其他值,这个值也是对象,
// 在这里进行响应式处理,对这个新的值
observe(newval);
val = newval;
//set 数据的时候,去执行dep的更新函数
dep.notify();
}
}
})
}
function observe(obj) {
if (typeof obj !== 'object') {
return;
}
new Observer(obj); //交给观察者,判断数据类型,并根据不同类型做不同对响应式操作
}
function proxy(vm) {
Object.keys(vm.$data).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm.$data[key];
},
set(v) {
vm.$data[key] = v;
}
})
})
}
//Observer:观察者,作用是判断是对象还是数组,来做不同的响应式操作
class Observer {
constructor(val) {
if (Array.isArray(val)) {
//to do something
} else {
//object
this.walk(val); //进行遍历数据,并且对其做响应式处理
}
}
walk(obj) {
//Object.keys() 返回一个由一个给定对象的自身可枚举属性组成的数组
//forEach() 为每个数组元素执行一次提供的函数
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
})
}
}
class Compile {
constructor(el, vm) {
this.$vm = vm;
//类似 Jquery 的元素获取方法,不需要额外的 Jquery 来支持
//指定一个或多个匹配元素的 CSS 选择器。
// 可以使用它们的 id, 类, 类型, 属性, 属性值等来选取元素。
// 对于多个选择器,使用逗号隔开,返回第一个匹配的元素。
this.$el = document.querySelector(el);
//执行编译
this.compile(this.$el);
}
compile(el) {
// childNodes 元素的子节点集合
el.childNodes.forEach(node => {
if (node.nodeType === 1) {
console.log('编译元素')
//是一个 element
this.compileElement(node);
//遍历特性
//递归循环整棵DOM树
if (node.childNodes.length > 0) {
this.compile(node)
}
} else if (this.isInter(node)) {
console.log('编译文本')
this.compileText(node);
}
})
}
isInter(node) { //判断是一个插值 {{ XXX }}
//node.nodeType === 3 文本节点
// /\{\{(.*)\}\}/ 小括号分组使用,可以用 $1 来拿小括号里的东西
// . 匹配任何字符 * 匹配前面的表达式零次或多次
return node.nodeType === 3 && /^\{\{([\s\S]*)\}\}$/.test(node.textContent)
}
compileText(node) {
//RegExp.$1 取的是上面小括号里面的东西
//指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串
//总共可以有99个匹配
//this.$vm 指的是 KVue 实例,KVue 是一个对象
//this.$vm[RegExp.$1] 获取的是 count 的值
// node.textContent = this.$vm[RegExp.$1.trim()];
this.update(node, RegExp.$1, 'text')
}
compileElement(node) {
//属性的获取
//attributes 属性返回指定节点的属性集合,即 NamedNodeMap。
//是一个对象
const attrs = node.attributes;
//Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。
Array.from(attrs).forEach(a => {
//例: k-text='number'
const attrName = a.name; //k-text
const exp = a.value; //number
//startsWith() 方法用于检测字符串是否以指定的前缀开始。
if (attrName.startsWith('k-')) {
//substring() 方法用于提取字符串中介于两个指定下标之间的字符。
//一个新的字符串,
// 该字符串值包含 start 处到 stop-1 处的所有字符,其长度为 stop 减 start。
const dir = attrName.substring(2);
//如果 this[dir] 存在 运行 this[dir](node,exp)
this[dir] && this[dir](node, exp);
}
//事件处理
//@click = 'add'
if (this.isEvent(attrName)) {
const dir = attrName.substring(1); //click
this.eventHandler(node, exp, dir);
}
})
// console.log( attrs)
}
isEvent(attrName) {
return attrName.indexOf('@') == 0
}
eventHandler(node, exp, dir) {
const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp];
node.addEventListener(dir, fn.bind(this.$vm))
}
text(node, exp) {
// node.textContent = this.$vm[exp]
this.update(node, exp, 'text')
}
textUpdater(node, val) {
node.textContent = val;
}
html(node, exp) {
// node.innerHTML = this.$vm[exp]
this.update(node, exp, 'html')
}
htmlUpdater(node, val) {
node.innerHTML = val;
}
model(node, exp) {
//赋值并更新data里的那个数据
this.update(node, exp, 'model');
//事件监听,监听到发生了input的value发生了变化,就要把新的value赋值给data里的相应的数据
node.addEventListener('input', (e) => {
// console.log(this);//箭头函数的this指向定义该函数对象,默认使用父级的this,指向上下文的this
this.$vm[exp] = e.target.value;
})
}
modelUpdater(node, val) {
node.value = val;
}
update(node, exp, dir) {
//这里这样获取,会丢失this指向
const fn = this[dir + 'Updater'];
fn && fn(node, this.$vm[exp.trim()]);
//watcher 的创建:出现一个动态的数据,就需要一个Watcher
//在编译的时候,会遍历dom树,发现插值和指令,插值是动态数据,指令上也会有动态的数据
//都需要创建一个watcher实例
new Watcher(this.$vm, exp, function(val) {
fn && fn(node, val);
})
}
}
//依赖收集
//例如:
// <p>{{ name1 }}</p>
// <p>{{ name2 }}</p>
// <p>{{ name1 }}</p>
//这里将会有三个更新函数,分别对应三个值:name1,name2,name1
//也会有三个watcher函数,watcher1,watcher2,watcher3,
//watcher1,watcher3就被放进dep1里
//一个watcher2会被放进一个dep2里
//这里当dep是指管家,当name1,name2,发生数据改变,
// 就会由dep1,dep2,告诉手下的watcher1,watcher2,watcher3,要去更新视图了
class Dep {
constructor() {
//存储所有的watcher
this.deps = [];
}
addDep(watcher) {
this.deps.push(watcher)
}
notify() {
this.deps.forEach(watcher => {
watcher.update()
})
}
}
//界面中出现一个动态当值,就需要创建一个watcher
class Watcher {
constructor(vm, key, fn) {
this.vm = vm;
this.key = key;
this.fn = fn;
//触发依赖收集
Dep.target = this; //保存当前实例
this.vm[this.key]; //读取key,会触发getter
Dep.target = null; //释放target
}
update() {
this.fn.call(this.vm, this.vm[this.key])
}
}
//Kvue 类
class KVue {
constructor(option) {
this.$options = option;
this.$data = option.data;
//把数据做响应式处理
observe(this.$data);
//代理:用户可以直接通过KVue实例直接访问data里的响应式数据数据
proxy(this);
//编译模版
new Compile(option.el, this);
}
}
//发布-订阅者模式它定义对象间的一种一对多的依赖关系,
// 当一个对象的状态发生改变时,所有依赖于它的值都将得到通知
kvue02.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<p>{{count}}</p>
<div class="div1" style="width: 50px;height: 50px;background: #fc3605;"></div>
<p k-text="count"></p>
<p k-html="desc"></p>
<p @click="add">{{number1}}</p>
<input type="text" k-model="number1" />
</div>
<script src="kvue/kvue02.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
const app = new KVue({
el: '#app',
data: {
count: 0,
count2: 2,
desc: '<span>我是span标签</span>',
number: 0,
number1: '我是number1'
},
methods: {
add() {
this.number++;
}
}
})
// setInterval(()=>{
// app.count++
// },1000)
</script>
</body>
</html>
vue数据响应式处理的图解