vue的简单使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{message}}
<input v-model="modelData" />{{modelData}}
</div>
<script>
/*
1.引入cdn
2.el是挂载点,就是所有vue指令、表达式都是需要在挂载点中使用
3.{{}} -> 表达式
4.v-model -> 指令,双向绑定;
问:
vue的表达式是如何实现的呢?
你问过vue的双向绑定,如何实现的呢?
*/
let vm = new Vue({
el:"#app",
data:{
message:"测试数据",
modelData:"双绑数据"
}
});
</script>
</body>
</html>
模拟vue表达式的实现
/*
表达式的实现:
一、初次渲染
1.在作用域只能找到表达式
2.把message拿到,和数据进行查找,填充到表达式的位置
二、数据的响应
数据在更改的时候,视图也跟着更改
代码实现:
1.拿到数据,渲染到表达式里面
正则匹配到表达式,去数据里找表达式的数据进行替换
complle();//渲染视图函数
1.找到el(节点);
2.找到子节点
childNodes -> 伪数组
3.如何找到里面的内容呢?
转化伪数组 循环找每一个的 nodeType;
进行判断分离 -> 文本节点(1)与元素节点(3);
4.得到节点的值
node.textContent -> dfasdf{{message}}sadfasd
5.使用正则找到节点内容中的表达式
/\{\{\s*(\S+)\s*\}\}/g;
匹配得到的值 是否 包含正则;
if(reg.test(textContent)){}
拿到正则匹配的内容 分组()
let $1 = RegExp.$1;
6.替换文本的值
1.得到数据
this._data[$1];
2.替换
node.textContent = textContent.replace(reg,this._data[$1]);
3.修正正则bug
/\{\{\s*([^\s\{\}]+)\s*\}\}/g;
7.元素节点的渲染 -> 递归compileNode
思维总结:先找到作用域节点里面所有节点,区分文本节点和元素节点,判断文本节点有没有表达式(正则匹配),有表达式就找到下标内容,内容(message)对象的值,替换到表达式中;元素节点就重复一遍(递归);
*/
class Vue{
constructor(options){
this.options = options;//传进来的参数
this._data = this.options.data;//模范vue的_data
this.complle();
}
// 渲染视图
complle(){
let ele = document.querySelector(this.options.el);//大节点
this.compileNode(ele);
}
compileNode(ele){
let childNodes = ele.childNodes;//得到所有子节点
[...childNodes].forEach(node=>{
if(node.nodeType === 3){//文本节点;
let textContent = node.textContent; //dfasdf{{message}}sadfasd
let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
if(reg.test(textContent)){//判断是否有正则匹配内容 -> test();
let $1 = RegExp.$1;//message
node.textContent = textContent.replace(reg,this._data[$1]);
}
}else if(node.nodeType === 1){//元素节点;
if(node.childNodes.length>0){
this.compileNode(node);
}
}
})
}
}
模仿vue数据改变视图也改变
数据劫持defineProperty
{
/*
参数:
对象(空对象|对象)、key(对象名称)、配置
一些配置:
get(){} : 当访问的时候触发
set(newValue){} : 当修改的时候触发 -> newValue = 修改后的值
configurable:true : 配置
enumerable:true : for in
*/
// 新建defineProperty()属性
let obj = Object.defineProperty({},"name",{
configurable:true,//是否可以配置 -> 默认不可以配置 -> 不能执行删除操作
enumerable:true,//for in等一些操作 -> 默认不可操作
get(){
console.log("get...");
return "张三";
// get 里面需要return;
},
set(newValue){
console.log("set... -> ",newValue);
}
});
// obj.name;
// obj.name = "李四";
// 默认不能配置
// delete obj['name'];
// console.log(obj);
// 默认不可for in
// for(let i in obj){
// console.log(obj[i]);
// }
}
{
// 修改defineProperty属性
let obj = {
name:'张三',
get:'李四'
}
// 可以为它设置
Object.defineProperty(obj,"name",{
configurable:true,
enumerable:true,
get(){
console.log("get...");
return "李四";
},
set(newValue){
console.log("set... -> ",newValue);
}
});
console.log(obj);
// 缺点,当有多个的时候 需要进行循环;
}
{
/*
es6 Proxy
不需要进行循环
参数:
1.obj
2.配置
*/
let obj = {
name:"张三",
age:20
}
let newObj = new Proxy(obj,{
get(traget,key){
console.log("get...");
return traget[key];
},
set(traget,key,newValue){
console.log("set...->",newValue);
}
});
newObj.name = "lisi";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./myvue.js"></script>
</head>
<body>
<div id="app">
dfasdf{{message}}s{{message}}adfasd
<div>
{{message}}asdfadf
<div></div>
</div>
<input v-model="myData" type="text">{{myData}}
</div>
<script>
let vm = new Vue({
el:"#app",
data:{
message:"测试数据",
myData:"我的数据"
}
});
console.log(vm._data);
vm._data.message = 2;
</script>
</body>
</html>
/*
表达式的实现:
一、初次渲染
1.在作用域只能找到表达式
2.把message拿到,和数据进行查找,填充到表达式的位置
二、数据的响应
数据在更改的时候,视图也跟着更改
代码实现:
1.拿到数据,渲染到表达式里面
正则匹配到表达式,去数据里找表达式的数据进行替换
complle();//渲染视图函数
1.找到el(节点);
2.找到子节点
childNodes -> 伪数组
3.如何找到里面的内容呢?
转化伪数组 循环找每一个的 nodeType;
进行判断分离 -> 文本节点(1)与元素节点(3);
4.得到节点的值
node.textContent -> dfasdf{{message}}sadfasd
5.使用正则找到节点内容中的表达式
/\{\{\s*(\S+)\s*\}\}/g;
匹配得到的值 是否 包含正则;
if(reg.test(textContent)){}
拿到正则匹配的内容 分组()
let $1 = RegExp.$1;
6.替换文本的值
1.得到数据
this._data[$1];
2.替换
node.textContent = textContent.replace(reg,this._data[$1]);
3.修正正则bug
/\{\{\s*([^\s\{\}]+)\s*\}\}/g;
7.元素节点的渲染 -> 递归compileNode
思维总结:先找到作用域节点里面所有节点,区分文本节点和元素节点,判断文本节点有没有表达式(正则匹配),有表达式就找到下标内容,内容(message)对象的值,替换到表达式中;元素节点就重复一遍(递归);
*/
/* class Vue{
constructor(options){
this.options = options;//传进来的参数
this._data = this.options.data;//模范vue的_data
this.complle();
}
// 渲染视图
complle(){
let ele = document.querySelector(this.options.el);//大节点
this.compileNode(ele);
}
compileNode(ele){
let childNodes = ele.childNodes;//得到所有子节点
[...childNodes].forEach(node=>{
if(node.nodeType === 3){//文本节点;
let textContent = node.textContent; //dfasdf{{message}}sadfasd
let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
if(reg.test(textContent)){//判断是否有正则匹配内容 -> test();
let $1 = RegExp.$1;//message
node.textContent = textContent.replace(reg,this._data[$1]);
}
}else if(node.nodeType === 1){//元素节点;
if(node.childNodes.length>0){
this.compileNode(node);
}
}
})
}
} */
/*
劫持数据:
1.为_data添加get()和set(); -> observer()
2.for in循环每一个key有define...
注意get的return;
3.当数据修改渲染视图:
一、把设置的值改到视图中 -> 自定义事件
1.继承自定义事件
2.通过自定义事件传新值 视图渲染;
3.使用正则替换内容
let newValue = e.detail;
let oldValue = this._data[$1];
let reg = new RegExp(oldValue,"g");
node.textContent = node.textContent.replace(reg,newValue);
class Vue extends EventTarget {
constructor(options) {
super();
this.options = options;//传进来的参数
this._data = this.options.data;//模范vue的_data
this.observer(this._data);
this.complle();
}
observer(data) {
for (let key in data) {
let value = data[key];
let _this = this;
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
return value;
},
set(newValue) {
// console.log("set... -> ",newValue);
let event = new CustomEvent(key, {
detail: newValue
});
_this.dispatchEvent(event);
value = newValue;
}
})
}
}
// 渲染视图
complle() {
let ele = document.querySelector(this.options.el);//大节点
this.compileNode(ele);
}
compileNode(ele) {
let childNodes = ele.childNodes;//得到所有子节点
[...childNodes].forEach(node => {
if (node.nodeType === 3) {//文本节点;
let textContent = node.textContent; //dfasdf{{message}}sadfasd
let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
if (reg.test(textContent)) {//判断是否有正则匹配内容 -> test();
let $1 = RegExp.$1;//message
node.textContent = textContent.replace(reg, this._data[$1]);
{
this.addEventListener($1, e => {
let newValue = e.detail;
let oldValue = this._data[$1];
let reg = new RegExp(oldValue,"g");
node.textContent = node.textContent.replace(reg,newValue);
});
}
}
} else if (node.nodeType === 1) {//元素节点;
if (node.childNodes.length > 0) {
this.compileNode(node);
}
}
})
}
}
*/
/*
发布订阅模式:
class Vue extends EventTarget {
constructor(options) {
super();
this.options = options;//传进来的参数
this._data = this.options.data;//模范vue的_data
this.observer(this._data);
this.complle();
}
observer(data) {
for (let key in data) {
let value = data[key];
// 实例化收集器:
let dep = new Dep();
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
if (Dep.traget) {
// 触发的时候收集
dep.addSub(Dep.traget);//-> 等同于把实例化对象给了收集器
}
return value;
},
set(newValue) {
// 当值修改的时候执行'通知'函数
dep.notifi(newValue);
value = newValue;
}
})
}
}
// 渲染视图
complle() {
let ele = document.querySelector(this.options.el);//大节点
this.compileNode(ele);
}
compileNode(ele) {
let childNodes = ele.childNodes;//得到所有子节点
[...childNodes].forEach(node => {
if (node.nodeType === 3) {//文本节点;
let textContent = node.textContent; //dfasdf{{message}}sadfasd
let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
if (reg.test(textContent)) {//判断是否有正则匹配内容 -> test();
let $1 = RegExp.$1;//message
node.textContent = textContent.replace(reg, this._data[$1]);
new Watcher(this._data,$1,(newValue) => {
console.log(newValue);
// 可以渲染视图了!
let oldValue = this._data[$1];
let reg = new RegExp(oldValue,"g");
node.textContent = node.textContent.replace(reg,newValue);
});
}
} else if (node.nodeType === 1) {//元素节点;
if (node.childNodes.length > 0) {
this.compileNode(node);
}
}
})
}
}
// 收集器
class Dep {
constructor() {
this.subs = [];//所有订阅者都放在这里面
}
addSub(sub) {//有一个订阅者过来就执行一下
this.subs.push(sub);
}
// 通知; -> 通知每一个订阅者做一些事情
notifi(newValue) {//传值
// 触发update
this.subs.forEach(sub => {
sub.update(newValue);
});
}
}
// 订阅者
class Watcher {//什么时候实例呢? 属性有多少就实例多少 -> message myData...
constructor(data,key,cb) {
this.cb = cb;
Dep.traget = this;//静态属性;
// 等同于触发了get();
data[key];
Dep.traget = null;
}
update(newValue) {
// console.log("更新了..?");
// console.log(newValue);
this.cb(newValue);
}
}
*/
/*
双向绑定
1.元素节点中处理:
2.找指令
循环attrs 判断v-开头的
let attrs = node.attributes;
[...attrs].forEach(attr=>{
let attrName = attr.name;
let attrValue = attr.value;
if(attrName.indexOf("v-")==0){
}
})
*/
class Vue extends EventTarget {
constructor(options) {
super();
this.options = options;//传进来的参数
this._data = this.options.data;//模范vue的_data
this.observer(this._data);
this.complle();
}
observer(data) {
for (let key in data) {
let value = data[key];
// 实例化收集器:
let dep = new Dep();
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
if (Dep.traget) {
// 触发的时候收集
dep.addSub(Dep.traget);//-> 等同于把实例化对象给了收集器
}
return value;
},
set(newValue) {
// 当值修改的时候执行'通知'函数
dep.notifi(newValue);
value = newValue;
}
})
}
}
// 渲染视图
complle() {
let ele = document.querySelector(this.options.el);//大节点
this.compileNode(ele);
}
compileNode(ele) {
let childNodes = ele.childNodes;//得到所有子节点
[...childNodes].forEach(node => {
if (node.nodeType === 3) {//文本节点;
let textContent = node.textContent; //dfasdf{{message}}sadfasd
let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
if (reg.test(textContent)) {//判断是否有正则匹配内容 -> test();
let $1 = RegExp.$1;//message
node.textContent = textContent.replace(reg, this._data[$1]);
new Watcher(this._data,$1,(newValue) => {
// 可以渲染视图了!
let oldValue = this._data[$1];
let reg = new RegExp(oldValue,"g");
node.textContent = node.textContent.replace(reg,newValue);
});
}
} else if (node.nodeType === 1) {//元素节点;
let attrs = node.attributes;
[...attrs].forEach(attr=>{
let attrName = attr.name;
let attrValue = attr.value;
if(attrName.indexOf("v-")==0){
attrName = attrName.substr(2);//取消 v-;
if(attrName === "model"){
node.value = this._data[attrValue];
// 监听input事件
node.addEventListener("input",e=>{
// console.log(e.target.value);
let value = e.target.value;
this._data[attrValue] = value;
})
}
}
})
if (node.childNodes.length > 0) {
this.compileNode(node);
}
}
})
}
}
// 收集器
class Dep {
constructor() {
this.subs = [];//所有订阅者都放在这里面
}
addSub(sub) {//有一个订阅者过来就执行一下
this.subs.push(sub);
}
// 通知; -> 通知每一个订阅者做一些事情
notifi(newValue) {//传值
// 触发update
this.subs.forEach(sub => {
sub.update(newValue);
});
}
}
// 订阅者
class Watcher {//什么时候实例呢? 属性有多少就实例多少 -> message myData...
constructor(data,key,cb) {
this.cb = cb;
Dep.traget = this;//静态属性;
// 等同于触发了get();
data[key];
Dep.traget = null;
}
update(newValue) {
// console.log("更新了..?");
// console.log(newValue);
this.cb(newValue);
}
}
/*
总结:
表达式是如何实现的?
1.通过正则找到花括号的表达式,渲染到视图
2.数据更新视图也更新: 通过数据劫持get和set,更新数据渲染到视图;
当然这两步也包含了很多知识点:正则,元素节点...
双向绑定是如何实现的?
1.通过元素节点判断 v- 开头
2.当取消v-的时候等于双向绑定的指令时,将数据渲染到input.value中,监听 input 的改动;修改数据;数据修改后视图自动渲染;
*/