一、vue区分动态绑定class和style:
二、reduce方法
1、语法
arr.reduce(callback,[initialValue])
reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。
callback (执行数组中每个值的函数,包含四个参数)
1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
2、currentValue (数组中当前被处理的元素)
3、index (当前元素在数组中的索引)
4、array (调用 reduce 的数组)
initialValue (作为第一次调用 callback 的第一个参数。)
2、实例解析 initialValue 参数
先看第一个例子:
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
})
console.log(arr, sum);
打印结果:
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10
这里可以看出,上面的例子index是从1开始的,第一次的prev的值是数组的第一个值。数组长度是4,但是reduce函数循环3次。
再看第二个例子:
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
},0) //注意这里设置了初始值
console.log(arr, sum);
打印结果:
0 1 0
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10
这个例子index是从0开始的,第一次的prev的值是我们设置的初始值0,数组长度是4,reduce函数循环4次。
结论:如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。如果提供initialValue,从索引0开始。
注意:如果这个数组为空,运用reduce是什么情况?
var arr = [];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
})
//报错,"TypeError: Reduce of empty array with no initial value"
但是要是我们设置了初始值就不会报错,如下:
var arr = [];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
},0)
console.log(arr, sum); // [] 0
所以一般来说我们提供初始值通常更安全
3、reduce的简单用法
当然最简单的就是我们常用的数组求和,求乘积了。
var arr = [1, 2, 3, 4];
var sum = arr.reduce((x,y)=>x+y)
var mul = arr.reduce((x,y)=>x*y)
console.log( sum ); //求和,10
console.log( mul ); //求乘积,24
4、reduce的高级用法
(1)计算数组中每个元素出现的次数
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
let nameNum = names.reduce((pre,cur)=>{
if(cur in pre){
pre[cur]++
}else{
pre[cur] = 1
}
return pre
},{})
console.log(nameNum); //{Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
(2)数组去重
let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre,cur)=>{
if(!pre.includes(cur)){
return pre.concat(cur)
}else{
return pre
}
},[])
console.log(newArr);// [1, 2, 3, 4]
(3)将二维数组转化为一维
let arr = [[0, 1], [2, 3], [4, 5]]
let newArr = arr.reduce((pre,cur)=>{
return pre.concat(cur)
},[])
console.log(newArr); // [0, 1, 2, 3, 4, 5]
将多维数组转化为一维
let arr = [[0, 1], [2, 3], [4,[5,6,7]]]
const newArr = function(arr){
return arr.reduce((pre,cur)=>pre.concat(Array.isArray(cur)?newArr(cur):cur),[])
}
console.log(newArr(arr)); //[0, 1, 2, 3, 4, 5, 6, 7]
(4)对象里的属性求和
var result = [
{
subject: 'math',
score: 10
},
{
subject: 'chinese',
score: 20
},
{
subject: 'english',
score: 30
}
];
var sum = result.reduce(function(prev, cur) {
return cur.score + prev;
}, 0);
console.log(sum) //60
三、计算属性computed
1.计算属性的本质是computed对象(属性)中套了一个对象(属性),而这个对象里有两个方法,一个set,另一个是get。
2.一般情况set方法不需要实现,只需要get方法,即为只读属性。但是当我们在修改属性值的时候,就会触发set方法。
3.计算属性和methods对比
当界面多次渲染属性时,computed只调用一次,性能较高,内部对计算属性做了缓存,会观察计算属性是否改变,没改变就返回原来结果。
四、块级作用域等于闭包
1.在ES6之前,for和if没有作用域,而函数是有的。以下是函数的闭包。
ES5中的var是没有块级作用域的(if/for)
ES5之前因为if和for没有块级作用域的概念,所以在很多时候,我们都必须借助于function的作用域来解决应用外面变量的问题
ES6中的let是由有块级作用域的(if/for)
ES6中,加入了let,let是有if和for的块级作用域、
2.const
在开发中,优先使用const,除非要改变某一标识符的时候,才使用let。
注意:
(1)在给const修饰的标识符赋值之后,不能修改。
(2)在使用const定义标识符,必须进行赋值。
(3)常量的含义是指向的对象不能修改,但可以改变对象内部属性。
这里const修饰了一个变量obj,它现在指向一个对象,对象里面有几个属性,现在整体的obj有个内存地址0x333,obj指向对象的本质就是它保存了对象的0x333这个地址。
现在有个新的对象,有个新的地址0x666,经过const修饰之后,就不能指向新的对象。
现在假设要改0x333这个地址里面的对象属性,假如name值变成了’kobe’,但这里的地址没变,里面的属性值可以随便改,但是如果要obj这个变量经过const修饰之后指向一个新的对象就不可以了。
3.对象字面量增强写法
对象字面量:在图中,对象字面量就是{}。
对象字面量不仅能放属性,也能放函数。
属性的增强写法。它自动会把属性名称作为key,属性值作为value,所以可以简化写法。
函数的增强写法,不需要先写key,再写个function()。
五、事件监听
单个参数,拿到event
方法中多个参数拿到event------$event
六、v-if/v-else
input复用问题:
七、v-for
在DOM渲染到浏览器之前,会有一个虚拟DOM。
这里的插入元素,没有key时,它会逐个对比(相当于顺序表),挨个对比,把不一样的逐个按顺序替换。
在绑定了key之后(key不是随便绑定的,是需要与循环的item一一对应,所以不要绑定index,index并不与item一一对应,要绑定item---->v-for=“item in arr” :key=“item” ),有了key内存里就是链表,插入的时候是根据key插入,不是根据顺序插入,key就好比身份证号,是唯一的,在插队的时候,根据身份证号可以直接插队,没有身份证号就会按照顺序插队。
有了key之后进行diff算法时,它会先查看有key的li发生变化了没有,没有变化就会直接运用,在插入元素的时候,会创建一个新元素,并把这个新元素插入到正确的位置,以此提高性能。
有些时候改变数组的值,界面上不会实时更新,所以需要使用数据的一些响应式方法。
八、过滤器filters
一般来说,filters是一个函数,把要过滤的参数传入filters的函数里面。
补充:数组遍历的几种方式:
编程范式:命令式编程(一步步命令) / 声明式编程(拿到数据,把数据保存到某个位置,然后做个指令声明,比如v-for,之后会对数据自动进行编译,现在愈发流行)
编程范式:面向对象编程(尽可能把很多东西抽象成一个对象,因为对象可封装可继承还有多态性,第一公民是对象)/ 函数式编程(第一公民是函数,把东西抽象成函数,好处:可进行链式编程)
九、高阶函数
1.filter函数
2.map函数
3.reduce函数(重点)
十.v-model
1.实现了双向绑定
2.本质:3.v-model结合radio类型
4.v-model结合checkbox(单选框和多选框)
单选是布尔类型,多选是数组类型。
5.v-model结合select
单选是字符串类型,多选是数组类型。
7.值绑定
有时候value值会被直接写死,如上图,但是实际开发这里都是可选的,此时动态的把服务器给的数据遍历出来,给用户选择。值绑定的意思就是动态的获取,然后绑定值(实质就是v-bind)。
8.修饰符
默认情况下v-model输入的值都会转为string类型,加个.number修饰符就可以变为number类型。
十一.组件化开发
1.组件的原理
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容, 哈哈哈哈</p>
<p>我是内容, 呵呵呵呵</p>
</div>`
})
// 2.注册组件(传入两个参数:组件名称、组件构造器)
Vue.component('my-cpn', cpnC)
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
</script>
组件的使用要放到vue实例里面
2.全局组件和局部组件
<div id="app">
<cpn></cpn>
</div>
<div id="app2">
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈啊</p>
</div>
`
})
// 2.注册组件(全局组件, 意味着可以在多个Vue的实例下面使用)
// Vue.component('cpn', cpnC)
// 疑问: 怎么注册的组件才是局部组件了?
// 在vue实例中有个components属性,在components属性中进行注册就可以变为局部组件。
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
// cpn:使用组件时的标签名
// 这个局部组件只能在id为app的页面中显示,而app2中此组件无法展示
cpn: cpnC
}
})
const app2 = new Vue({
el: '#app2'
})
</script>
3.父组件和子组件
<div id="app">
<cpn2></cpn2>
<!--<cpn1></cpn1>-->
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建第一个组件构造器(子组件)
const cpnC1 = Vue.extend({
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容, 哈哈哈哈</p>
</div>
`
})
// 2.创建第二个组件构造器(父组件)
const cpnC2 = Vue.extend({
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容, 呵呵呵呵</p>
<cpn1></cpn1>
</div>
`,
components: {
cpn1: cpnC1
}
})
// root组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn2: cpnC2
}
})
</script>
4.语法糖(去掉extend)
<div id="app">
<cpn1></cpn1>
// vue.component注册的是全局组件
// 不用在下面的vue实例里面声明就可以直接使用cpn1
<cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.全局组件注册的语法糖
// 1.创建组件构造器
// const cpn1 = Vue.extend()
// 语法糖: 这里去掉extend这一行
// 2.注册组件
// 这里语法糖就是:把之前构造组件的extend代码取消
// 直接把extend里的对象,写到注册组件的第二个参数
// 等同于去掉extend,把注册组件时传的第二个参数组件构造器替换成这个对象
Vue.component('cpn1', {
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容, 哈哈哈哈</p>
</div>
`
})
// 2.注册局部组件的语法糖
// 直接在vue实例里写个components属性,然后声明一下
// 就等同于局部组件的注册了,也省去了extend构造
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
// 写法:components: { '组件名称':{template:`内容`}}
components: {
'cpn2': {
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容, 呵呵呵</p>
</div>
`
}
}
})
</script>
5.模板分离写法
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<!--1.script标签, 注意:类型必须是text/x-template 再加上id-->
<script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
</div>
</script>
<!--2.template标签-->
<template id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,呵呵呵</p>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.注册一个全局组件
Vue.component('cpn', {
// 取到id为cpn的模板
template: '#cpn'
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
// 全局组件,不用在此处写component属性
})
</script>
6.组件不可以访问vue实例数据,有自己的数据保存地方
举例:
用函数相当于每次动态创造一个新的内存
这就是为什么data是个函数,为了组件被多处复用的时候,有各自的数据,相互之间不会产生影响。
7.父子组件的通信
(1)父传子
<div id="app">
// 必须用v-bind绑定 取父组件的值
<!--<cpn v-bind:cmovies="movies"></cpn>-->
<!--<cpn cmovies="movies" cmessage="message"></cpn>-->
<cpn :cmessage="message" :cmovies="movies"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 父传子: props
const cpn = {
template: '#cpn',
// props: ['cmovies', 'cmessage'],
props: {
// 1.类型限制
// cmovies: Array,
// cmessage: String,
// 2.提供一些默认值, 以及必传值
cmessage: {
type: String,
default: 'aaaaaaaa',
required: true
},
// 类型是对象或者数组时, 默认值必须是一个函数
cmovies: {
type: Array,
default() {
return []
}
}
},
data() {
return {}
},
methods: {
}
}
const app = new Vue({
el: '#app',
data: {
// 相当于父组件的值
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
cpn
}
})
</script>
父传子:在子组件定义props,在父组件中的子组件标签写v-bind:子组件的data=“父组件的data”,通过这个把父组件的值传给子组件,子组件拿到父的值后,就可以通过自己需要的样式对值进行渲染。
(注意:组件名称,组件data的值的名称,以及方法的名称,均要小写,并且避免在子组件中修改绑定的值,props的值应该在父组件中修改)
驼峰标识需写成短横线形式,否则数值获取不到。
(2)子传父
子传父:在子组件里有个事件,这个事件里面有个this.$emit(‘子组件事件名称’,参数),在父组件的子组件标签里写@子组件事件名称=“父组件事件名称”
<!--父组件模板-->
<div id="app">
<cpn @item-click="cpnClick"></cpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<button v-for="item in categories"
@click="btnClick(item)">
{{item.name}}
</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 1.子组件
const cpn = {
template: '#cpn',
data() {
return {
categories: [
{id: 'aaa', name: '热门推荐'},
{id: 'bbb', name: '手机数码'},
{id: 'ccc', name: '家用家电'},
{id: 'ddd', name: '电脑办公'},
]
}
},
methods: {
btnClick(item) {
// 发射事件: 自定义事件
this.$emit('item-click', item)
}
}
}
// 2.父组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn
},
methods: {
cpnClick(item) {
console.log('cpnClick', item);
}
}
})
</script>
(3)案例:修改值
(4)父子访问
$refs用的较多-----父访问子
不建议使用parent,因为一旦用了$parent,就代表组件的复用性降低了。
访问根组件是$root(访问到vue实例),也用的很少,因为vue实例里面一般放的是最重要的东西,比如路由、vuex。
十二、slot插槽
1.基本使用
为了使组件具备扩展性,每次使用组件都能加不一样的东西。
在封装组件的时候,当大体结构一致,而内容有不一样的情况时,就留一个slot插槽。
设置默认值
2.具名插槽
<body>
<div id="app">
<cpn v-show="isShow"><span slot="center">标题</span></cpn>
</div>
<template id="cpn">
<div>
<slot><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot><span>右边</span></slot>
</div>
</template>
<script>
/**
父组件模板的所有东西都会在父级作用域内编译
子组件模板所有的东西都会在子级作用域编译
*/
const cpn = {
template:'#cpn',
data(){
return{
isShow:true
}
}
}
var vm = new Vue({
el: '#app',
data: {
isShow:false
},
methods: {},
components:{
cpn
}
});
</script>
</body>
3.作用域插槽
<body>
<div id="app">
<cpn></cpn>
<cpn>
<br>
<br>
<!-- 目的是获取子组件的pLanguage -->
<template slot-scope="slot">
<span v-for="item in slot.data">{{item}} - </span>
<br>
<span>{{slot.data.join(' - ')}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="pLanguages"><li v-for="item in pLanguages">{{item}}</li></slot>
</div>
</template>
<script>
/**
父组件替换插槽的标签,但是内容是由子组件来提供
*/
const cpn = {
template:'#cpn',
data(){
return{
pLanguages: ['javascript','c++','go','java']
}
}
}
var vm = new Vue({
el: '#app',
data: {
isShow:false
},
methods: {},
components:{
cpn
}
});
</script>
</body>
现在已经用v-slot替换此语法了