最终目标:用原生植物js同时实现自订模块,Vue3单向存取
孔布龙科学知识贮备:
必不可少科学知识1,自订原素(customElement)
专业术语太少,Chalancon标识符:
//html:
<user-card data-open=”true”></user-card>
//javascript:
class Learn extends HTMLElement{
constructor(props) {
super(props);
console.log(this.dataset);
this.innerHTML = 这是我自订的原素;
this.style.border = 1px solid #899;
this.style.borderRadius = 3px;
this.style.padding = 4px;
}
}
window.customElements.define(user-card,Learn);
效用:
导出:透过window.customElements方式能建立自订原素,里头的define方式是用以选定自订原素的中文名称,和自订原素相关联的类。
这儿有两个技术细节,自订原素尾端很大会用中截叶分隔,要不然是合宪的。
这时在那个类里头就能表述原素里的大部份文本了,这和Vue里头的模块早已较为类似于了,有了那个此基础后他们再往里头去展开开拓就能同时实现模块了。
必不可少科学知识2,Proxy
这混蛋估算他们都晓得,Vue3统计数据积极响应的核心理念,Vue2用的是Object.defineProperty; 很强悍,较好用,嘿嘿个单纯的标识符:
let obj = {
a:2938,
b:siduhis,
item:name
}
obj = new Proxy(obj,{
set(target, p, value, receiver) {
console.log(窃听到,p,被修正了,由原本的:,target[p],换成了:,value);
}
});
document.onclick = ()=>{
obj.item = newValue;
}
效用:
那个如果深入去讲的话有很多能讲,比如说修正值的时候会触发set方式,读取值的时候会触发get方式等等,具体的他们去看看官网文档会更好。
必不可少科学知识3,事件代理
首先,我利用事件代理去处理模块中的事件,主要是写起来方便,开拓也很方便,嘿嘿看个最单纯版本的事件代理:
//html
<ul class=”list”>
<li class=”item” data-first=”true”>这是第两个</li>
<li class=”item”>2222</li>
<li class=”item”>three</li>
<li class=”item” data-open=”true”>打开</li>
<li class=”item”>这是最后两个</li>
</ul>
//javascript
let list = document.querySelector(.list);
list.onclick = function(ev){
let target = ev.target;
console.log(点击了+target.innerHTML);
}
效用:
这是最单纯版本,在ul身上存取了点击事件,利用事件冒泡原理,点击任何两个li都会触发其父级ul的点击事件,透过ul的事件也能反向找到被精确点击的li原素,从而把相应的li的文本打印出来,怎么样,很单纯吧~
你可能注意到了上面标识符中,有两个li的身上有data自订属性,那个一会有用
再来看两个升级版本,在这儿,能通多判断li身上不同的属性,从而去执行不同的函数,这样的话就有点语法糖的意思了:
let eventfn = function(ev){
let target = ev.target;
let dataset = target.dataset;
for(b in dataset){
if(eventfn[b]){
eventfn[b]({obj:target,parent:this});
}
}
}
eventfn.first = function(){
console.log(点击了第两个,并且传了一些参数, arguments);
}
eventfn.open = function(){
console.log(点击了打开);
}
list.onclick = eventfn;
看那个属性有没有相关联的事件函数,如果有,则执行,并且传递一些参数进去,那个参数以后可能会用到,这是两个开拓点。到这儿,他们事件处理基本就成型了
第一步,建立模块文本
思路分析:1, 文本最好是直接写在页面上,然后需要填统计数据的地方用{{}}包起来2, template标签能用以包裹模板,并且不会被显示在页面上3, 在模块里复制template里的文本作为模块的文本,并且导出里头的{{}}4, 还需要解析里头的各种指令,比如data-open这代表两个open事件
这是效用图 上标识符:
<template id=”userCardTemplate”>
<style>
.image {
width: 100px;
}
.container {
background: #eee;
border-radius: 10px;
width: 500px;
padding: 20px;
}
</style>
<img src=”img/bg_03.png” class=”image”>
<div class=”container”>
<p class=”name” data-open=”true”>{{name}}</p>
<p class=”email”>{{email}}</p>
<input type=”text” v-model=”message”>
<span>{{message}}</span>
<button class=”button”>Follow</button>
</div>
</template>
这是效用图 上标识符:
<template id=”userCardTemplate”>
<style>
.image {
width: 100px;
}
.container {
background: #eee;
border-radius: 10px;
width: 500px;
padding: 20px;
}
</style>
<img src=”img/bg_03.png” class=”image”>
<div class=”container”>
<p class=”name” data-open=”true”>{{name}}</p>
<p class=”email”>{{email}}</p>
<input type=”text” v-model=”message”>
<span>{{message}}</span>
<button class=”button”>Follow</button>
</div>
</template>
第二步,开始写模块类
class UserCard extends HTMLElement {
constructor() {
super();
var templateElem = document.getElementById(userCardTemplate);
var content = templateElem.content.cloneNode(true);
this.appendChild(content);
this._data = {
name:用户名,
email:
[email protected],
message:单向
}
}
}
window.customElements.define(user-card,UserCard);
这时吧user-card那个原素往页面上丢,得到的效用是这样的了:
第三步,导出
那么接下来要做的事情是导出原素里头的子原素,看看里头是不是包含了{{}}这样的符号,并且要把尾端的文本拿出来,和data里头的统计数据展开比对,如果相关联上了,那就把统计数据填充到那个地方就能了,说起来单纯,做起来还是有很大难度的,这儿头会用到正则匹配,于是我在class里写了那个么个方式:
compileNode(el){
if(node.nodeType === 3){//判断是文本节点,于是直接正则伺候
let text = node.textContent;
let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
//大概的意思是匹配前面有两个{{,后面也有两个}}的这么一串文本
if(reg.test(text)){//如果能找到这样的字符串
let $1 = RegExp.$1;//那就把里头的文本拿出来,比如‘name’
this._data[$1] && (node.textContent = text.replace(reg,this._data[$1]));//看看统计数据里头有没有name这么个东西,如果有,那就把统计数据里头name相关联的值填到当前那个位置。
};
}
})
}
把那个方式丢到constructor里头运行一下就能了,得到效用:
第四步,同时实现统计数据视图存取
到这儿,还是只单纯的把数据渲染到了页面上,如果统计数据再次发生变化,他们还没有找到通知机制让视图发生改变,怎么办呢? 这时就需会用到Proxy了。这儿还需要配合自订事件,嘿嘿看Proxy部分,这儿其实很单纯,增加两个方式就可以了:
observe(){
let _this = this;
this._data = new Proxy(this._data,{//窃听统计数据
set(obj, prop, value){//统计数据改变的时候会触发set方式
//事件通知机制,发生改变的时候,透过自订事件通知视图发生改变
let event = new CustomEvent(prop,{
detail: value//注意这儿我传了个detail过去,这样的话更新视图的时候就能直接拿到新的统计数据
});
_this.dispatchEvent(event);
return Reflect.set(…arguments);//这儿是为了确保修换成功,不写其实也没关系
}
});
}
事件通知有了,但是需要在导出函数里头窃听一下事件,以便视图及时作出改变:
compileNode(el){
let child =node.nodeType === 3){//判断是文本节点,于是直接正则伺候
let text = node.textContent;
let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
//大概的意思是匹配前面有两个{{,后面也有两个}}的这么一串文本
if(reg.test(text)){//如果能找到这样的字符串
let $1 = RegExp.$1;//那就把里头的文本拿出来,比如‘name’
this._data[$1] && (node.textContent = text.replace(reg,this._data[$1]));//看看统计数据里头有没有name这么个东西,如果有,那就把统计数据里头name相关联的值填到当前那个位置。
//增加了事件窃听,窃听每两个匹配到的统计数据,并且再一次更新视图
//注意这儿的e.detail是上面observe里头的自订事件传过来的
this.addEventListener($1,(e)=>{
node.textContent = text.replace(reg,e.detail)
})
};
}
})
}
到这一步,他们就能同时实现修正统计数据的时候,视图也发生改变了:
let card = document.querySelector(user-card);
document.onclick = function(){
console.log(点击了);
card._data.name = 新的用户名;
}
第五步,同时实现单向存取
估算你也看到了,我在template里头写了两个输入框,并且输入框上面还带了两个属性:v-model=”message” 所以估算你也猜到我要做什么了,怎么做呢? 其实很单纯: 在导出文本的时候,判断一下input原素,并且看看它身上是不是有v-model属性,如果有,窃听它的input事件,并且修正统计数据。
再次修正导出函数:
compileNode(el){
let child = el.childNodes;
[…child].forEach((node)=>{
if(node.nodeType === 3){
let text = node.textContent;
let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
if(reg.test(text)){
let $1 = RegExp.$1;
this._data[$1] && (node.textContent = text.replace(reg,this._data[$1]));
this.addEventListener($1,(e)=>{
node.textContent = text.replace(reg,e.detail)
})
};
}else if(node.nodeType === 1){
let attrs = node.attributes;
if(attrs.hasOwnProperty(v-model)){//判断是不是有那个属性
let keyname = attrs[v-model].nodeValue;
node.value = this._data[keyname];
node.addEventListener(input,e=>{//如果有,窃听事件,修正统计数据
this._data[keyname] = node.value;//修正统计数据
});
}
if(node.childNodes.length > 0){
this.compileNode(node);//递归同时实现深度导出
}
}
})
}
第六步,处理事件
嘿嘿看看完整的模块标识符:
class UserCard extends HTMLElement {
constructor() {
super();
var templateElem = document.getElementById(userCardTemplate);
var content = templateElem.content.cloneNode(true);
this.appendChild(content);
this._data = {//表述数据
name:用户名,
email:
[email protected],
message:单向
}
this.compileNode(this);//导出原素
this.observe();//窃听统计数据
this.bindEvent();//处理事件
}
bindEvent(){
this.event = new popEvent({
obj:this,
popup:true
});
}
observe(){
let _this = this;
this._data = new Proxy(this._data,{
set(obj, prop, value){
let event = new CustomEvent(prop,{
detail: value
});
_this.dispatchEvent(event);
return Reflect.set(…arguments);
}
});
}
compileNode(el){
let child = el.childNodes;
[…child].forEach((node)=>{
if(node.nodeType === 3){
let text = node.textContent;
let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
if(reg.test(text)){
let $1 = RegExp.$1;
this._data[$1] && (node.textContent = text.replace(reg,this._data[$1]));
this.addEventListener($1,(e)=>{
node.textContent = text.replace(reg,e.detail)
})
};
}else if(node.nodeType === 1){
let attrs = node.attributes;
if(attrs.hasOwnProperty(v-model)){
let keyname = attrs[v-model].nodeValue;
node.value = this._data[keyname];
node.addEventListener(input,e=>{
this._data[keyname] = node.value;
});
}
if(node.childNodes.length > 0){
this.compileNode(node);
}
}
})
}
open(){
console.log(触发了open方式);
}
}
能发现在这儿头多了两个方式,两个是bindEvent,没错,那个是用以处理事件的了,方式的标识符在下面,结合着第三个必不可少科学知识点去看就能看懂了。
class popEvent{
constructor(option){
/*
* 接收四个参数:
* 1,对象的this
* 2,要窃听的原素
* 3,要窃听的事件,默认窃听点击事件
* 4,是否冒泡
* */
this.eventObj = option.obj;
this.target = option.target || this.eventObj;
this.eventType = option.eventType || click;
this.popup = option.popup || false;
this.bindEvent();
}
bindEvent(){
let _this = this;
_this.target.addEventListener(_this.eventType,function(ev){
let target = ev.target;
let dataset,parent,num,b;
popup(target);
function popup(obj){
if(obj === document){ return false;}
dataset = obj.dataset;
num = Object.keys(dataset).length;
parent = obj.parentNode;
if(num<1){
_this.popup && popup(parent);
num = 0;
}else{
for(b in dataset){
if(_this.eventObj.__proto__[b]){
_this.eventObj.__proto__[b].call(_this.eventObj,{obj:obj,ev:ev,target:dataset[b],data:_this.eventObj});
}
}
_this.popup && popup(parent);
}
}
})
}
}
另外两个是open方式,那个方式是干嘛用的呢?再回过头去看看template里头的标识符:<p class=”name” data-open=”true”>{{name}}</p>这一串是不是很熟悉,猜到我想做什么了么?
没错,同时实现事件指令
当点击含有自订属性:data-open的原素的时候,就能触发模块里的open方式,并且在open方式里还能够得到任何你需要的参数。:
点击用户名的时候,触发了open方式。
完整标识符奉上,注意标识符最后的小技术细节哦~
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title>Title</title>
<style>
</style>
</head>
<body>
<template id=”userCardTemplate”>
<style>
.image {
width: 100px;
}
.container {
background: #eee;
border-radius: 10px;
width: 500px;
padding: 20px;
}
</style>
<img src=”img/bg_03.png” class=”image”>
<div class=”container”>
<p class=”name” data-open=”true”>{{name}}</p>
<p class=”email”>{{email}}</p>
<input type=”text” v-model=”message”>
<span>{{message}}</span>
<button class=”button”>Follow</button>
</div>
</template>
<user-card data-click=”123″></user-card>
<script type=”module”>
class popEvent{
constructor(option){
/*
* 接收四个参数:
* 1,对象的this
* 2,要窃听的原素
* 3,要窃听的事件,默认窃听点击事件
* 4,是否冒泡
* */
this.eventObj = option.obj;
this.target = option.target || this.eventObj;
this.eventType = option.eventType || click;
this.popup = option.popup || false;
this.bindEvent();
}
bindEvent(){
let _this = this;
_this.target.addEventListener(_this.eventType,function(ev){
let target = ev.target;
let dataset,parent,num,b;
popup(target);
function popup(obj){
if(obj === document){ return false;}
dataset = obj.dataset;
num = Object.keys(dataset).length;
parent = obj.parentNode;
if(num<1){
_this.popup && popup(parent);
num = 0;
}else{
for(b in dataset){
if(_this.eventObj.__proto__[b]){
_this.eventObj.__proto__[b].call(_this.eventObj,{obj:obj,ev:ev,target:dataset[b],data:_this.eventObj});
}
}
_this.popup && popup(parent);
}
}
})
}
}
class UserCard extends HTMLElement {
constructor() {
super();
var templateElem = document.getElementById(userCardTemplate);
var content = templateElem.content.cloneNode(true);
this.appendChild(content);
this._data = {
name:用户名,
email:
[email protected],
message:单向
}
this.compileNode(this);
this.observe(this._data);
this.bindEvent();
this.addevent = this.__proto__;
}
bindEvent(){
this.event = new popEvent({
obj:this,
popup:true
});
}
observe(){
let _this = this;
this._data = new Proxy(this._data,{
set(obj, prop, value){
let event = new CustomEvent(prop,{
detail: value
});
_this.dispatchEvent(event);
return Reflect.set(…arguments);
}
});
}
compileNode(el){
let child = el.childNodes;
[…child].forEach((node)=>{
if(node.nodeType === 3){
let text = node.textContent;
let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
if(reg.test(text)){
let $1 = RegExp.$1;
this._data[$1] && (node.textContent = text.replace(reg,this._data[$1]));
this.addEventListener($1,(e)=>{
node.textContent = text.replace(reg,e.detail)
})
};
}else if(node.nodeType === 1){
let attrs = node.attributes;
if(attrs.hasOwnProperty(v-model)){
let keyname = attrs[v-model].nodeValue;
node.value = this._data[keyname];
node.addEventListener(input,e=>{
this._data[keyname] = node.value;
});
}
if(node.childNodes.length > 0){
this.compileNode(node);
}
}
})
}
open(){
console.log(触发了open方式);
}
}
window.customElements.define(user-card,UserCard);
let card = document.querySelector(user-card);
card.addevent[click] = function(){
console.log(触发了点击事件!);
}
</script>
</body>
</html>
最后
放松一下咯~
作者:Mr_无忧
链接:https://juejin.im/post/6893880467305529352