这里写目录标题
前言
这篇内容主要当作自己的学习VUE的学习笔记进行记录,从小白开始入门,不断摸索VUE的核心知识点,该笔记将会长期更新。
个人对于vue的一点小理解
刚刚入门vue不久,学习了一些vue的基本语法后发现其实vue框架就是前端的一些标签内容、属性、样式都用一些“占位变量”、“占位操作”进行设置,而这些“占位变量”的设置是在JS中的vue的data中去定义的,而占位操作和数据更新是在methods中进行完成的。传统dom中的标签是实打实的设置真实的样式和属性值、内容、操作方法,在JS中dom便依赖这些属性和样式去获取操作的对象。相比于传统使用dom去操作元素对象而言,vue更强调一种抽象的思想,也就是把一个框架躯壳摆在前端,实际的内容、属性和操作是在JS中的new vue对象中完成的(灵魂所在),因此要发生一些更新、修改等操作时更加方便,简化了传统的dom方法。
1 简单的VUE程序和基础VUE语法
1.1 第一个Vue程序
1.2 v-text、v-html、v-on、v-show指令
v-show的小结 |
---|
1.3 简易计数器设计
设计计数器的逻辑思路 |
---|
注意:上面的数据会随着methods中的操作同步更新,但是每次刷新网页时。对应的初始数据仍然是从vue中的data获取的,也就是说只要data中的数据值不变,即使进行数据操作后刷新网页这些值也会丢失。理解:例如计数器的data初始值为1,通过加操作使得计数值加到10,但是如果此时刷新网页后计数值仍然显示为1,而并未显示刷新前的数据。
1.4 案例效果(1.1 1.2 1.3)
<!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的基础使用框架和基本语法-1</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- CDN方法引入VUE的库 否则VUE的语法是无法工作的 -->
<style>
/* 展示代码方便 此处就不添加CSS文件 而是以style的方式设置标签的CSS样式 */
img {
width: 100px;
/* height: 60px; */
}
</style>
</head>
<body>
<div id="app">
<h1>v-text和v-html方法演示</h1>
<h2 v-text="message+'后缀测试'">测试</h2>
<!-- 标签使用v-text属性的时候 v-text的优先级最高 message的内容全部替换 而标签内部的内容无法显示-->
<h2 v-text="">测试</h2>
<h3 style="color:rgb(101, 100, 180)">使用差值表达式(即使用两层大括号括起来)的时候message可以不完整替换,可以和其他的文本一起拼接使用
同样也支持使用一些对于message的内容进行一些表达式的操作
<br>
例如:{{message+'后缀测试'}}
<br>
这是一个表达式:{{expression*2+3}}
</h3>
<h4 v-html="info+'后缀测试'"></h4>
<h4 v-text="htmlContent"></h4>
<h4 v-html="htmlContent"></h4>
<!-- v-html标签的内容是文本则会以文本格式渲染 若是html则会按照html的显示规则进行显示 -->
<hr>
<h1>v-on方法演示</h1>
<input type="button" value="v-on单击指令" v-on:click="show">
<input type="button" value="简写指令" @click="show">
<input type="button" value="v-on双击指令" v-on:dblclick="show">
<input type="button" value="hover悬浮" v-on:mouseenter="hover">
<p @click="changeFood">{{food}}</p>
<hr>
<h1>计数器案例实现</h1>
<input type="button" value="-" @click="subNum">
<span>{{calnum}}</span>
<!-- span标签是行内元素inline 支持在一行显示 而不会自动换行 -->
<input type="button" value="+" @click="addNum">
<hr>
<h1>v-show方法</h1>
<img src="./img/cat.png" alt="图片" v-show="isShow">
<img src="./img/cat.png" alt="图片" v-show="age>18">
<!-- 此处的v-show当做了img标签中的一个属性 而且v-show只存在两种值:true和false ,因此也支持表达式-->
<button @click="changeisShow">切换图片显示状态</button>
<button @click="addAge">增加年龄</button>
</div>
<script>
var app = new Vue({
el: "#app",
//el是挂载对象,该属性值不能作为body标签和html标签的ID,一般都做DIV标签的ID
data: {
//data是VUE提供的后台数据 可以渲染到前端页面上去 其中的数据名是自定义而不是预定义的
//这些数据名称可以作为v-text v-html或者其他属性的值 从而完成数据的传输
//注意点:每次刷新网页 data的初始数据仍然由下面的数据给出 尽管在method进行了一些数据操作 但是在每次刷新网页后这些操作记录就会丢失
message: "hello vue",
info: 'information',
htmlContent: '<a href="https://blog.csdn.net/qq_44174803/article/details/121657088">这是我的VUE笔记blog</a>',
expression: 12,
food: "西红柿炒鸡蛋",
isShow: false,
age: 18,
calnum: 1
},
methods: {
//method如名字所示 用于存放一些方法操作(包括对data数据的操作 使用data中数据时用this调用即可)
//注意此处的methods不要写成method否则是无法识别的
show: function () {
alert("attention:这是一次点击事件!");
},
changeFood: function () {
//当操作方法为changeFood的时候 则会执行如下代码
this.food += "不加糖!";
//此处的this能直接获取到并能修改data中的数据
console.log("不要加糖");
},
hover: function () {
alert("这是一次悬浮事件");
},
changeisShow: function () {
this.isShow = !this.isShow; //isShow变量值每次都取反
},
addAge: function () {
this.age++;
console.log(this.age);
},
addNum: function () {
if (this.calnum < 10) {
this.calnum++;
} else {
window.alert("已经达到上限值了");
}
},
subNum: function () {
if (this.calnum > 0) {
this.calnum--;
} else {
window.alert("已经达到下限值了");
}
}
}
})
</script>
</body>
</html>
1.5 v-bind、v-if指令
注意:上述v-bind:src可以简写为:src(这是更常见的写法);此外的元素属性也支持这样的简写方式。
v-bind中设置class属性的事项,不能直接设置v-bind:class=“类名”,一般进行class设置时候有以下两种方式:但比较推荐第二种,简洁明晰。
非常需要关注的点是
标签中存在src="url"和v-bind:src="url"(:src="url")是不一样的。前者给src赋值单纯是url三个字;而后者由于有v-bind指令限制,导致引号中的内容是一个表达式而不是字符串,因此获取的是url的内容而不是url这三个字,或者说我们此处把他当做一个变量来看待然后要取的是变量中的内容。
此处的logo是一个style标签中的类选择器名称
(1):class="isActive?'logo':''" 三目表达式方式 倘若isActive为真 最后效果等效于:class="'logo'"
(2):class="{logo:isActive}" 对象的方式,当isActive为真,效果等效于:class="{logo:true}"
v-if、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>VUE 基本语法2</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- CDN方法引入VUE的库 否则VUE的语法是无法工作的 -->
<style>
.logo {
width: 100px;
border: 1px solid black;
}
</style>
</head>
<body>
<div id="grammar">
<h1>v-if指令</h1>
<p>解释:虽然v-if和v-show都是用于标签元素的显示与隐藏,<strong>效果基本一致</strong>,但是原理不同,v-if是基于dom树的,显示的时候
会把对应的标签元素增加到dom树上,隐藏的时候直接在dom树上删去该元素。而v-show只是基于dispaly属性,隐藏的时候
只需要将display属性设置为none即可,而显示的时候保持默认值(inline)即可显示。
</p>
<p style="color:red;font-weight:bold">⚠️注意:需要经常进行显示或者隐藏切换的时候一般使用v-show方法 因为频繁地操作dom树会减缓显示性能</p>
<input type="button" value="切换显示" @click="shiftShow">
<p v-if="isShow">正在测验v-if方法</p>
<p v-show="isShow">正在测验v-show方法</p>
<p v-if="temperature>=40">当前温度超标!!</p>
<hr>
<h1>v-bind指令</h1>
<img v-bind:src="imgSrc" alt="" v-bind:title="imgTitle">
<br>
<img :src=" imgSrc" alt="" :title="imgTitle+'!!!!!!!!!!!!!!!'" :class="isActive?'logo':''" @click="shiftActive">
<!-- 第二种写法是简写的方式 一般使用简写方式比较常见(冒号+属性名) -->
<br>
<img :src=" imgSrc" alt="" :title="imgTitle+'!!!!!!!!!!!!!!!'" :class="{logo:isActive}" @click="shiftActive">
</div>
<script>
var grammar = new Vue({
el: '#grammar',
//注意ID选择器前面一定要加上# 否则无法识别出来
data: {
isShow: false,
temperature: 40,
stylename: ".logo",
imgSrc: "./img/logo.png",
imgTitle: "抽水马桶",
isActive: false
},
methods: {
shiftShow: function () {
this.isShow = !this.isShow; //每次取反
},
shiftActive: function () {
this.isActive = !this.isActive;
}
}
});
</script>
</body>
</html>
1.6 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>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<style>
.imgShift {
width: 100px;
}
.play {
width: 300px;
border: 1px solid black;
position: absolute;
left: 130px;
}
#contain {
width: 600px;
height: 300px;
position: relative;
border: 1px solid black;
background-color: bisque;
}
.button_right {
position: absolute;
top: 200px;
left: 420px;
}
.button_left {
position: absolute;
top: 200px;
left: 50px;
}
</style>
</head>
<body>
<div id="shift">
<h1>图片切换效果实现</h1>
<hr>
<div id="contain">
<!-- <img :src="" alt=""> -->
<a href="javascript:void(0)" @click="subImg" v-show="index>0">
<img :src="imgLeft" alt="" :class="{imgShift:true,button_left:true }">
<!-- 在设置v-bind:class属性时,遇到多个属性使用逗号隔开 -->
</a>
<img :src="imgArray[index]" alt="" :class="{play:true}">
<!-- 显示的图片范围是在imgArray中的 但是具体哪一张图片显示 由下标index来决定 -->
<a href="javascript:void(0)" @click="addImg" v-show="index<imgArray.length-1">
<img :src="imgRight" alt="" :class="{imgShift:true,button_right:true}">
</a>
</div>
</div>
<script>
var shift = new Vue({
el: "#shift", //记住 挂载元素的时候 一定要加上#或者.
data: {
imgLeft: "./img/left.png",
imgRight: "./img/right.png",
imgArray: ["./img/1.png", "./img/2.png", "./img/3.png"],
index: 0
},
methods: {
addImg: function () {
if (this.index < this.imgArray.length - 1) {
this.index++;
}
console.log(this.index);
},
subImg: function () {
if (this.index > 0) {
this.index--;
}
console.log(this.index);
}
}
});
</script>
</body>
</html>
第一张 |
---|
第二张 |
---|
最后一张 |
---|
1.7 v-for指令
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>v-for指令</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- CDN方法引入VUE的库 否则VUE的语法是无法工作的 -->
</head>
<body>
<div id="app">
<ul>
<li v-for="item in city">
中国的一线城市之一:{{item}}
</li>
</ul>
<ul>
<li v-for="(item,index) in city">
{{index+1}}中国的一线城市之一:{{item}}
</li>
</ul>
<!-- 此处index是数组的下标 item就是数组的项 -->
<ul>
<!-- 当title前加上冒号的时候 代表得结合VUE中的data数据一起使用 而不加冒号时就是正常用引号中的内容作为title -->
<li v-for="item in food" :title="item">
{{item}}
</li>
</ul>
<ul>
<!-- 当title前加上冒号的时候 代表得结合VUE中的data数据一起使用 而不加冒号时就是正常用引号中的内容作为title -->
<li v-for="item in food" :title="item.name">
{{item.name}}
</li>
</ul>
<ul>
<!-- 当title前加上冒号的时候 代表得结合VUE中的data数据一起使用 而不加冒号时就是正常用引号中的内容作为title -->
<li v-for="item in food" title="item.name">
<!-- 此时的title显示就是固定死的 title.name -->
{{item.name}}
</li>
</ul>
<button @click="addFood">增加食物</button>
<button @click="removeFood">移除食物</button>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
city: ["北京", "上海", "广州", "深圳"],
food: [
{ name: "aaa" },
{ name: "bbb" },
{ name: "ccc" }
]
},
methods: {
addFood: function () {
this.food.push({ name: "ddd" });
// 在数组最后面加上对象元素
},
removeFood: function () {
this.food.shift();
// 移除数组最左边的元素(也就是最前面的元素)
}
}
});
</script>
</body>
</html>
1.8 v-on指令补充
v-on指令的更多事件修饰符可参考上面图片中的链接 |
---|
v-on补充案例:
<!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-on补充指令</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- CDN方法引入VUE的库 否则VUE的语法是无法工作的 -->
</head>
<body>
<div id="app">
<input type="button" value="点击" @click="doIt(666,'你好')">
<!-- 上面的doIt传递的是实参 -->
<br>
<span>只要按键按下就会响应:</span>
<input type="text" @keyup="sayHi">
<br>
<span>只有回车enter按键按下才会响应:</span>
<input type="text" @keyup.enter="sayHi">
</div>
<script>
var app = new Vue({
el: "#app",
data: {
},
methods: {
doIt: function (t1, t2) {
// 此处t1接受的是数据 t2接收的是字符串 完成了参数传递(此处定义的是形式参数)
console.log(t1);
console.log(t2);
},
sayHi: function () {
alert("Nice to meet you!!!")
}
}
});
</script>
</body>
</html>
1.9 v-model指令
无论修改data绑定的数据还是表单元素的值,只要修改其中的一方,两者就会同步更新。这样的好处是:当我们写入表单中的数据时,能够便捷地直接更新后台的绑定数据。但是每次刷新页面后数据仍然显示为data中的数据值,因为未连接数据库,因此无法做页面刷新时候的数据储存。
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>v-model指令</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- CDN方法引入VUE的库 否则VUE的语法是无法工作的 -->
</head>
<body>
<div id="app">
<p>修改第一个框的内容 message不发生改变 因为表单元素没有绑定v-model</p>
<input type="text" :value="message">
<br>
<p>修改第二个框的内容 message内容发生改变 因为表单元素绑定了v-model</p>
<input type="text" v-model="message" @keyup.enter="get">
<h2>{{message}}</h2>
<p v-text="message+'!'"></p>
<button @click="set">一键修改所有message内容</button>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
message: "程序员"
},
methods: {
get: function () {
alert("全栈工程师");
},
set: function () {
this.message = "前端开发";
}
}
});
</script>
</body>
</html>
1.10 简易记事本实现案例
记事本实现逻辑思路:
记事本实现代码:
<!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>
/* 此处为了更加清晰的显示样式 这里就不使用引入CSS文件的方式 而直接在此处加上CSS样式 */
* {
/* 清除浏览器的默认样式 */
margin: 0px;
padding: 0px;
}
body {
background-color: #F5F5F5;
}
#header {
color: coral;
text-align: center;
margin: 30px auto;
padding-top: 70px;
}
#main {
/* 发生元素位置偏差的主要因素 很有可能是外层容器的尺寸大小未设置 导致显示不正常 */
width: 700px;
/* border: 1px solid red; */
margin: 0 auto;
}
.input {
width: 600px;
height: 60px;
margin: 0 auto;
display: block;
text-align: center;
font-size: 15px;
color: dimgrey;
}
ul {
width: 600px;
list-style: none;
margin: 10px auto;
/* border: 1px solid gold; */
/* 取消前面的默认点样式 */
}
.cha_loc {
/* top: 20px; */
right: 20px;
position: absolute;
}
.cha {
width: 20px;
height: 20px;
}
ul li {
width: 600px;
height: 60px;
margin: 1px auto;
position: relative;
}
.inner {
/* width: 600px;
height: 60px; */
/* border: 1px solid red; */
background-color: white;
line-height: 60px;
/* display: block; */
}
#footer {
width: 600px;
height: 50px;
background-color: white;
margin: 0 auto;
border: 1px solid #EEEEEE;
position: relative;
}
.crush {
position: absolute;
top: 15px;
right: 30px;
background-color: white;
color: darkgray;
text-decoration: none;
border: none;
}
.remain {
position: absolute;
top: 15px;
left: 30px;
color: darkgray;
}
#last {
width: 300px;
margin: 0 auto;
/* border: 1px solid red; */
text-align: center;
margin: 20px auto;
}
#footimg {
width: 200px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- CDN方法引入VUE的库 否则VUE的语法是无法工作的 -->
</head>
<body>
<div id="app">
<div id="header">
<!-- 实际上在h5中可以直接使用header标签 -->
<h1>简易记事本</h1>
</div>
<div id="main">
<!-- 包含输入框和展示列表 -->
<input type="text" v-model="message" class="input" @keyup.enter="addItem" placeholder="请输入任务">
<!-- <p>{{message}}</p> -->
<ul>
<li v-for="(item,index) in arr">
<div class="inner"><span>{{index+1}}.</span> {{item}}
<!-- 列表中的每个index值是数组arr的下标 而且互相是不同的 固定的 -->
<span class="cha_loc" @click="removeItem(index)">
<img src="./img/cha.png" alt="" class="cha">
</span>
</div>
</li>
</ul>
</div>
<div id="footer">
<span class="remain" v-show="arr.length!=0">{{arr.length}} items left</span>
<button class="crush" @click="clear" v-show="arr.length!=0">Clear</button>
</div>
</div>
<footer id="last">
<img src="./img/2.png" alt="" id="footimg">
</footer>
<script>
var app = new Vue({
el: "#app",
data: {
message: "",
arr: []
},
methods: {
addItem: function () {
// console.log("message:" + this.message); d
this.arr.push(this.message);
this.message = ""; //每次加完之后自动清空
//data中的message由于使用了v-model指令 因此和输入框文本是绑定的 此处直接push就行
},
removeItem: function (index) {
// 传入指定下标后才能知道删除哪个
console.log("index:" + index);
this.arr.splice(index, 1); //代表从index下标开始的第一个元素
},
clear: function () {
this.arr = []; //直接将数组赋值为空即可
}
}
});
</script>
</body>
</html>
注意,在写CSS样式之前,一定要清理浏览器的样式,加上如下代码:否则会影响元素的布局。
* {
/* 清除浏览器的默认样式 */
margin: 0px;
padding: 0px;
}
1.11 网络应用之Axios+Vue
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。在Vue实例中,经常会使用到Axios进行http的网络请求。
下面是在Vue中应用axios的一个模版框架
<!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+Axios模版框架</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- CDN方法引入VUE的库 否则VUE的语法是无法工作的 -->
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.js"></script>
<!-- 通过CDN方式引入axios工具 -->
</head>
<body>
<div id="app">
<input type="button" value="获取笑话1" @click="getJoke1">
<p>{{jokes1}}</p>
<input type="button" value="获取笑话2" @click="getJoke2">
<p>{{jokes2}}</p>
<input type="button" value="获取笑话3" @click="getJoke3">
<p>{{jokes3}}</p>
<input type="button" value="获取笑话3" @click="getJoke4">
<p>{{jokes4}}</p>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
jokes1: "笑话1",
jokes2: "笑话2",
jokes3: "笑话3",
jokes4: "笑话4"
},
methods: {
getJoke1: function () {
axios.get(
"https://autumnfish.cn/api/joke"
).then(function (response) {
// 回调函数 响应成功时执行
console.log(response);
console.log(response.data);
this.jokes1 = response.data;
}, function (error) {
// 响应出错 当上面的回调函数不成功时才会执行下面这一条响应失败的回调函数
console.log(error);
});
},
getJoke2: function () {
var that = this; //由于进行axios请求时 this指向的内容会发生改变 变成undefined或者window类型 而不在指向Vue对象 所以在进行axios请求前需要保存指向的对象
axios.get(
"https://autumnfish.cn/api/joke"
).then(function (response) {
// 回调函数 响应成功时执行
console.log(response);
console.log(response.data);
that.jokes2 = response.data;
}, function (error) {
// 响应出错 当上面的回调函数不成功时才会执行下面这一条响应失败的回调函数
console.log(error);
});
},
getJoke3: function () {
axios.get(
"https://autumnfish.cn/api/joke"
).then(function (response) {
// 回调函数 响应成功时执行
console.log(response);
console.log(response.data);
this.jokes3 = response.data;
}.bind(this), function (error) {
// 响应出错 当上面的回调函数不成功时才会执行下面这一条响应失败的回调函数
console.log(error);
}.bind(this));
},
getJoke4: function () {
axios.get(
"https://autumnfish.cn/api/joke"
).then(response => {
// 回调函数 响应成功时执行
console.log(response);
console.log(response.data);
this.jokes4 = response.data;
}, error => {
// 响应出错 当上面的回调函数不成功时才会执行下面这一条响应失败的回调函数
console.log(error);
});
}
}
});
</script>
</body>
</html>
上面笑话中只有笑话1无法显示,但实际上axios是成功接收到笑话本文的,只是没有渲染在页面上。该原因是在第一个按钮的响应事件中的axios的回调函数中直接使用了this.jokes1=response.data去接受回调的内容,但实际上此时的this指向对象已经发生了改变而不在是指向Vue了,因此data中的jokes数据并未得到更新,所以才无法显示。而在下面的三个笑话实现中 是解决该问题的三种可行办法。不过值得一提的是this在get中的参数内是可以使用的(因为此时的axios请求还未发出去),只不过不能在其内部的回调函数中使用。
1.12 综合应用案例——音乐播放器实现
实现效果:
实现源代码:
https://download.csdn.net/download/qq_44174803/56401822
VUE学习的一些前置知识
webpack打包技术
想让我们写的代码从简单的课设变成一个工程项目,学习打包技术是十分有必要的,因此在学习Vue之前我们有必要学习下打包技术。虽然在项目开发时,webpack是自动配置的,但是我们仍然有必要去了解webpack的打包原理,此处不对于webpack打包的过程进行介绍,将简单讲解一下原理和注意事项。
webpack手动打包技术的详解和步骤可参考下面资料:
webpack讲义
webpack概念: webpack 是前端项目工程化的具体解决方案 。主要功能:它提供了友好的前端模块化开发支持,以及代码压缩混淆 、处理浏览器端JavaScript 的兼容性 、性能优化等强大的功能。好处:让程序员把工作的重心放到具体功能的实现上,提高了前端开发效率和项目的可维护性 。注意:目前企业级的前端项目开发中,绝大多数的项目都是基于webpack进行打包构建的。
webpack.config.js的配置文件:
webpack.config.js相当于一个说明书,他会告诉webpack工具如何进行打包。在配置文件中包含了打包的路径和一些文件辅助打包工具和一些工具插件。
下述的配置文件支持实时打包,并且将src下的index.html文件复制到了内存中(根目录下的),此外也支持自定义设置端口地址。下面的配置文件包含了基本的webpack打包时所需要使用的一些功能和插件,实时打包html、css、less等文件的功能和每次打包前自动清除dist文件夹的功能等等。
//注意事项⚠️:每次修改webpack的配置文件之后 都要进行一次npm run dev重新打包 这样修改后的配置文件才会生效
//webpack是基于node.js的一个工具 因此在其配置中也支持node.js的语法 例如支持commonJS规范(下面中的require导入模块 就是commonJS的方式)
//1.导入 node.js中专门操作路径的模块
const path=require('path');
//2. 导入html插件,得到一个构造函数
const HtmlPlugin=require('html-webpack-plugin');
//创建HTML插件的实例对象(拥有这个插件后 实现了将html文件放入内存中 使得webpack可以对html文件进行实时打包)
const htmlPlugin=new HtmlPlugin({
template:'./src/index.html', //指定源文件的存放路径
filename:'./index.html', //指定生成文件的存放路径(此处代表放在根目录下)
});
//创建一个自动清除dist文件夹的类(每次打包之前直接删掉dist文件夹后才进行打包build)
const {CleanWebpackPlugin}=require('clean-webpack-plugin'); //require进行导入库 这个库在项目的终端中已经通过npm进行安装过
const cleanPlugin=new CleanWebpackPlugin(); //创建一个对象 (才能进行挂载)
module.exports={
mode:'development', //development版本适合开发(打包快,优化较慢) production版本适合上线(打包慢,优化性能好)
// eval-source-map仅在“开发模式”下使用,不建议在“生产模式下使用”
// webpack默认会使用Source map工具,但是报错行和源码行不一致,使用eval-source-map可以让控制台的报错行数和源码行数一致
devtool:'eval-source-map',
// devtool:'nosources-source-map', //这个 nosources-source-map 可以在生产模式下使用 因为只报错代码函数 不展示具体的源码
entry:path.join(__dirname,'./src/index.js'), //指定打包的入口路径 此处的__Dirname指的是当前文件所在的文件目录
output:{
path:path.join(__dirname,'./dist'), //输出文件的存放路径
// filename:'bundle.js', //输出文件的名称
filename:'js/bundle.js', //生成一个js文件夹存放bundle.js
},
//plugins进行插件挂载
plugins:[htmlPlugin,cleanPlugin], //定义好实例对象htmlPlugin后 需要将其挂载到plugins节点上才能生效
//devServer节点可以帮助修改端口号
devServer:{
open:true, //初次完成打包后自动打开浏览器
host:'127.0.0.1', //实时打包所使用的主机地址
port:9000, //实时打包素所使用的端口号(由于80端口号是默认端口号 因此使用80端口时在URL地址栏只显示主机地址而不显示端口号)
},
module:{ //所有第三方模块的匹配规则
rules:[ //文件后缀名的匹配规则
//test中存放的是文件的后缀名 正则表达式表示的 表示要处理什么样的文件
{test:/\.css$/,use:['style-loader','css-loader']} , //注意此处的loader是有顺序的 不要写反两者 而多个loader检测执行顺序是从后往前的(也就是css-loader先被检测到)
{test:/\.less$/,use:['style-loader','css-loader','less-loader']}, //此处不能缺少css-loader 因为less-loader处理完了得需要交给css-loader处理 不能直接跨级到style-loader处理
// 当loader一级一级处理完到style-loader时,最后再将处理的文件整合回webpack中进行打包
//loader的写法一
// {test:/\.jpg|png|gif$/,use:'url-loader?limit=839401'}, //limit是参数 当图片小于等于839401字节时会被自动转成base64格式的图片
//loader的写法二:以对象的形式
{test:/\.jpg|png|gif$/,
use:{
loader:'url-loader', //通过loader属性指定要调用的loader
options:{ //通过options属性指定参数项
limit:839400,
outputPath:'image' //设置打包发布(build)时的生成路径
}
}
},
//高级JS的打包前loader处理
{
test:'/\.js$/',
exclude:/node_modules/, //排除项 表示babel-loader只处理开发者缩写的JS代码 而不考虑node_modules原本有得JS代码
use:{
loader:'babel-loader',
options:{
//声明一个babel插件 用此插件来转化class中的高级语法
plugins:['@babel/plugin-proposal-class-properties'],
}
}
}
]
},
resolve:{
alias:{
// 告诉项目 @代表的就是src文件目录 访问src下的文件 使用“@/”+文件名即可进行访问
'@':path.join(__dirname,'./src/'),
}
}
}
一些注意事项:
在进行package.json包初始化的时候,倘若使用npm init -y会报错,则可以尝试先试用npm init命令在终端中运行,当弹出Is this OK? (yes) 时,再输入y按下回车键即可完成。
在打包过程中可能会出现无法生效的原因 注意报错信息的提示 仔细分析是因为哪块部分导致的打包失败.。例如下面这个案例分析:错误原因就是入口文件的地址entry拼写错误导致打包失败,有的打包失败是因为版本问题,那可以重装webpack版本来进行解决。
configuration has an unknown property 'enrty'. These properties are valid:
端口号被占用如何解决:
- 杀死占用进程,释放端口号
- 换一个空闲的端口号
Error: listen EADDRINUSE: address already in use 127.0.0.1:8080
ES6模块化
注意点:
默认导出方式和按需到处方式可以同时使用,而且允许两者之间的数据重叠
例如下面:
默认导出文件default_output.js文件
export var b=1,c=1;
export default{
b,
c
}
默认导入文件default_input.js文件
import {b,c} from'./default_output.js'
import m from'./default_output.js'
console.log(m);
console.log(b+" "+c);
console.log(m.c+" "+m.b);
// 因此默认导出方式和按需到处方式可以共用 而且允许两者之间的数据重叠
node运行结果:
Promise知识点
首先我们来看下面的代码:
//通过ES6规范引入读取文件的模块
import fs from 'fs'
//该种方式并不是通过then方式来执行回调函数的,所以readfile的第三个参数是回调函数部分
//按顺序读取文件并且输出打印——回调地狱的模式
fs.readFile('./files/1.txt','utf8',(err1,r1)=>{
if(err1) return console.log(err1.message);
console.log(r1);
fs.readFile('./files/2.txt','utf8',(err2,r2)=>{
if(err2) return console.log(err2.message);
console.log(r2);
fs.readFile('./files/3.txt','utf8',(err3,r3)=>{
if(err3) return console.log(err3.message);
console.log(r3);
});
});
});
上述代码多层回调函数的相互嵌套,就形成了 回调地狱 。示例代码如下:
回调地狱的缺点:
- 代码耦合性太强,牵一发而动全身, 难以维护
- 大量冗余的代码相互嵌套,代码的 可读性变差
如何解决回调地狱的问题:
为了解决回调地狱的问题,ES6(ECMAScript 2015 )中新增了 Promise 的概念
如果上一个.then() 方法中 返回了一个新的 Promise 实例对象 ,则可以通过下一个 .then() 继续进行处理。通过 .then() 方法的 链式调用 ,就解决了回调地狱的问题。
//引入then-fs模块用于文件读取 从而支持基于Promise模式
import thenFs from 'then-fs'
var type=thenFs.readFile('./files/1.txt','utf8')
.then(r1=>{
console.log("sucess"); //读取成功时打印
return thenFs.readFile('./files/1.txt','utf8');
})
console.log(type); //尝试打印出来看看return的内容的类型是不是Promise类型
Promise支持链式调用 ,从而来解决回调地狱的问题。示例代码如下:
正常按顺序输出:
//基于Promise进行链式方式读取(此处省略了失败的回调函数 但是使用了catch的方式进行捕获错误)
thenFs.readFile('./files/1.txt','utf8')
.then(r1=>{
console.log(r1); //读取成功文件1时打印
return thenFs.readFile('./files/2.txt','utf8');
})
.then(r2=>{
console.log(r2); //成功读取文件2时打印
return thenFs.readFile('./files/3.txt','utf8');
})
.then(r3=>{
console.log(r3); //成功读取文件3时打印
//后面没有内容了 就没有必要打印了
})
.catch(err=>{ //捕获错误信息 只要上面有报错则会立即跳到这一行来执行(而不再执行错误处后面的代码了)
console.log(err.message);
})
不存在11.txt的文件报错:
//基于Promise进行链式方式读取(此处省略了失败的回调函数 但是使用了catch的方式进行捕获错误)
thenFs.readFile('./files/11.txt','utf8')
.then(r1=>{
console.log(r1); //读取成功文件1时打印
return thenFs.readFile('./files/2.txt','utf8');
})
.then(r2=>{
console.log(r2); //成功读取文件2时打印
return thenFs.readFile('./files/3.txt','utf8');
})
.then(r3=>{
console.log(r3); //成功读取文件3时打印
//后面没有内容了 就没有必要打印了
})
.catch(err=>{ //捕获错误信息 只要上面有报错则会立即跳到这一行来执行(而不再执行错误处后面的代码了)
console.log(err.message);
})
//catch捕获方式2——如果不希望前面的错误导致后续的.then 无法正常执行,则 可以将 .catch 的调用提前
thenFs.readFile('./files/11.txt','utf8')
.catch(err=>{ //捕获错误信息 只要上面有报错则会立即跳到这一行来执行 在此处捕获错误(我们认为将错误修正了 处理完后 会正常执行catch之后的代码)
console.log(err.message);
})
.then(r1=>{
console.log(r1); //读取成功文件1时打印 无文件的时候打印undefined
return thenFs.readFile('./files/2.txt','utf8');
})
.then(r2=>{
console.log(r2); //成功读取文件2时打印
return thenFs.readFile('./files/3.txt','utf8');
})
.then(r3=>{
console.log(r3); //成功读取文件3时打印
//后面没有内容了 就没有必要打印了
})
Promise.all () 方法
Promise.all() 方法 会发起并行的 Promise 异步操作,等 所有的异步操作全部结束后 才会执行下一步的 .then操作(等待机制)。示例代码如下:
// 引入基于Promise方式的读取文件模块
import thenFs from 'then-fs'
//thenFs.readFile返回的内容是一个Promise对象 因此 PromiseArray是一个Promise对象数组
//此处只是定义数组 告诉数组的文件路径等信息(但未真正读取文件)
const PromiseArray=[
thenFs.readFile('./files/1.txt','utf8'),
thenFs.readFile('./files/2.txt','utf8'),
thenFs.readFile('./files/3.txt','utf8')
]
// Promise.all开始真正异步操作读取文件存入数组中 虽然读取的顺序不确定 但是储存在数组中的位置固定
Promise.all(PromiseArray)
.then(([r1,r2,r3])=>{
console.log(r1,r2,r3);
})
.catch(err=>{
console.log(err.message);
});
注意:数组中Promise 实例的顺序,就是最终结果的顺序!
Promise.race () 方法
Promise.race()方法会发起并行的 Promise 异步操作, 只要任何一个异步操作完成,就立即执行下一步的.then 操作 (赛跑机制)。示例代码如下
// 引入基于Promise方式的读取文件模块
import thenFs from 'then-fs'
//thenFs.readFile返回的内容是一个Promise对象 因此 PromiseArray是一个Promise对象数组
//此处只是定义数组 告诉数组的文件路径等信息(但未真正读取文件)
const PromiseArray=[
thenFs.readFile('./files/1.txt','utf8'),
thenFs.readFile('./files/2.txt','utf8'),
thenFs.readFile('./files/3.txt','utf8')
]
// 将promise对象的数组作为Promise.race的参数
// 只要该数组中有一个文件完成了读取 就执行then回调函数率先打印
Promise.race(PromiseArray)
.then((result)=>{
console.log(result);
})
.catch(err=>{
console.log(err.message);
});
率先读取的是1.txt文档并调用了回调函数打印输出。
基于 Promise 封装读文件的方法
//fs是不支持promise方式的
import fs from 'fs'
// 此处基于Promise封装方法进行读取文件 实质上就是在完成then-fs模块读取文件的原理解析
// then-fs模块读取文件是利用已经封装好的方法readfile 而此处正式手动进行读取文件方法的封装
// then-fs在readFile后返回的是一个promise对象,而此处也可以进行模拟 在读取文件函数getFile方法中手动直接return 一个promise方法
function getFile(){
return new Promise(function(resolve,reject){ //resolve是Promise成功的回调函数 reject是失败的回调函数 此处两个回调函数作为promise的参数
//模拟readFile方法中读取文件 因此会先先读取文件之后再返回promise对象
fs.readFile('./files/1.txt','utf8',(err,result)=>{ //err和result是fs读区文件的失败和成功的回调函数
if(err) return reject(err); //失败信息传到then的失败回调函数中 return reject代表执行完reject函数后就会return退出ß
resolve(result); //成功信息传到then的成功回调函数中
})
});
}
// getFile()的返结果是一个promise对象
getFile().then(result=>{
console.log(result);
},err=>{
console.log(err.message);
})
async/await
3.async/await的 使用注意事项
①如果在 function 中使用了 await ,则 function 必须 被 async 修饰
②在 async 方法中, 第一个 await 之前的代码会同步执行,await 之后的代码会异步执行
③使用了await之后,thenFs读取文件之后不再是promise对象,而变成了文件中的内容了,因此单纯读取文件到变量中是异步的,而不用再使用回调函数因此避免了顺序不定的问题。
async-await代码执行顺序案例分析:
下述中1~3的txt内容分别是111 222 333。
代码:
import thenFs from 'then-fs'
console.log('A')
async function getAllFile() {
console.log('B')
const r1 = await thenFs.readFile('./files/1.txt', 'utf8')
const r2 = await thenFs.readFile('./files/2.txt', 'utf8')
const r3 = await thenFs.readFile('./files/3.txt', 'utf8')
console.log(r1,r2,r3)
console.log('D')
}
getAllFile()
console.log('C')
执行结果:
上述代码的执行顺序是:先打印出A,然后进入getAllFile()函数中执行同步任务打印出B。之后碰到了await导致整个函数延迟执行,因此先暂缓当前function的执行打印输出C。等到所有同步任务执行结束之后,再重新回到await的地方将含有await部分的代码进行异步执行,当所有含await的代码异步执行完毕后,再执行await之后的同步代码。因此此处先打印完r1 r2 r3之后再执行打印D。
注意点⚠️:上述只是await后面的代码(指的是thenFs.readFile是异步执行),而consolo.log(r1,r2,r3)仍然是同步执行,因此是先输出1,2,3文件夹的内容再输出D。读取文件的异步操作虽然先后顺序不定,但是不妨碍console.log的输出顺序。
案例2:
import thenFs from 'then-fs'
console.log('A')
async function getAllFile() {
console.log('B')
const r1 = await thenFs.readFile('./files/1.txt', 'utf8')
const r2 = await thenFs.readFile('./files/2.txt', 'utf8')
const r3 = await thenFs.readFile('./files/3.txt', 'utf8')
console.log(r1,r2,r3)
console.log('D')
const r4 = await thenFs.readFile('./files/3.txt', 'utf8')
console.log(r4)
console.log("end")
}
getAllFile()
console.log('C')
结论:
使用async-await将使得整个函数延后执行,即同步的代码执行完毕后再执行;对于async函数的内部,使用了await的地方,会先执行,然后再执行函数内await之后的同步代码。
不使用async-await的函数,将先执行函数内部的同步函数(也就是跳过异步任务 会先将函数内的所有同步任务执行完),执行完同步后,跳出该函数,继续执行其它同步函数,待同步函数执行完后,再回到异步请求函数内部执行。
API接口案例
5.创建 db 数据库操作模块
import mysql from 'mysql2'
// 这是一个连接数据库的模块
const pool=mysql.createPool({
host:'127.0.0.1',
port:3306, //这个端口号不要改动
database:'db_1', //需要操作的数据库名称(这得在数据库中提前创建好)
user:'root', //登陆数据库的用户名
password:'admin123' //登陆数据库的密码
})
//默认导出一个数据库对象(基于promise) 倘若不掉用promise方法就导出的话 那么导出的部分是无法基于promise语法使用的
export default pool.promise();
6.创建 user_ctrl 模块
import db from '../db/index.js'
// 导入之前获取到的数据库实例
//这是一个处理数据的模块(连接数据库后,通过SQL代码从数据库进行获取)
//基于获取到的数据库模块 自定义一个函数用于获取所有用户列表的数据
export async function getAllUser(req,res){
// db.query返回的是Promise对象,使用await将其简化不需要回调函数直接获取到内容
const [rows]=await db.query('select id,name,age from users');
res.send({ //发送信息(包含数据)
status:0,
message:"获取用户列表数据成功",
data:rows,
})
};
7.创建 user_router 模块
import express from 'express'
// 按需导入getAllUser模块
import { getAllUser } from '../controller/user_ctrl.js';
// 创建路由对象
const router=new express.Router();
//挂在路由规则
router.get('/user',getAllUser); //当用户访发送get请求问到user时,则执行getAllUser
// 使用ES6的默认导出语法,将路由对象共享出去
export default router;
8.导入并挂载路由模块
import express from 'express'
//导入路由对象
import userRouter from './router/user_router.js'
const app=new express(); //new出一个express对象
// 挂在用户路由模块
app.use('/api',userRouter);
app.listen('8080',()=>{
console.log("server is sucessfully running at 'http:127.0.0.1'");
});
这是我在终端中事先创建好的数据库表格,并且插入好了几条数据。
此处成功在浏览器中获取到在mysql中插入的数据。
ES6模版字符串
<!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>ES6 模版字符串</title>
</head>
<body>
<div id="app"></div>
<script>
// 1. 模版字符串和单双引号中的字符串没啥区别 也是string类型
let str = `我是一个模版字符串`;
console.log(str, typeof str);
// 2. 模版字符串可用于变量拼接
let lover = 'DLJ';
let word1 = lover + " is my lover"; //普通的JS中通过加号的拼接方式
let word2 = `${lover} is my lover`; //使用模版字符串后的变量拼接 (注意引号的类型)
console.log(word1);
console.log(word2);
// 3. 模版字符串中可以存在换行符 不会报错
let test = `<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>`;
console.log(test);
document.getElementById('app').innerHTML = test;
</script>
</body>
</html>
Vue中的数据代理
数据代理定义:通过一个对象代理对另一个对象中属性的操作(读/写)。
现在我们提出一个需求,假设存在A和B两个对象,A中只有属性x,B中只有属性y,我们需要让让B能对A中的x属性进行访问和修改,但原本B是没有x属性的,此时便是数据代理的应用场景。
因此B相当于A的一个代理人员,若x是A的股票,那么B就是A的股票经理,对A持有的股票进行买卖操作。实现方法:利用属性添加函数defineProperty把x属性添加到B的名下(但核心的股票所有权仍然是在A那的 ,只不过是B帮忙进行操作显示)
代码示例:
<!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>
<script>
let A = {
x: 100
};
let B = {
y: 200
};
// 现在我们需要让B能对A中的x属性进行访问和修改——————因此B相当于A的一个代理人员 若x是A的股票 那么B就是A的股票经理
//利用属性添加函数defineProperty 把x属性添加到B的名下 (但核心 股票所有权仍然是在A那的 只不过是B帮忙进行操作显示)
Object.defineProperty(B, 'x', {
get() {
console.log("访问A中的x");
return A.x;
},
set(value) {
console.log("修改A中的x");
A.x = value;
}
})
</script>
</body>
</html>
进一步分析Vue存在的数据代理:
下图中的vm.name和vm._data.name便形成了数据代理关系。此处_data就是上面的A,Vue实例对象vm就是上面的B。
Vue进行监测数据的原理
此处简单讲讲Vue对data中的属性进行自动监测的思想。虽然Vue给我们提供了一个watch方式供我们监测数据的变化,但是watch只是给开发者提供的一个接口方便我们观察到数据变化而去执行其他指令。在Vue中实际上存在一套自动监测数据的变化,自动捕捉data中的数据改变而重新渲染模版;而且watch方式也是基于这套自动监测原理所实现的。
注意点:
假设当前Vue对象vm的data中存在name和address属性,vm上的name和address属性实际上是数据代理而生成的(可见上一节Vue存在的数据代理),其中的getter和setter也是为了数据代理操作而支持的。也就是说当我们读取或者更改Vue对象vm的name和address属性时,会自动调用setter和getter进行数据代理操作,读取或者修改_data中的name和addresss属性。例如:我们在控制台修改vm.name后,则会调用vm的set name方法(也叫做setter),在set name函数中将vm._data.name进行修改为最新值,因此vm._data.name遭到了修改。然后当我们读取修改后的vm.name时,自动会调用get name方法(也就是getter),读取vm._data.name的值并且返回(实际上就是 return vm._data.name;)。因为从上一小节我们得知vm的name和address属性和vm上的_data中的name和address属性形成了一对数据代理关系。(实际上这一段内容解释了数据代理图中的第二步)
上面说完了vm中的name、address属性的setter和getter作用。此处我们发现vm._data被代理的对象中也存在着setter和getter。而vm._data中的getter和setter需要和vm上挂载的getter和setter进行区分,这里的getter和setter作用是:当我们在_data中读取或者修改数据后,会自动调用_data中的getter和setter;而且每次vm._data中的数据已经发生改变,会触发vm._data中的setter(注意⚠️是vm._data的值先改变才触发setter,因此vm._data.name的改变不是发生在vm._data的setter中的,而是在vm的setter中),而在vm._data中的setter中存在一个重新解析模版的操作:将数据进行改变后并且会重新进行解析模版,这样自动进行解析模版的效果就是重新渲染了一遍模版完成了显示上的数据更新。
重点记忆:
在Vue中,只要修改某个数据并且触发了该数据的setter,那么在这个数据的setter中就会重新进行模版解析。
下面我们讲解vm._data是如何替代vm.data(实际上不存在vm身上)工作的,也就是vm.data=》vm._data的路径:
我们需要注意的是,对于某个对象上的getter和setter,不能在getter和setter中对该对象上的属性进行操作。例如:
data中存在name属性,但是如果data上的setter是用于读取data的属性data.name的,那么会存在一个无线递归,因此要特别注意。
对于这种会形成递归的方法,Vue通过一个中介observer对象obj来接管data中的属性,避免了自身在data中存在getter和setter方法,这样就把data中的属性name和address转移到了obj身上,同时也存在这两种属性的对象和方法。由于data是传入的参数,因此obj能完成对data中数据的监视,能够读取或修改obj身上的name或者address属性时,会分别调用obj身上的getter或者setter;从而间接的完成了对data数据的操作。最后再把obj对象赋值给vm._data即可,因此vm._data完成了对vm.data(实际上vm.data是不存在的 data没有挂在vm上,但是由于代码中我们写的形式是vm.data 因此完成了从“vm.data到vm._data”这样的任务交接工作);实际上也就对应着下图数据代理的第一步。
理解组件和组件实例的本质:
组件就是一个构造函数,不同的组件是不同的构造函数。当我们创建一个组件时,我们接收到的是VueComponent构造函数,当我们通过标签使用这些组件的时候,则会自动进行new VueComponent(options)创建一个组件实例对象;options是Vue.extend传入的对象参数。
const school = Vue.extend({
template: ` //使用模版字符串 此处template没有实际的结构 外层只允许有一个根节点(一定要把包裹div标签 否则可能无法显示完全) 直接渲染到app所在区域
<div>
<p>学校名称:{{schoolName}}</p>
<p>学校地址:{{address}}</p>
</div>
`,
data() { //在Vue的组件中 data一定写成函数形式 return一个数据对象
return {
schoolName: '小海豚幼儿园',
address: '北京'
}
}
});
vue.js关于vue.extend部分的源码:
Vue.extend = function (extendOptions) {
// ...
var Sub = function VueComponent (options) {
this._init(options);
};
// ...
return Sub
};
}
依照上面的代码分析:上面我们使用school去接收Vue.extend的返回结果,因此school就是一个组件,school就是function VueComponent (options)这个构造对象(由Vue.extend所返回的)。当我们在template或者真实的html结构中写上<school></school>
的时候,Vue在解析模版时会自动调用new VueComponent(options)去new出一个VueComponent的组件实例对象出来;而当我们对多次使用<school></school>
的时候,也就会利用VueComponent构造函数new出多个一摸一样的对象。但注意:这些new出来的对象之间的数据没有联系,不串联
原型(prototype)、原型链和原型继承
相关知识点参考下述链接:
https://zhuanlan.zhihu.com/p/35790971
Vue与VueComponent的关系: