简单vue2双向数据绑定代码
,支持解析{{}},v-bind,v-on指令
,支持数据监听,mounted钩子回调和methods方法
调用
let iswatch=false;//页面初始加载不执行监听
function DataBinginit(options) { /*option:主调用函数传回来的参数对象*/
let self = this;
this.data = options.data(); /*data对象集合*/
this.methods = options.methods; /*methods:方法集合*/
this.watchs = options.watchs; /*watchs:监听对象集合*/
Object.keys(this.data).forEach(function(key) { /*遍历每一个属性*/
self.proxyKeys(key); /*创建访问或修改data中的每一个属性的服务*/
});
observe(this.data); /*创建记录数据变化的服务*/
new Compile(options.el, this); //模板编译
iswatch=true;
options.mounted.call(this); // 所有事情处理好后执行mounted函数
}
DataBinginit.prototype = {
proxyKeys: function(key) {
let self = this;
//Object.defineProperty(obj, prop, descriptor)
//obj: 需要被操作的目标对象
//prop: 目标对象需要定义或修改的属性的名称
//descriptor: 将被定义或修改的属性的描述符
Object.defineProperty(this, key, {
enumerable: false, //表示该属性是否可枚举,即是否通过for-in循环或Object.keys()返回属性,如果直接使用字面量定义对象,默认值为true
configurable: true, //表示能否通过delete删除此属性,能否修改属性的特性,或能否修改把属性修改为访问器属性,如果直接使用字面量定义对象,默认值为true delete:
/*Writable:false,*/ //当writable为false(并且configrubale为true),[[value]]可以通过defineeProperty修改, 但不能直接赋值修改
get: function getter() { //一个给属性提供 getter 的方法(访问对象属性时调用的函数,返回值就是当前属性的值),如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined
return self.data[key];
},
set: function setter(newVal) { //一个给属性提供 setter 的方法(给对象属性设置值时调用的函数),如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined
self.data[key] = newVal;
}
});
}
}
function Observer(data) {
this.data = data; /*data*/
this.walk(data);
}
Observer.prototype = {
walk: function(data) {
let self = this;
Object.keys(data).forEach(function(key) {
self.defineReactive(data, key, data[key]); //访问或修改data中的每一个属性
});
},
defineReactive: function(data, key, val) {
let dep = new Dep();
let childObj = observe(val);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function getter() { //缓存实体,new出来的 dep数组用来保存数据
if (Dep.target) {
dep.addSub(Dep.target);
}
return val;
},
set: function setter(newVal) { /*更新缓存里的值(重新获取)*/
if (newVal === val) {
return;
}
val = newVal;
dep.notify();
}
});
}
};
function observe(value, vm) {
if (!value || typeof value !== 'object') {
return;
}
return new Observer(value);
};
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub); /*缓存*/
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update(); /*缓存更新*/
});
}
};
/*编译展现*/
function Compile(el, vm) {
this.vm = vm;
this.el = document.querySelector(el); /*当前模板dom对象*/
this.fragment = null;
this.init();
}
Compile.prototype = {
init: function() {
if (this.el) {
this.fragment = this.nodeToFragment(this.el); //创建虚拟dom
this.compileElement(this.fragment);
this.el.appendChild(this.fragment);
} else {
console.log('Dom元素不存在');
}
},
nodeToFragment: function(el) {
let fragment = document.createDocumentFragment();
let child = el.firstChild;
while (child) {
// 将Dom元素移入fragment中
fragment.appendChild(child);
child = el.firstChild
}
return fragment;
},
compileElement: function(el) {
let childNodes = el.childNodes;
let self = this;
[].slice.call(childNodes).forEach(function(node) {
let reg = /\{\{(.*)\}\}/; //用来判定是否为 {{*}} ;
let text = node.textContent; //当前node(选中dom)的文本内容(就是要显示的文本等)
if (self.isElementNode(node)) { //dom
self.compile(node);
} else if (self.isTextNode(node) && reg.test(text)) { //文本
self.compileText(node, reg.exec(text)[1]);
}
if (node.childNodes && node.childNodes.length) { //递归调用
self.compileElement(node);
}
});
},
compile: function(node) {
let nodeAttrs = node.attributes; //用户属性集合
let self = this;
Array.prototype.forEach.call(nodeAttrs, function(attr) {
let attrName = attr.name;
if (self.isDirective(attrName)) { /*过滤用户属性保留V-指令*/
let exp = attr.value; //获取 用户v-指令的值
let dir = attrName.substring(2);
if (self.isEventDirective(dir)) { // 是否为事件指令
self.compileEvent(node, self.vm, exp, dir); //执行对应事件
}else if(self.isattrDirective(dir)){
dir=dir.substring(6);
self.compileAttr(node, self.vm, exp, dir); /*v-model:属性 数据绑定编译处理*/
}else { // 为v-model 指令
self.compileModel(node, self.vm, exp, dir); /*v-model 数据绑定编译处理*/
}
node.removeAttribute(attrName);
}
});
},
compileText: function(node, exp) {
let self = this;
let initText = this.vm[exp];
this.updateText(node, initText);
new Watcher(this.vm, exp, function(value) {
self.updateText(node, value);
self.compilewatch(node, self.vm, exp);
});
},
compileEvent: function(node, vm, exp, dir) {/*事件*/
let self = this;
let eventType = dir.split(':')[1];
let ev=self.getargs(exp);
let args='';
if(ev){
args=ev;
let zk=exp.indexOf('(');
exp=exp.substr(0,zk)
}
let cb = vm.methods && vm.methods[exp];
if (eventType && cb) {
node.addEventListener(eventType,cb.bind(vm,args), true);
//node.addEventListener(eventType, cb.apply(vm,args), false);
//node.addEventListener(eventType,cb.call(vm,'name','age'), false);
}
},
getargs:function(exp){
let reg = /\((.*)\)/;
let args;
if(reg.test(exp)){
args=reg.exec(exp)[1].split(",");
let reg1 = /\"(.*)\"/,
reg2 = /\'(.*)\'/;
for(let i in args){
if(reg1.test(args[i])){args[i]=reg1.exec(args[i])[1]}
if(reg2.test(args[i])){args[i]=reg2.exec(args[i])[1]}
}
}
return args;
},
compilewatch:function(node, vm, exp) {/*监听值改变*/
let wt = vm.watchs && vm.watchs[exp];
if (wt&&iswatch) {
wt.call(vm);
}
},
compileModel: function(node, vm, exp, dir) {
let self = this;
let val = this.vm[exp];
this.modelUpdater(node, val,"value"); /*数据赋给dom*/
new Watcher(this.vm, exp, function(value) {
self.modelUpdater(node, value,"value");
self.compilewatch(node, self.vm, exp);
});
node.addEventListener('input', function(e) { //dom 值赋给数据
let newValue = e.target.value;
if (val === newValue) {
return;
}
self.vm[exp] = newValue; //复制
val = newValue;
});
},
compileAttr: function(node, vm, exp, dir) {
let self = this;
let val = this.vm[exp];
this.modelUpdater(node, val,dir); /*数据赋给dom*/
new Watcher(this.vm, exp, function(value) {
self.modelUpdater(node, value,dir);
self.compilewatch(node, self.vm, exp);
});
},
updateText: function(node, value) {
node.textContent = typeof value == 'undefined' ? '' : value; //更新dom {{*}}的值
},
modelUpdater: function(node, value, attr) {
node[attr] = typeof value == 'undefined' ? '' : value; //更新 attr属性的值
},
isDirective: function(attr) {
return attr.indexOf('v-') == 0;//指令
},
isEventDirective: function(dir) {
return dir.indexOf('on:') === 0;//事件
},
isattrDirective: function(dir) {
return dir.indexOf(':') === 4||dir.indexOf(':') === 5&&dir.indexOf('on:') === -1;//属性
},
isElementNode: function(node) {
return node.nodeType == 1;
},
isTextNode: function(node) {
return node.nodeType == 3;
}
}
/*数据缓存*/
Dep.target = null; /*用来缓存遍历当前this*/
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get(); // 获取缓存的当前属性的值
}
Watcher.prototype = {
update: function() {
this.run(); /*更新值*/
},
run: function() {
let value = this.vm.data[this.exp]; /*获取实时的值*/
let oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function() {
Dep.target = this; // 缓存自己
let value = this.vm.data[this.exp] // 从缓存里获取当前属性的值
Dep.target = null; // 释放自己
return value;
}
};
return new DataBinginit(dataoptions);
}
复制代码
- 扩展
Date.prototype.format
支持Date.format格式化date
调用代码
let me = this;
let date = new Date();
return date.format("当前时间为:YYYY-MM-DD,星期W,为第Q季度,时间为:hh:mm:ss:c");
}
Date.prototype.format = function (format) {
//new Date( year, month, date, hrs, min, sec)
//new Date() //参数可以为整数 也可以为字符串 但格式必须正确 example: new Date(2009,1,1) //正确 new Date("2009/1/1") //正确
//example new Date().format( "当前日期为:YYYY-MM-DD,星期W,为第Q季度,时间为:hh:mm:ss:c")
let o = {
"Y+": this.getFullYear() + '',
"M+": this.getMonth() + 1, //month MM
"D+": this.getDate(), //day DD
"h+": this.getHours(), //hour hh
"m+": this.getMinutes(), //minute mm
"s+": this.getSeconds(), //second ss
"Q+": Math.floor((this.getMonth() + 3) / 3), //quarter 季度 q
"c+": this.getMilliseconds(), //millisecond 毫秒 c
"W": ['一', '二', '三', '四', '五', '六', '日'][this.getDay() - 1] //week 星期
}
for (let k in o) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr((("" + o[k]).length >= 2 ? 2 : ("" + o[k]).length)))
}
}
return format
}
复制代码
- 页面调用
<html lang="cn">
<head>
<meta charset="UTF-8">
<title>双向数据绑定原理</title>
</head>
<style>
#app {
text-align: center;
}
</style>
<body>
<div id="app">
<h2>{{title}}</h2>
<!--<input v-model="name">-->
<input id="input" v-model="name" placeholder="请输入" v-bind:name="attrname"></input>
<h1>{{name}}</h1>
<h1>{{name1}}</h1>
<h1>当前时间为<span>{{date}}</span></h1>
<button v-on:click="clickMe('1','2')">click me 来清空input输入框值!</button>
<button v-on:click="getval">获取input输入框值!</button>
<button v-on:click="clickto">click me try!</button>
</div>
</body>
<script src="https://juejin.cn/post/index.js"></script>
<script type="text/javascript">
new DataBing({
el: '#app',
data(){
return{
title: 'hello world!',
name: '333',
name1: '哈哈哈',
attrname: "attrname",
date: getNowDateStr(),
}
},
methods: {
clickMe: function (c1,c2) {
var ev = window.event;
this.name ='';
this.attrname = 'attrname';
console.log(ev,c1,c2)
},
clickto:function () {
this.name = 'hello world';
this.attrname = '测试'+getNowDateStr();
},
getval:function(){
alert(this.name);
}
},
watchs: {
name:function(){
console.log(getNowDateStr()+'输入框的值为 : '+this.name);
},
attrname:function(){
console.log('输入框的name为:'+ this.attrname);
}
},
mounted: function () {
window.setInterval(() => {
this.date=getNowDateStr();
}, 1000);
}
});
</script>
</html>
复制代码
- vue3
proxy
语法详解,可以借助proxy自行实现一个简单的双向数据绑定
// Proxy 用于修改某些操作的默认行为, 等同于在语言层面做出修改, 所以属于一种“ 元编程”( meta programming), 即对编程语言进行编程。
// Proxy 可以理解成, 在目标对象之前架设一层“ 拦截”, 外界对该对象的访问, 都必须先通过这层拦截, 因此提供了一种机制, 可以对外界的访问进行过滤和改写。 Proxy 这个词的原意是代理, 用在这里表示由它来“ 代理” 某些操作, 可以译为“ 代理器”。
// Proxy 支持的拦截操作一览, 一共 13 种。
// get(target, propKey, receiver): 拦截对象属性的读取, 比如proxy.foo和proxy['foo']。
// set(target, propKey, value, receiver): 拦截对象属性的设置, 比如proxy.foo = v或proxy['foo'] = v, 返回一个布尔值。
// has(target, propKey): 拦截propKey in proxy的操作, 返回一个布尔值。
// deleteProperty(target, propKey): 拦截delete proxy[propKey] 的操作, 返回一个布尔值。
// ownKeys(target): 拦截Object.getOwnPropertyNames(proxy)、 Object.getOwnPropertySymbols(proxy)、 Object.keys(proxy)、 for...in循环, 返回一个数组。 该方法返回目标对象所有自身的属性的属性名, 而Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。
// getOwnPropertyDescriptor(target, propKey): 拦截Object.getOwnPropertyDescriptor(proxy, propKey), 返回属性的描述对象。
// defineProperty(target, propKey, propDesc) : 拦截Object.defineProperty(proxy, propKey, propDesc)、 Object.defineProperties(proxy, propDescs) , 返回一个布尔值。
// preventExtensions(target) : 拦截Object.preventExtensions(proxy) , 返回一个布尔值。
// getPrototypeOf(target) : 拦截Object.getPrototypeOf(proxy) , 返回一个对象。
// isExtensible(target) : 拦截Object.isExtensible(proxy) , 返回一个布尔值。
// setPrototypeOf(target, proto) : 拦截Object.setPrototypeOf(proxy, proto) , 返回一个布尔值。 如果目标对象是函数, 那么还有两种额外操作可以拦截。
// apply(target, object, args) : 拦截 Proxy 实例作为函数调用的操作, 比如proxy(...args) 、 proxy.call(object, ...args) 、 proxy.apply(...) 。
// construct(target, args) : 拦截 Proxy 实例作为构造函数调用的操作, 比如new proxy(...args) 。
// 技巧: 一个技巧是将 Proxy 对象, 设置到object.proxy属性, 从而可以在object对象上调用。
var object = { proxy: new Proxy(target, handler) };
var handler = {
get: function(target, name) {
console.log("get")
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
set: function(target, thisBinding, args) {
console.log("set")
return 20;
},
apply: function(target, thisBinding, args) {
console.log("apply")
return args[0];
},
construct: function(target, args) {
console.log("construct")
return { value: args[1] };
}
};
var fproxy = new Proxy({}, handler);
var fproxy2 = new Proxy(function(x, y) {
return x + y;
}, handler);
let obj = Object.create(proxy);
fproxy2(1, 2)
// 虽然 Proxy 可以代理针对目标对象的访问, 但它不是目标对象的透明代理, 即不做任何拦截的情况下, 也无法保证与目标对象的行为一致。 主要原因就是在 Proxy 代理的情况下, 目标对象内部的this关键字会指向 Proxy 代理。
// 有些原生对象的内部属性, 只有通过正确的this才能拿到, 所以 Proxy 也无法代理这些原生对象的属性。这时, this绑定原始对象, 就可以解决这个问题。
const target = new Date('2015-01-01');
const handler = {
get(target, prop) {
if (prop === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, prop);
}
};
const proxy = new Proxy(target, handler);
proxy.getDate() // 1
// ES5 提供了 Object.defineProperty 方法, 该方法可以在一个对象上定义一个新属性, 或者修改一个对象的现有属性, 并返回这个对象。
// Object.defineProperty(obj, prop, descriptor)
// 参数:
// obj: 要在其上定义属性的对象。
// prop: 要定义或修改的属性的名称。
// descriptor: 将被定义或修改的属性的描述符。
// 下面一个简单的输入框变化, 数据展示
var obj = {
value: ''
}
var value = '';
Object.defineProperty(obj, "value", {
get: function() {
return value
},
set: function(newVal) {
value = newVal
}
})
document.querySelector('#input').oninput = function() {
var value = this.value;
obj.value = value;
document.querySelector('#text').innerHTML = obj.value;
}
// 当然一般不会只是改变一个属性, 如下所示, 遍历劫持对象所有的属性
//要劫持的对象
const data = {
name: ''
}
//遍历对象,对其属性值进行劫持
Object.keys(data).forEach(function(key) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
console.log('get');
},
set: function(newVal) {
// 当属性值发生变化时我们可以进行额外操作
console.log(`我修改了成了${newVal}`);
},
})
})
data.name = 'gg'
// 这个只是简单的劫持
// 缺点: Object.defineProperty的第一个缺陷, 无法监听数组变化
// 区别:
// Proxy可以直接监听对象而非属性
// Proxy直接可以劫持整个对象, 并返回一个新对象, 不管是操作便利程度还是底层功能上都远强于Object.defineProperty。
// Proxy可以直接监听数组的变化
// Proxy有多达13种拦截方法, 不限于apply、 ownKeys、 deleteProperty、 has等等是Object.defineProperty不具备的。
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END