HTML入门与进阶以及HTML5
CSS
JS-上
JS-下
jQuery
Node.js + Gulp 知识点汇总
MongoDB + Express 入门及案例代码
Vue项目开发-仿蘑菇街电商APP
Vue 知识点汇总(下)–附案例代码及项目地址
(1)Vue是一个渐进式的框架,什么是渐进式的呢?
(2)Vue有很多特点和Web开发中常见的高级功能
这些特点,你不需要一个个去记住,我们在后面的学习和开发中都会慢慢体会到的,一些技术点我也会在后面进行讲解。
(3)学习Vuejs的前提?
从零学习Vue开发,并不需要你具备其他类似于Angular、React,甚至是jQuery的经验。
但是你需要具备一定的HTML、CSS、JavaScript基础。
使用一个框架,我们第一步要做什么呢?安装下载它
安装Vue的方式有很多:
方式一:直接CDN引入
你可以选择引入开发环境版本还是生产环境版本
方式二:下载和引入
开发环境 https://vuejs.org/js/vue.js
生产环境 https://vuejs.org/js/vue.min.js
方式三:NPM安装
后续通过webpack和CLI的使用,我们使用该方式。
注:本文多数内容属于Vue2.6之前的内容,只有较为重要的地方才会补充2.6版本之后的内容,望周知。
通常我们学习一个概念,最好的方式是去看维基百科(对,千万别看成了百度百科)
https://zh.wikipedia.org/wiki/MVVM
维基百科的官方解释,我们这里不再赘述。
View层:
Model层:
VueModel层:
1.MVVC 和 MVC
在前端的MVC模式中,M还是表示Modal层,负责与后台交互数据,V表示View,负责页面上DOM的渲染,C表示绑定在DOM元素上的事件,当Controllor中的事件被调用,会去调用Modal中的数据,然后交给View重新渲染数据
框架篇—MVC、MVP、MVCS、MVVM、VIPER使用关系总结
mvc和mvvm的区别
MVC
MVVM为前端view里面的
如何将data中的文本数据,插入到HTML中呢?
我们可以像下面这样来使用,并且数据是响应式的
但是,在某些情况下,我们可能不希望界面随意的跟随改变
v-once:
某些情况下,我们从服务器请求到的数据本身就是一个HTML代码
如果我们希望解析出HTML展示
在不想用mustache语法的时候,可以使用这样
但它不够灵活。建议使用mastache语法
将代码原封不动的解析出来,不做任何处理
将未解析出来的代码块进行隐藏
但基本不会用到
前面我们学习的指令主要作用是将值插入到我们模板的内容当中。
但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定。
这个时候,我们可以使用v-bind指令:
作用:动态绑定属性
缩写::
预期:any (with argument) | Object (without argument)
参数:attrOrProp (optional)
v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值(这个学到组件时再介绍)
在开发中,有哪些属性需要动态进行绑定呢?
比如通过Vue实例中的data绑定元素的src和href,代码如下:
v-bind有一个对应的语法糖,也就是简写方式
在开发中,我们通常会使用语法糖的形式,因为这样更加简洁。
简写方式如下:
(1)绑定方式:对象语法
对象语法有下面这些用法:
用法一:直接通过{}绑定一个类
Hello World
用法二:也可以通过判断,传入多个值
Hello World
用法三:和普通的类同时存在,并不冲突
注:如果isActive和isLine都为true,那么会有title/active/line三个类
Hello World
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
Hello World
(2)绑定方式:数组语法
数组语法有下面这些用法:
用法一:直接通过{}绑定一个类
Hello World
用法二:也可以传入多个值
Hello World
用法三:和普通的类同时存在,并不冲突
注:会有title/active/line三个类
Hello World
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
Hello World
案例:vue v-for出来的列表,点击当前,当前被点击的字体变颜色
Document
我们可以利用v-bind:style来绑定一些CSS内联样式。
在写CSS属性名的时候,比如font-size
绑定class有两种方式:
(1)绑定方式一:对象语法
:style="{color: currentColor, fontSize: fontSize + 'px'}"
style后面跟的是一个对象类型
(2)绑定方式二:数组语法
style后面跟的是一个数组类型
多个值以,分割即可
我们知道,在模板中可以直接通过插值语法显示一些data中的数据。
但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示
我们可以将上面的代码换成计算属性:
计算属性中也可以进行一些更加复杂的操作,比如下面的例子:
每个计算属性都包含一个getter和一个setter
我们可能会考虑这样的一个问题:
在官方文档中,强调了computed区别于method最重要的两点
{{computedTest}}
()
来调用,如{{methodTest()}}
,否则,视图会出现test1的情况,见下图text
还没有发生改变,多次访问 getText
计算属性会立即返回之前的计算结果,而不必再次执行函数。而方法只要页面中的属性发生改变就会重新执行在前端开发中,我们需要经常和用于交互。
v-on介绍
当通过methods中定义方法,以供@click调用时,需要注意参数问题:
在某些情况下,我们拿到event的目的可能是进行一些事件处理。
Vue提供了修饰符来帮助我们方便的处理一些事件:
这三个指令与JavaScript的条件语句if、else、else if类似。
Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件
v-if的原理:
v-show的用法和v-if非常相似,也用于决定一个元素是否渲染:
v-if和v-show都可以决定一个元素是否渲染,那么开发中我们如何选择呢?
开发中如何选择呢?
当我们有一组数据需要进行渲染时,我们就可以使用v-for来完成。
v-for的语法类似于JavaScript中的for循环。
我们来看一个简单的案例:
如果在遍历的过程中不需要使用索引值
如果在遍历的过程中,我们需要拿到元素在数组中的索引值呢?
v-for可以用户遍历对象:
比如某个对象中存储着你的个人信息,我们希望以列表的形式显示出来。
官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性。
为什么需要这个key属性呢(了解)?
当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点
所以我们需要使用key来给每个节点做一个唯一标识
所以一句话,key的作用主要是为了高效的更新虚拟DOM。
因为Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,视图会发生对应的更新。
Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新。
注:通过索引值修改数组中的元素不是响应式的
表单控件在实际开发中是非常常见的。特别是对于用户信息的提交,需要大量的表单。
Vue中使用v-model指令来实现表单元素和数据的双向绑定。
案例的解析:
当然,我们也可以将v-model用于textarea元素
v-model其实是一个语法糖,它的背后本质上是包含两个操作:
1.v-bind绑定一个value属性
2.v-on指令给当前元素绑定input事件
也就是说下面的代码:等同于下面的代码:
等同于
当存在多个单选框时
复选框分为两种情况:单个勾选框和多个勾选框
单个勾选框:
多个复选框:
lable好处就是用户可以点击文字也会选中
和checkbox一样,select也分单选和多选两种情况。
单选:只能选中一个值。
多选:可以选中多个值。
篮球
羽毛球
兵乓球
足球
您的爱好是:{{hobbies}}
用Chrome浏览器
用360浏览器
查看了几个文档后发现是Chrome不兼容Object.observe
解决方法
使用 Vue.set(object, key, value)
方法将响应属性添加到嵌套的对象上。 还可以使用 vm.$set
实例方法,这也是全局 Vue.set
方法的别名。
vue中遇到的坑 — 变化检测问题(数组相关)
深入响应式原理
也就是说,因为360浏览器太老(没有更新)的原因,没有废弃object.server,所以才能够这样用。现阶段只能使用vue.set
lazy修饰符:
number修饰符:
trim修饰符:
人面对复杂问题的处理方式:
任何一个人处理信息的逻辑能力都是有限的
所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。
但是,我们人有一种天生的能力,就是将问题进行拆解。
如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。
组件化也是类似的思想:
如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
组件化是Vue.js中的重要思想
它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
任何的应用都会被抽象成一颗组件树。
组件化思想的应用:
有了组件化的思想,我们在之后的开发中就要充分的利用它。
尽可能的将页面拆分成一个个小的、可复用的组件。
这样让我们的代码更加方便组织和管理,并且扩展性也更强。
组件的使用分成三个步骤:
我们来看看通过代码如何注册组件
查看运行结果:
这里的步骤都代表什么含义呢?
1.Vue.extend():
2.Vue.component():
3.组件必须挂载在某个Vue实例下,否则它不会生效。
全局组件和局部组件
当我们通过调用Vue.component()注册组件时,组件的注册是全局的
这意味着该组件可以在任意Vue示例下使用。
如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件
父组件和子组件
在前面我们看到了组件树:
我们来看通过代码如何组成的这种层级关系:
父子组件错误用法:以子标签的形式在Vue实例中使用
是只能在父组件中被识别的。
是会被浏览器忽略的。注册组件语法糖
在上面注册组件的方式,可能会有些繁琐。
Vue为了简化这个过程,提供了注册的语法糖。
主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
语法糖注册全局组件和局部组件:
组件是一个单独功能模块的封装:
这个模块有属于自己的HTML模板,也应该有属性自己的数据data。
组件中的数据是保存在哪里呢?顶层的Vue实例中吗?
我们先来测试一下,组件中能不能直接访问Vue实例中的data
我们发现不能访问,而且即使可以访问,如果将所有的数据都放在Vue实例中,Vue实例就会变的非常臃肿。
结论:Vue组件应该有自己保存数据的地方。
组件数据的存放
组件自己的数据存放在哪里呢?
组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)
只是这个data属性必须是一个函数
而且这个函数返回一个对象,对象内部保存着数据
为什么data在组件中必须是一个函数呢?
父子组件的访问方式: $children
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。
父组件访问子组件:使用$children
或$refs
子组件访问父组件:使用$parent
我们先来看下$children
的访问
this.$children
是一个数组类型,它包含所有子组件对象。
我们这里通过一个遍历,取出所有子组件的message状态。
父子组件的访问方式: $refs
$children
的缺陷:
$refs
的使用:
$refs
和ref指令通常是一起使用的。this.$refs.ID
就可以访问到该组件了。父子组件的访问方式: $parent
如果我们想在子组件中直接访问父组件,可以通过$parent
注意事项:
$parent
来访问父组件,但是在真实开发中尽量不要这样做。$parent
直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。非父子组件通信
刚才我们讨论的都是父子组件间的通信,那如果是非父子关系呢?
非父子组件关系包括多个层级的组件,也包括兄弟组件的关系。
在Vue1.x的时候,可以通过$dispatch
和$broadcast
完成
$dispatch用于向上级派发事件
$broadcast用于向下级广播事件
但是在Vue2.x都被取消了
在Vue2.x中,有一种方案是通过中央事件总线,也就是一个中介来完成。
但是这种方案和直接使用Vuex的状态管理方案还是逊色很多。
并且Vuex提供了更多好用的功能,所以这里我们暂且不讨论这种方案,后续我们专门学习Vuex的状态管理。
编译作用域
在真正学习插槽之前,我们需要先理解一个概念:编译作用域。
官方对于编译的作用域解析比较简单,我们自己来通过一个例子来理解这个概念:
我们来考虑下面的代码是否最终是可以渲染出来的:
中,我们使用了isShow属性。
isShow属性包含在组件中,也包含在Vue实例中。
答案:最终可以渲染出来,也就是使用的是Vue实例的属性。
为什么呢?
官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
而我们在使用
的时候,整个组件的使用过程是相当于在父组件中出现的。
那么他的作用域就是父组件,使用的属性也是属于父组件的属性。
因此,isShow使用的是Vue实例中的属性,而不是子组件的属性。
为什么使用slot
slot翻译为插槽:
组件的插槽:
栗子:移动网站中的导航栏。
但是,每个页面的导航是一样的吗?No,我以京东M站为例
如何封装这类组件呢?slot
它们也很多区别,但是也有很多共性。
如果,我们每一个单独去封装一个组件,显然不合适:比如每个页面都返回,这部分内容我们就要重复去封装。
但是,如果我们封装成一个,好像也不合理:有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字,等等。
如何封装合适呢?抽取共性,保留不同。
最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
是搜索框,还是文字,还是菜单。由调用者自己来决定。
这就是为什么我们要学习组件中的插槽slot的原因。
slot基本使用
了解了为什么用slot,我们再来谈谈如何使用slot?
在子组件中,使用特殊的元素
就可以为子组件开启一个插槽。
该插槽插入什么内容取决于父组件如何使用。
我们通过一个简单的例子,来给子组件定义一个插槽:
中的内容表示,如果没有在该组件中插入任何其他内容,就默认显示该内容
有了这个插槽后,父组件如何使用呢?
具名插槽slot
当子组件的功能复杂时,子组件的插槽可能并非是一个。
比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?
这个时候,我们就需要给插槽起一个名字
如何使用具名插槽呢?
非常简单,只要给slot元素一个name属性即可
我们来给出一个案例:
这里我们先不对导航组件做非常复杂的封装,先了解具名插槽的用法。
作用域插槽:准备
作用域插槽是slot一个比较难理解的点,而且官方文档说的又有点不清晰。
这里,我们用一句话对其做一个总结,然后我们在后续的案例中来体会:
父组件替换插槽的标签,但是内容由子组件来提供。
我们先提一个需求:
子组件中包括一组数据,比如:pLanguages: [‘JavaScript’, ‘Python’, ‘Swift’, ‘Go’, ‘C++’]
需要在多个界面进行展示:
内容在子组件,希望父组件告诉我们如何展示,怎么办呢?
利用slot作用域插槽就可以了
我们来看看子组件的定义:
作用域插槽:使用
在父组件使用我们的子组件时,从子组件中拿到数据:
我们通过获取到slotProps属性
在通过slotProps.data就可以获取到刚才我们传入的data了
在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即
v-slot
指令)。它取代了slot
和slot-scope
这两个目前已被废弃但未被移除且仍在文档中的特性。新语法的由来可查阅这份 RFC。
slot有三种类型
default
name
v-slot
在子组件中:
标签来确定渲染的位置,里面放如果父组件没传内容时的后备内容。一个不带 name
的
出口会带有隐含的名字“default
”。name
属性来表示插槽的名字有时我们需要多个插槽。例如对于一个带有如下模板的
组件:
对于这样的情况,
元素有一个特殊的特性:name
。这个特性可以用来定义额外的插槽:
一个不带 name
的
出口会带有隐含的名字“default”。
v-slot
//具名插槽的缩写
// v-slot:default
默认插槽
// v-slot:header
具名插槽
//v-slot:footer
{{slotProps.testProps}}
在向具名插槽提供内容的时候,我们可以在一个 元素上使用
v-slot
指令,并以 v-slot
的参数的形式提供其名称:
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
现在 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有
v-slot
的 中的内容都会被视为默认插槽的内容。
然而,如果你希望更明确一些,仍然可以在一个 中包裹默认插槽的内容:
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
任何一种写法都会渲染出:
Here might be a page title
A paragraph for the main content.
And another one.
注意 v-slot 只能添加在 上 (只有一种例外情况),这一点和已经废弃的 slot
特性)不同。
v-slot 作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,设想一个带有如下模板的
组件:
{{ user.lastName }}
我们可能想换掉备用内容,用名而非姓来显示。如下:
{{ user.firstName }}
然而上述代码不会正常工作,因为只有
组件可以访问到 user
而我们提供的内容是在父级渲染的。
为了让 user
在父级的插槽内容中可用,我们可以将 user
作为
元素的一个特性绑定上去:
{{ user.lastName }}
绑定在
元素上的特性被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot
来定义我们提供的插槽 prop 的名字:
{{ slotProps.user.firstName }}
在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 slotProps
,但你也可以使用任意你喜欢的名字。
独占默认插槽的缩写语法
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot
直接用在组件上:
{{ slotProps.user.firstName }}
这种写法还可以更简单。就像假定未指明的内容对应默认插槽一样,不带参数的 v-slot
被假定对应默认插槽:
{{ slotProps.user.firstName }}
注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确:
{{ slotProps.user.firstName }}
slotProps is NOT available here
只要出现多个插槽,请始终为所有的插槽使用完整的基于 的语法:
{{ slotProps.user.firstName }}
...
解构插槽 Prop
作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里:
function (slotProps) {
// 插槽内容
}
这意味着 v-slot
的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop,如下:
{{ user.firstName }}
这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。它同样开启了 prop 重命名等其它可能,例如将 user
重命名为 person
:
{{ person.firstName }}
你甚至可以定义后备内容,用于插槽 prop 是 undefined 的情形:
{{ user.firstName }}
注意
的语法动态插槽名
2.6.0 新增
动态指令参数也可以用在 v-slot
上,来定义动态的插槽名:
...
JavaScript原始功能
在网页开发的早期,js制作作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的。
那个时候的代码是怎么写的呢?直接将代码写在标签中即可
随着ajax异步请求的出现,慢慢形成了前后端的分离
客户端需要完成的事情越来越多,代码量也是与日俱增。
为了应对代码量的剧增,我们通常会将代码组织在多个js文件中,进行维护。
但是这种维护方式,依然不能避免一些灾难性的问题。
比如全局变量同名问题:看右边的例子
另外,这种代码的编写方式对js文件的依赖顺序几乎是强制性的
但是当js文件过多,比如有几十个的时候,弄清楚它们的顺序是一件比较同时的事情。
而且即使你弄清楚顺序了,也不能避免上面出现的这种尴尬问题的发生。
匿名函数的解决方案
我们可以使用匿名函数来解决方面的重名问题
在aaa.js文件中,我们使用匿名函数
但是如果我们希望在main.js文件中,用到flag,应该如何处理呢?
显然,另外一个文件中不容易使用,因为flag是一个局部变量。
使用模块作为出口
我们可以使用将需要暴露到外面的变量,使用一个模块作为出口,什么意思呢?
来看下对应的代码:
我们做了什么事情呢?
非常简单,在匿名函数内部,定义一个对象。
给对象添加各种需要暴露到外面的属性和方法(不需要暴露的直接定义即可)。
最后将这个对象返回,并且在外面使用了一个MoudleA接受。
接下来,我们在main.js中怎么使用呢?
我们只需要使用属于自己模块的属性和方法即可
这就是模块最基础的封装,事实上模块的封装还有很多高级的话题:
但是我们这里就是要认识一下为什么需要模块,以及模块的原始雏形。
幸运的是,前端模块化开发已经有了很多既有的规范,以及对应的实现方案。
常见的模块化规范:
CommonJS、AMD、CMD,也有ES6的Modules
模块化有两个核心:导出和导入
CommonJS的导出:
CommonJS的导入
export基本使用
export指令用于导出变量,比如下面的代码:
上面的代码还有另外一种写法:
导出函数或类
上面我们主要是输出变量,也可以输出函数或者输出类
上面的代码也可以写成这种形式:
export default
某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名
这个时候就可以使用export default
我们来到main.js中,这样使用就可以了
这里的myFunc是我自己命名的,你可以根据需要命名它对应的名字
另外,需要注意:
export default在同一个模块中,不允许同时存在多个。
import使用
我们使用export指令导出了模块对外提供的接口,下面我们就可以通过import命令来加载对应的这个模块了
首先,我们需要在HTML代码中引入两个js文件,并且类型需要设置为module
import指令用于导入模块中的内容,比如main.js的代码
如果我们希望某个模块中所有的信息都导入,一个个导入显然有些麻烦:
通过可以导入模块中所有的export变量
但是通常情况下我们需要给起一个别名,方便后续的使用
index.html
Document
作者
书籍名称
出版日期
价格
购买数量
操作
{{item.id}}
{{item.author}}
{{item.name}}
{{item.data}}
{{item.price | showPrice}}
{{item.count}}
总价格:{{totalPrice}}
购物车为空
style.css
table{
border: 1 px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
}
th, td{
padding: 8px 16px;
border:1 px solid #e9e9e9;
text-align: left;
}
th{
background-color: #f7f7f7;
color: #5c6b77;
font-weight:600;
}
main.js
const app = new Vue({
el:"#app",
data:{
books:[
{
id: 1,
author: '曹雪芹',
name: '红楼梦',
data:"????-??-??",
price: 32.0,
count: 1
}, {
id: 2,
author: '施耐庵',
name: '水浒传',
data:"????-??-??",
price: 30.0,
count: 1
}, {
id: '3',
author: '罗贯中',
name: '三国演义',
data:"????-??-??",
price: 24.0,
count: 1
}, {
id: 4,
author: '吴承恩',
name: '西游记',
data:"????-??-??",
price: 20.0,
count: 1
}
]
},
methods:{
decrement(index){
this.books[index].count--;
},
increment(index){
this.books[index].count++;
},
removeHandler(index){
this.books.splice(index, 1);
},
},
computed:{
totalPrice(){
let totalPrice = 0;
for(let i = 0; i < this.books.length; i++){
totalPrice += this.books[i].price * this.books[i].count;
}
return totalPrice;
}
},
//全局过滤器 ,filter 不能定义在创建的Vue对象后面 ,filters局部过滤器
filters:{
showPrice(price){
return "¥" + price.toFixed(2);
}
},
})
几种for语法
用的是前面购物车案例的代码
将普通函数转换成JavaScript高阶函数和