From:https://blog.csdn.net/weixin_42371679/article/details/112408800
vue 源码阅读解析:https://zhuanlan.zhihu.com/p/419896443
深入理解vue底层原理:https://blog.csdn.net/dream2222222222/article/details/103256281
Vue 源码解读 ( 系列 ):https://juejin.cn/user/1028798616461326/posts
视频:https://search.bilibili.com/all?keyword=Vue.js源码全方位深入解析
一、安装
vue 爱好者:https://vue3js.cn/
vue 官网、文档、指南、教程
- vue2 :https://v2.cn.vuejs.org/
- vue3 :https://cn.vuejs.org/
二、基础练习
1)引入vue.js
解释:
注意:被挂载的 div 要放在 vue 的上面,否则 vue 管不到
2)v-for
3)做一个计数器(v-on:click)
方法一:直接在标签里实现
<div id="app">
<h2>当前计数: {{count}}</h2>
<button v-on:click="count++">+</button>
<button v-on:click="count--">-</button>
</div>
<script src="../js/vue.js"></script>
<script>
const bpp=new Vue({
el: "#app",
data : {
count: 0,
movices: ['one','two','three']
}
})
</script>
方法二:采用函数(如果代码太多的话)
三、vue 中的 mvvm
四、vue的生命周期
五、各种标签
1)插值操作
mustache 语法(双大括号语法)
2)v-once
加了 v-once 后,值就不会改变了
运行结果:
3)v-html
会将传递过来的html进行解析
运行结果:
4)v-pre 不让 {{}} 被 vue 解析
5)v-cloak
在 div 中加入 v-cloak 属性,可以让 div 在 vue 没解析之前不显示。v-cloak 在 vue 解析完之后就会消失
6)v-bind
绑定 url
可以将 "vue 中的属性" 挂载到 "标签的属性"
绑定 class
动态绑定 class:可以绑定是否将class属性添加到标签中
另一种比较简洁的写法
绑定 style
绑定 disable
<!--当item为true时,button不起作用-->
<button :disable="item">按钮</button>
7)v-on 用于进行事件监听
v-on 的简单使用
<body>
<div id="app">
<h2>当前计数: {{count}}</h2>
<button v-on:click="add">+</button>
<button v-on:click="mid">-</button>
</div>
<script src="../js/vue.js"></script>
<script>
const bpp=new Vue({
el: "#app",
data : {
count: 0,
movices: ['one','two','three']
},
methods: {
add: function () {
console.log("add被执行");
this.count++
},
mid: function () {
console.log("mid被执行");
this.count--
}
}
})
</script>
</body>
v-on 的语法糖
<button @cick="add">+</button>
<button @cick="incr">-</button>
v-on 的参数问题
<body>
<div id="app">
<button @click="print('hello')">按钮</button>
</div>
<script src="../js/vue.js"></script>
<script>
const bpp=new Vue({
el: "#app",
data : {
count: 0,
movices: ['one','two','three']
},
methods: {
print(name){
console.log(name)
}
}
})
</script>
</body>
如果函数不是写在{{}}里面或者函数没有参数,则()可以省略
<button @click="print">按钮</button>
如果函数中有参数,但是没有传入参数的话,那么就将undefined传进去
<button @click="print()">按钮</button>
如果函数中有参数,但是没有写()那么将点击事件传进去
<button @click="print">按钮</button>
如果既要传递参数,又要获得event
<body>
<div id="app">
<button @click="print('hello',$event)">按钮</button>
</div>
<script src="../js/vue.js"></script>
<script>
const bpp=new Vue({
el: "#app",
data : {
count: 0,
movices: ['one','two','three']
},
methods: {
print(name,event){
console.log(name+'-----'+event)
}
}
})
</script>
</body>
v-on 的修饰符问题
.stop 停止该按钮的作用。停止某个事件的作用,如果不加 stop 修饰的话,点击按钮那某 divClick 也会起作用,这不是想要的,所以可以加上 stop 就阻止了
.prevent 阻止默认事件的发生。该例子阻止了表单的提交
监听某个键帽的作用。当按下回车的时候,才让函数起作用
.once。让点击事件只起一次作用
8) v-if、v-else-if、v-else
9)v-show 和 v-if
<body>
<div id="app">
<h2 v-if="isShow">你好呀</h2>
<h2 v-show="isShow">你好呀</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:"#app",
data:{
isShow:true
}
})
</script>
</body>
当 isShow 为 false 时,v-if 是直接被清除了,
而 v-show 则是多了个style=“display:none”
10)v-for
遍历数组
<body>
<div id="app">
<ul>
<li v-for="item in movices">{{item}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:"#app",
data:{
movices:['喜羊羊与灰太狼','熊出没','海贼王']
}
})
</script>
</body>
遍历对象
<body>
<div id="app">
<ul>
<li v-for="(value,key) in info">{{value}}-{{key}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:"#app",
data:{
movices:['喜羊羊与灰太狼','熊出没','海贼王'],
info:{
name:"张三",
sex:"男",
age:180
}
}
})
</script>
</body>
v-for 绑定 和 非绑定key的区别
<div id="app">
<ul>
<li v-for="item in movices" key="item">{{item}}</li>
</ul>
</div>
示例:
将数组遍历到页面上,然后点击
11)v-model
双向绑定
双向绑定:就是对属性值进行改变时,页面中展示的值跟着改变,对页面展示的值进行改变时,属性值也跟着改变。也就是:数据变化更新视图,视图变化更新数据
v-model 绑定各种类型
原理:v-model 其实就是一个语法糖,它的背后本质上是包含两个操作:
- 1、v-bind 绑定一个value属性
- 2、v-on 指令给当前元素绑定input事件
绑定 text 类型:
<body>
<div id="app">
<input type="text" :value="message" @input="btnClick">
{{message}}
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:"#app",
data:{
message:"你好啊"
},
methods:{
btnClick(event){
this.message=event.target.value
}
}
})
</script>
</body>
v-model 绑定 radio 类型:
<body>
<div id="app">
<input type="radio" v-model="sex" id="sex" value="男">男
<input type="radio" v-model="sex" id="sex" value="女">女<br/>
<h2>您选择的性别是:{{sex}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:"#app",
data:{
message:"你好啊",
sex:''
}
})
</script>
</body>
绑定 checkbox:
<body>
<div id="app">
<h1>单选框</h1>
<input type="checkbox" v-model="agree">同一协议<br/>
<h2>您的选择是{{agree}}</h2>
<button :disable="!agree">下一步</button>
<h1>多选框</h1>
<input type="checkbox" value="篮球" v-model="hobbies">篮球<br/>
<input type="checkbox" value="足球" v-model="hobbies">足球<br/>
<input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球<br/>
<input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球<br/>
<h2>您的选择是:{{hobbies}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:"#app",
data:{
agree:false,
hobbies:[]
}
})
</script>
</body>
绑定 select:
<body>
<div id="app">
<select v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="菠萝">菠萝</option>
<option value="橘子">橘子</option>
</select>
<h2>您的选择是:{{fruit}}</h2>
<select v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="菠萝">菠萝</option>
<option value="橘子">橘子</option>
</select>
<h2>您的选择是:{{fruits}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:"#app",
data:{
fruit:"香蕉",
fruits:[]
}
})
</script>
</body>
值绑定:
就是动态地给 value 赋值,可以通过 v-bind 动态地给 value 绑定值
<body>
<div id="app">
<label v-for="item in books">
<input type="checkbox" :value="item" v-model="books">{{item}}<br/>
</label>
<h2>书籍有:{{books}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:"#app",
data:{
books:['数据结构与算法','算法导论','编译原理','计算机网络','计算机组成原理']
}
})
</script>
</body>
v-model 修饰符
.lazy 这个修饰符让v-model不用实时绑定,而是等到回车的时候才进行绑定
<input type="text" v-model.lazy="message">{{message}}
.number 这个修饰符让输入框中的值保持是数字,如果没有加这个修饰符那么即使让它的初始化是数字,输入多个数字时它会自动转为string类型
<input type="number" v-model.number="age">{{age}}--{{typeof age}}
.trim 这个修饰符用于去除左右两边的空格
<input type="text" v-model.trim="name">
六、计算属性
计算属性的优点:计算属性是有缓存的
使用它时不需要加(),因为它是当做一个属性来使用的
1)计算属性的简单使用
computed 属性的复杂操作
2)计算属性的 setter 和 getter
计算属性本质上是长这样的
computed:{
fullName:{
setter:function () {
},
getter:function () {}
}
}
但是,一般是不写 setter 方法的,所以只是这样的
computed:{
fullName:{
getter:function () {}
}
}
为了简写,所以一般看到的都是这样的
computed:{
fullName:function () {}
}
}
3)计算属性 和 methods 的比较
- 计算属性是有缓存的,如果要多次显示计算属性的内容,计算属性只会被调用一次,
- 而 methods 是没有缓存的,显示多少次,methods 就被调用多少次。
4)var 和 let 的比较
var 是函数作用域,let是块级作用域
var a = 1;
{
var a = 2;
//...
}
//...
对于一些开发者来说,希望使用大括号为任意代码块创建一个局部环境,但在JavaScript中,只有函数能够创建新的作用域。在上述代码中,代码块中的变量 a 其实引用的是代码块外部的那个同名变量,实际上,虽然 JavaScript 用于使用大括号创建代码块,但其作用仅限于在 if 语句或循环语句中组织代码体而已。在 ES6 中引出let关键字,就是为了解决这一问题。
var 是函数作用域,let 是块级作用域。比如在 for 循环内,用 var 声明变量,在 for 循环外也可以访问到,但 let 不同,若使用 let 在 for 循环内声明变量,那么在外部是访问不到的。如下:
//使用var声明变量
for (var i = 0; i < 10; i++) { }
console.log("i的值=" + i)
//使用let声明变量
for (let j = 0; i < 10; i++) { }
console.log("j的值=" + j)
var 存在变量提升,let 不存在变量提升
顶层对象:在浏览器中指的是 window,在 node 环境中指的是 global 对象。
- var 声明的变量会被提升到 顶端对象,但是赋值的部分不会提升。( var 声明的变量属于顶层对象window,因此可以通过 window. 变量名 来访问这些变量, )
- let 声明的变量不属于顶层对象。( let 和 const 声明的变量不能 顶层对象访问。 )
在使用关键字 var 声明变量时是存在的,var 可以先使用再声明,在变量没有被声明的时候,其值为 undefined;但在使用 let 关键字声明变量时,必须先声明再使用,否则会报错。如下:
//var的使用
console.log("还未被定义的a=" + a)
var a = 1;
console.log("已被定义的a=" + a)
//let的使用
console.log("还未被定义的b=" + b)
let b = 1;
console.log("已被定义的b=" + b)
var 变量可以重复声明,let 变量不可以重复声明
//var
var a = 1;
var a = 2;
console.log(a)
//let
let b = 1;
let b = 2;
console.log(b) //报错
5)闭包
在《JS高级程序设计-第3版》中对闭包解释是:"闭包是指有权访问另外一个函数作用域中的变量的函数。" 闭包函数可以访问[包裹其的函数]内的各种参数和变量,即便外部函数已经执行完毕
官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
闭包的特点:
- 1. 作为一个函数变量的一个引用,当函数返回时,其处于激活状态。
- 2. 一个闭包就是当一个函数返回时,一个没有释放资源的栈区。
简单的说,javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。
为什么要闭包
- 使外部得以访问函数内部的变量;
- 避免全局变量的使用,防止全局变量污染(匿名函数);
- 让某些关键变量得以常驻内存,免于被回收销毁(闭包函数);
让某些变量得以常驻内存
JavaScript 是自带垃圾回收机制的,对于函数来说,在其执行完毕后会被垃圾回收机制回收来进行内存释放,函数内部的局部对象 (各种变量之类) 也会被连同销毁,使内存中仅仅保存全局作用域。那么怎么让变量得以常驻内存 ?方法就是:将立即执行函数 与 闭包 结合。
1.原理
"闭包函数" 就是一个嵌套在父函数里面并且有使用父函数变量的子函数,闭包函数 的执行必定依赖于父函数提供的数据,但要是调用闭包函数时父函数已经被销毁,闭包函数怎么执行呢 ?
答案是没法执行,因为闭包函数所依赖的变量也都被销毁,总不能因为要执行闭包函数再把父函数提出来,不太合理。
所以不能就这么回收掉,但是保存整个父函数又有点离谱,所以 JavaScript 垃圾回收机制只会保存闭包函数在父函数中所依赖到的变量,这些被保存起来的变量不会被内存回收器回收,直到确认子函数不会再被调用 ( 子函数被删除或者失去指针 )为止;
类比一下,如果你学过 webpack 的话,应该会知道 webpack 在打包一个文件的时候会把这个文件依赖的所有文件一起打包,就是为了防止使用的时候出问题,垃圾回收机制是在删减的时候留下需要的,weboack 是在打包的时候加上需要的。
2. 为什么是 "立即执行函数" ?
为什么推荐用立即执行函数来配合闭包进行变量保存…
一开始我猜为了在闭包函数保存完需要的变量后父函数能被及时回收释放内存,才采用了匿名立即执行函数来作为闭包函数的父函数。因为立即执行函数自我回调执行完成后会被立即销毁回收,用一次就释放,节约内存 ( 但因为销毁快,外界无法引用其内部的变量 ),后来看到了一个例子,作者将使用了立即执行函数的闭包和没有使用立即执行函数的闭包进行了比较,让我改变了想法:
// 例1 :这个例子中没有使用立即执行函数;
function createFunction() {
var Array = [];
for( var i = 0; i<10; i++) {
//将函数赋值给每个数组元素;
Array[i] = function() {
return i;
};
}
return Array;
}var aa = createFunction();
console.log(aa[0]()); //10
console.log(aa[1]()); //10
由于作用域链的配置机制,因为每个函数的作用域链中保存的都是 createFunctions() 的活动对象,所以每个函数引用的都是活动对象中的同一个变量 i。
( 活动对象: 在JavaScript中,当一个函数被创建时最后一步便是活动对象推入作用域链,函数中访问一个变量时会从作用域链中搜索具有相应名字的变量,函数执行完后局部活动对象会被销毁,活动对象中包含了参数列表和arguments对象等属性。活动对象包含变量对象所有的属性 )
当 createFunctions() 函数执行结束返回后,变量 i 的值就已经固定为10,而每个函数保存的变量对象里的 i 都出自 createFunctions() 的活动对象,每个函数拿到的 i 都是出自同一个活动对象的,都一样,所以最后不论输出哪个数组元素得到的都是10.。
即说明了闭包中所保存的是整个活动对象,而不是某个具体的变量,这种机制并不是我们想要的,我们希望它能把每个变量单独保存下来,所以就有了能解决这个问题的,使用了立即执行函数的例子,即 例2:
function createFunction() {
var result = new Array();
for( var i = 0; i<10; i++) {
result[i] = function(num) {
//每接收一个num就会创建新的一个函数作用域;
return function() {
//在每个作用域的内部创建并返回一个返回num的闭包函数
return num;
};
}(i);
//变量i的当前值会作为实参赋值给上面的形参num;
}
return result;
}
//在外部使用函数内变量;
var bb = createFunction();
alert(bb[0]()); //0
alert(bb[1]()); //1
闭包函数依赖到了外部立即执行函数的 num,所以 num 会连同闭包函数被保存下来免于销毁,这样 result[ ] 中被赋值进去的每个函数都能返回一个自己的 num,我们的目的就能达到了,完成这一目标的关键就是使用了立即执行函数。
这个闭包函数的父函数函数每接收一个 num 就会创建新的一个函数作用域(见例3),作用域中传入i后,变量 i 的当前值会作为实参赋值给上面的形参 num,而在当前每个作用域的内部,又创建并返回了一个返回 num 的闭包函数。这样一来传入每个函数作用域中闭包函数的 num 就是不同的了。如此类推,被赋值进入 result 数组中的每个函数作用域都有一个自己 num (其实是时 num 副本),可以返回各自不同的数值了。
for(var i = 0; i < 5; i++) {
abc(i);
};function abc(i) {
setTimeout(function() {
console.log(i); // 0 1 2 3 4
}, 200);
}
//这里就相当于创建了5个函数作用域;
可见立即执行函数在保存变量时泛用性比普通函数强;
外部访问函数内变量跟立即执行函数没什么必然关系,不使用立即执行函数也可以进行保存,上面说到的结合立即执行函数的写法只是针对某些特殊情况下无法依据需求保存变量的问题,我们不得不承认立即执行函数泛用性好一些。在外部调用父函数即可拿到闭包函数内的变量。
//这两种写法是会报错的;
(function() {
//函数体;
})();function() {
//函数体;
}();
JavaScript引 擎先看到了你的 "function" 关键字,然后就开始以函数声明标准规范你后续的代码,最终 JavaScript 引擎发现你用一个小括号结束了你的函数,它觉得这是错的。
我们不能否定它判定的规则,人家认为写了 function 就是要声明函数,那我们就不要上来直接写function了:
var myFunction = function () {
/* 函数体 */
}();
var myObj = {
myFunction: function () { /* 函数体 */ }
}();
让 JavaScript 引擎先看到小括号而不是 function 关键字,它就会觉得你在写函数表达式,也就判定为合理了;
总结
闭包会造成内存泄漏。闭包会把一些东西永驻内存,而且前面提到的它所依赖的东西都不会被销毁,自己的局部活动对象和依赖到的活动对象都会被包含到它自己的作用域链里,所以它的体量往往是比普通函数大上老些。
6)ES6字面量增强写法
ES5 中,对象的书写形式
<script>
const name='hello'
const age=20
const height=1.80
const person={
name:name,
age:age,
heigth:height
}
console.log(person)
</script>
ES6 中,对象的书写形式
<script>
const name='hello'
const age=20
const height=1.80
const person={
name,
age,
height
}
console.log(person)
</script>
ES5 中,对象中方法的写法:
const demo={
run:function () {
}
}
ES6 中,对象中方法的写法:
const demo={
run(){}
}
七、数组中哪些方法是响应式的
响应式就是直接在控制台上对数组中的元素进行增删改查时,页面中的展示会跟着变化。
1)push方法
从最后一位添加元素,可以一次性添加多个值
2)pop()
从最后一位删除元素
3)shift()
从第一位删除元素
4)unshift()
从第一位添加元素,可以一次性传入多个值unshift(…num)
5 )splice()
splice的作用:
删除:
splice(start,删除的元素个数) ,表示从start开始,要删除多少个元素;
splice(start)表示删除start开始后面的元素
替换:
splice(start,end,替换的元素)表示从start开始删除end个元素,然后再插入替换元素,可以替换多个元素
6)sort()
排序
7)reverse()
反转
八、过滤器
可以对内容进行修饰
格式:{{原本的内容 | 过滤器}}
例子:想要对价格的显示进行一些修饰,在前面添加¥符号和让它有两位小数显示
filters:{
priceFilter(price){
return '¥'+price.toFixed(2)
}
}
九、JavaScript 高阶函数
1)filter
对数组中的数据进行过滤,满足条件的则放到新数组中
const nums=[20,40,80,100,111,212,212]
//filter
const newNum1=nums.filter(function (n) {
return n<100
})
2)map
对数组中的数据进行处理后放到新数组里面
const nums=[20,40,80,100,111,212,212]
//2、map
const newNum2=newNum1.map(function (n) {
return n*2
})
3)reduce
对数组中的数据进行累加
const nums=[20,40,80,100,111,212,212]
//3、reduce
const newNum3=nums.reduce(function (previousValue,n) {
return previousValue+n
},0) //初始值是0
十、组件
组件使用的步骤:
- 1)创建组件构造器
- 2)注册组件
- 3)使用组件
例子:
<body>
<div id="app">
<my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>
//1、注册组件构造器
const conC=Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈</p>
<p>我是内容,呵呵呵呵</p>
</div>`
})
//2、注册组件
Vue.component('my-cpn',conC)const app=new Vue({
el:"#app",
data:{}
})
</script>
</body>
1)全局组件 和 局部组件
它们的区别在于注册的方式
- 全局组件 :可以被不同的 Vue实例 中的 dom 中使用
- 局部组件 :只能在注册这个组件的 Vue 实例中的 dom 中使用
全局组件的注册方式
Vue.component('my-cpn',conC)
局部组件的注册方式
在 vue 实例中注册
const app=new Vue({
el:"#app",
data:{
cpn:conC
}
})
2)父组件 和 子组件
子组件在父组件的组件构造器中注册
3)组件注册的语法糖格式
不再需要单独写template,而是将组件构造器里的东西写到注册里面
4)模板抽离
前面注册的时候模板都是写在注册里的,非常难看,我们可以通过标签将模板写在这里面
5)组件中的数据访问
- 子组件 不能直接访问 Vue实例里面的数据
- vue组件 应该有自己保存数据的地方
- 组件中的 data 是 function 格式的
6)组件中 data 只能是函数
因为一个组件一般都是被使用多次的,而使用函数的话它是会返回一个对象的,然后不同函数返回的对象就是不一样的,就不会导致每个组件都是在操作同一个数据,避免了数据的错误。如果它是用对象的话,那么多个组件就是在操作同一个数据,造成数据的错误。
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>当前计数:{{counter}}</h2>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="../../js/vue.js"></script>
<script>
Vue.component('cpn',{
template:"#cpn",
data(){
return{
counter:0
}
},
methods:{
increment(){
this.counter++
},
decrement(){
this.counter--
}
}
})
const app=new Vue({
el:"#app"
})
</script>
</body>
7)父子组件通信
组件之间经常协同工作,通常父子组件会是这样的关系:组件 A 在它的模版中使用了组件 B 。它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件。
在 Vue.js 中,父子组件的关系可以总结为 props down, events up 。
- 父组件通过 props 向下传递数据给子组件,
- 子组件通过 events 给父组件发送消息。
父传子 ( 通过 props )
子传父
8)props 中的驼峰标识问题
如果组件中的 data 是驼峰标识的,那么 dom 中需要用 - 来表示,否则会出错。
9)父子组件通信 --- 结合双向绑定
要求:将父组件中的数据传递到子组件中,然后在子组件中对数据进行改变后,可以将改变后的数据传递到父组件中。同时,要求父组件中显示的数据是子组件的2倍。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>父子组件通信-双向绑定</title>
</head>
<body>
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"></cpn>
</div>
<template id="cpn">
<div>
<p>data:{{dnumber1}}</p>
<p>props:{{number1}}</p>
<input type="text" :value.number="dnumber1" @input="number1input" />
<p>data:{{dnumber2}}</p>
<p>props:{{number2}}</p>
<input type="text" :value.number="dnumber2" @input="number2input" />
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app=new Vue({
el:"#app",
data:{
num1:0,
num2:1
},
components:{
cpn:{
template:"#cpn",
props:{
number1:Number,
number2:Number
},
data(){
return{
dnumber1:this.number1,
dnumber2:this.number2
}
},
methods:{
number1input(event){
this.dnumber1=event.target.value;
this.$emit('num1change',this.dnumber1);this.dnumber2=this.dnumber1*100;
this.$emit('num2change',this.dnumber2)
},
number2input(event){
this.dnumber2=event.target.value;
this.$emit('num2change',this.dnumber2);this.dnumber1=this.dnumber2/100;
this.$emit('num1change',this.dnumber1)
}
}
}
},
methods:{
num1change(value){
this.num1=parseFloat(value)
},
num2change(value){
this.num2=parseFloat(value)
}
}
})
</script>
</body>
</html>
10)父访问子
有两种方式:
- 通过 $ children(很少用,一般在获取所有子组件的时候才会使用)
- 通过 $ refs:
<body>
<div id="app">
<cpn></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>我是子组件</div>
</template>
<script src="../../js/vue.js"></script>
<script>
const cpn={
template:"#cpn",
data(){
return {
name:"我是子组件中的name"
}
},
methods:{
showmessage(){
console.log('showmessage')
}
}
};
const app=new Vue({
el:"#app",
data:{
message:'你好啊'
},
methods:{
btnClick(){
//console.log(this.$children[0].name)
console.log(this.$refs.aaa.name)
}
},
components:{
cpn
}
})
</script>
</body>
十一、插槽
插槽,其实就相当于 打桩,占个位置。
vue 中的插槽就是在组件的<template></template>
标签中加入<slot></slot>
标签,然后如果需要在这里面加入新的内容就可以直接添加
具名插槽
如果没有对插槽指定具体的名称,那么所有的插槽都会替换
运行结果:
加了名称,就会替换了那些加名称的
运行结果:
替换指定名称的插槽
十二、编译作用域
各种标签,属性的使用都是在本模板内起作用的
十三、插槽作用域
一般在组件中有默认的显示数据的方式。如果父组件调用了子组件,但是父组件并不想要子组件显示数据的方式,而是想要按照自己的方式显示数据,但是在父组件中是不能直接访问子组件中的数据的。所以可以采用插槽的方式来处理。
十四、模块化的 导出、导入
模块化有两个核心:导出和导入
模块 的 导出
module.exports={
flag:true,
test(a,b){
return a+b
},
demo(a,b){
return a*b
}
}
模块 的 导入
//Commonjs的导入
let{test,demo,flag}=require('moduleA');
//等同于
let _mA=required('moduleA');
let test=_mA.test;
let demo=_mA.demo;
let flag=_mA.flag;
十五、ES6 的导出和导入
可以通过在<script>
标签中加入type="module"
属性来标志这个一个模块,就不会出现模块间数据冲突的问题
1)变量的导出和导入
2)函数/类的导出和导入
3)export default
一个模块只能有一个 export default,使用这个东西之后在导入时就可以自定义名称
十六、webpack
1)什么是 webpack
webpack是一个前端模块化打包工具
对于有些技术,浏览器是识别不了的,而通过webpack打包之后就可以转化为浏览器可以识别的东西
2)webpack 的安装
node和webpack和npm的关系:
webpack模块化打包;
webpack为了可以正常运行,必须依赖node环境;
node环境为了可以正常的执行化很多代码,必须其中包含各种依赖的包;
npm工具就是用来管理这些包的。
安装 node,node 安装完之后附带安装了 npm
安装 webpack。先安装淘宝镜像,因为 npm 的源在国外,速度很慢
https://blog.csdn.net/qq_38232598/article/details/91346993
可以通过npm get registry来获取npm源
全局安装 webpack:npm install webpack@3.6.0 -g
局部安装 webpack,–save-dev 是开发时依赖,项目打包后不需要继续使用
cd 对应目录,执行命令:npm install webpack@3.6.0 --save-dev
3)webpack 的基本使用
一般创建两个文件夹dist和src,dist放的是打包后的代码,src放的是我们原始代码
进入 src 所在的目录,然后执行以下指令进行打包:webpack ./src/main.js ./dist/bundle.js
4)webpack的配置
对入口和出口进行映射
对 webpack 进行映射
在package.json中可以对指令进行映射,然后执行npm run 映射的名称,如果在这里映射了webpack,那么使用映射后的指令默认优先使用本地安装的webpack,而不是全局的,而只要是使用webpack指令,那么使用的就是全局的。
5)loader
我们除了对js文件进行打包之外,还需要对其他文件进行打包,所以需要将其他文件导入到mian.js中,但是打包其他文件需要一些加载器
https://webpack.docschina.org/loaders/css-loader/
打包 css 文件
注意:安装时指定以下版本,否则可能会出错
安装 css-loader:npm install css-loader@2.0.2 --save-dev
安装 style-loader:npm install style-loader@0.23.1 --save-dev
可以在 package.json 中看到安装的具体版本信息
打包 less 文件
安装less-loader和less,其中less不是一个loader,而是为了将less解析为css
老规矩,指定具体版本:npm install less-loader@4.1.0 less@3.9.0 --save-dev
处理图片文件
安装 url-loader:npm install url-loader@1.1.2 --save-dev
当图片小于limit时,图片会被编译成base64的格式
当图片的大于limit时,需要再下载file-loader
6)将ES6转为ES5
在使用 webpack 进行打包时,还是不能将 ES6 转为 ES5,所以需要使用相应的 loader 对其进行转化。安装 babel-loader,babel-core 和 babel-preset-es2015(es2015也就是es6)
命令:npm install babel-loader@7 babel-core babel-preset-es2015 --save-dev
在webpack.config.js中添加如下配置
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/, //排除掉不是自己写的代码
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
打包后的 bundle.js中的相关代码简单介绍
7)vue 的配置过程
安装 vue
npm install vue@2.5.21 --save-dev
导入vue
使用 runtime-only,使用 template 的问题
这是因为我们使用的是runtime-only版本的,它不允许有template,因为我们引入了vue实例,所以就有template,所以报错。
解决方法:在webpack.config.js中进行配置,让它可以找到compiler可以编译template
8)template 和 el 的区别
template里面的内容会被添加到el挂载的dom里面
十六、vue 的终极解决方案
运行结果:
改进 1
将 App 中的内容抽取之后写在一个js文件里
export default{
template: `
<div>
<h2>{{message}}</h2>
<button @click="btnClick">按钮</button>
<h2>{{name}}</h2>
</div>
`,
data(){
return{
message:"我是message",
name:"我是name"
}
},
methods:{
btnClick(){
console.log(this.message,this.name)
}
}
};
改进 2
将app.js文件内容放到App.vue里面(然后就不需要app.js了)
<template>
<div>
<h2>{{message}}</h2>
<button @click="btnClick">按钮</button>
<h2>{{name}}</h2>
</div>
</template><script>
export default {
name: "App" ,
data(){
return{
message:"我是message",
name:"我是name"
}
},
methods:{
btnClick(){
console.log(this.message,this.name)
}
}
}
</script><style scoped>
</style>
然后需要加载vue-loader
和vue-template-compiler
因为版本太大的vue-loader需要安装相应的vue-loader应该低一点
npm install vue-loader@13.0.0 vue-template-compiler@2.5.21 --save-dev
在 webpack.config.js 中进行相应的配置
十七、plugin
1)什么是plugin
- plugin 是插件的意思,通常是用于对某个现有的架构进行扩展。
- webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等。
2)loader 和 plugin 的区别
- loade主要用于转换某些类型的模块,它是一个转换器
- plugin是插件,它是对 webpack 本身的扩展,是一个扩展器
3)plugin 的使用过程
步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)
步骤二:在webpack.config.js中的plugins中配置插件
例子:
这个是 webpack 自带的 plugin,所以直接引入就行了,不需要安装
在webpack.config.js中进行配置
在 bundle.js 中就多了这个东西
htmlwebpackplugin插件
在使用webpack打包后,html并不会打包进去,但是这个html确是我们需要使用的,所以就用到了htmlwebpackplugin插件
安装:npm install html-webpack-plugin@3.2.0 --save-dev
在 webpage.config.js 中进行相应的配置
问题:没有<div id="app"></div>
这个模板
解决:在new htmlwebpackplugin()添加一个模板
更改index.html
在webpack.config.js中导入template
然后就能生成我们想要的 index.html了
压缩js的plugin
安装 uglifyjs-webpack-plugin:npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
在webpack.config.js中进行相应的配置
十八、配置文件的抽离
将配置文件分为公共、开发和运行配置文件
需要安装webpack-merge:npm install webpack-merge@4.1.5 --save-dev
base.config.js
const path = require('path'); //动态地获取路径
const webpack=require('webpack');
const htmlwebpackplugin=require('html-webpack-plugin');
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, '../dist'), //__dirname是一个全局变量,是node里面的东西
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.css$/i,
use: ["style-loader","css-loader"],
},
{
test: /\.less$/,
use: [{
loader: "style-loader", // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader", // compiles Less to CSS
}]
},
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 13000,
},
},
],
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
},
],
},
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015'],
}
}
}
],
},
resolve:{
alias:{ //配置别名
'vue$': 'vue/dist/vue.esm.js' //让它可以去这个路径中找到compiler编译template
}
},
plugins:[
new webpack.BannerPlugin('最终版权归aaa所有!'),
new htmlwebpackplugin({
template:'index.html'
})
]
};
prod.config.js
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')module.exports = webpackMerge(baseConfig, {
plugins: [
new UglifyjsWebpackPlugin()
]
})
然后在 package.json 中配置路径
十九、脚手架 ( 就是 命令行 )
1)脚手架的安装
CLI是什么意思:
- CLI是command-Line Interface,翻译为命令行界面,但是俗称脚手架。
- Vue Cli是一个官方发布vue.js项目脚手架。
- 使用vue-cli可以快速搭建Vue开发环境以及webpack配置。
脚手架的前提:node
安装脚手架 vue cli,全局安装:cnpm install -g @vue/cli@3.2.1
查看版本:vue --version
如果想要使用vue2 的 脚手架,可以拉取脚手架2:npm install -g @vue/cli-init
Vue CLI2 初始化项目:vue init webpack my-project
Vue CLI3 初始化项目:vue create my-project
2)安装某个项目的脚手架
使用 cli2 初始化项目:vue init webpack my-project
cli 目录解析
运行vue cli2:npm run dev
vue cli2 和 vue cli3 的区别
使用 vue cli3 创建项目 命令:vue create vuecli3
生成的目录结构
vue cli3 目录结构
运行 vue cli3
命令:npm run serve
输入以下网址就可以在网页上显示
3)runtime-compile 和 runtime-only 的区别
使用 runtime-compile 和 runtime-only 执行后的 mian.js 的区别:
runtime-compile的执行过程:template->ast->render->vdom->ui
runtime-only的执行过程:render->vdom->ui
区别:
- 1)runtime-only的性能更高
- 2)runtime-only的代码量更少
4)配置文件的查看和修改(使用vue ui管理)
配置文件的查看
启动配置服务器(在哪个目录启动都可以):vue ui
导入一个项目,进入到管理页面
看vue真实的版本要去打包之后的文件中看,而不是看源码里面的,可以在以下两个文件中查看
配置文件的修改
创建 vue.config.js(只能是这个名称)
二十、箭头函数
是ES6中一种定义函数的方式
1)参数问题
两个参数 写法:
const sum=(num1,num2)=>{
return num1+num2;
}
console.log(sum(2,3));
一个参数 写法:
const power2 = num => { num*num;}
没有参数
const demo = ()=> {console.log("没有参数");}
2)this 指向的问题
在箭头函数中,this是向外层作用域中,一层一层向外找,直到有this
const obj={
aaa(){
setTimeout(function () {
console.log(this);
},1000);setTimeout(()=>console.log(this),1000);
}
};
obj.aaa();
const obj={
aaa(){
setTimeout(function () {
setTimeout(function () {
console.log(this);
});
setTimeout(()=>{
console.log(this);
});
});
setTimeout(()=>{
setTimeout(function () {
console.log(this);
});
setTimeout(()=>{
console.log(this);
});
});
}
};
obj.aaa();
二十一、html5 的 history 模式
1)pushstate()
history.push({},title,url)
它可以改变url,类似于栈,浏览器中地址栏显示的总是栈顶url
2)back()
history.back()
它类似于出栈操作,每次执行这个函数,都相当于执行浏览器中的返回操作
3)go()
history.go(数值)
数值如果是负数,则相当于浏览器中的返回操作,如果是正数,则相当于浏览器中的前进
4)forward()
history.forward()
相当于history.go(1)
5)关系
histrory.back()相当于history.go(-1)
history.forward()相当于history.go(1)
6)replaceState
history.replaceState({},title,url)
会替代上一个url,并且浏览器中不能通过前进或后退来回到之前的url
二十二、路由
1)前端渲染和后端渲染
后端渲染
就是将页面在服务器渲染完之后,将整个渲染好的页面传到浏览器,在浏览器中只能看到 html+css
后端路由
前端渲染
浏览器中显示的网页中的大部分内容,都是由前端写的 js 代码在浏览器中执行,最终渲染出来的网页。
前端路由
管理前端的页面,一个 url 对应一个组件
2)安装和配置 vue-router
搭建vue-router步骤:
(一)安装vue-router
命令:npm install vue-router@3.0.1 --save-dev
(二)在模块化工程中使用它(因为是一个插件, 所以可以通过Vue.use()来安装路由功能)
第一步:导入路由对象,并且调用 Vue.use(VueRouter)
第二步:创建路由实例,并且传入路由映射配置
第三步:在Vue实例中挂载创建的路由实例
index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'//通过Vue.use(插件) 来安装插件
Vue.use(Router)//创建并导出router,vue-router只有挂载到vue实例里面,才能生效
export default new Router({
routes: [ //在这里面配置相关的映射信息
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
}
]
})
main.js
import Vue from 'vue'
import App from './App'
import router from './router' //导入routerVue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router, //挂载router
render: h => h(App)
})
3)使用vue-router步骤
第一步: 创建路由组件
第二步: 配置路由映射: 组件和路径映射关系
第三步: 使用路由: 通过<router-link>
和<router-view>
<router-view>
用于显示组件中的内容,如果没有<router-view>
那么组件中的内容就显示不出来<router-view>
写在哪,组件的内容就显示在哪
4)设置默认首页的路径
在index.js中的路由映射中进行配置
5)更改路径为history模式
在index.js中设置如下:
5)<router-link>
属性
to 属性
这个用于跳转的,相当于<a></a>
tag 属性
这个用于指定这是个什么标签
replace
这个的作用相当于history.replaceState(),加了这个东西之后浏览器就不能进行前进或后退操作
active-class
当<router-link>
对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class, 设置active-class可以修改默认的名称.
在index.js中进行设置
6)通过代码进行路由跳转
注意:这里不能用history.pushState(),因为它会绕过路由,而我们需要的是进行路由跳转
7)动态路由的使用
也就是在路径中进行传参
1)传值
在index.js的路由映射中配置
然后在App.vue中设置要传递的值
2)获取值
这个 this.$route 是在index.js中的routes[]中配置的其中一个,表示当前活跃的route
8)打包文件的解析
先打包项目:npm run build
9)路由懒加载
打包后的js,应用程序的代码全部都放在app.801c2ae2e69a05c33dfc65f8.js里面
使用懒加载之后
注册组件时,采用这种方式
打包后,每一个路由一个文件
10)路由嵌套
在一个组件中通过路由嵌套访问另一个组件的内容
使用步骤:
(一)创建被嵌套的组件
homeMessage.vue
<template>
<div>
<ul>
<li>message1</li>
<li>message2</li>
<li>message3</li>
<li>message4</li>
<li>message5</li>
</ul>
</div>
</template>
<script>
export default {
name:'message'
}
</script>
homeNew.vue
<template>
<div>
<ul>
<li>new1</li>
<li>new2</li>
<li>new3</li>
<li>new4</li>
<li>new5</li>
</ul>
</div>
</template>
<script>
export default {
name:'new'
}
</script>
(二)在 index.js 中进行路由映射配置
(三)使用路由: 通过<router-link>
和<router-view>
,<router-view>
用于显示组件中的内容,如果没有那么组件中的内容就显示不出来写在哪,组件的内容就显示在哪
11)参数传递
主要有两种方式:params和query
params:
- 配置路由格式: /router/:id
- 传递的方式: 在path后面跟上对应的值
- 传递后形成的路径: /router/123, /router/abc
query:
- 配置路由格式: /router, 也就是普通配置
- 传递的方式: 对象中使用query的key作为传递方式
- 传递后形成的路径: /router?id=123, /router?id=abc
12)$route 和 $router 的区别
- $router 为 VueRouter 实例,想要导航到不同 URL,则使用 $router.push 方法
- $route 为当前 router 跳转对象里面可以获取 name、path、query、params 等
13)vue-router 的全局导航守卫
几个生命周期的回调函数
为什么要使用导航守卫?
前置守卫
导航钩子的三个参数解析:
- to: 即将要进入的目标的路由对象.
- from: 当前导航即将要离开的路由对象.
- next: 调用该方法后, 才能进入下一个钩子
在index.js的路由映射中添加这个东西
后置钩子
不需要主动调用next()函数
14)vue 的生命周期
**created:**一个 dom 创建并初始化之后就回调这个函数
mounted: 将组件编译并渲染完成后回调这个函数
updated: 对组件有什么更新都会回调这个函数,只要不跳转到其他页面,就会一直循环
destoryed: 如果跳转到其他页面,就会回调这个函数
15)keep-alive
keep-alive可以让组件不被destroy
<keep-alive>
<router-view/>
</keep-alive>
示例:在home这个页面中有两个组件,如果它现在页面中显示的是消息组件,而如果从home页面跳转到关于页面,那么这个home页面就会被destroy,如果再跳转回home页面,它自然就不会再显示消息组件了,因为它会重新被创建,而且它默认不是显示到消息组件的。而如果是不被destory,并且它被够在跳转回home页面时,记住home页面中的那个最后记录的地址,自然就能跳转回home页面跳转到其他页面前的最后的模样。
keep-alive的属性:
exclude属性:可以排除掉不想一直被保留的组件
二十三、tabbar
1)tabbar 的实现思路
2)创建一个简单的 tabbar
App.vue
<template>
<div id="app">
<tab-bar>
<tab-bar-item>
<img slot="item-icon" src="./assets/img/tabbar/home.svg" alt="图片不见了"/>
<div slot="item-text">首页</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/img/tabbar/category.svg" alt="图片不见了"/>
<div slot="item-text">分类</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt="图片不见了"/>
<div slot="item-text">购物车</div>
</tab-bar-item>
<tab-bar-item>
<img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt="图片不见了"/>
<div slot="item-text">我的</div>
</tab-bar-item>
</tab-bar>
</div>
</template><script>
import TabBar from './components/tabbar/TabBar';
import TabBarItem from './components/tabbar/TabBarItem';
export default {
name: 'App',
components:{
TabBar,
TabBarItem
}
}
</script><style>
@import "./assets/css/base.css";</style>
TabBar.vue
<template>
<div id="tab-bar">
<slot></slot>
</div>
</template><script>
export default {
name: "TabBar"
}
</script><style scoped>
#tab-bar{
display: flex;
background-color: #f6f6f6;
position: fixed;
left: 0;
right: 0;
bottom: 0;
box-shadow: 0 -1px 1px rgba(100,100,100,.08);
}</style>
TabBarIItem
<template>
<div class="tab-bar-item">
<slot name="item-icon"></slot>
<slot name="item-text"></slot>
</div>
</template><script>
export default {
name: "TabBarItem"
}
</script><style scoped>
.tab-bar-item{
flex: 1;
text-align: center;
height: 49px;
}.tab-bar-item img{
height: 24px;
width: 24px;
}
</style>
3)插槽中设置一些属性
如果直接将属性添加在<slot></slot>
上,那么在使用时,这些属性有些是会显示不出的,所以一般把这些属性写在它外面的<div></div>
上
4)tabbar与路由的配合
1、创建不同 tabbar 要显示的视图
2、将不同的视图在index.js中进行映射配置
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
const home=()=>import('../view/home/home');
const category=()=>import('../view/category/category');
const profile=()=>import('../view/profile/profile');
const shopcart=()=>import('../view/shopcart/shopcart');
Vue.use(Router)export default new Router({
routes: [
{
path: '/',
redirect:'/home'
},
{
path:"/home",
component:home
},
{
path:"/category",
component: category
},
{
path:"/shopcart",
component:shopcart
},
{
path:"/profile",
component:profile
}
]
})
3、在 TabBarItem 中配置配置路径跳转
4、在 App.vue 中传入不同 TabBarItem 对应的 path
二十四、promise
1)基本介绍
是一种异步编程的解决方案
在异步编程中,当网络请求非常复杂时,就会出现回调地狱。
我们来考虑下面的场景 ( 有夸张的成分 ):
- 我们需要通过一个 url1 从服务器加载一个数据 data1,data1 中包含了下一个请求的 url2
- 我们需要通过 data1 取出 url2,从服务器加载数据 data2,data2 中包含了下一个请求的 url3
- 我们需要通过 data2 取出 url3,从服务器加载数据 data3,data3 中包含了下一个请求的 url4
- 发送网络请求 url4,获取最终的数据 data4
promise 就可以用来解决这个问题
2)promise的简单使用
需求:在控制台上,每隔 1s 进行打印
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>promise的基本使用</title>
</head>
<body>
<script>
new Promise((resolve, reject) => {
//第一次网络请求的代码
setTimeout(()=>{
resolve()
},1000)
}).then(()=>{
//第一次拿到结果的处理代码
console.log('hello world');
console.log('hello world');
console.log('hello world');
console.log('hello world');
console.log('hello world');
console.log('hello world');return new Promise((resolve, reject) => {
//第二次网络请求的代码
setTimeout(()=>{
resolve()
},1000)
})
}).then(()=>{
//第二次处理的代码
console.log('hello promise');
console.log('hello promise');
console.log('hello promise');
console.log('hello promise');
console.log('hello promise');
console.log('hello promise');return new Promise((resolve,reject)=>{
//第三次网络请求的代码
setTimeout(()=>{
resolve()
},1000)
})
}).then(()=>{
console.log('hello vue');
console.log('hello vue');
console.log('hello vue');
console.log('hello vue');
console.log('hello vue');
console.log('hello vue');
})
</script>
</body>
</html>
执行结果: