框架vs库vs组件
-
框架 vue react 我们写方法 ,框架帮我们调用
-
库 jQuery 人家写方法,我们用
-
vue是双向数据绑定。单向数据流
start Vue
导入Vue
- npm i vue --save (--save会生成对应的package.json)
- npm init -y 初始化项目。会生成一个package.json
- script引入:
new Vue
<body>
<div id="app">
{{name}}
</div>
</body>
<script>
new Vue({
el:"#app",
data:{
name:"polikesen"
},
methods:{
f(){
}
}
})
</script>
小胡子语法
小胡子语法 ,里面只能写 Vue变量、表达式(不能写语句)
vue指令
在vue中 所有v-xxx的行内属性都是vue指令
- v-text=“vue变量” 类似innerText 网速慢时页面上可能会显示小胡子 为了避免可以用v-text
- v-html=“vue变量” 类似innerHTML 可以识别字符串中的DOM结构,里面的值 只会当作普通html, 不会当作vue模版进行渲染
- v-once 后边不跟值 代表有这个属性的元素 里面的语法 vue只会渲染一次
- v-model 表单元素使用 数据的双向绑定的核心
- v-pre 后边不跟值 告诉vue:有这个指令的标签及其后代标签 都不进行编译,可以提升vue的编译速度
- v-if
- v-show
- v-cloak 后边不跟值 就是利用了vue渲染完成之前 元素上还会有v-cloak属性 渲染完成之后 ,vue会吧v-cloak属性去掉 这样页面上就不会显示出小胡子语法 必须配合css的display:none;使用
- v-bind(简写:) 修饰内置的行内属性
- v-on :事件绑定(简写:@)
vue修饰符
- 事件修饰符:.stop(阻止冒泡) .prevent(阻止默认事件) .capture(在冒泡阶段触发) .passive(先执行绑定事件,再触发默认事件) .self(触发绑定元素本身时才有用) .once(只触发一次)
- 按键修饰符: .enter/.13
- 系统修饰符: .ctrl
- v-model的修饰符: .lazy(onchange事件 失去焦点时触发)) .number(parseFloat转换 转不出来还是字符串)) .trim(去除首尾空格)
vue自定义指令
全局指令:Vue.directive(指令名,function(el,obj))
directives:{
指令名1(el,obj){
},
指令名2(el,obj){
}
}
filters 过滤器方法
全局过滤器: Vue.filter(过滤器名,function(val,传递的其他参数){val就是管道符前边的值})
Vue数据对象
改变vue中的数据 能触发试图更新 前提是 该数据有set 和 get; 若没有get和set更改数据是不会触发试图的更新,但是 数据是改了的 给对象新增属性时, vue不会触发试图的更新,解决方法:
- 一开始就在data中写上对应的属性
- 整个对象的替换,替换后的对象中的每一个属性 都会被监听
- 通过$set(对象,属性名,属性值)
- 初始时 创建一个无关变量,在新增对象的属性时, 我们可以去更新刚才的无关变量
Vue双向数据绑定实现原理(v-model...)
响应式数据:data中准备的要在视图中渲染的数据(model)
- 数据劫持
vue是通过利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,从而进行进一步操作。
- Objec.defineProperty()
let obj = {};
let ary = [];
let val = 0;
Object.defineProperty(obj, 'qqq', {
// value:'zhuefeng',
// enumerable:true,//可枚举(可循环到)
// configurable:true,//可删除
// writable:true,//可改写
get() {
console.log('get')
return val
},
set(value) { //value就是给对应属性设置的值 set里面写return 没啥用
console.log('set', arguments)
val = value;
ary.forEach(fn=>{
fn();
})
return 'set'
}
})
缺点: definePropertyf方法,只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历
- Proxy代理
let obj2 = {a:23,b:65};
let o = new Proxy(obj2,{
get(...arg){
console.log(arg);
},
set(...arg){
console.log(arg)
}
})
Proxy是Object.defineProperty的全方位加强版,Proxy可以直接监听对象而非属性,Proxy也可以直接监听数组的变化
mvvm实现原理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
{{name}}{{age}}{{name}}
<h2>{{age}}</h2>
<input type="text" v-model="name">
<input type="text" v-model="age">
<h2>{{name}}{{age}}</h2>
</div>
</body>
</html>
<script>
/*
1. 把页面中的Vue语法 转成正常的数据
- 先获取数据
*/
function nodeToFragment($el, vm) { //vm是当前实例
let fragment = document.createDocumentFragment(); //创建一个文档碎片,用来存储页面中的节点
let child;
while (child = $el.firstChild) {
//界面中已经存在的元素 appendChild操作是移动元素位置 而不是添加
compile(child, vm); //编译模板函数
fragment.appendChild(child); //把child从#app中
}
//通过while循环 我们把要编译的模版转移到了文档碎片上 页面上成了空白
//下一步 我们再编译模版 然后把编译好的模版放到页面上
// console.log(fragment)
$el.appendChild(fragment)
}
function compile(node, vm) {
//这里需要我们判断node是文本节点 还是元素节点
if (node.nodeType == 1) {
//说明是元素节点
let attrs = node.attributes; //获取node的所有行内属性
// console.log(attrs);
[...attrs].forEach(item => {
// console.log(item.nodeName,item.nodeValue)
if (/v\-/.test(item.nodeName)) {
let valN = item.nodeValue; //指令对应的vue变量名 ;name age
// console.log(valN,vm.$data)
let val = vm.$data[valN]; //vue对应的值:珠峰 10
//把val设置成对应的元素的值 node就是我们对应的元素
node.addEventListener('input', (e) => {
vm.$data[valN] = e.target.value;
}, false)
new Watcher(node, vm, valN)
node.value = val; //把元素对应的value设置成对应的val
// console.log(node)
}
});
//若没有v-指令 则对该节点继续进行编译,编译该节点的自节点
// console.log(node);
[...node.childNodes].forEach(item => compile(item, vm))
} else {
//文本节点 我们要去查找小胡子语法 把小胡子语法对应的变量换成对应的值
// console.log(node.textContent);//通过这个可以获取到对应的文本字符串
let str = node.textContent;
node.str = str; //str是带着小胡子的编译之前的值
if (/\{\{(\w+)\}\}/.test(str)) {
str = str.replace(/\{\{(.+?)\}\}/g, ($0, $1) => {
// console.log($1)
new Watcher(node, vm, $1)
return vm.$data[$1]
})
// console.log(str);
node.textContent = str;
}
}
}
function observe(obj) {
//数据劫持
let keys = Object.keys(obj); //获取obj中所有的属性名
keys.forEach(key => {
//执行真正的数据劫持
defineReactive(obj, key, obj[key])
})
}
function defineReactive(obj, key, value) {
let dep = new Dep(); //针对每一个属性 各子创建了一个事件池 name事件池中存放的都是用到name的那些节点的更新操作
Object.defineProperty(obj, key, {
get() {
// console.log(`${key}被使用了`)
if (Dep.target) {
//存储的是更新的 对应的DOM的操作
dep.addSub(Dep.target)
}
return value;
},
set(newValue) {
if (newValue == value) return; //若前后两次的数据没改变就不需要更新DOM
value = newValue;
// console.log(`${key}被设置了`)
dep.notify();
}
})
}
//订阅器
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.forEach(item => {
item.update();
})
}
}
class Watcher {
//订阅者
constructor(node, vm, key) {
Dep.target = this;
this.node = node;
this.vm = vm;
this.key = key;
this.update();
Dep.target = null;
}
update() {
this.get(); //this.value存储的是 更改后的数据的新值
let str = this.node.str;
if (str) {
str = str.replace(/\{\{(.+?)\}\}/g, ($0, $1) => {
if ($1 == this.key) {
return this.value
}
return this.vm.$data[$1]
})
this.node.textContent = str; //把更新完的str 重新放回页面
}else{
//证明是input
this.node.value = this.value;
}
}
get() {
this.value = this.vm.$data[this.key]
}
}
function Vue(options) {
this.$el = document.querySelector(options.el); //$el就是我们要操作的元素
this.$data = options.data || {}; //$data中存储的值对应的是Vue中的变量
observe(this.$data); // 对数据进行劫持
nodeToFragment(this.$el, this); //把页面中的节点转移到文档碎片上
}
let vm = new Vue({
el: '#app',
data: {
name: '珠峰',
age: 10
}
})
</script>