整理下初学时做过的js基础编程题目和大家分享以下,如果大家觉得有用,别忘了点一下赞哦
vue响应式数据
定义一个data对象,实现对象内属性值的改变的监听操作
效果展示:抖音视频链接
这里问的是数据劫持结合发布者和订阅者模式,我直接来实现一个vue2.0数据的双向绑定。
首先 我们得有一个html
<div id="app">
<div>
<input type="text" v-model="name" placeholder="姓名">
<input type="text" v-model="age" placeholder="年龄">
<input type="text" v-model="email" placeholder="邮箱">
<input type="text" v-model="tel" placeholder="电话">
</div>
<div>
<p>姓名:<span>{{ name }}</span></p>
<p>年龄:<span>{{ age }}</span></p>
<p>邮箱:<span>{{ email }}</span></p>
<p>电话:<span>{{ tel }}</span></p>
</div>
</div>
<script src="./mvvm.js"></script>
<script>
new MVVM("#app",{
name:"",
age:"",
email:"",
tel:""
})
然后,我们实现 MVVM 的中的 VM,我们要知道要完成
- 监听数据源的变化(input)
- 主动更新DOM的变化
实现过程得有
-
VM:数据劫持,用
Object.defineProperty
实现,给每个_data中的数据都加上数据劫持,这里要注意取值器函数的使用,return obj[key]
也会触发取值器,从而导致死循环,我这里采用用一个空对象劫持,返回_data数据。 -
M:监听input变化,得给 id 为 app的标签内每个带
v-model
的input都附加一个input
事件,只要input.value
一改变,就出触发数据劫持。 -
V:实现主动更新DOM,主要分为两点,第一点是一开始就要根据数据更新一次,第二点是每次input的值要更新下DOM,具体思路是我要找到id为app的标签下的所有带
{{}}
的文本节点,然后取他们对应的内容,通过文本节点的元素父节点修改innerText
,在第二点中input的变化,通过
input的数据改变 -> 数据劫持 -> Dom变化 ,这里就不详说了,我也是和 小野森森 学的,这是他的视频链接
小野老师视频地址
//mvvm.js
class MVVM {
class MVVM {
constructor(el, data) {
this.el = document.querySelector(el);//要渲染的区域
this._data = data; //数据
this.dompool = {};// 存储数据和dom的dom池
this.init();
}
init() {
this.initData(); //数据劫持
this.bindInput(this.el); //找到'v-model',实现input数据的自动更新
this.bindDom(this.el); //实现dom更新
}
initData() {
const self = this;
this.data = {};
for (let key in this._data) {
Object.defineProperty(self.data, key, {
get() {
console.log("查看数据", key, self._data[key]);
return self._data[key];
},
set(newVal) {
console.log("设置新数据", key, newVal);
// 实现数据更新时,数据 -> 视图
self.dompool[key].inner.innerText = newVal;
self._data[key] = newVal;
},
});
}
}
bindInput(el) {
const self = this;
const inputs = el.querySelectorAll("input");
// 给每个带有 "v-model" 属性节点的 input 添加 input事件
inputs.forEach((input) => {
const Vmodel = input.getAttribute("v-model");
if (Vmodel) {
input.addEventListener(
"input",
function () {
// 实现监听input的变化,数据的自动同步
self.data[Vmodel] = input.value;
},
false
);
self.dompool[Vmodel] = {ipt : input}
}
});
}
bindDom(el) {//实现dom的初始化更新
const self = this;
const childrens = el.childNodes; // el标签下的所有节点
const reg = /\{\{(.+)\}\}/g;
// 筛选带有 插值语法 {{}} 的文本节点
childrens.forEach((node) => {
if (node.nodeType === 3 && node.nodeValue.trim().length) {
if (reg.test(node.nodeValue)) {
const inner = node.nodeValue.match(/\{\{(.+)\}\}/g)[0]
const value =inner.replace(/\{|\}/g,'')
// 把 属性名 => dom元素节点 的键值对存储
self.dompool[value].inner = node.parentElement;
// 实现初始化
// {{}} 渲染数据
self.dompool[value].inner.innerText = self.data[value] || '未输入数据'
// v-model 数据渲染input框
self.dompool[value].ipt.value = self.data[value] || '未输入数据'
}
}
// 递归遍历其他里层节点,其中forEach就是个终止条件
node.childNodes && self.bindDom(node);
});
}
}
这里我扩展下 this指向的问题,如果不想用 self 来拿外部作用域的this,我们也可以用 bind
- bind的用法
注意:箭头函数是没有this的,所以用call,apply,bind方法会报错
bindInput(el){
const inputs = el.querySelectorAll("input")
inputs.forEach(function(input){
let Vmodel = input.getAttribute("v-model")
if(Vmodel){
input.addEventListener("input",function(){
// 实现监听input的变化,数据的自动同步
this.data[Vmodel] = input.value
}.bind(this),false)
}
}.bind(this))
}
- 箭头函数"更改"this指向
代替self,一种转牛角尖的写法,不推荐使用,用箭头函数"更改"Object.defineProperty
的this指向,本来Object.defineProperty
中的this是指向第一个参数,就是需要定义属性的那个对象。
initData(){
const self = this
this.data = {}
for(let key in this._data){
Object.defineProperty(this.data,key,{
get:()=>{
console.log("读取数据",key,this._data[key])
return this._data[key]
},
set:(newValue)=>{
console.log("设置数据",key,newValue)
//实现数据 -> dom变化 dom的自动更新
this.dompool[key].innerText = newValue
this._data[key] = newValue
}
})
}
}
最后得出,不用self的版本,仅仅用来转牛角尖,不推荐使用
class MVVM{
constructor(el,data){
this.el = document.querySelector(el)
this._data = data
this.dompool = {}
this.init()
}
init(){
this.initData()
this.bindInput(this.el)
this.bindDom(this.el)
}
initData(){
const self = this
this.data = {}
for(let key in this._data){
Object.defineProperty(this.data,key,{
get:()=>{
console.log("读取数据",key,this._data[key])
return this._data[key]
},
set:(newValue)=>{
console.log("设置数据",key,newValue)
//实现数据 -> dom变化 dom的自动更新
this.dompool[key].innerText = newValue
this._data[key] = newValue
}
})
}
}
bindInput(el){
const inputs = el.querySelectorAll("input")
inputs.forEach(function(input){
let Vmodel = input.getAttribute("v-model")
if(Vmodel){
input.addEventListener("input",function(){
// 实现监听input的变化,数据的自动同步
this.data[Vmodel] = input.value
}.bind(this),false)
}
}.bind(this))
}
bindDom(el){
const childNodes = el.childNodes
childNodes.forEach(node=>{
// 筛选带有 插值语法 {{}} 的文本节点
if(node.nodeType === 3 && node.nodeValue.trim().length && /\{\{(.+)\}\}/g.test(node.nodeValue)){
let value = node.nodeValue.replace(/\{|\}/g,"").trim()
this.dompool[value] = node.parentElement;
this.dompool[value].innerText = this.data[value] || "--未输入数据--"
}
//递归遍历其他里层节点,其中forEach就是个终止条件
this.bindDom(node)
})
}
}
这里在扩展下关于DOM节点的 childNodes 和children
- childNodes 和children的区别
1.定义
childNodes 返回一个包含元素的所有子节点的类数组
children 返回一个包含元素的所有元素节点的类数组
2.里面包含的方法
childNodes自带entries
和forEach
方法
children不自带这些两个数组的方法