<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="test">
<h1 class="te1">{{name}}</h1>
<h1 class="te2">{{name}}</h1>
<h1 class="te3">{{name}}</h1>
<h1 class="te4">{{test}}</h1>
<h1 class="te5">{{name}}</h1>
<h1 class="te6">{{name}}</h1>
<h1 class="te7">{{name}}</h1>
<h1 class="te8">{{test2}}</h1>
<input v-model="name" />
<input v-model="test" />
<textarea v-model="test2" placeholder="" placeholder-class="textarea-placeholder"></textarea>
</div>
<script type="module">
export class SelfVue {
constructor({ data, el }) {
console.log(data, 'data')
this.data = data
this.el = document.getElementById(el)
this.init(data, this.el, el)
}
init(data, el, id) {
let str = stringIze(el)
//给每个元素都加上特殊的
function addId(str) {
for (let i = 0; i < str.length;) {
let tempStr = str.slice(i)
let inner = /<(.+)>{{(.+)}}<\/.+>/.exec(tempStr)
let inner2 = /<([^/<>]+)>/.exec(tempStr)
if (inner2) {
let id = ' data-v-' + (new Date().getTime() + Math.floor(Math.random() * 1000) + Math.floor(Math.random() * 1000) + Math.floor(Math.random() * 1000))
str = str.slice(0, i) + str.slice(i, i + inner2.index) + inner2[0].slice(0, inner2[1].split(' ')[0].length + 1) + id + tempStr.slice(inner2.index + inner2[1].split(' ')[0].length + 1)
i = i + inner2.index + inner2[0].length + 1 + id.length
} else {
break
}
}
return str
}
str = addId(str)
for (const key in data) {
str = changeText(key, str, data[key])
str = handleModel(key, str, data[key])
defineDeby(key, str, data[key])
}
for (const key2 in data) {
setChange(key2, str, data[key2])
}
function defineDeby(key, str, value, nonee) {
console.log('define', data, key, data[key], data.test, nonee)
Object.defineProperty(data, key, {
set(newVal) {
console.log(key + 'change', newVal, data)
value = newVal
str = stringIze(el)
str = addId(str)
for (const key2 in data) {
str = changeText(key2, str, data[key2])
str = handleModel(key2, str, data[key2])
defineDeby(key2, str, data[key2], 111)
}
for (const key2 in data) {
setChange(key2, str, data[key2])
}
console.log(str, 'str obj')
}, get() {
console.log('read' + key, value)
return value
}
})
}
console.log(str, 'changeStr', document.getElementById(id), document)
//改变v-model变量的值
function handleModel(key, str, value) {
let a = ['input', 'textarea']
if (str.indexOf(`v-model="${key}"`) > -1) {
for (let i = 0; i < str.length;) {
let tempStr = str.slice(i)
if (tempStr.indexOf(key) === -1) break
let inner = /<([a-zA-Z0-9]+)[^/]*(data-v-[0-9]+)[^/]*v-model=\"([a-zA-Z][a-zA-Z0-9]*)\"[^/]*>/.exec(tempStr)
let inner2 = /<([a-zA-Z0-9]+).*(data-v-[0-9]+).*v-model=\"([a-zA-Z][a-zA-Z0-9]*)\".*>/.exec(tempStr)
if ((inner && inner[3] === key && inner[1] === 'textarea')) {
str = str.slice(0, i) + str.slice(i, i + inner.index) + inner[0].slice(0, inner[0].length - 1) + ` value="${value}">` + tempStr.slice(inner[0].length + inner.index)
console.log(str.slice(0, i), '1', str.slice(i, i + inner.index), '2', inner[0].slice(0, inner[0].length - 1), '3', tempStr.slice(inner[0].length + inner.index), '4')
i = i + inner.index + inner[0].length + 1 + ` value="${value}">`.length
} else if ((inner2 && inner2[3] === key && inner2[1] === 'input')) {
str = str.slice(0, i) + str.slice(i, i + inner2.index) + inner2[0].slice(0, inner2[0].length - 1) + ` value="${value}">` + tempStr.slice(inner2[0].length + inner2.index)
console.log(str.slice(0, i), '1', str.slice(i, i + inner2.index), '2', inner2[0].slice(0, inner2[0].length - 1), '3', tempStr.slice(inner2[0].length + inner2.index), '4')
i = i + inner2.index + inner2[0].length + 1 + ` value="${value}">`.length
}
else {
i++
}
}
document.getElementById(id)?.parentNode.removeChild(document.getElementById(id))
document.getElementsByTagName('body')[0].appendChild(parseElement(str))
console.log(str, 'str', document.getElementsByTagName('body'))
}
return str
}
function setChange(key, str, value) {
for (let i = 0; i < str.length;) {
let tempStr = str.slice(i)
if (tempStr.indexOf(key) === -1) break
let inner = /<([a-zA-Z0-9]+)[^/]*(data-v-[0-9]+)[^/]*v-model=\"([a-zA-Z][a-zA-Z0-9]*)\"[^/]*>/.exec(tempStr)
let inner2 = /<([a-zA-Z0-9]+).*(data-v-[0-9]+).*v-model=\"([a-zA-Z][a-zA-Z0-9]*)\".*>/.exec(tempStr)
if ((inner && inner[3] === key && inner[1] === 'textarea')) {
console.log(document.querySelector(`${inner2[1]}[${inner2[2]}]`))
document.querySelector(`${inner[1]}[${inner[2]}]`).value = value
document.querySelector(`${inner[1]}[${inner[2]}]`).addEventListener('change', (e) => {
console.log('key', 'change')
data[key] = e.target.value
})
i = i + inner.index + inner[0].length + 1 + ` value="${value}">`.length
} else if ((inner2 && inner2[3] === key && inner2[1] === 'input')) {
console.log(document.querySelector(`${inner2[1]}[${inner2[2]}]`))
document.querySelector(`${inner2[1]}[${inner2[2]}]`).addEventListener('change', (e) => {
console.log('key', 'change')
data[key] = e.target.value
})
i = i + inner2.index + inner2[0].length + 1 + ` value="${value}">`.length
}
else {
i++
}
}
}
// 改变{{}}为变量值
function changeText(key, str, value) {
if (str.indexOf(key) > -1) {
for (let i = 0; i < str.length;) {
let tempStr = str.slice(i)
if (tempStr.indexOf(key) === -1) break
let inner = /{{(.+)}}/.exec(tempStr)
if (inner && inner[1] === key) {
str = str.slice(0, i) + str.slice(i, i + inner.index) + value + tempStr.slice(inner[0].length + inner.index)
i = i + inner.index + inner[0].length + 1
} else {
i++
}
}
}
document.getElementById(id)?.parentNode.removeChild(document.getElementById(id))
document.getElementsByTagName('body')[0].appendChild(parseElement(str))
return str
}
/*将元素节点类型字符串化*/
function stringIze(obj) {
var o = document.createElement("div");
o.appendChild(obj);
return o.innerHTML;
}
/*字符串解析成元素节点类型*/
function parseElement(str) {
var o = document.createElement("div");
o.innerHTML = str;
return o.childNodes[0];
}
}
}
let selfVue = new SelfVue({ data: { name: '111', test: '1', test2: 'z' }, el: 'test' })
</script>
</body>
</html>
这个是一开始使用的方法,但是考虑到一直销毁元素创建元素,可能不是很好,就用了另外的一种方式
上面的这种方式是将dom转成字符串进行遍历处理;下面的方式是直接遍历dom,拷贝一份dom,在数据变化后,使用拷贝的dom来判断某个元素是否是采用data的数据,需要改变了
下面的方法是 一直在同一个dom上面操作,没有销毁创建等操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<div id="test">
<h1>
<strong>{{text}}</strong>
<strong>{{text.length+name}}</strong>
</h1>
<p>{{name}}</p>
<p @click="changeTest"></p>
<p>{{new Date()}}</p>
<input type="text" v-model="name">
<textarea placeholder="" placeholder-class="textarea-placeholder" type="text" v-model="text"></textarea>
</div>
<body>
<script>
class SelfVue {
constructor({ data, el }) {
this.data = data
this.el = el
this.init()
}
init() {
let children = document.getElementById(this.el).children
//深拷贝出来,保存{{}},v-model之类的
let cloneDocu = clone(document.getElementById(this.el))
mapChildren(children, this, cloneDocu.children)
for (let key in this.data) {
defineObey(this, key, this.data[key])
}
function defineObey(_this, key, value) {
Object.defineProperty(_this.data, key, {
set(newValue) {
console.log(key + 'change', newValue)
value = newValue
mapChildren(children, _this, cloneDocu.children)
},
get() {
return value
}
})
}
function mapChildren(children, _this, cloneDocu) {
let biao = ['TEXTAREA', 'INPUT']
for (let i = 0; i < children.length; i++) {
if (biao.indexOf(children[i].nodeName) > -1 && cloneDocu[i].getAttribute('v-model')) {
let model = cloneDocu[i].getAttribute('v-model')
for (let key in _this.data) {
if (model === key) {
children[i].value = _this.data[key]
children[i].addEventListener('input', (e) => {
if (_this.data[key] !== e.target.value)
_this.data[key] = e.target.value
})
}
}
}
//获取非子节点的文本
var arr = [];
var content = cloneDocu[i];
for (var j = 0, len = content.childNodes.length; j < len; j++) {
if (content.childNodes[j]?.nodeType === 3) { // 通过nodeType是不是文本节点来判断
arr.push(content.childNodes[j].nodeValue);
}
}
var str = arr.join("");
let exec = /{{(.+)}}/.exec(str)
if (exec) {
exec[1] = handleStr(exec[1], _this)
children[i].innerText = eval(exec[1])
}
mapChildren(children[i].children, _this, cloneDocu[i].children)
}
}
function handleStr(str, _this) {
const dataKeys = Object.keys(_this.data)
for (let j = 0; j < str.length;) {
const tempStr = str.slice(j)
let min = []
for (let k = 0; k < dataKeys.length; k++) {
if (tempStr.indexOf(dataKeys[k]) > -1) {
if (tempStr.indexOf(dataKeys[k]) < (min[0] || 999999) && tempStr[tempStr.indexOf(dataKeys[k]) - 1] !== '.') {
min[0] = tempStr.indexOf(dataKeys[k])
min[1] = dataKeys[k]
}
}
}
if (min[0] !== undefined && min[1] !== undefined) {
str = str.slice(0, j) + str.slice(j, j + min[0]) + '_this.data.' + tempStr.slice(min[0])
j = j + min[0] + min[1].length + '_this.data.'.length
} else {
j++
}
}
return str
}
function clone(origin) {
var originNew = origin.cloneNode();
// originNew.innerText = origin.innerText
//进行attr属性的复制过来 同理,想在克隆的时候复制其他同样可以加进来
if (origin.attributes != null) {
for (var index = 0; index < origin.attributes.length; index++) {
var attr = origin.attributes[index];
var name = attr.name;
var value = attr.value;
originNew.setAttribute(name, value);
}
}
//克隆子标签
var originChildNodes = origin.childNodes;
for (var index = 0; index < originChildNodes.length; index++) {
var node = originChildNodes[index];
if (node != null) {
//递归进行子标签的克隆
var childNode = clone(node);
originNew.appendChild(childNode);
}
}
return originNew;
};
}
}
let a = { data: { name: 'xxx', text: 'shh' }, el: 'test' }
let vue = new SelfVue(a)
</script>
</body>
</html>