一、认识Mustache、实例挂载及Vue编译渲染视图
- 只要挂载到 vm 实例上的内容都可以“直接”在视图中使用!
- 极少数window全局对象中的内容,也可以在视图中直接使用,例如:JSON可以,但是eval不可以...但是可以把不能直接使用的东西,赋值给实例的状态,视图中使用状态即可
1.1 Mustache库:vue中没有使用这个库
import Mustache from 'mustache';
var data = {
title: 'This is my TITLE for MUSTACHE'
}
var html = Mustache.render(
`<h1>{{ title }}</h1>`,
data
);
document.getElementById('app').innerHTMl = html;
1.2 Mustache小胡子语法 {{ ... }}
+ Vue的`<template>`模板语法是特殊的数据绑定方法
+ 因为vue的模板都是基于HTML的,所以模板中直接写HTML都是能够被HTML解析器解析的
+ vue有自己的表达式、自定义属性、指令,这些都不能编译,Vue提供一套模板**编译系统**
+ 开发者写的template,通过分析 HTML字符串转换成AST树,这些表达式、自定义属性、指令会变成虚拟DOM树,虚拟DOM最后变为真实DOM,最后render渲染
+ 小胡子语法中写的是“数据/状态”或者“JS表达式(代码运行有结果的)”
+ 判断:三元运算符
+ 循环:数组的map/filter/find/reduce/some/every...方法
+ 不同类型的值,最后渲染的结果是不一样的
+ number/string/boolean直接渲染(写啥就渲染啥)
+ null/undefined渲染的是空
+ Symbol/BigInt都是先变为字符串,再进行渲染的
+ 但是小胡子语法中不能直接出现Symbol函数,需要指定一个状态存储Symbol类型的值
+ 对象类型会默认装换为字符串进行渲染
+ 普通对象/数组对象:是基于JSON.strngify(...)把其变为JSON字符串
+ 正则、日期等剩下的对象,基本上都是基于String(...)处理的
1.2.1 插值表达式的使用
<!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>
<body>
<div id="app">
<!-- 1.基本应用 -->
<h2>当前计数:{{ counter }}</h2>
<h2>message:{{ message }}</h2>
<!-- 2.表达式 & JS API-->
<h2>计数的双倍:{{ counter*2 }}</h2>
<h2>显示的信息:{{ info.split(" ")}}</h2>
<!-- 3.三元运算符 -->
<h2>{{ age >= 18 ? "成年人" : "未成年人" }}</h2>
<!-- 4.注意:这里不能定义语句,不能绑定多个表达式 -->
<!-- <h2>{{ const name = "hhh"}}</h2> -->
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
counter: 1,
message: "hello world",
info: "my name is hhh",
age: 22
}
}
});
app.mount("#app");
</script>
</body>
</html>
1.2.2 插值表达式中的属性绑定
# 属性:
- attribute:HTML的扩展 比如:title、src、href,缩写attr
- property:在对象内部存储数据,通常用来描述数据结构,缩写prop
# vue中的属性
- Mustache中是不支持在HTML属性中插值
- Vue中因为用底层的模板编译系统,支持Vue内置的属性
- 想要在html中插入JS的表达式,可以用v-bind,v-bind:href="url"
# disabled="true"
- 对于模板解析,true是个字符串,并不是逻辑真
- :disabled="true" 逻辑真
# truthy falsy
- falsy: false 0 "" null undefined NaN
- truthy: 除了falsy以外的值
# 对于disabled逻辑真假的属性来说
- :disabled="null/undefined" 不会渲染在标签上
- 只有true false "" 和 truthy会在解析过程中将disabled属性包含在元素上
- truthy::disbled="123"像这样的 => :disabled="true"
1.3 Vue框架:Vue自定义构造函数,每一次使用Vue都是创造这个类的一个实例(vm)
// vm.__proto__ -> Vue.prototype -> Object.prototype
let vm = new Vue({
data(){
return {
msg: '你好世界',
score: {
x: 10,
y: 20
},
arr: [10, 20, 30],
flag: new Error()
}
}
});
vm.$mount('#app');//基于querySelector获取视图容器:指定的容器不能是HTML和BODY
setTimeout(() => {
vm.msg = 'hello world';
}, 2000);
1.4 vue实例挂载与指定视图的底层机制:
vm.$mount(selector/DOM)
+ Vue.prototype.$mount
+ 在没有基于 template/render 指定单独视图的情况下, #app容器就是我们需要渲染的视图;如果指定了单独视图,则$el存储的是我们指定的视图!!
+ vm.$el 存储的就是我们需要渲染的视图
1.5 指定视图有多种方式,但是vue底层是按照如下的逻辑进行处理的:
@1 首先看是否存在el配置项
+ 有:继续下一步
+ 没有:则后期在执行$mount的时候,把里面指定的容器作为el
@2 再看是否有 render/template 配置项
+ 有:把配置项指定的内容作为视图渲染
+ 没有:把el指定的容器作为视图进行渲染
视图渲染完毕,会创建一个“虚拟DOM对象”,挂载到VM实例的$el属性上!!
@3 把渲染完毕的结果,替换el/$mount指定的容器(把虚拟DOM变为真实DOM的过程)!!
1.6 Vue2数据驱动视图渲染(也就是更改数据后,视图会自动更新)的底层机制:
基于Object.defineProperty对data中的数据/状态,进行监听劫持「get/set」
- 当修改状态值,会触发set函数
- 在set函数中,除了修改了状态值,而且还会通知视图重新渲染!!
1.7 defineProperty语法
1.7.1 对象“成员”的规则:
@1 可枚举性 enumerable
+ 能够被for/in(或Object.keys)迭代到的属性是“可枚举属性”,反之则是不可枚举的
+ 一般来讲,自定义的属性是可枚举的、内置属性是不可枚举的;这不是绝对的概念,因为枚举性是可以被更改的!
@2 可编辑性 writable
+ 也就是成员的值是否可以被更改
@3 可删除性 configurable
+ 也就是成员是否可以被delete移除
@4 成员的值 value
Object.getOwnPropertyDescriptor(obj,key) 获取对象某个的成员的规则
Object.getOwnPropertyDescriptors(obj) 获取对象所有成员的规则
// 基于传统方案(对象.xxx/对象[xxx])设置的成员,规则都是true「可删除、可编辑、可枚举」
let obj = {};
obj.x = 10;
1.7.2 我们可以基于Object.defineProperty(obj,key,config)新增/设置成员的规则!
1.默认规则值都是false
configurable:false
enumerable:false
writable:false
2.但是自己可以改
// 手动设置属性
let obj = {};
Object.defineProperty(obj, 'x', {
value: 10,
enumerable: false,
writable: true,
configurable: true
});
// 给数组原型扩展方法
Object.defineProperty(Array.prototype, 'unique', {
writable: true,
configurable: true,
enumerable: false,
value: function unique() {
// ...
}
});
1.7.3 defineProperty除了可以设置规则,还可以对某个成员进行监听/劫持
- get 获取成员值的时候触发,函数的返回值就是我们获取的成员值
- set 设置/修改成员值的时候触发,val就是要新设置的值,如果set中啥都不处理,成员值是没办法修改的
- 设置劫持后,和原本的value/writable规则冲突「设置劫持,就不能设置这两个规则了」!!
let obj = {
x: 10
};
let proxy = { ...obj }; // 代理对象(默认和obj初始值是一样的)
Object.defineProperty(obj, 'x', {
get(){
// console.log('getter');
return proxy.x;
},
set(val){
// console.log('setter', val);
proxy.x = val;
}
});
1.8 挂载源码
//给Vue增加init方法
Vue.prototype._init = function (options) {
//初始化数据
const vm = this;
// vm.$options 就是获取用户的配置,将用户的选项挂载到实例上
vm.$options = options;
// 初始化状态
/*
判断是否有vm.$options
+ 有:initData(vm)
1. 拿到data,判断是否是函数,是函数就改变this是vm,不是就是对象data
2. vm._data = data; 将返回的对象放到了_data上
3. 对数据进行劫持 vue2里采用了一个api defineProperty=> observe(data);
4. 将vm._data 用vm来代理就可以了 vm.name => vm._data.name
+ 没有不做操作
*/
initState(vm);
if (options.el) {
// 实现数据的挂载
vm.$mount(options.el);
}
}
Vue.prototype.$mount = function (el) {
const vm = this;
el = document.querySelector(el);
let ops = vm.$options;
// 先进行查找有没有render函数
if (!ops.render) {
let template;
// 没有render看一下是否写了template
if (!ops.template && el) {
// 没写template采用外部的template
template = el.outerHTML;
} else {
// 写了template就用ops中的
if (el) {
template = ops.template;
}
}
if (template) {
// 这里需要对模板进行编译
const render = compileToFunction(template);
ops.render = render;// jsx 最终会被编译成h('xxx')
}
}
// 最终就可以获取render方法
// script 标签引用的vue.global.js 这个编译过程是在浏览器运行的
// runtime 是不包含模板编译的,整个编译时打包的时候通过loader来转义的.vue文件的,用runtime的时候不能使用template
ops.render;
}
1.9 Vue框架是如何去编译和渲染视图的?
@1 基于相关插件,把template视图,编译为虚拟DOM「vnode」
第一次渲染和每一次视图更新,不论修改了几个状态,所有视图都要重新编译(全量编译)!
@2 进行DOM-DIFF「是有视图更新的时候会进行」
DOM-DIFF是两次虚拟DOM对象比较的算法
@3 把虚拟DOM(或者补丁包)变为真实DOM
框架底层操作真实DOM的过程「开发者只需要操作数据,不需要操作DOM;框架对于DOM操作,做了大量优化(例如回流重绘的减少...)以此来提高性能」
二、 认识Vue的13个内置指令
-
所有在Vue中,模板上属性的
v-*都是指令 -
为什么叫指令?模板应该按照怎样的逻辑进行渲染或绑定行为 -
Vue提供了大量的内置指令 v-if v-else v-else-if v-for..... 开发者也可以给Vue扩展指令,也就是
自定义指令
三个关于视图编译优化的指令:v-pre、v-once、v-cloak
2.1 v-once:只编译一次
- 在视图第一次渲染的时候,会进行编译;但是后期状态改变,视图更新的时候,不再进行编译「展示的都是第一次渲染后的内容」!!
- 适用于:已经明确知道,编译一次后不需要再进行改变的内容,可以保证视图“更新”的时候,跳过v-once指定的内容,加快更新的速度!!
2.1.1 v-once
const App = {
data() {
return {
title: 'This is my TITLE',
author: 'Xiaohe'
}
},
template: `
<div>
<h1 v-once>{{ title }} - <span> {{ author }}</span></h1>
<button @click="change">CHANGE</button>
</div>
`,
methods: {
change() {
this.title = 'This is your TITLE';
this.author = 'Hezi';
}
}
}
Vue.createApp(App).mount('#app');
当点击按钮修改状态的时候,这个组件及里面的子组件只渲染一次,里面的值不会改变,但是实例上的值已经改变,所以外面的组件可以正常渲染
2.1.2 {{ 绑定变量 }}
const TITLE = 'This is my TITLE';
const App = {
data() {
return {
title: 'This is my TITLE',
author: 'Xiaohe'
}
},
// 视图上Vue指定的插入方式的数据变量必须声明在实例上
template: `
<div>
<h1>{{ TITLE }} - <span> {{ author }}</span></h1>
<button @click="change">CHANGE</button>
</div>
`,
methods: {
change() {
this.title = 'This is your TITLE';
this.author = 'Hezi';
}
}
}
Vue.createApp(App).mount('#app');
2.1.3 ES6模板语法
标签内没有其他的标签用v-once,反之用ES6模板语法只渲染一次这个变量,其他内部标签变量或状态不影响
const TITLE = 'This is my TITLE';
const App = {
data() {
return {
title: 'This is my TITLE',
author: 'Xiaohe'
}
},
template: `
<div>
<h1>${TITLE} - <span> {{ author }}</span></h1>
<button @click="change">CHANGE</button>
</div>
`,
methods: {
change() {
this.title = 'This is your TITLE';
this.author = 'Hezi';
}
}
}
Vue.createApp(App).mount('#app');
2.2 v-pre
v-pre:不编译此内容(包含其后代内容也不编译)
- 真实项目中,视图中的内容,大部分是需要基于状态/数据动态绑定处理的,这部分内容肯定需要Vue编译;但是还有一小部分内容,就是写死的静态页面「我们写啥,最后就渲染为啥」,此部分内容我们可以设置v-pre,来加快整个页面的编译速度!!
- 但是在Vue3中,会默认跳过静态节点的编译,不需要我们自己设置v-pre了!!
<!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>指令</title>
</head>
<body>
<div id="app">
<div v-pre>
<h2>{{message}}</h2>
<p>当前计数:{{counter}}</p>
</div>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
message: "hello world",
counter: 100
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.3 v-cloak
- 确保在JS已经获取到,并且视图编译后,再显示编译后视图的内容,在此之前,让编写的视图模板不呈现
- 在后期工程化开发中,不再需要v-cloak了!!
<!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>指令</title>
<style>
[v-clock] {
display: none;
}
</style>
</head>
<body>
<div id="app">
<h2 v-cloak>{{ message }}</h2>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
message: "hello world"
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.4 v-html
可以识别字符串中的html标签,当做正常的标签进行渲染,类似于innerHTML(使用的时候,要注意可能会出现XSS攻击)
2.4.1 在模板中加标签
const App = {
data() {
return {
title: 'This is my TITLE'
}
},
template: `
<div >{{'<h1>' + title + '</h1>' }}</div>
`
}
Vue.createApp(App).mount('#app');
插值不会解析HTML,因为插值是JS表达式,没有对DOM的操作,返回纯HTML( rawHTML)字符串
2.4.2 加v-html的效果
const App = {
data() {
return {
title: '<h1>This is my TITLE</h1>'
}
},
template: `
<div v-html="title"></div>
`
}
Vue.createApp(App).mount('#app');
不要试图用v-html做子模板,vue本身有一个底层的模板编译系统,而不是直接使用字符串来渲染的
- 这样仅仅只是解决了解析成HTML,再嵌套其他逻辑就没办法做到
- 应该把子模板放到子组件中,让模板的重用和组合更强大
- 不要把用户提供的内容作为v-html的插值,这种插值容易导致XSS的攻击
2.4.3 XSS的攻击
v-html动态的渲染HTML,使用基本是innerHTML
- innerHTML有时候会容易导致XSS攻击,原因是标签使用不当
var text = '<img src="123" onerror="alert(123)" />';
document.getElementById('app').innerHTML = text;
2.5 v-text
- v-text:把所有的内容(包含HTML标签字符串),都当做普通文本进行渲染,类似于innerText或者小胡子语法
<!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>指令</title>
</head>
<body>
<div id="app">
<!--v-text-->
<h2>{{ message }}</h2>
<h2 v-text="message"></h2>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
message: "hello world"
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.6 v-memo vue3新增
memorize 动词-记住
<!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>指令</title>
</head>
<body>
<div id="app">
<!-- 记住一个模板的子树,元素和组件都可以使用,该指令接收一个固定长度的数组作为依赖值进行记忆比对,如果数组中的值都和上次渲染的时候相同,则整个子树都不会渲染,直接跳过 即当数组中的内容发生改变,里面的标签元素才会更新,反之直接跳过-->
<div v-memo="[name]">
<h2>姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2>
<h2>身高: {{ height }}</h2>
</div>
<button @click="updateInfo">改变信息</button>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
name: 'zs',
age: 18,
height: 1.8
}
},
methods: {
updateInfo() {
this.name = 'kobe'
this.age = 20
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.7 v-bind
v-bind:简写为“:”,给元素的属性(不论是内置还是自定义)动态绑定值
- 改变属性值的类型
<div index="10">:给元素设置index属性,属性值是“10”(字符串类型);正常情况下,属性值都是字符串类型的,但是我们有时候需要属性值是其它类型的,此时就需要v-bind指令了!!
<div v-bind:index="10"> 或者 <div :index="10">:index属性值是数字10
<Vote :data="{x:10,y:20}"/>:data属性值是一个对象类型
- 把状态/JS表达式的结果作为属性值
<div index="flag">:index的属性值是"flag"
<div :index="flag">:把flag状态存储的值(例如:true)作为index属性的值
2.7.1 动态绑定class
<!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>v-bind动态绑定class</title>
<style>
.active {
color: red;
}
</style>
</head>
<body>
<div id="app">
<!-- v-bind的绑定属性 -->
在组件中,父组件传递给子组件的class会默认和子组件的根元素的class进行合并,显示合并的class
在vue3中允许多个根元素,会报错,这事我们可以手动给子元素加$attrs.xx来实现增加,$attrs是父组件传递给子组件的属性集合
<!-- 1.绑定基本的属性 -->
<!-- 1.1 绑定img的src属性 -->
<img v-bind:src="imgUrl" alt="">
<!-- 1.2 绑定a的href属性 -->
<a v-bind:href="href">百度一下</a>
<button @click="switchImage">切换图片</button><br/>
<img :src="showImgUrl" alt="">
<!-- 2.v-bind绑定class属性 -->
<!-- 2.1 基本绑定class -->
<h2 :class="classes">hello world</h2>
<!-- 2.2 动态绑定class支持对象语法 -->
<button :class="isActive ? 'active': '' " @click="btnClick">我是按钮</button>
<!-- 对象语法的基本使用 className: boolean-->
<button :class="{ active: isActive }" @click="btnClick">我是按钮</button>
<!-- 对象语法的多个键值对 -->
<button :class="{ active: isActive, wx: true, qq: fase}" @click="btnClick">我是按钮</button>
<!-- 动态绑定的class是可以和普通的class同时使用 -->
<button class="abc nba" :class="{ active: isActive, wx: true, qq: fase}" @click="btnClick">我是按钮</button>
<!-- 动态绑定的class可以是一个函数 -->
<button class="abc nba" :class="getDynamicClasses()" @click="btnClick">我是按钮</button>
<!-- 2.3 数组语法 -->
<h2 :class="['abc','cba']"> Hello Array</h2>
<h2 :class="['abc', className]"> Hello Array</h2>
<h2 :class="['abc', className, isActive ? 'active': '']" @click="btnClick"> Hello Array</h2>
<h2 :class="['abc', className, {active: isActive}]" @click="btnClick"> Hello Array</h2>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
imgUrl1: 'https://img1.baidu.com/it/u=1765908055,4034886621&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500',
imgUrl2: 'https://img2.baidu.com/it/u=2988589017,2923917558&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800',
showImgUrl: 'https://img1.baidu.com/it/u=1765908055,4034886621&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500',
href: "http://www.baidu.com",
classes: "abc cba nba",
isActive: false,
className: 'wx'
}
},
methods: {
switchImage() {
this.showImgUrl = this.showImgUrl === this.imgUrl1 ? this.imgUrl2 : this.imgUrl1
},
btnClick() {
this.isActive = !this.isActive;
},
getDynamicClasses(){
return { active: this.isActive, wx: true, qq: false}
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.7.2 v-bind绑定style属性
<!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>v-bind绑定style属性</title>
</head>
<body>
<div id="app">
<!--
camelCase:小驼峰命名法 thisIsMyVariable
+ 一般都是变量名、方法名
kebab-Case:短横线命名法 this-is-my-varible
+ 脊柱命名法 spinal-case、train-case
+ 对象的属性名、CSS常规的类名(BEM规范)
snake_case:蛇形命名法 this_is_my_variable
+ 大写的常量 const ERROR_TYPE = {}
PascalCase: 大驼峰命名法 ThisIsMyVariable
匈牙利命名法:变量=属性 + 类型(描述)
-->
<!-- 1.普通的html语法 -->
<h2 style="color: red; font-size: 30px;">哈哈哈</h2>
<!-- 2.style中的某些值,来自data中 -->
<!-- 2.1动态绑定style,在后面跟上 对象类型,要加引号 =重要-->
<h2 :style="{color: fontColor, 'font-size': '30px'}">哈哈哈</h2>
<h2 :style="{color: fontColor, 'font-size': fontSize}">哈哈哈</h2>
<!-- 2.2动态的绑定属性,这个属性是一个对象,不常用 -->
<h2 :style="objStyle">哈哈哈</h2>
<!-- 3.style 的数组语法 了解 -->
<h2 :style="[objStyle, { backgroundColor: 'purple'}]">嘿嘿嘿</h2>
补充:
<!-- 1. 渲染数组中最后一个被浏览器支持的值,如果浏览器本身支持不带前缀的值,那就渲染不带前缀的值-->
<button
:style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"
>confirm</button>
<!-- 2. :style使用中,vue会在运行时自动检测添加相应的前缀,如果浏览器支持无前缀的值,你写了也会去掉前缀渲染-->
<button
:style="[{ '-webkit-transition': 'opacity .3s }]"
>confirm</button>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
fontColor: "red",
fontSize: '30px',
objStyle: {
fontSize: '50px',
color: 'green'
}
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.7.3 v-bind绑定属性名
<!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>v-bind绑定属性名</title>
</head>
<body>
<div id="app">
<!-- 1.动态的属性名参数不能出现空格和引号,HTML的合法属性名不能出现空格引号 -->
<!-- <h1 v-bind:['data-' + name]="tag">Hello World</h1> -->
<h2 :[name]="'aaa'">Hello World</h2>
<!-- 2.null作为属性是无效的,你可以利用null解除属性绑定 -->
<h2 :[null]="'aaa'">Hello World</h2>
<h2 :[myAttr]="'aaa'">Hello World</h2>
<button @click="removeMyAttr">Remove my Attr</button>
<!-- 3.不能访问全局变量,受限列表 -->
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
tag: 'h1',
name: 'title',
myAttr: 'data-attr'
}
},
methods: {
removeMyAttr () {
this.myAttr = null;
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.7.4 v-bind直接绑定对象
<!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>v-bind直接绑定对象</title>
</head>
<body>
<div id="app">
<h2 :name=name :age=age :height=height>Hello World</h2>
<!-- v-bind 绑定对象 v-bind=对象-->
<h2 v-bind="infos">Hello World</h2>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
infos: { name: 'wx', age: 18, height: 1.8 },
name: 'wx',
age: 18,
height: 1.8
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.7.5 webpack4样式相关配置方案
- 全部下载依赖
yarn add sass sass-loader postcss postcss-loader autoprefixer css-loader vue-style-loader -D
- webpack.config.js配置
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// const VueLoaderPlugin = require('vue-loader/lib/plugin');
const { VueLoaderPlugin } = require('vue-loader');
const autoprefixer = require('autoprefixer');
/**
* 1. webpack各种依赖自检的版本兼容问题非常大
* 2. webpack性能优化是很困难的
* 3. 复杂配置的上手困难
*
*/
/**D
* sass less -> sass sass-loader
* postcss postcss-loader -> autoprefixer
* css-loader: 模块化解析
* vue-style-loader 放入style标签中
*/
module.exports = {
mode: 'development',
entry: './src/main.js',
output: {
path: resolve(__dirname, 'dist'),
filename: 'main.js'
},
// 引用外部文件cdn
externals: {
'vue': 'Vue'
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
autoprefixer({
overrideBrowserslist: [
"> 1%",
"last 2 versions"
]
})
]
}
}
},
'sass-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: resolve(__dirname, 'public/index.html')
})
]
};
- main.js
import './main.scss'
const App = {
data(){
return {
isActive: true
}
},
template: `
<div :class="{ active: isActive }"></div>
`,
}
// @ts-ignore
Vue.createApp(App).mount('#app');
- npm run dev
sass-loader会报错,退回yarn remove sass-loader
yarn add sass-loader@^10.1.1 -D
npm run dev
yarn remove postcss-loader
yarn add postcss-loader@4 -D
yarn remove css-loader
yarn add css-loader@^4 -D
- main.scss
div {
width: 100px;
height: 100px;
background-color: #000;
transition: background-color .3s;
&.active {
background-color: red;
}
:hover {
background-color: orange;
}
}
2.7.6 用原生实现class和style对象与数组的绑定
要实现的代码
<div id="app">
<button id="btn">Change</button>
</div>
import Vue from '../modules/Vue';
import './main.scss';
const vm = new Vue({
el: '#app',
data() {
return {
isShow: true,
hasError: false,
titleStyle: {
color: '#fff',
fontSize: '20px'
},
titleShow: true,
isContentBig: true,
subTitleColor: 'orange'
}
},
template: `
<div
:class="[
'box',
isShow ? 'show' : '',
hasError ? 'danger' : ''
]"
>
<h1
:style="[
titleStyle,
{
display: titleShow ? 'block' : 'none'
}
]"
>This is TITLE</h1>
<h2
:style="{
display: titleShow ? 'block' : 'none',
color: subTitleColor,
fontSize: '20px'
}"
>This is SUB_TITLE</h2>
<p
:class="{
danger: hasError,
big: isContentBig
}"
>This is CONTENT</p>
</div>
`
});
console.log(vm);
var oBtn = document.getElementById('btn');
oBtn.addEventListener('click', function () {
vm.hasError = true;
vm.subTitleColor = 'purple';
}, false);
main.scss
.box {
display: none;
width: 300px;
height: 300px;
background-color: #999;
&.show {
display: block;
}
&.danger {
background-color: red;
}
.big {
font-size: 30px;
}
}
Vue/index.js
import { compileAttr } from "./compile";
import { reactive } from "./reactive";
import { isObject } from './utils';
class Vue {
constructor(options) {
const { el, data, template } = options;
this.$data = data();
this.$el = document.querySelector(el);
this.$stylePool = new Map();
this.init(this, template);
}
init(vm, template) {
this.initData(vm);
this.render(vm, template);
}
initData(vm) {
const _data = vm.$data;
if (isObject(_data)) {
reactive(vm, _data);
}
}
render(vm, template) {
const container = document.createElement('div');
container.innerHTML = template;
this.compileAttrs(vm, container);
this.$el.appendChild(container);
}
compileAttrs(vm, container) {
//[div, h1, h2, p]
const allNodes = [...container.getElementsByTagName('*')];
allNodes.forEach(el => {
// 节点的属性 [:class] [:style] [:style] [:class]
const attrs = [...el.attributes];
attrs.forEach(attr => {
// attr=> :class=值 :style :style :class
// name=> :class value=> 值是 [...] 或 {...}
const { name, value } = attr;
compileAttr(vm, el, name, value);
});
el.removeAttribute(':class');
el.removeAttribute(':style');
});
}
}
export default Vue;
reactive.js
import { attrUpdadte } from "./update";
export function reactive(vm, target) {
for (let key in target) {
Object.defineProperty(vm, key, {
get() {
return target[key];
},
set(newValue) {
target[key] = newValue;
attrUpdadte(vm, key);
}
});
}
}
compile.js
import { REG_ARR, REG_OBJ, REG_SPACE } from './regular';
import { transformToKebab } from './utils';
/**
Map {
el: {
type: class/style,
expression: value
}
}
*/
export function compileAttr(vm, el, name, value) {
// 去掉空格 和 冒号
value = value.replace(REG_SPACE, '');
name = name.replace(':', '');
vm.$stylePool.set(el, {
type: name,
expression: value
});
switch (name) {
case 'class':
if (REG_OBJ.test(value)) {
// ['danger:hasError', 'big:isContentBig']
const keyValueArr = value.match(REG_OBJ)[1].split(',');
let classStr = '';
keyValueArr.forEach(item => {
const [key, value] = item.split(":");
if (vm[value.trim()]) {
// 取出所有类名
classStr += ` ${key.trim()}`;
}
});
// el: 循环到的节点 相当于给p添加类
el.setAttribute('class', classStr.trim());
} else if (REG_ARR.test(value)) {
// ['box', 'show', 'danger']
const classArr = renderArr(vm, value);
// div.box.show.danger
el.setAttribute('class', classArr.join(' ').trim());
}
break;
case 'style':
let styleStr = '';
if (REG_OBJ.test(value)) {
// {display: 'block', color: 'orange', fontSize: '20px'}
const styleObj = renderObj(vm, value);
for (let key in styleObj) {
styleStr += `${transformToKebab(key)}:${styleObj[key]};`;
}
el.setAttribute('style', styleStr);
} else if (REG_ARR.test(value)) {
// 数组[0:{color: '#fff', fontSize: '20px'}, 1:{display: 'block'}]
const styleArr = renderArr(vm, value);
styleArr.forEach(item => {
for (let key in item) {
styleStr += `${transformToKebab(key)}:${item[key]};`
}
});
//color:#fff;font-size:20px;display:block;
// console.log(styleStr);
}
el.setAttribute('style', styleStr);
break;
default:
break;
}
}
function renderArr(vm, value) {
// 执行三元表达式
const _arr = (new Function(
'vm',
`with(vm){
return ${value}
}`
))(vm);
// 过滤掉状态为false的时候的空字符串
return _arr.filter(item => item);
}
function renderObj(vm, value) {
return (new Function(
'vm',
`with(vm){
return ${value}
}`
))(vm);
}
regular.js
// 匹配空格
export const REG_SPACE = /\s+/g;
// 匹配{}
export const REG_OBJ = /^\{(.+?)\}$/;
// 匹配[]
export const REG_ARR = /^\[(.+?)\]$/;
// 匹配大写字母
export const REG_UPPERCASE = /([A-Z])/g;
update.js
import { compileAttr } from "./compile";
export function attrUpdadte(vm, key) {
const _stylePool = vm.$stylePool;
for (let [k, v] of _stylePool) {
// 包含
if (v.expression.indexOf(key) !== -1) {
compileAttr(vm, k, v.type, v.expression);
}
}
}
utils.js
import { REG_UPPERCASE } from "./regular";
export function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
export function transformToKebab(key) {
return key.replace(REG_UPPERCASE, '-$1').toLowerCase();
}
渲染结果图
2.8 v-on 事件绑定
v-on:简写“@” 实现事件绑定
原理: 给当前元素的某个事件行为,基于addEventListener实现事件绑定
div.addEventListener('click',function(ev){
//做一些特殊的事情...
change.call(vm,...); //保证方法执行中的this是vue的实例
});
2.8.1 绑定事件的基本使用
<!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>事件</title>
<style>
.box {
width: 100px;
height: 100px;
background-color: orange;
margin-top: 10px;
}
</style>
</head>
<body>
<div id="app">
<!-- 1.完整的写法 -->
<div class="box" v-on:click="divClick"></div>
<!-- 2.语法糖写法 -->
<div class="box" @click="divClick"></div>
<!-- 3.绑定的方法的位置可以写成一个表达式 -->
<h2>{{ counter }}</h2>
<button @click="increment">+1</button>
<button @click="counter++">+1</button>
<!-- 4.绑定其他方法 -->
<div class="box" @mousemove="divMousemove"></div>
<!-- 5.元素绑定多个事件 -->
<div class="box" @click="divClick" @mousemove="divMousemove"></div>
<div class="box" v-on="{ click: divClick, mousemove: divMousemove}"></div>
<div class="box" @="{ click: divClick, mousemove: divMousemove}"></div>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
counter: 100
}
},
methods: {
divClick() {
console.log('divClick');
},
increment() {
this.counter++;
},
divMousemove() {
console.log('divMousemove');
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.8.2 绑定事件参数传递
<!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>事件</title>
</head>
<body>
<div id="app">
<!-- 1. 默认传递event对象-->
<button @click="btn1Click">按钮1</button>
<!-- 2. 明确参数-->
<!--
不会像原生JS一样,btn2Click没有立即执行,还是要等待点击的时候,才会把btn2Click执行,并且传递'wx', age,类似于:div.onclick=change.bind(vm,'wx', age)
-->
<button @click="btn2Click('wx', age)">按钮2</button>
<!-- 3. 自己的参数和event对象-->
<!-- 模板中想要明确的获取event对象:$event -->
<button @click="btn3Click('wx', age, $event)">按钮3</button>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
age: 18
}
},
methods: {
// 1.默认参数:event对象
// 总结:如果在绑定事件的时候,没有传递任何的参数,那么event对象会被默认传递进来
btn1Click(event) {
console.log("btn1click:", event);
},
// 2.明确参数,不传递event也可以获取到event对象
btn2Click(name, age) {
console.log("btn2click:", name, age);
},
// 3.明确参数+event对象
btn3Click(name, age) {
console.log("btn3click:", name, age, event);
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.8.3 绑定事件的修饰符
DOM4标准的事件监听与滚屏优化
addEventListener(evetType, handler, capture || options);
options是一个对象,里面默认都是false
1. once:true 只调用一次事件处理函数,会移除当前事件的监听器
+ 第二次点击不会触发事件处理函数,但是点击之后还会有事件冒泡存在,因为事件是不需要绑定的,浏览器固然存在,冒泡也是默认的行为
2. passive:true 会报错:永远不调用阻止事件默认行为的方法 preventDefault
window.addEventListener('touchstart', function(e){
// touchstart的默认行为是 scroll滚动
// 滚屏优化:chrome firefox中默认为true window -> touchstart passive -> true
// e.preventDefault();
// console.log(123);//123
// console.log(e.defaultPrevented);// false 没有调用,证明passive是true
touchstart执行顺序 =>
1. 处理器程序执行 console.log(123);
这中间有很大性能问题 -> 等待的时间 浪费 ->导致scroll的卡顿
2. 执行默认行为 scroll
当执行e.preventDefault(),阻止滚动,所以屏幕不会滚动了
不执行e.preventDefault(),等待处理器函数执行完毕,再执行scroll
e.preventDefault();
console.log(123);//123
console.log(e.defaultPrevented);// true
},{
/*设置passive会提高滚屏性能的原因:
阻止默认行为的方法不会调用
有两个线程去处理滚动的问题,就没有等待的时间,让性能提升
1. 处理器程序的执行
2. 执行默认行为*/
// passive: true
passive:false
});
Event实例的只读属性 cancelable 表明该时间是否可以被取消,当事件被阻止后,该时间久好像没有被触发一样。如果事件不能被取消,则其cancelable属性的值为false,且事件发生时无法在事件监听回调中停止事件
在许多事件的监听回调中调用 preventDefault前,都需要检测cancelable属性的值
绑定事件的修饰符
# 事件修饰符 @click.prevent = 'xxx'
.stop 阻止事件的冒泡传播
.prevent 阻止事件的默认行为 和 .passive不能连用
.capture 让事件在捕获阶段触发「默认都是在目标和冒泡阶段触发」
.self 并没有阻碍冒泡传播机制,只是会判断事件源是不是这个元素本身,如果是我们把函数执行,反之不执行
.once 当前事件只能触发一次,第一次触发完就把事件绑定移除
# 按键修饰符 @keydown.enter/13 = 'xxx'
.enter/tab/delete/space/esc/up/down/left/right
.13
.ctrl/alt/shift/meta 目的 “.alt.63 实现组合按键「ALT+C」”
.exact 精准修饰符 例如: @keyup.ctrl 只要按下ctrl就触发,不论是否还按着其它键呢;@keyup.ctrl.exact 当前只按住ctrl键才会触发
# 鼠标修饰符
.left/right/middle
# 自定义按键修饰符
Vue.config.keyCodes.修饰符名字 = 对应的键盘码;
<!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>事件</title>
<style>
.box{
width: 100px;
height: 100px;
background-color: orange;
}
</style>
</head>
<body>
<div id="app">
<div class="box" @click="divClick">
<button @click.stop="btnClick">按钮</button>
</div>
<a href="" @click.prevent.stop='func'>
<input type="text" placeholder="请输入搜索内容" v-model='text' @keydown='func'>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
}
},
methods: {
btnClick(){
console.log('btnClick');
},
divClick(){
console.log('divClick');
},
func(ev){
alert('修饰符');
/*enter代替
if(ev.keyCode === 13){
alert('修饰符')
}
*/
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.8.4 todoList
<!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>
<body>
<div id="app">
<todo-list></todo-list>
</div>
<template id="todolist">
<div>
<div>
<input type="text" placeholder="Typing..." v-model="inputValue">
<button @click="addTodo">ADD</button>
</div>
<div>
<ul>
<template v-if="todoList.length > 0">
<li v-for="todo of todoList" :key="todo.id">
<input type="checkbox" :checked="todo.completed" @click="toggleTodo(todo.id)">
<span :style="{
textDecoration: todo.completed ? 'line-through' : ''
}">{{ todo.content }}</span>
<button @click="deleteTodo(todo.id)">DEL</button>
</li>
</template>
<template v-else>
<li>- No data available -</li>
</template>
</ul>
</div>
</div>
</template>
<script src="./js/vue.js"></script>
<script>
const todoList = {
data() {
return {
todoList: [],
inputValue: ''
}
},
template: '#todolist',
methods: {
addTodo() {
const _inputValue = this.inputValue.trim();
if (_inputValue.length === 0) return;
const hasThisContent = this.todoList.some(todo => todo.content === _inputValue);
if (hasThisContent) {
alert('This content existed in todoList');
return;
}
this.todoList.push({
id: new Date().getTime(),
content: _inputValue,
completed: false
});
this.inputValue = '';
},
deleteTodo(id) {
this.todoList = this.todoList.filter(todo => todo.id !== id);
},
toggleTodo(id) {
this.todoList = this.todoList.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
});
}
}
};
const App = {
components: {
todoList
}
};
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
2.9 v-if 条件渲染
2.9.1 需求demo
<!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>条件渲染</title>
</head>
<body>
<div id="app">
<ul v-if="names.length">
<li v-for="item in names">{{item}}</li>
</ul>
<h2 v-else>当前names没有数据,请求获取数据后展示</h2>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
names: ['abc', 'cba', 'nba']
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.9.2 v-if的使用
<!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>条件渲染</title>
</head>
<body>
<div id="app">
<!-- v-if="条件" -->
<div class="info" v-if="Object.keys(info).length">
<h2>个人信息</h2>
<ul>
<li>姓名:{{ info.name }}</li>
<li>年龄:{{ info.age }}</li>
</ul>
</div>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
info: {name: 'wx', age: 18}
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.9.3 v-else的使用
<!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>条件渲染</title>
</head>
<body>
<div id="app">
<!-- v-if="条件" -->
<!-- 此处div也可以用template代替 -->
<div class="info" v-if="Object.keys(info).length">
<h2>个人信息</h2>
<ul>
<li>姓名:{{ info.name }}</li>
<li>年龄:{{ info.age }}</li>
</ul>
</div>
<!-- v-else -->
<div v-else>
<h2>没有输入个人信息</h2>
<p>请输入信息后,再进行展示</p>
</div>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
info: {name: 'wx', age: 18}
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.9.4 v-else-if的使用
<!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>条件渲染</title>
</head>
<body>
<div id="app">
<!--这三个必须紧挨在一起-->
<h1 v-if="score > 90">优秀</h1>
<h2 v-else-if="score > 80">良好</h2>
<h3 v-else-if="score > 60">及格</h3>
<h4 v-else>不及格</h4>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
score: 40
}
},
methods: {
}
});
app.mount("#app");
</script>
</body>
</html>
2.9.5 条件渲染-阶段案例
<!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>二维码显示隐藏</title>
<style>
img{
width: 200px;
height: 200px;
}
</style>
</head>
<body>
<div id="app">
<div>
<button @click="toggle">切换</button>
</div>
<template v-if="isShowCode">
<img src="https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg" alt="">
</template>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
isShowCode: true
}
},
methods: {
toggle(){
this.isShowCode = !this.isShowCode;
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.10 v-if和v-show的区别
<!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>二维码显示隐藏</title>
<style>
img{
width: 200px;
height: 200px;
}
</style>
</head>
<body>
<div id="app">
<div>
<button @click="toggle">切换</button>
</div>
<!--
v-show和v-if的区别
1.v-show是不支持template
2.v-show不可以和v-else一起用
本质区别:
1.v-show指令值是true和false,用来控制元素的显示隐藏
+ 原理:无论值是啥,元素肯定要进行渲染,它的DOM实际都是有存在的,也就是,只是通过css的display属性来进行切换
2.也可以控制元素的显示隐藏,只不过其原理和v-show是不同的;
+ 原理:v-if当条件为false时,会用注释节点代替,其对应的原生压根不会被渲染到DOM中
如果我们的原生需要在显示隐藏之间频繁的切换,那么使用v-show
如果不会频繁的发生切换,那么使用v-if
-->
<div v-show="isShowCode">
<img src="https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg" alt="">
</div>
<div v-if="isShowCode">
<img src="https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg" alt="">
</div>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
isShowCode: true
}
},
methods: {
toggle(){
this.isShowCode = !this.isShowCode;
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.10.1 实现v-if和v-show以及生命周期函数的执行
var Vue = (function () {
function Vue(options) {
var recycles = {
beforeCreate: options.beforeCreate.bind(this),
created: options.created.bind(this),
beforeMount: options.beforeMount.bind(this),
mounted: options.mounted.bind(this)
}
recycles.beforeCreate();
// el
this.$el = document.querySelector(options.el);
this.$data = options.data();
// template 和 methods不暴露在实例上
this._init(this, options.template, options.methods, recycles);
}
Vue.prototype._init = function (vm, template, methods, recycles) {
// 实例创建完成
recycles.created();
var container = document.createElement('div');
container.innerHTML = template;
var showPool = new Map();
var eventPool = new Map();
initData(vm, showPool);
initPool(container, methods, showPool, eventPool);
bindEvent(vm, eventPool);
render(vm, showPool, container, recycles);
}
function initData(vm, showPool) {
var _data = vm.$data;
for (var key in _data) {
(function (key) {
Object.defineProperty(vm, key, {
get: function () {
return _data[key];
},
set: function (newValue) {
_data[key] = newValue;
update(vm, key, showPool);
}
})
})(key);
}
}
function initPool(container, methods, showPool, eventPool) {
var _allNodes = container.getElementsByTagName('*');
var dom = null;
for (var i = 0; i < _allNodes.length; i++) {
// 每一个原生节点
dom = _allNodes[i];
var vIfData = dom.getAttribute('v-if');
var vShowData = dom.getAttribute('v-show');
var vEvent = dom.getAttribute('@click');
if (vIfData) {
showPool.set(
dom,
{
type: 'if',
prop: vIfData
}
);
// 存储完了删除属性
dom.removeAttribute('v-if');
} else if (vShowData) {
showPool.set(
dom,
{
type: 'show',
prop: vShowData
}
);
dom.removeAttribute('v-show');
}
// console.log(showPool);
if (vEvent) {
// vEvent 事件名
eventPool.set(
dom,
methods[vEvent]
)
}
dom.removeAttribute('@click');
// console.log(eventPool); button toggle函数
}
// console.log(container.innerHTML); 此时已删除所有的v-show、v-if、@click
}
function bindEvent(vm, eventPool) {
for (var [dom, handler] of eventPool) {
// 实例挂载函数名 = 函数
vm[handler.name] = handler;
// 绑定事件处理函数
dom.addEventListener('click', vm[handler.name].bind(vm), false);
}
}
function render(vm, showPool, container, recycles) {
var _data = vm.$data;
var _el = vm.$el;
for (var [dom, info] of showPool) {
switch (info.type) {
case 'if':
info.comment = document.createComment('v-if');
!_data[info.prop] && (dom.parentNode.replaceChild(info.comment, dom));
break;
case 'show':
// 如果是假就display
!_data[info.prop] && (dom.style.display = 'none');
break;
default:
break;
}
}
recycles.beforeMount();
_el.appendChild(container);
recycles.mounted();
}
function update(vm, key, showPool) {
var _data = vm.$data;
for (var [dom, info] of showPool) {
if (info.prop === key) {
switch (info.type) {
case 'if':
!_data[key] ? (dom.parentNode.replaceChild(info.comment, dom))
: info.comment.parentNode.replaceChild(dom, info.comment);
break;
case 'show':
// 如果是假就display
!_data[info.prop] ? (dom.style.display = 'none')
: (dom.removeAttribute('style'));
break;
default:
break;
}
}
}
}
return Vue;
})();
export default Vue;
import Vue from '../modules/Vue'
const vm = new Vue({
el: '#app',
data() {
return {
isShowCode: true
}
},
beforeCreate() {
// console.log(this);// 指向Vue
console.log('beforeCreate');
},
created() {
console.log('created');
},
beforeMount() {
console.log('beforeMount');
},
mounted() {
console.log('mounted');
},
template: `
<div>
<button @click="toggle">切换</button>
</div>
<div v-show="isShowCode">
<img width="200px" src="https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg" alt="">
</div>
<div v-if="isShowCode">
<img width="200px" src="https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg" alt="">
</div>
`,
methods: {
toggle() {
this.isShowCode = !this.isShowCode;
}
}
});
console.log(vm);
2.11 v-for 列表渲染
2.11.1 v-for的基本使用
<!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>vue列表渲染</title>
<style>
.item{
margin-top: 10px;
background-color: orange;
}
.item .title{
color: red;
}
</style>
</head>
<body>
<div id="app">
<!-- 1.电影列表进行渲染 -->
<h2>电影列表</h2>
<ul>
<li v-for="item in movies">{{ item }}</li>
</ul>
<!-- 2.电影列表同时展示索引 -->
<h2>电影列表</h2>
<ul>
<li v-for="(item, index) in movies">{{ index+1 }}=={{ item }}</li>
</ul>
<!-- 3.遍历数组复杂数据 -->
<h2>电影列表</h2>
<div class="item" v-for="item in products">
<h3 class="title">{{item.name}}</h3>
<span>价格:{{item.price}}</span>
<p>秒杀:{{item.desc}}</p>
</div>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
movies: ['星际穿越', '少年派', '大话西游', '哆啦A梦'],
products: [
{id: 110, name: 'Macbook', price: 9.9, desc: '9.9秒杀,快来抢购!'},
{id: 111, name: 'iPone', price: 8.8, desc: '9.9秒杀,快来抢购!'},
{id: 112, name: '小米电脑', price: 9.9, desc: '9.9秒杀,快来抢购!'},
]
}
},
methods: {
}
});
app.mount("#app");
</script>
</body>
</html>
2.11.2 v-for的其他类型
<!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>vue列表渲染</title>
</head>
<body>
<div id="app">
<!-- 1.遍历数组 -->
<!-- 2.遍历对象:遍历对象的顺序,会按照Object.keys()的结果进行遍历 -->
<ul>
<li v-for="(value, key, index) in info">{{ value }}=={{ key }}=={{index}}</li>
</ul>
<!-- 3.遍历字符串 每一个字母和空格都会遍历到-->
<ul>
<li v-for="item in message">{{ item }}</li>
</ul>
<!-- 4.遍历数字 遍历出1到10的数字-->
<ul>
<li v-for="item in 10">{{ item }}</li>
</ul>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
message: 'hello world',
info: {name: 'wx', age: 18, height: 1.8}
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.11.3 v-for和template
<!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>vue列表渲染</title>
</head>
<body>
<div id="app">
<!-- 如果div没有实际的意义,那么可以使用template替换 -->
<div v-for="(value, key, index) in infos">
<span>{{value}}</span>
<strong>{{key}}</strong>
<i>{{index}}</i>
</div>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
infos: {name: 'wx', age: 18, height: 1.8}
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.11.4 数组更新的检测
<!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>vue列表渲染</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in names">{{item}}</li>
</ul>
<button @click="changeArray">修改数组</button>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
names: ['abc', 'cba', 'nba']
}
},
methods: {
changeArray(){
// 1.直接将数组修改为一个新的数组
// this.names = ['james', 'kobe']
// 2.通过一些数组的方法(push pop shift unshift splice reverse sort),修改数字中的元素
// this.names.push('wx');
// 3.不修改原数组的方法是不能侦听
const newNames = this.names.map(item => item +1);
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.11.5 v-for循环中的就地更新和key属性
<!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>vue列表渲染</title>
</head>
<body>
<div id="app">
<button @click="insertF">插入f</button>
<ul>
<!-- key要求是唯一的 -->
<!--
VNode(Virtual Node)虚拟节点:是一个JavaScript对象
虚拟DOM:多个VNode Tree,可以进行diff算法、跨平台
template -> 虚拟DOM -> 真实DOM,渲染在浏览器上
移动端的button... -> 移动原生控件在移动端显示出来
桌面端的一些控件
VR设备
-->
<li v-for="(item, index) in letters" :key="index">{{item}}</li>
</ul>
<!-- 就地更新案例1:span会替换,a是就地更新,只更新innerText复用-->
<div v-if="isLogin">
<span>欢迎</span>
<a href="#">Hezi</a>
</div>
<div v-else>
<a href="javascript:;" @click="isLogin = true">登录</a>
<a href="#">注册</a>
</div>
<!-- 就地更新案例2:没有加key的情况 -->
<ul>
<li v-for="(item, index) in list">
<!-- 点击删除第二项的li:将'item-3'移到'item-2'中的span中,然后删除item-3的li,这就是就地更新 -->
<span>{{ item.value }}</span>
<!-- 给三个input依次手动添加123或绑定value,vue不会跟踪input的value,已经更新的标签input值不会更新,比如删除第二项,标签已经是item-3,但是input中的值还是2 -->
<input type="text" :value="tempArr[index]" />
<!-- 子组件也不会更新 -->
<my-component :num="tempArr[index]"></my-component>
<button @click="deleteItem(index)">删除</button>
</li>
</ul>
<!-- 就地更新案例2:加key的情况input和子组件index改变,值也会改变,key尽可能不使用index,如果操作当前列表,index会随时更新-->
<ul>
<li v-for="(item, index) in list" :key="item.id" :index="index">
<span>{{ item.value }}</span>
<input type="text" :value="tempArr[index]" />
<my-component :num="tempArr[index]"></my-component>
<button @click="deleteItem(index)">删除</button>
</li>
</ul>
</div>
<script src="./js/vue.js"></script>
<script>
const MyComponent = {
props: {
num: Number,
},
template: `
<span>{{num}}</span>
`
};
const app = Vue.createApp({
components: {
MyComponent
},
data() {
return {
letters: ['a', 'b', 'c', 'd', 'e'],
list: [
{
id: 1,
value: 'item-1'
},
{
id: 2,
value: 'item-2'
},
{
id: 3,
value: 'item-3'
}
],
isLogin: false,
tempArr: [1, 2, 3]
}
},
methods: {
/**
* 『在中间插入f』
* + 在vue中,对于相同父元素节点并不会重新渲染整个列表,因为对于列表中'a', 'b', 'c', 'd', 'e'是没有变化的,在操作真实DOM的时候,只需要在中间插入一个f的li即可
* + Vue事实上会对于有key和没有key调用两个不用的方法:
* + 有key:使用patchKeyedChildren方法
* 1.如果旧节点遍历完毕,有新节点插入,那么就新增节点即可,其他的都复用
* 2.移除也会进行复用
* 3.乱序的也会进行复用原来的节点
* + 没有key:patchUnkeyedChildren方法
* + f复用c的位置,依次往后,最后一个新增
* 1. 从头部开始遍历,遇到相同的节点就继续,遇到不同的节点就跳出循环
* 2. 从尾部开始遍历,遇到相同的节点就继续,遇到不同的节点就跳出循环
* 3. 如果最后新节点更多,最后就添加新节点
* 4. 如果旧节点跟多就移除旧节点
* 5. 如果中间操作不知道如何排列的位置序列,那么就使用可以建立索引最大限度的使用旧节点
*/
insertF() {
this.letters.splice(2, 0, 'f');
},
deleteItem(index) {
this.list.splice(index, 1);
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.11.6 v-if与v-for的联合使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<ul>
<!--
不推荐“在同一元素上”使用v-if和v-for,放在一起存在优先级问题,从实例中render函数中可以看出来优先级问题
+ vue2中:v-for的优先级比v-if要高「先把v-for都循环了,循环结束后,再逐一判断v-if,把条件是false的移除掉,这样比较浪费性能」
+ vue3中:v-if的优先级要高于v-for「渲染的时候会优先渲染v-if,此时v-for还没有处理,我们没有办法拿到循环的item和index」
+ 所以,会报属性todo渲染了但是没有定义在实例上
-->
<!--<li v-for="todo of todoList" :key="todo.id" v-if="!todo.completed">
{{ todo.content }}
</li>-->
<!--选出todolist中为false的-->
<!--解决方法1:在template上写v-for包裹住li,在li写v-if-->
<!--<template v-for="todo of todoList" :key="todo.id">
<li v-if="!todo.completed">{{ todo.content }}</li>
</template>-->
<!--解决方法2:先用”计算属性“过滤符合条件的元素,然后v-for遍历这个计算属性即可-->
<!--<li v-for="todo of NotCompletedTodoList" :key="todo.id">
{{ todo.content }}
</li>-->
<!-- todoList有值就显示,没有值就不显示 -->
<!--解决方法1:如果没有值,即使写在一起也不会报错,但是不推荐这样写-->
<!--<li v-for="todo of todoList" v-if="todoList.length > 0">
{{ todo.content }}
</li>-->
<!--解决方法2:v-if用template-->
<!--<template v-if="todoList.length > 0">
<li v-for="todo of todoList">
{{ todo.content }}
</li>
</template>-->
</ul>
<!--解决方法3:v-if加在ul上(列表不存在,ul也没必要存在)-->
<ul v-if="todoList.length > 0">
<li v-for="todo of todoList">
{{ todo.content }}
</li>
</ul>
</div>
<script src="./js/vue.js"></script>
<script>
const App = {
data() {
return {
todoList: [
// {
// id: 1,
// content: 'CONTENT 1',
// completed: true
// },
// {
// id: 2,
// content: 'CONTENT 2',
// completed: false
// },
// {
// id: 3,
// content: 'CONTENT 3',
// completed: true
// }
]
}
},
computed: {
NotCompletedTodoList() {
return this.todoList.filter(item => !item.completed);
}
}
};
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
2.11.7 实现v-for的响应式和模板编译
1.我们需要实现v-for的数据响应式和模板编译 首先请看需要实现的代码
// app.js
const app = createApp({});
app.mount('#app');
2.因为我们需要用到
createApp函数,所以下一步编写此函数,然后导入到app.js中
// application.js
export function createApp(options){
return {
mount
}
}
// Hezi/index
export { createApp } from './application';
3.创建模块testA和testB
//testA
import { createReactive } from '../../Hezi';
const template = `
<ul class="list">
<h1>{{ title }}</h1>
{{ dataTime }}
<for data="list" tag="li" class="item">
<span>姓名:{ name }</span>
<span>年龄:{ age }</span>
</for>
</ul>
`
function testA() {
const state = createReactive({
title: '学生列表',
dataTime: '2023-02-03 09:05',
list: [
{
id: 1,
name: '张三',
age: 18
},
{
id: 2,
name: '李四',
age: 20
},
{
id: 3,
name: '王五',
age: 22
}
]
});
return [template, state];
}
export default testA;
//testB
import { createReactive } from '../../Hezi';
const template = `
<ul class="list">
<h1>{{ title }}</h1>
{{ dataTime }}
<for data="list" tag="li" class="item">
<span>Name:{ name }</span>
<span>Age:{ age }</span>
</for>
</ul>
`
function testB() {
const state = createReactive({
title: '老师信息列表',
dataTime: '2023-02-03 09:05',
list: [
{
id: 1,
name: '小明',
age: 26
},
{
id: 2,
name: '小红',
age: 28
},
{
id: 3,
name: '小李',
age: 30
}
]
});
return [template, state];
}
export default testB;
4.导入并注册到createApp中
//app.js
import { createApp } from './Hezi';
import testA from './components/testA';
import testB from './components/testB';
const app = createApp({
components: [
testA,
testB
]
});
app.mount('#app');
5.实现数据响应式函数
createReactive
// reactive.js
import { isObject } from "./utils";
import { proxyHandler } from "./handler";
// 数据响应式
export function createReactive(data){
return createReactiveData(data, proxyHandler);
}
// get set
function createReactiveData(data, proxyHandler){
if(!isObject(data)) return data;
return new Proxy(data, proxyHandler);
}
// Hezi/index.js
export { createApp } from './application';
export { createReactive } from './reactive';
5.1用到了两个函数isObject、proxyHandler
//utils.js
export function isObject(data) {
return typeof data === 'object' && data !== null;
}
// handler.js
import { createReactive } from "./reactive";
import { isObject } from "./utils";
const get = createGetter();
const set = createSetter();
function createGetter() {
return function (target, key, receiver) {
const res = Reflect.get(target, key, receiver);
if (isObject(res)) {
return createReactive(res);
}
return res;
}
}
function createSetter() {
return function (target, key, value, receiver) {
// @ts-ignore
return Reflect(target, key, value, receiver);
}
}
export const proxyHandler = {
get,
set
}
6.把注册的函数执行,编译模板及挂载到app上
// application.js
import { compileTemplate } from "./compile";
const domNodePool = [];
export function createApp(options) {
for (let option in options) {
switch (option) {
case 'components':
initComponents(options[option]);
break;
default:
break;
}
}
return {
mount
}
}
function initComponents(components) {
for (let component of components) {
let [template, state] = component();
const node = compileTemplate(template, state);
// 保存两个ul
domNodePool.push(node);
}
}
function mount(el) {
const app = document.querySelector(el);
const oFrag = document.createDocumentFragment();
// [ul.list, ul.list]
domNodePool.forEach(node => {
oFrag.appendChild(node);
});
app.appendChild(oFrag);
}
// compile.js
const customTags = ['if', 'for'];
const reg_sigle_bracket = /\{(.+?)\}/g;
const reg__double_bracket = /\{\{(.+?)\}\}/g;
// 编译模板
export function compileTemplate(template, data) {
template = replaceVar(template, data, reg__double_bracket);
const _node = document.createElement('div');
_node.innerHTML = template;
return compileNode(_node, data);
}
// 编译不是html标签的node
function compileNode(node, data) {
const allNodes = node.querySelectorAll('*');
allNodes.forEach(item => {
const tagName = item.tagName.toLowerCase();
if (customTags.includes(tagName)) {
replaceNode(item, tagName, data);
}
});
// console.log(allNodes);
return [...node.childNodes].find(item => item.nodeType === 1);
}
// 处理for标签
function replaceNode(node, tag, data) {
// list
const dataKey = node.getAttribute('data');
// item
const className = node.className;
// li
const realTag = node.getAttribute('tag');
switch (tag) {
case 'for':
vFor(node, data, dataKey, className, realTag);
break;
default:
break;
}
}
// 把for变为html标签
function vFor(node, data, dataKey, className, realTag) {
const oFrag = document.createDocumentFragment();
data[dataKey].forEach(item => {
// item是每一个数据 => Proxy {id: 1, name: '张三', age: 18}
const el = document.createElement(realTag);
el.className = className ? className : '';
// 替换单大括号
el.innerHTML = replaceVar(node.innerHTML, item, reg_sigle_bracket);
oFrag.appendChild(el);
});
// 循环出来的li替换原有的for
node.parentNode.replaceChild(oFrag, node);
}
// for标签数据替换
function replaceVar(html, data, reg) {
return html.replace(reg, (node, key) => {
const obj = {};
key = key.trim();
return obj[key] = data[key];
});
}
2.12 v-model
2.12.1 v-model的基本使用
<!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>vue v-model</title>
</head>
<body>
<div id="app">
<!-- 1.手动实现双向绑定 -->
<!--
v-model原理:v-bind 绑定value属性的值,v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中
当type为text或textarea时,使用value属性和input事件;
当type为checkbox或radio时,使用value属性和change事件;
select框,使用value属性和change事件。
-->
<!-- <input type="text" :value="message" @input="changeInput" /> -->
<!-- 2.v-model实现双向绑定 -->
<!-- <input type="text" v-model="message" /> -->
<!-- 3.登录功能 -->
<label for="account">
账号:<input type="text" id="account" v-model="account" />
</label>
<label for="password">
密码:<input type="password" id="password" v-model="password" />
</label>
<button @click="loginClick">登录</button>
<h2>{{message}}</h2>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
message: 'Hello Vue',
account: '',
password: ''
}
},
methods: {
changeInput(event){
this.message = event.target.value
},
loginClick(){
const account = this.account
const password = this.password
console.log(account, password);
// todo...
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.12.2 v-model绑定textarea
<!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>vue v-model</title>
</head>
<body>
<div id="app">
<textarea cols="30" rows="10" v-model="content"></textarea>
<p>输入的内容:{{content}}</p>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
content: ''
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.12.3 v-model绑定checkbox
<!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>vue v-model</title>
</head>
<body>
<div id="app">
<!--
复选框的处理操作:
+ 给每个复选框设置value
+ 创建一个状态,值是一个数组,数组中包含选中复选框的value值
+ 给复选框的v-model绑定相同的状态
原理:
+ 拿现有的状态值「数组」和每个复选框进行匹配,如果数组中包含了某个复选框的value值,则此复选框默认选中即可!!
+ 给每个复选框监听change事件,在事件触发后,判断复选框的选中状态,根据是否选中,从状态数组中新增或者移除对应的value值!!
-->
<!-- 1.checkbox单选框:绑定到属性中的值是一个boolean -->
<label for="agree">
<input type="checkbox" id="agree" v-model="isAgree">同意协议
</label>
<h2>单选框的值:{{isAgree}}</h2>
<!-- 2.checkbox多选框:绑定到属性中的值是一个数组 -->
<!-- 注意:多选框当中,必须明确的绑定一个value值 -->
<!-- 绑定同一个hobbies,可以不同name分组 -->
<div class="hobbies">
<h2>请选择你的爱好:</h2>
<label for="sing">
<input id="sing" value="sing" type="checkbox" v-model="hobbies">唱
</label>
<label for="dance">
<input id="dance" value="dance" type="checkbox" v-model="hobbies">跳
</label>
<label for="rap">
<input id="rap" value="rap" type="checkbox" v-model="hobbies">rap
</label>
<label for="baskteball">
<input id="basketball" value="basketball" type="checkbox" v-model="hobbies">篮球
</label>
<h2>爱好:{{hobbies}}</h2>
</div>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
isAgree: false,
hobbies: []
}
},
methods: {
}
});
app.mount("#app");
</script>
</body>
</html>
2.12.4 v-model绑定radio
<!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>vue v-model</title>
</head>
<body>
<div id="app">
<!-- 绑定同一个v-model,可以不同name区分 -->
<div class="gender">
<label for="male">
<input type="radio" id="male" v-model="gender" value="male">男
</label>
<label for="female">
<input type="radio" id="female" v-model="gender" value="female">女
</label>
<h2>性别:{{gender}}</h2>
</div>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
gender: 'female'
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.12.5 v-model绑定select
<!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>vue v-model</title>
</head>
<body>
<div id="app">
<!-- select单选 -->
<select v-model="fruit">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<h2>单选:{{fruit}}</h2>
<!-- select多选 -->
<select multiple size="3" v-model="fruits">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<h2>多选:{{fruits}}</h2>
<!--
下拉框的使用:
+ 指定好对应的option选项和每个选项的value
+ 创建一个状态,如果是单选,状态值是一个值,如果是多选,状态值是一个数组「值都是每个选项的value值」
+ 给select设置v-model等于状态值即可
原理:
+ 把状态值赋值给select的value,这样就可以让指定value的配置项默认选中
+ 监听change事件,在选择改变的时候,获取最新选择项的value值,去修改对应的状态
-->
<select @change="selectValue" :value="fruit">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<h2>单选:{{fruit}}</h2>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
fruit: 'orange',
fruits: []
}
},
methods: {
selectValue(e) {
this.fruit = e.target.value;
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.12.6 v-model的值绑定
<!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>vue v-model</title>
</head>
<body>
<div id="app">
<!-- 1.select的值绑定 -->
<select multiple size="3" v-model="fruits">
<option v-for="item in allFruies"
:key="item.value"
:value="item.value"
>{{item.text}}</option>
</select>
<h2>多选:{{fruits}}</h2>
<!-- 2.checkbox的值绑定 -->
<div class="hobbies">
<h2>请选择你的爱好:</h2>
<template v-for="item in allHobbies" :key="item.value">
<label :for="item.value">
<input :id="item.value" :value="item.value" type="checkbox" v-model="hobbies">{{item.text}}
</label>
</template>
<h2>爱好:{{hobbies}}</h2>
</div>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
// 水果
allFruies: [
{value: 'apple', text: '苹果'},
{value: 'orange', text: '橘子'},
{value: 'banana', text: '香蕉'}
],
fruits: [],
// 爱好
allHobbies: [
{value: 'sing', text:"唱"},
{value: 'dance', text:"跳"},
{value: 'rap', text:"rap"},
{value: 'basketball', text:"篮球"}
],
hobbies: []
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.12.7 v-model的修饰符
<!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>vue v-model</title>
</head>
<body>
<div id="app">
<!-- 1.lazy修饰符:默认v-model绑定input事件,输入后就会把最新的值和属性绑定同步,如果加上lazy,会绑定change事件,只有提交或者回车才会触发 -->
<input type="text" v-model.lazy="message">
<h2>message:{{message}}</h2>
<!-- 2.number:自动将内容转化为数字 -->
<input type="text" v-model.number="counter">
<h2>counter: {{counter }}-{{typeof counter}}</h2>
<input type="number" v-model="counter2">
<h2>counter2: {{counter2 }}-{{typeof counter2}}</h2>
<!-- 3.trim:去除首位的空格 -->
<input type="text" v-model.trim="content">
<h2>content:{{content}}</h2>
</div>
<script src="./js/vue.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
message: 'xxx',
counter: 0,
counter2: 0,
content: ''
}
}
});
app.mount("#app");
</script>
</body>
</html>
2.13 v-slot
后续介绍......
2.14 自定义指令
三、JS铺垫
3.1 ES5-ES6贯穿对象深拷贝问题
ES5深拷贝
// ES5深拷贝
/**
* 对象深拷贝
* @param {拷贝对象} origin
* @param {目标对象} target
* @returns target
*/
function deepClone(origin, target){
// target是否存在 如果不存在创建空对象
var tar = target || {},
// 判断是否为引用数据类型
toStr = Object.prototype.toString,
arrType = '[object Array]';
// 循环获取对象的key
for(var k in origin){
// 排除origin的公有属性
if (origin.hasOwnProperty(k)){
// 判断是一个对象
if (typeof origin[k] === 'object' && origin[k] !== null) {
tar[k] = toStr.call(origin[k]) === arrType ? [] : {};
// 再次深克隆
deepClone(origin[k], tar[k]);
} else {
// 如果不是对象就直接复制
tar[k] = origin[k];
}
}
}
return tar;
}
var obj = {
name: '小王',
age: 18,
info: {
hobby: ['travel', 'piano', { a: 1 }],
career: {
teacher: 4,
engineer: 9
}
}
};
const newObj = deepClone(obj, {});
console.log(obj === newObj);// false
console.log(obj, newObj);
newObj.info.hobby[2].a = 123;
WeakMap 的作用
// Map 键名:任意类型,一般的对象,键名只能是字符串、数字
// WeakMap 键名:只能是对象
案例:
const oBtn1 = document.querySelector('#btn1');
const oBtn2 = document.querySelector('#btn2');
oBtn1.addEventListener('click', handleBtn1Click, false);
oBtn2.addEventListener('click', handleBtn2Click, false);
function handleBtn1Click (){}
function handleBtn2Click (){}
// 需要移除节点
oBtn1.remove();
oBtn2.remove();
// 手动垃圾回收,weakmap就是解决这个事情的
handleBtn1Click = null;
handleBtn2Click = null;
//========================
const oBtnMap = new WeakMap();
// 键名是弱引用,如果键名和外部断开连接了,则自动回收键名和键值
oBtnMap.set(oBtn1, handleBtn1Click);
oBtnMap.set(oBtn2, handleBtn2Click);
oBtn1.addEventListener('click', oBtnMap.get(oBtn1), false);
oBtn2.addEventListener('click', oBtnMap.get(oBtn2), false);
oBtn1.remove();
oBtn2.remove();
ES6深拷贝
/**
* ES6深拷贝
* function(){} 静态的不拷贝
*/
function deepClone(origin){
// 基本类型值
if (origin == undefined || typeof origin !== 'object') {
return origin;
}
if (origin instanceof Date){
return new Date(origin);
}
if (origin instanceof RegExp){
return new RegExp(origin);
}
// origin的构造器会创建一个新的数组或对象,当前的target就是新的对象
const target = new origin.constructor();
for(let k in origin){
if (origin.hasOwnProperty(k)){
target[k] = deepClone(origin[k]);
}
}
return target;
}
const newObj = deepClone(obj);
newObj.info.hobby[2].a = 123;
console.log(obj, newObj);
存在循环调用的情况造成死循环,用weakmap记录防止死循环
function deepClone(origin, hashMap = new WeakMap()){
// 基本类型值
if (origin == undefined || typeof origin !== 'object') {
return origin;
}
if (origin instanceof Date){
return new Date(origin);
}
if (origin instanceof RegExp){
return new RegExp(origin);
}
// 在每次进来的时候获取origin
const hashKey = hashMap.get(origin);
// 判断存不存在,存在返回就行
if (hashKey){
return hashKey;
}
// origin的构造器会创建一个新的数组或对象,当前的target就是新的对象
const target = new origin.constructor();
// 记录origin target
hashMap.set(origin, target);
for(let k in origin){
if (origin.hasOwnProperty(k)){
target[k] = deepClone(origin[k], hashMap);
}
}
return target;
}
const newObj = deepClone(obj);
newObj.info.hobby[2].a = 123;
console.log(obj, newObj);
let test1 = {};
let test2 = {};
test2.test1 = test1;
test1.test2 = test2;
console.log(deepClone(test2));
3.2 JS 中循环的6种方式
JS中循环的几种方式:for循环、while循环、do while循环 | for循环 | forin循环| for of循环
let arr = [10, 20, 30, 40],
obj = {name: 'wx',year: 10, 1: 100};
Object.prototype.AA = 12;
for(let key in obj){
// key遍历的属性名
// obj[key]属性值
// 优先遍历属性名为数字的
// if(!obj.hasOwnProperty(key)) break;会把所属类原型上自定义的属性方法也遍历到
}
for( let key in arr){
if(!obj.hasOwnProperty(key)) break;
console.log(key, arr[key]);
}
ES6新增for of循环
+ 获取的不是属性名是属性值
+ 不会遍历原型上公有的属性方法(哪怕是自定义的)
+ 只能遍历可被迭代的数据类型值(Symbol.iterator):Array/Sting/Arguments/NodeList/Set/Map等,但是普通对象是不可被迭代的,所以不能用for of循环
for(let item of arr){
console.log(item);
}
for(let item of obj){
console.log(item);
}