4.7蓝桥杯做题
1.心愿便利贴
在初始化的时候输入框并没有做验证,输入内容发布后,许愿贴并不能出现。
目标
请在 index.html
文件中补全代码,具体需求如下:
- 将填写完的表单正确渲染到许愿墙上。
- 完成表单验证,姓名(必填项) 2-4 个字符,许愿内容(必填项)长度在 1 到 30 个字符。
本项目使用到的 element-ui
表单验证 API 如下:
表单属性
参数 | 说明 | 类型 |
---|---|---|
rules | 表单验证规则 | object |
表单项属性
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
prop | 表单域 model 字段,在使用 validate、resetFields 方法的情况下,该属性是必填的 | string | 传入 Form 组件的 model 中的字段 | - |
required | 是否必填 | boolean | - | false |
trigger | 验证时触发的事件 | string | click/focus/change/blur/input | - |
min | 最小长度 | number | - | - |
max | 最大长度 | number | - | - |
message | 验证不通过时的错误信息 | string | - | - |
使用示例
<el-form-item label="姓名" prop="activeName">
<el-input v-model="form.activeName" placeholder="请输入姓名"></el-input>
</el-form-item>
// ....
rules: {
activeName: [
{ required: true, message: '请输入活动名称', trigger: 'blur' },
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
],
}
完成后效果如下:
<!-- TODO 待修改的代码 -->
<div class="card" :class="item.css" v-for="(item,index) in wishList" :key="index">
copy
rules: {
// TODO 待补充验证的代码
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' },
{ min: 2, max: 4, message: '长度在 2 到 4 个字符', trigger: 'blur' }
],
content: [
{ required: true, message: '请输入许愿内容', trigger: 'blur' },
{ min: 1, max: 30, message: '长度在 1 到 30 个字符', trigger: 'blur' }
]
}
这道题主要是要看懂题目给的例子,看懂就能进行模仿做出题目。看懂题目案例,就能使用题目给的数据进行验证,分两种情况,一种是框里什么都没有写,就验证不能请输入,另一种情况就是输入的长度不对,进行两次验证。
2.消失的 Token
在浏览器中预览 index.html
页面效果如下:
此时输入用户名后回车/点击确定,数据发生改变,但还是停留在登录页,无法正确显示登录成功界面。
目标
找到 index.html
中的 TODO
部分,仔细阅读 store
文件夹下的相关代码并结合 Vuex
相关知识,排查代码中存在的问题,修改后使得登录界面输入 admin 时,点击确认按钮/回车可以正确显示如下界面:
这道题通过查看Vuex仓库 store 文件夹下的 index.js 可知有两个Vuex模块,模块名 base 所对应的是 BaseModule ,模块名 user 所对应的是 UserModule ,再通过查看 UserModule.js 与 UserModule.js 可知,只有 user 模块通过 namespaced: true 开启了命名空间功能。因此,在调用 user 模块中的 getters 与 mutations 时,需要使用以下特定语法:
'模块名/模块中的getters或mutations'
修改前
var app = new Vue({
el: '#app',
data() { },
computed: {
welcome() {
return store.getters.welcome
},
username() {
return store.getters.username
},
token() {
return store.getters.token
}
},
methods: {
// 回车/点击确认的回调事件
login(username) {
username && store.commit('login', { username, token: 'sxgWKnLADfS8hUxbiMWyb' })
username && store.commit('say', '登录成功,欢迎你回来!')
}
}
})
所以,修改后的代码如下:
// TODO 修改下面错误代码
var app = new Vue({
el: '#app',
data() { },
computed: {
welcome() {
return store.getters.welcome
},
username() {
return store.getters['user/username']
},
token() {
return store.getters['user/token']
}
},
methods: {
// 回车/点击确认的回调事件
login(username) {
username && store.commit('user/login', { username, token: 'sxgWKnLADfS8hUxbiMWyb' })
username && store.commit('say', '登录成功,欢迎你回来!')
}
}
})
3.封装 Promisefy 函数
目标
请在 index.js
文件中的补全代码,完成 promisefy
函数的封装。将 fs
中的 readFile
方法 promise
化。也就是说 readFileSync
方法执行后,会返回一个 promise
,可以调用 then
方法执行成功的回调或失败的回调。
在实际应用中,一个函数满足这几个条件,就可以被 promisify
化:
- 该方法必须包含回调函数
- 回调函数必须执行
- 回到函数第一个参数代表 err 信息,第二个参数代表成功返回的结果
在控制台运行:
node index
此时应打印出 true
,即:回调形式的 fs.readFile
方法读取同个文件的结果与 Promise
形式读取结果一致。
**思路:**在promise函数中设置两个函数,分别为处理正确和错误的两个函数,promisify 是个函数,参数里面传个函数,promisify 的返回值也是个函数,调用这个函数,这个函数的返回是 promise 对象。在promise函数中,设置变量判断处于那个状态,设置值接收,两个函数在内部判断并将值赋值给外层接收。
const promisefy = (fn) => {
// TODO 此处完成该函数的封装
//读取文件
return (textPath,utf8)=>{
//返回一个新的Promise对象,有正确值和错误值输出
return new Promise((resolve,reject)=>{
//读取文件,返回错误信息或者读取到的值
fs.readFile(textPath,utf8,(err,data)=>{
if(err){
return reject(err)
}
return resolve(data)
})
})
}
}
4.趣购
目标
<div class="good-list">
<div v-for="good in goods"
:key="good.name"
class="good"
draggable="true"
@dragstart="dragstart($event,good)">
<img :src="good.cover" />
<span>{{ good.name }}</span>
<span>¥{{ good.price }}</span>
</div>
</div>
上面先为每个商品绑定draggable="true"
使其变成可拖放元素,再为其绑定dragstart
事件,其对应的dragstart
事件处理程序如下:
dragstart(ev,good){
// 向dataTransfer属性中添加拖拽数据
ev.dataTransfer.setData("name", good.name);
ev.dataTransfer.setData("price", good.price);
}
根据题目信息,我们很容易知道可以在dataTransfer
属性中保存事件的数据。
draggable:这个属性是枚举类型 (en-US),而不是布尔类型。这意味着必须显式指定值为 true 或者 false,像 这样的简写是不允许的。正确的用法是 。
dragstart事件:当用户开始拖拽一个元素或选中的文本时触发
之后需要为购物车图标绑定放置事件drop
:
<div id="trolley" class="trolley" @dragover.prevent @drop="drop" >
<span id="bought" class="bought" v-if="bought.length !== 0">{{
bought.length
}}</span>
<img src="./images/trolley.jpeg" />
</div>
根据题目信息可以得知,可以通过drop事件来获取可拖放元素的数据,而要想触发drop事件需要先清除dragover事件的默认行为,在Vue中可以通过.prevent修饰符来清除事件的默认行为,所以在购物车图标上还需要绑定一个@dragover.prevent。drop事件对应的事件处理程序如下:
drop(ev){
// 先获取dataTransfer上保存的可拖放元素的数据
const name = ev.dataTransfer.getData("name");
const price = ev.dataTransfer.getData("price");
// 向bought数组中添加该商品的信息(向购物车中添加商品)
this.bought.push({name,price:Number(price)}) // 这里将price转换成number类型,方便之后的计算
},
观察题目代码推断出data
中的bought
是用来存放购物车的数据的
通过上面的步骤后题目的要求我们已经实现了一半了,下面需要解决的问题就是将购物车(bought
)内的数据渲染到页面上,观察发现页面中使用到了两个计算属性来显示购物车(bought
)数据:
<div class="result">
<div>
购物车商品:<span id="goods">{{ goodsDetail }}</span>
</div>
<div>
购物车商品总计:<span id="total">{{ totalPrice }}</span>
</div>
</div>
所以接下来只需要补全goodsDetail
和totalPrice
这两个计算属性就ok了:
totalPrice() {
// 通过数组的reduce求和函数来获取购物车商品总计
return this.bought.reduce((a, b) => {
return a + b.price
}, 0);
},
goodsDetail() {
/**
* 这里用了两次reduce
* 第一次是为了将bought中相同的商品合并为同一个对象,并为其添加一个amount字段表示其数量
* 第二次是为了将数据转换成符合题目要求的字符串格式
*/
return this.bought.reduce((a, b) => {
const good = a.find(item => item.name === b.name) // 先查询a中与b相同的商品
if (good) {
// 如果a中有与b相同的商品,则将其amount加1即可
good.amount++
} else {
// 如果a中没有与b相同的商品,则向其push b商品的信息并初始化一个amount字段
a.push({ name: b.name, price: Number(b.price), amount: 1 })
}
return a
}, []).reduce((a, b) => {
return a + b.name + '*' + b.amount + ' '
}, '');
},
reduce 为数组中的每一个元素依次执行回调函数,reduce可以操作数组里的数据,这道题使用reduce将数据合并新数据进数组里,或者对数组里的数据进行更改,reduce跟常用的map,forEach一样,也是用于遍历循环,只不过它可以设置初始值,这样可以大大增强代码的可读性。
还可以使用类一种方法使用forEach来遍历判断后添加数据或者更改数据。
<!-- TODO: 补充拖拽事件,请不要改动任何 id 属性 -->
<template>
<div class="container">
<div class="good-list">
<div
v-for="good in goods"
:key="good.name"
class="good"
draggable="true"
@dragstart="dragstart($event, good)"
>
< img :src="good.cover" />
<span>{{ good.name }}</span>
<span>¥{{ good.price }}</span>
</div>
</div>
<div id="trolley" class="trolley" @dragover.prevent @drop="drop($event)">
<span id="bought" class="bought" v-if="bought.length !== 0">{{
bought.length
}}</span>
< img src="./images/trolley.jpeg" />
</div>
<div class="result">
<div>
购物车商品:<span id="goods">{{ goodsDetail }}</span>
</div>
<div>
购物车商品总计:<span id="total">{{ totalPrice }}</span>
</div>
</div>
</div>
</template>
module.exports = {
data() {
return {
goods: [
{
name: "畅销书",
price: 30,
cover: "./images/book.jpeg",
},
{
name: "收纳箱",
price: 60,
cover: "./images/box.jpeg",
},
{
name: "纸巾",
price: 20,
cover: "./images/paper.jpeg",
},
{
name: "电视",
price: 1000,
cover: "./images/tv.jpg",
},
],
//购物车所有商品的数量
bought: [],
//购物车列表
shoplist:[]
};
},
// TODO: 请补充实现代码
computed: {
//购物车总价
totalPrice() {
sumPrice = 0;
this.shoplist.forEach((item) => {
sumPrice += item.price * item.num;
});
return sumPrice;
},
//购物车显示列表
goodsDetail() {
s = "";
this.shoplist.forEach((item) => {
s += item.name + "*" + item.num + " ";
});
return s;
},
},
methods: {
//开始拖拽事件
dragstart(event, good) {
//存储商品
event.dataTransfer.setData("name", good.name);
event.dataTransfer.setData("price", good.price);
//动态给每个商品添加数量属性
this.$set(good, "num", 1);
//进行存储
event.dataTransfer.setData("num", good.num);
},
drop(event) {
//获取商品
const name = event.dataTransfer.getData("name");
//计算商品数量
this.bought.push(name)
const price = event.dataTransfer.getData("price");
const num = event.dataTransfer.getData("num");
//判断购物车中是否有数据
if (this.shoplist.length !== 0) {
//查找商品与当前加入商品相同的
index = this.shoplist.findIndex((item) => {
return item.name == name;
});
//如果不相同直接存储到商品列表中
if (index == -1) {
this.shoplist.push({ name, price, num });
} else {
//如果相同就直接加该商品的数量即可
this.shoplist[index].num++;
}
} else {
this.shoplist.push({ name, price, num });
}
},
},
};
5.乾坤大挪移心法
目标
到 main.js
文件中的“乾坤大挪移心法” mentalMethod
函数,完成函数中的 TODO 部分。
mentalMethod
需要返回一个函数,可以一直进行调用,但是最后一次调用不传参。- 函数通过以下方式执行,返回结果均为
'战胜峨眉,武当,少林'
。
mentalMethod('峨眉')('武当')('少林')();
mentalMethod('峨眉','武当')('少林')();
mentalMethod('峨眉','武当','少林')();
注意逗号为英文逗号。
完成后,在命令行输入 node main.js
效果如下:
思路:这道题ES6的拓展运算符以及函数的封装及作用域的问题。这是一道循环调用函数的题,要实现可以一直调用并传参,但是最后一次调用不传参,也就是当函数没有参数传入时就退出循环函数的调用,并将传入的参数进行整理后并输出。
方法一:这是一个钻空子的写法,应为本题中传入的参数都是相同的,所以用了一个钻空子的写法让他的输出参数都是固定的,这个方式在传参不同时,就是不可取的。通过这个方式,明白函数是如何实现循环调用的以及拓展运算符的相关知识点。
console.log(this.mentalMethod(1,2)())//战胜峨眉,武当,少林
// 此时的的...args是作为一个剩余参数使用的,剩余参数是聚合用的,将函数传入的参数进行组合为一个数组。
function mentalMethod(...args) {
console.log((args))//[1,2],将传入的参数组合为一个数组进行输出。
if (args.length) {
//当传入的参数为空时,就会跳出这个if循环,此时就会进入输出'战胜峨眉,武当,少林'。
//所以不论传入的参数时是什么,有几个最终输出的都是最终return的结果。
console.log((111))
return this.mentalMethod;
}
return '战胜峨眉,武当,少林'
},
方式二:这个方式解决传入的参数不同的情况下,进行输出的。
<template>
</template>
<script>
export default{
data(){
return{
arr:[]
}
},
mounted() {
console.log(this.mentalMethod('峨眉')('武当')('少林')());
console.log(this.mentalMethod('峨眉', '武当')('少林')());
console.log(this.mentalMethod('峨眉', '武当', '少林')());
},
methods:{
mentalMethod(...args) {
//由于在首先执行mentalMethod()函数时,就会将第一个参数传入,所以数组的初始值就应该为第一个传入的参数。
let arr = args
console.log(args)
//输出["峨眉"]
//此时就想到了在函数中在套入函数,在该函数中进行循环push,此时arr就不会置为空。
console.log(...args)
//输出峨眉
let fn = function(...rest){
//此时再调用该函数时,传入的参数是从第二个开始的
console.log(rest)
//输出["武当"]
console.log(...rest)
//输出武当
if(rest.length>0){
arr.push(...rest)
console.log(arr)
//输出["峨眉", "武当"]
return fn
}else{
//此时表示传入的参数为空,也就是不能再继续循环调用,应退出循环了。
arr[0] = '战胜'+ arr[0]
console.log(arr)
//输出["战胜峨眉", "武当", "少林"]
let a = ''
//就需要对输出的数组内容进行整理
arr.forEach((item,index) =>{
if(index+1 !=arr.length){
//遍历是从0开始算起的,而长度是从1算起的,所以要将索引值加1
//判断不是最后一个就加上值和,
a += item+','
}else{
//是最后一个则直接加
a+=item
}
})
return a
}
}
return fn;
},
}
}
</script>
<style>
</style>
相关知识点:
1… args是什么意思?
…被称为扩展运算符。
通常,…args表示"任意数量的值"。 例如,您可以传递null或1,2,3,4-没关系,并且该方法足够聪明来处理它。
这是ES6中引入的新语法。 请在此处查看文档developer.mozilla.org/en/docs/Web/JavaScript/Reference/
关于o (…args) =>,…args是rest参数。它始终必须是参数列表中的最后一个条目,并且将被分配一个数组,该数组包含所有尚未分配给先前参数的参数。
2.ES6学习–函数剩余参数 (rest参数)
ES6 引入 rest 参数(形式为“…变量名”),用于获取函数的多余参数,这样就不需要使用arguments
对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。(可以拿到除开始参数外的参数)
这个rest 参数和arguments不一样,它是程序员自定义的一个普通标识符,只是需要在前面加上三个点:…
function func(a, ...rest) {
console.log(a)
console.log(rest)
}
func(1) // 1 []
func(1, 2, 3, 4) //1 [2,3,4]
又比如,在前面定义2个参数
function func(a, b, ...rest) {
console.log(a, b)
console.log(rest)
}
func(1, 2) // 1 2 []
func(1, 2, 3, 4) 1 2 [3,4]
注意:剩余参数后面不能跟其他参数,否则会报错
function func(a, ...rest, b) {
}//报错
当使用剩余参数后,函数的length属性会发生一些变化
function func(a, b, ...rest) {
}
func.length // 2
即length不包含rest,为2。
剩余参数前面可以不跟其他参数,即一个参数也没有。如
function func(...rest) {
console.log(rest)
}
func(1) // [1]
func(1, 2, 3, 4) // [1,2,3,4]
总结:
今天做了心愿便利贴、消失的 Token、封装 Promisefy 函数,趣购。心愿便利贴是根据题目给的案例,按照题目给的案例方法进行验证数据输入是否符合要求。消失的 Token考查了使用vuex的特殊命名空间后使用仓库是路径如何写,这道题通过查看Vuex仓库 store 文件夹下的 index.js 可知有两个Vuex模块,模块名 base 所对应的是 BaseModule ,模块名 user 所对应的是 UserModule ,再通过查看 UserModule.js 与 UserModule.js 可知,只有 user 模块通过 namespaced: true 开启了命名空间功能。封装 Promisefy 函数考查了Promise的异步封装函数写法和读取文件的回调形式的 fs.readFile
结合一起使用读取文件,主要是使用回调的方式写这道题。在promise函数中设置两个函数,分别为处理正确和错误的两个函数,promisify 是个函数,参数里面传个函数,promisify 的返回值也是个函数,调用这个函数,这个函数的返回是 promise 对象。在promise函数中,设置变量判断处于那个状态,设置值接收,两个函数在内部判断并将值赋值给外层接收。在做趣购时根据题目给的一些属性进行操作,使得商品可以移动到购物车,商品移动时也能将商品的信息带给购物车,购物车进行计算出购买商品的数量和总价。上面先为每个商品绑定draggable="true"
使其变成可拖放元素,再为其绑定dragstart
事件,将需要传递的参数绑定到dragsstart一起携带过去。购物车则先解除默认的事件@dragover.prevent,绑定新的数据@drop=“drop”,接收传递过来的数据。然后补全goodsDetail
和totalPrice
这两个计算属性,使用reduce()对数组进行添加,计算,合并等操作。或者使用forEach()的方法对数组进行遍历查找,添加的操作。 乾坤大挪移心法,这道题看了好久才明白需要进行什么操作。这道题的考点就在于函数的循环调用的使用以及对传入的数据使用拓展运算符进行展开或是聚合。在使用第一种方式进行解决时,只是对该函数进行循环调用,对于其中传入的参数并没有做什么操作,但是还是能够通过,原因就是题目传入的参数都是相同的,让它的return都是相同的,但这种方式是不可取的,当参数发生变化时,就会出现错误。第二种方式就是对其中的参数进行了拓展或是聚合的操作,初步想法是先放置一个空数组,当传入的参数不是空的时候,就将其中的参数进行push进数组。此时就会发现一个问题,由于当传入的参数不是空的情况下,就会继续循环调用该函数,也就是再次进入mentalMethod()函数,此时arr又会再一次置为空,所以此方法无效。所以就需要在在函数中在套入函数,在该函数中进行循环push,此时arr就不会置为空。在这个循环调用的函数的封装时,就需要多次输出其中传入的参数的值,并实时对代码进行输出查看。