个人案例
- ColorUI-高颜值,高效率的小程序组件库
[图片] 简介 hi!开发者!ColorUI迎来了2.0的升级,相比之前的版本,2.0版本重构了基础代码,增加了更多的配色,这是一个全新的小程序UI解决方案。 ColorUI是一个Css类的UI组件库!不是一个Js框架。相比于同类小程序组件库,ColorUI更注重于视觉交互! 项目为个人开源项目,如果项目有帮到你,希望能支持下开发者。 [图片] 截图 [图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片] 使用 浏览GitHub:https://github.com/weilanwl/ColorUI/
2018-12-25 - 【教程】关于uniapp中对axios的封装
对于axios请求做的其他操作,例如拦截、过滤等等····· 下图中baseUri自己定义所需的baseURL即可 [代码]import axios from "axios"; import {baseUri} from "./api.js" const service = axios.create({ baseURL: baseUri, timeout: 600000, }); axios.defaults.retry = 4; axios.defaults.retryDelay = 4000; axios.defaults.withCredentials = true; function startLoading() { //使用Element loading-start 方法 console.log(baseUri); uni.showLoading({ title: "加载中..." }); } function endLoading() { //使用Element loading-close 方法 uni.hideLoading(); } let needLoadingRequestCount = 0 export function showFullScreenLoading() { if (needLoadingRequestCount === 0) { startLoading() } needLoadingRequestCount++ } export function tryHideFullScreenLoading() { if (needLoadingRequestCount <= 0) return needLoadingRequestCount--; if (needLoadingRequestCount === 0) { endLoading() } } service.interceptors.request.use((config) => { showFullScreenLoading(); return config; }, error => { tryHideFullScreenLoading(); return Promise.reject(error) }); // axios 请求处理超时处理 service.interceptors.response.use( function(response) { tryHideFullScreenLoading(); return response; } ); export default service; [代码]
2021-11-27 - 华中某科技大学校友会小程序概要设计
整体说明 1 小程序校友数据基母校开发。由于历史数据繁多,数据库中难免存在疏漏。若无法成功认证身份,请您提交人工审核后耐心等待,或联系校友会由管理员手动审核。 2 校友数据库目前只收录建院以来全日制本科生、研究生数据,暂不支持教职工、附中、继续教育学院等部门进行认证。未收录部分已纳入系统后期开发计划,敬请期待。 3 目前在校学生可以正常认证登录,校友类型显示为“在校生”,在校生将在毕业后由校友会统一转换为校友身份。 4 海外手机号、非大陆学籍校友可能会出现身份认证不成功的情况,请电话联系校友会,由管理员手动认证。 5 所有校友数据信息将按照法律规定严格保密,不提供给任何第三方,也不用于任何商业目的。 首页设计校友在进入小程序后,请先进行登录及认证(登录及认证指引参见下文)。完成后请继续点击首页“我的名片”全面完善个人资料,然后即可体验小程序全部功能。 [图片] 注册与认证模块进入小程序后,依次点击“我的”-“点击登录账号”(蓝色线框处)进入校友身份认证环节 [图片] 认证界面如下图所示,已在完成注册认证的校友,请直接点击图标即可一键完成认证注册步骤 [图片] 若不方便进行实名信息认证,可点击下方人工认证,选择对应校友类型填写验证信息。 [图片] 校友福利模块后续将逐步推出“地方校友分会”、“返校预约”、“同学查找”等精彩功能。 后台管理模块 [图片] [图片]
2021-11-25 - 【日志】uniapp开发心得记录--目前为止
Uniapp 当引入vant时,报错postcss-loader且一大串icon方面的,打开vant下的icon下的index.wxss重排代码格式且在url前加空格即可 当引入uni-ui的插件时:安装插件,按路径导入所需组件,再注册组件即可 判断空对象方式:JSON.stringfy(object) == “{}” 返回上一页时想携带参数(不支持,navigateTo可以),可以换一种角度思考,通过页面调用栈修改上一页或上几页的数据,间接达到携带参数到上一页的需求 [代码]let pages = getCurrentPages(); //获取所有页面栈实例列表 console.log(pages); let nowPage = pages[pages.length - 1]; //当前页页面实例 let prevPage = pages[pages.length - 2]; //上一页页面实例 console.log(prevPage.$vm); prevPage.$vm.Index = this.index*1; //修改上一页data里面的Index 参数值 prevPage.$vm.goodsList[prevPage.$vm.Index] = this.goods; //修改上一页data里面的goodsList对应Index下标 参数值 [代码] 页面适配方面,移动端尽量做到宽高用rpx/vw,vh表示,margin或top这种定位的最好用百分比表示,而不要固定死多少rpx uniapp的input输入框的value获取方式只能有两种方式,一种是@input事件触发获取,另一种是@blur事件触发获取,而不能像vue框架一样直接可以从双向绑定中获取。 @input:输入框在输入内容时触发;@blur:焦点移出输入框时触发
2021-11-25 - 【教程】小程序轮播图的简单封装
最终效果 [图片] 教程 首先WXML中代码是对小程序现有组件swiper和swiper-item的进一步封装改造 明确好思路后,最重要、最关键的就是CSS的调试,这也是进一步改造轮播图成自己UI图样子的重点 HTML代码: [图片] 其中image的src根据自己data中路径的格式引入即可,我这里是数组形式 CSS代码: [图片] [图片]
2021-11-21 - 《玩转组队》小程序
《玩转组队》小程序设立目的是给华南农业大学全体学生提供一个比赛上方便的平台,小程序通过收集现正举行的比赛信息介绍,并提供对应比赛的队伍招募,寻找队伍的招募平台,让大家打比赛时能更高效地找到心仪的队友
2021-11-21 - 【教程】小程序导航栏的简单封装
最终效果 [图片] [图片] 教程 首先WXML中代码推荐外层一个view,内层一个wx:for循环的view(因为这样可以不只是两个,想变成三个、四个、五个导航栏都行) 明确好格式后,最重要、最关键的就是CSS的调试了 上面例子中WXML代码: [图片] CSS代码: 未选中时样式 [图片] flex布局 选中时样式: [图片] 选中后,字体颜色变化,加个下边框
2021-11-21 - 【教程】小程序的list-item封装
最终效果如图: [图片] 教程 核心仍然是CSS的掌握,想实现这种list-item,最简单的方式便是用CSS3中的flex布局了。 下面是WXML代码,以及CSS代码: WXML: [代码]<view class="c-task" bindtap="tapTask"> <view class="card"> <view class="cover"> <image class="image" src="{{contestItem.images[0]}}" mode="aspectFill"></image> </view> <view class="content"> <view class="title">{{contestItem.name}}</view> <view class="tip"> <view >报名截止:{{contestItem.timeEnd}}</view> </view> </view> </view> </view> [代码] CSS: [代码].c-task .card { border-radius: 4px; background-color: white; } .card .cover .image { height: 100px; width: 100%; background-color: #f8f8f8; border-top-right-radius: 4px; border-top-left-radius: 4px; display:block; } .card .content { padding: 8px; border: 1px solid #f0f7fa; border-top: none; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; } .content .title { height: 40px; font-size: 14px; color: #1a1a1a; overflow : hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .card .tip { display: flex; align-items: center; justify-content: space-between; margin-top: 10px; font-size: 12px; color: #777; } .card .price { color: red; font-size: 15px; } [代码] 在最后,我建议大家为了移动端适配不同屏幕时,可以将你的rpx或px单位换成百分比,这样基本可以做到不同分辨率,例如Iphone6/7/8和iphone4不同分辨率的适配
2021-11-20 - 【教程】小程序呼出菜单的悬浮框封装
最终效果如图: [图片] [图片] 教程 通过设定一个圆形悬浮框在右下角后,绑定一个点击事件,决定菜单栏是呼出还是收拢,通过一个变量isShow来判断另外两个菜单要不要显示出来 [代码]WXML: <image src="/images/openIT.png" class="buttom" animation="{{animMain}}" bindtap="showOrHide"></image> JS: data: { isShow: false,//是否已经弹出 animMain: {},//旋转动画 animAdd: {},//item位移,透明度 animDelLots: {},//item位移,透明度 animEnd: {},//item位移,透明度, isLogin:false }, //点击弹出或者收起 showOrHide: function () { if (this.data.isShow) { //缩回动画 this.takeback(); this.setData({ isShow: false }) } else { //弹出动画 this.popp(); this.setData({ isShow: true }) } }, //弹出动画 popp: function () { //main按钮顺时针旋转 var animationMain = wx.createAnimation({ duration: 500, timingFunction: 'ease-out' }) var animationDelLots = wx.createAnimation({ duration: 500, timingFunction: 'ease-out' }) var animationAdd = wx.createAnimation({ duration: 500, timingFunction: 'ease-out' }) var animationEnd = wx.createAnimation({ duration: 500, timingFunction: 'ease-out' }) animationMain.rotateZ(180).step(); animationDelLots.translate(0, -240 / 750 * systemInfo.windowWidth).opacity(1).step(); animationAdd.translate(0, -360 / 750 * systemInfo.windowWidth).opacity(1).step(); animationEnd.translate(0, -120 / 750 * systemInfo.windowWidth).opacity(1).step(); this.setData({ animMain: animationMain.export(), animDelLots: animationDelLots.export(), animAdd: animationAdd.export(), animEnd: animationEnd.export(), }) }, //收回动画 takeback: function () { //main按钮逆时针旋转 var animationMain = wx.createAnimation({ duration: 500, timingFunction: 'ease-out' }) var animationDelLots = wx.createAnimation({ duration: 500, timingFunction: 'ease-out' }) var animationAdd = wx.createAnimation({ duration: 500, timingFunction: 'ease-out' }) var animationEnd = wx.createAnimation({ duration: 500, timingFunction: 'ease-out' }) animationMain.rotateZ(0).step(); animationDelLots.translate(0, 0).rotateZ(0).opacity(0).step(); animationAdd.translate(0, 0).rotateZ(0).opacity(0).step(); animationEnd.translate(0, 0).rotateZ(0).opacity(0).step(); this.setData({ animMain: animationMain.export(), animDelLots: animationDelLots.export(), animAdd: animationAdd.export(), animEnd: animationEnd.export(), }) }, [代码] 该JS代码中运用到了animation中的动画,具体使用方式可查看小程序文档进行进一步学习。 文档链接:https://developers.weixin.qq.com/miniprogram/dev/api/ui/animation/wx.createAnimation.html 剩余的WXML代码: [代码]<!--miniprogram/components/menu/menu.wxml--> <view class="drawer_screen" bindtap="showOrHide" wx:if="{{isShow}}" catchtouchmove="myCatchTouch"></view> <view > <view animation="{{animEnd}}" class="block"> <view wx:if="{{isShow}}" class="block-text" style="color:white;">队员招募</view> <image src="/images/findMe.png" class=" small" bindtap="findTeam"></image> </view> <view animation="{{animDelLots}}" class="block"> <view wx:if="{{isShow}}" class="block-text" style="color:white;">寻找团队</view> <image src="/images/findPer.png" class=" small" bindtap="findCaptain"></image> </view> <image src="/images/openIT.png" class="buttom" animation="{{animMain}}" bindtap="showOrHide"></image> </view> [代码]
2021-11-20 - 云开发私人实时聊天室
云开发私人实时聊天室说明 在最开始开发小程序时,本人和团队成员实现小程序的聊天室时遇到一些困难,查阅了一些资料,有些讲得太泛,有些讲的太难,在一个阶段克服了这个困难后,收获了很多,对整个流程也熟悉了很多,在这里记录自己的一个思路,希望也能对开发新手有帮助。 项目基本配置 1.项目创建及云开发配置: 官方文档:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/quick-start/miniprogram.html PS:注意云函数目录是否为此样式:[图片] 若是普通目录样式记得在project.config.json中配置加入: [图片] 2.添加包colorui,用于样式使用,并在app.wxss中导入改包 [图片][图片] 3.在pages下新建文件夹index和新建page:index [图片] 聊天室静态页面 最终呈现的效果: 自己: [图片] 对方: [图片] 1. wxml 整体结构: [图片] 整一个页面说白了就是由一个scroll-view和一个回复框组成,scroll-view中由消息数组构成,消息的内容可以自己定义(时间,头像,消息内容等等) 具体源码: <!-- scroll-view来实现页面拖动 --> <scroll-view id='page' scroll-into-view="{{toView}}" upper-threshold="100" scroll-y="true" enable-back-to-top="true" class="message-list"> <!-- 每一条消息 --> <view class="cu-chat" wx:for="{{3}}" wx:key="index" id="row_{{index}}"> <!-- 自己发出的消息 --> <block wx:if="{{false}}"> <block wx:if="{{true}}"> <view class="datetime" style="width:100%">2021-11-16 18:10</view> </block> <view class="cu-item self" style="width: 750rpx; height: 120rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx"> <view class="main"> <view class="content bg-green shadow" style="position: relative; left: 0rpx; top: 22rpx;border-radius: 10rpx"> <text style="font-size:33rpx">这是一条消息</text> </view> </view> <view class="cu-avatar radius center" style="background-image: url({{useravatar}}); width: 71rpx; height: 71rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx" bindtap="go_myinfo"></view> </view> </block> <!-- 对方发出的消息 --> <block wx:else> <block wx:if="{{true}}"> <view class="datetime" style="width:100%">2021-11-16 19:10</view> </block> <view class="cu-item" style="width: 750rpx; height: 120rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx"> <view class="cu-avatar radius center" style="background-image: url({{match_avatar}}); width: 71rpx; height: 71rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx"> </view> <view class="main"> <view class="content bg-white shadow" style="position: relative; left: 0rpx; top: 22rpx;border-radius: 10rpx"> <text style="font-size:33rpx">这是对面的一条消息</text> </view> </view> </view> </block> </view> </scroll-view> <!-- 回复框 --> <view class="reply cu-bar"> <!-- 输入框 --> <view class="opration-area"> <input type="text" bindinput="getContent" value="{{textInputValue}}" maxlength="300" cursor-spacing="10" style="width: 544rpx; height: 64rpx; display: block; box-sizing: border-box;"></input> </view> <!-- 发送按钮 --> <button class="cu-btn bg-green shadow" bindtap='sendMsg' style="width: 150rpx; height: 64rpx; display: flex; box-sizing: border-box; left: -22rpx; top: 0rpx; position: relative">发送</button> </view> 2. wxss一些样式的配置,具体就不详细叙述了,见源码: /*消息窗口*/ .message-list { margin-bottom: 54px; } /*文本输入或语音录入*/ .reply .opration-area { flex: 1; padding: 8px; } /*回复文本框*/ .reply input { background: rgb(252, 252, 252); height: 36px; border: 1px solid rgb(221, 221, 221); border-radius: 6px; padding-left: 3px; } /*回复框*/ .reply { display: flex; flex-direction: row; justify-content: flex-start; align-items: center; position: fixed; bottom: 0; width: 100%; height: 108rpx; border-top: 1px solid rgb(215, 215, 215); background: rgb(245, 245, 245); } /*日期*/ .datetime { font-size: 10px; padding: 10px 0; color: #999; text-align: center; } 到此,静态的页面就已经做好啦,现在主要的难题也是数据部分,下面将先讲述数据库chatroom的设计及解释,最后进行js的代码编写。 数据库创建及设计1.数据库表创建:在编辑器打开云开发控制台,点击数据库,再点击集合名称右边加号,创建一个集合名称为chatroom的表。 [图片][图片] [图片] [图片] 2.chatroom设计具体页面如图: [图片] 其中, _id为记录创建时自动创建的标识属性,即主键 _openid和match_openid代表了自身和对方 records为一个对象数组,每个对象的属性分别是: msgText:消息属性(此案例中只有text属性,即文本,可自扩展为图片、音频等) openid:发送人的标识 sendTime:消息创建时间 sendTimeTS:消息创建时的时间戳(用于做时间比较,判断时间显示) showTime:消息是否显示时间 textContent:具体文本内容 其中, records:array类型, records中的记录:object类型 records中的sendTimeTS:number类型 records中的showTime:boolean类型 其余全为string类型 PS: 1、openid和match_openid可标识一个聊天室,是唯一不变的; 2、用户本身的openid是有可能在记录中的match_openid位置上的,谁发起了这个聊天室,openid这个位置就是那个发起用户的openid,所以在开发中,想要获取自己和所有其他人的聊天室,要查每条记录中的openid或者match_openid与自身openid是否匹配。 3.权限设置因为该表中的记录,非记录创建者也可以进行读写,这里的权限记得设置,不然会出问题: [图片] [图片] [图片] 具体功能实现(JS写法)1.先配置Page.data:6个属性,如有需要可自行扩展 [图片] chats存储数据库表中的records的所有信息; textInputValue是输入框内容。 2.绑定数据库表onChange函数: [图片] [图片] 这里的onChange输出e是这样的: [图片] type=init,获取了数据库表中该记录的所有内容,在这里将js中的chats进行赋值即可; 另外,当该记录内容变化时,type是update类型 3.wxml修改,wx-for将chats显示,以及一些判断和内容显示的设置: [图片] 到此,显示效果就有啦 [图片] 接下来,就是信息的添加了,下面将显示如何添加新信息到数据库 4.发送信息 先获取输入框内容: [图片] [图片] 发送函数: 增加一条信息,就是在records数组中加一条记录,所以在函数内部要对新纪录的属性进行一些赋值和判断等。 对showTime的处理: [图片] 消息空白处理: [图片] 对消息内的所有属性进行一个打包处理: [图片] 存储记录,并滑动页面: [图片] 最后,清空消息框内容 [图片] 发送一条消息,最终效果如图: [图片][图片] js源码const app = getApp() const db = wx.cloud.database() const _ = db.command const chatroomCollection = db.collection("chatroom") var util = require('../../utils/util.js'); Page({ data: { //这里的openid和match_openid应该是在上一级页面传进来的属性,这里由于只有聊天室所以暂时设置为一些固定值,用于测试 openid:'', match_openid:'', //这里的avatar是头像,具体传参方式自己设定,这里暂时设置为固定值,用于测试 useravatar:'', match_avatar:'', chats:[], textInputValue:'' }, onReady() { var that = this //查询openid和match_openid所标识的唯一聊天室 chatroomCollection.where({ _openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid)), match_openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid)) }) //绑定onChange,直观而言即表中该记录发生变动时,调用该函数 .watch({ onChange: this.onChange.bind(this), onError(err) { console.log(err) } }) }, //数据库表onchange绑定函数 onChange(e) { let that = this //type="init"的情况:初始化聊天窗口信息 if (e.type == "init") { that.initchats(e.docs[0].records) } //type="update"的情况:records中增加了一条记录 else { //在chats数组中增加该新消息 let i = that.data.chats.length const new_chats = [...that.data.chats] if (e.docs.length) new_chats.push(e.docs[0].records[i]) this.setData({ chats: new_chats }) } }, initchats(records) { this.setData({ chats: records }) //跳转到页面底部 this.goBottom() }, //获取输入文本 getContent(e) { this.data.textInputValue = e.detail.value }, sendMsg(){ let that = this //show代表了数据库表中的showTime属性,是否显示消息时间 var show = false //无记录时,true if (this.data.chats.length == 0) show = true //判断上下两条消息的时间差决定是否显示时间,这里设置了2分钟:120000毫秒,可自行修改 else { if (Date.now() - this.data.chats[this.data.chats.length - 1].sendTimeTS > 120000) show = true } const _ = db.command //消息空白处理 if (!that.data.textInputValue) { wx.showToast({ title: '不能发送空白信息', icon: 'none', }) return } //消息内容赋值 const doc = { openid: that.data.openid, msgText: "text", textContent: that.data.textInputValue, sendTime: util.formatTime(new Date()), sendTimeTS: Date.now(), showTime: show, } //添加数据库表中该记录的records数组,并跳转页面到底部 chatroomCollection.where({ _openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid)), match_openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid)) }) .update({ data: { records: _.push(doc) } }) .then(res => { that.goBottom() }) //消息设空 that.setData({ textInputValue: "" }) }, goBottom() { wx.createSelectorQuery().select('#page').boundingClientRect(function (rect) { if (rect) { // 使页面滚动到底部 wx.pageScrollTo({ scrollTop: rect.height + 4 }) } }).exec() }, }) 其中,util.js内容如下: const formatTime = date => { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const hour = date.getHours() const minute = date.getMinutes() const second = date.getSeconds() return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}` } const formatNumber = n => { n = n.toString() return n[1] ? n : `0${n}` } module.exports = { formatTime } 一个简单的demo就完成了,大家有什么问题欢迎随时q我。 -----------完结撒花----------
2021-11-18 - 社区的Markdown编辑器换行标签不生效
官方能解决开放社区发表文章时的Markdown编辑器中无法使用换行标签的问题吗? 代码块下方的一行文字会被代码块吸上去,且无法使用br的换行标签隔开,造成很不美观 [图片]
2021-11-18 - 【笔记】JS——操作DOM结点的总结
HTML DOM(文档对象模型) 当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。 通过 HTML 对象选择器查找 HTML 对象 本例查找 id=“frm1” 的 form 元素,在 forms 集合中,然后显示所有元素值,包含在 document.elements中,通过x.elements[i].value来调用,比如一个个input输入的值 DOM动画 应该通过 style = “position: relative” 创建容器元素。 应该通过 style = “position: absolute” 创建动画元素。 上左偏移量在极短的时间内迅速增大而造成了视觉上的“动画” [图片] 当用户进入后及离开页面时,会触发 onload 和 onunload 事件。 onload 和 onunload 事件可用于处理 cookie。 例如:if (navigator.cookieEnabled == true) onchange 和onfocus事件经常与输入字段验证结合使用,鼠标移出输入框时触发,输入框获得焦点时触发 [代码]<input type="text" id="fname" onchange="myFunction()" onfocus="this.style.color="yellow""> function myFunction() { var x = document.getElementById("fname"); x.value = x.value.toUpperCase(); } [代码] onmouseover ,onmousemove和 onmouseout 事件 onmouseover为鼠标刚移入区域;onmousemove为鼠标在区域内移动;onmouseout为移出区域 [图片] onmousedown, onmouseup 以及 onclick 事件 onmousedown, onmouseup 以及 onclick 事件构成了完整的鼠标点击事件。 首先当鼠标按钮被点击时,onmousedown 事件被触发;然后当鼠标按钮被释放时,onmouseup 事件被触发;最后,当鼠标点击完成后,onclick 事件被触发。 JavaScript HTML DOM 事件监听器 addEventListener() 方法 [代码]element.addEventListener(event, function, useCapture); [代码] 第一个参数是事件的类型(比如 “click” 或 “mousedown”)。 第二个参数是当事件发生时我们需要调用的函数。 第三个参数是布尔值,指定使用事件冒泡还是事件捕获。此参数是可选的。 **注意:**请勿对事件使用 “on” 前缀;请使用 “click” 代替 “onclick”。 事件冒泡还是事件捕获? [图片] DOM操作 nodeName 属性 nodeName 属性规定节点的名称。 nodeName 是只读的 元素节点的 nodeName 等同于标签名 属性节点的 nodeName 是属性名称 文本节点的 nodeName 总是 #text 文档节点的 nodeName 总是 #document nodeValue 属性 nodeValue 属性规定节点的值。 元素节点的 nodeValue 是 undefined 文本节点的 nodeValue 是文本文本 属性节点的 nodeValue 是属性值 nodeType 属性 nodeType 属性返回节点的类型。nodeType 是只读的。 [图片] DOM节点 追加:appendChild() [图片] 标签之间插入某新结点insertBefore() [图片] 删除结点removeChild() [代码]var child = document.getElementById("p1"); child.parentNode.removeChild(child); [代码] 替换结点replaceChild() [代码]<div id="div1"> <p id="p1">这是一个段落。</p> <p id="p2">这是另一个段落。</p> </div> <script> var para = document.createElement("p"); var node = document.createTextNode("这是新文本。"); para.appendChild(node); var parent = document.getElementById("div1"); var child = document.getElementById("p1"); parent.replaceChild(para, child); </script> [代码] DOM集合 getElementsByTagName() 方法返回 HTMLCollection 对象。 HTMLCollection 对象是类数组的 HTML 元素列表(集合)。 下面的代码选取文档中的所有 <p> 元素: 实例 [代码]var x = document.getElementsByTagName("p"); [代码] 该集合中的元素可通过索引号进行访问。 如需访问第二个 <p> 元素,您可以这样写: [代码]y = x[1]; [代码] length 属性定义了 HTMLCollection 中元素的数量: length 属性在您需要遍历集合中元素时是有用的: 实例 改变所有 <p> 元素的背景色: [代码]var myCollection = document.getElementsByTagName("p"); var i; for (i = 0; i < myCollection.length; i++) { myCollection[i].style.backgroundColor = "red"; } [代码] 无法对 HTMLCollection 使用数组方法,比如 valueOf()、pop()、push() 或 join()。 节点列表,与DOM集合几乎一样 NodeList 对象是从文档中提取的节点列表(集合)。 使用 getElementsByClassName() 方法,某些(老的)浏览器会返回 NodeList 对象而不是 HTMLCollection。 所有浏览器都会为 childNodes 属性返回 NodeList 对象。 大多数浏览器会为 querySelectorAll() 方法返回 NodeList 对象。 总结: HTMLCollection(前一章)是 HTML 元素的集合;NodeList 是文档节点的集合。 访问 HTMLCollection 项目,可以通过它们的名称、id 或索引号。 访问 NodeList 项目,只能通过它们的索引号。 只有 NodeList 对象能包含属性节点和文本节点。 觉得该篇文章有帮助到你复习操作dom节点的知识,请不要忘记忘记点击左下角的大拇指~
2021-11-17 - Vue开发之旅——我的博客搭建记录(三)
博客3.0,大改特改!(11.10-11.13)卡片(暂时这么叫,名字没想好) 网站的初衷是博客+二次元资料库,因此复制一遍发博客的代码,稍加修改(将数据存储至新的数据库)就是发卡片页了。建的第一个资料库就是阿手(b站搜索贝拉kira,把你的关注给我交了)。展示方面和博客展示一样的思路,图片使用懒加载处理,但使用了网络上的卡片插件(https://www.17sucai.com/pins/38899.html),将鼠标放至卡片上可以有一个上推的阴影效果,非常好看。为适配图片尺寸,将卡片设为横向矩形。ASOUL时代,沸腾期待!(点击查看更多可直达bb空间捏) [图片] 嵌套路由:千鸟,降临! 做完阿手下一个就是千鸟了。想着在卡片页面的左侧做一个导航栏,能够切换阿手/千鸟/其他。导航栏使用竖向的el-menu,根路由是/card,点击千鸟路径就变为/card/qianniao。这需要引入嵌套路由,直接上图,在route.js文件中为card设置children属性。 [图片] el-menu开启router,为每个item设置路径,就可实现导航路由功能咯。 [图片] [图片] 千鸟的立绘是竖向的,因此调整了卡片尺寸。鸟不灭!鸟不灭! 评论区?发病墙! 设置了一个匿名评论区,发现发布评论后得手动刷新才能更新出来。在App.vue中写了一个reload函数,通过provide提供变量,子组件通过inject注入变量,设置0.5秒时延触发页面自刷新。 [图片] [图片] [图片] 史诗级提升:本地上传!(11.15) 结合文档和博客研究出了使用elmentui+leancloud将本地文件转为base64上传。el-upload的手动上传需要自己写httpRequest函数,本地上传的关键就是写好这个函数。 [图片] [图片] 下一章介绍部署网页至服务器上,折磨了一晚上,这方面文章繁多,都是你复制我的我复制你的,而且是vue-cli3+vue2,不适配我的版本,好在最后找到了神中神文章解决了。中间摸了几天画画去了~(天选管人痴收到了海子姐晚安和白白回复,赢!赢!) [图片] [图片] [图片]
2021-11-17 - 关于vue组件中data数据是以函数返回值形式而不是对象的解释
在前面学习vue时,知道了vue实例中定义的data数据可以有两种形式 以对象形式存放 以函数返回的形式 当时只是听说函数返回有优势,但由于对象形式的方便和容易理解,我就一直用的对象形式存放在data中,直到我之后学到组件模块化开发 然后带着问题去翻阅各路大佬博客和官方文档,明白了这么做的原因,本篇博文以官方文档给出的原因为基础,来阐述这么设计的原因。 首先,明确一下组件的概念,组件是可复用的vue实例,一个组件被创建好之后,就可能被用在各个地方,而组件不管被复用了多少次,组件中的data数据都应该是相互隔离,互不影响的。基于这一理念,组件每复用一次,data数据就应该被复制一次,之后,当某一处复用的地方组件内data数据被改变时,其他复用地方组件的data数据不受影响 注意:不受影响!!! [代码]data() { return{ count: 0 } } [代码] 而不用函数定义 [代码]data:{ count:0 } [代码] 当data如此定义后,这就表示所有的组件实例共用了一份data数据,因此,无论在哪个组件实例中修改了data,都会影响到所有的组件实例 最后,给大家总结一下上述内容: 组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果。 觉得本篇文章对你有帮助的请不要忘记给文章点个赞
2021-11-16 - 关于Vue组件知识的总结与梳理!! 适合对vue组件概念比较模糊或还不太了解的同学!!
这是一篇关于vue组件内容的大概介绍和总结,希望对大家有用! (还没更新到vue3) 组件 Vue 1.x中 全局组件: 创建组件构造器 调用Vue.extend 注册组件 调用Vue.component 使用组件 局部组件: 创建组件构造器 在vue实例内部定义属性components,然后 cpn使用的组件名:组件构造器 使用组件 Vue 2.x 直接在vue实例的components里 组件不能访问vue实例的数据 因此组件有自己的data数据集,而且必须用函数返回的形式定义 组件构造: ‘注册名称’:注册模板 [图片] 而对于直接’注册名称’:注册模板 中写注册模板,会造成不美观,因此可以在外部定义组件格式 外部<template>定义,加上id,之后直接#id即可 [图片] 父子组件间的通信: 通过props向子组件传递数据 通过$emit Events,即事件向父组件发送信息 props(父传子) 在子组件中定义props props=[自定义变量名1,自定义变量名2],然后在该模板上设置属性,:自定义变量名=“父组件变量” 父组件 [代码]const app=new Vue({ el:"#app", data:{ message:"你好啊", movies:['海王','海贼王','海尔兄弟'] }, components:{ cpn//定义子组件有cpn } }) [代码] 子组件 [代码]const cpn={ template:'#cpn',//引用下面id为cpn的组件模板 props:['cmovies','cmessage'], data(){ return {} }, methods:{} } [代码] html网页内容 [代码]<template id="cpn">//定义id为cpn的组件模板内容,即传给了上面的子组件模板格式 <div> <p>{{cmovies}}</p> <h2>{{cmessage}}</h2> </div> </template> //网页主体内容 <div id="app"> <cpn :cmovies="movies" :cmessage="message"></cpn>//通过子组件中定义的props方法将父组件中的message和movies变量内容传给子组件的cmessage和cmovies </div> [代码] props也可以以对象形式存储//更推荐如此 [图片] type为类型,default为默认值,required要求是否一定要赋值 类型是对象或数组时,默认值必须是函数 $emit(子传父) 子组件methods中自定义事件 methods:{子组件自定义函数内部加一句 this.$emit(‘父组件中@的事件名’,值)}//提交的事件名不要大写 父组件中,通过v-on即@,来监听子组件事件 父子组件的相互访问 父组件访问子组件:使用$children、$refs 子组件访问父组件:使用$parent、$root $children this.$children是一个数组类型,包含所有子组件对象//一般不常用 $refs、$ref [代码]ref[代码] 被用来给元素或子组件注册引用信息,引用信息将会注册在父组件的 [代码]$refs[代码] 对象上。 例如给标签加上 ref=“one” 接下来便可如下调用 this.$refs.one 指到这个方法,之后再对该元素调用方法即可 $parent 如果访问到了root,则为Vue而不是Vue component//一般不推荐使用 然后通过 .属性或方法来进行操作 $root 直接访问根,即root, 然后通过 .属性或方法来进行操作 觉得该篇文章对你有帮助的请献上你们可爱的赞噢~
2021-11-16 - 微信小程序 订阅消息使用 详细教程
微信小程序订阅消息使用 官方文档连接 本说明的使用场景例子为:用户在组队大厅发送招募信息,后台管理系统收到后对用户的信息进行审核,并将审核结果,通过消息推送发送给用户。 逻辑步骤 逻辑步骤0:等待用户点击触发需要发送消息的事件 逻辑步骤1:向用户申请获得发送消息的权限 逻辑步骤2:等待发送的触发行为 逻辑步骤3:向特定用户发送消息 操作步骤 操作步骤1:在微信公众号平台获得合适的模板 [图片] 在公共模板库搜索适合的模板,找不到的话可以申请。 操作步骤2:编写查询发送权限函数 写一个获得访问权限的函数(也可以直接复制我的),封装好API里面,后面还需要用的话方便调用。 [代码]const checkSub = async(params)=>{//传入tmplId,检测用户是否开放权限,允许推送消息 var tmplIds = params.tmplIds //这里的tmplId是一个数组得注意一下 console.log(tmplIds) return new Promise((resolve,reject)=>{ wx.getSetting({ withSubscriptions: true, success(res){ console.log(res) if(res.subscriptionsSetting.itemSettings!=undefined){ var flag = res.subscriptionsSetting.itemSettings[tmplIds[0]] }else{ var flag = undefined } console.log(flag) if(flag==undefined){ console.log("debug") wx.requestSubscribeMessage({ tmplIds: tmplIds, success(res){ console.log(res) //点击完成后就返回成功就行 }, }) }else if(flag!='accept'){ wx.requestSubscribeMessage({ tmplIds: tmplIds, success(res){ console.log(res) resolve(true)//点击完成后就返回成功就行 } }) }else{//直接返回true,原本以为用户选择一直同意之后,就可以一直推送,这里是一个bug wx.requestSubscribeMessage({ tmplIds: tmplIds, success(res){ console.log(res) resolve(true)//点击完成后就返回成功就行 } }) } } }) }) } [代码] 操作步骤3:用户触发点击事件,调起询问权限 在用户触发的事件开始前,调用询问权限函数,获得发送订阅消息的权限 在本例子中,用户提交组队的表单,在成功提交前,询问用户是否接受“审核通过通知”,无论用户选择是或否,表单信息都可以顺利提交。但是选择否,用户则无法顺利收到订阅信息。 代码如下 [代码]submit: async function() { if(!this.checkTap()){ //防止用户多次点击,重复提交的函数 return 0 } const { contestName,imgs,mates,status,currNum } = this.data var imgUrls = []; for (var i=0; i<imgs.length; i++) { imgUrls.push(imgs[i].url); }//提交数据的图片连接 var tmplIds = ['复制你的templId进来']//审核通过通知权限 var param = {tmplIds} api.subscription.checkSub(param).then(res=>{//在这里调用检查函数 var params = {imgs: imgUrls,contestName,mates,status,currNum} api.team.publish(params).then(res=>{ //提交表单的函数 //提交成功 }) }) } [代码] PS:好了,现在用户点击了"是"之后,我们就可以向用户推送消息了,但是机会只有一次 ! 要好好把握 ! 操作步骤4: 编写发出推送的函数 这里我们使用云函数,可以很方便的使用, 将不同的模板封装在同一个函数里面,使用哪个就调用哪个. 下面是我的代码 [代码]const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database(); const _ = db.command exports.main = async (event, context) => { switch(event.action) { //通过传入的参数 action 来选择模板,其他模板为了方便阅读我删掉了,直接复制一下就搞自己的模板啦 case 'send1' :return send3(event)//审核模板通过模板 } } async function send1(event){ const {touser,status} = event.data //touser 是发送给用户的openid now_db = formatDate2(event.data.now_db) createTime = formatDate2(event.data.createTime) //待审核信息的创建时间 try{//这部分data的内容,和微信公众平台上的模板相对应 var data = { thing2: { value: "您发布的信息已审核" }, phrase1: { value:status//通过或拒绝 }, date3:{ value:now_db//现在的时间 }, date4:{ value:createTime //消息创建的时间 } } const result =await cloud.openapi.subscribeMessage.send({ // 此处发送 touser:touser, page: 'pages/user/index/index', //用户点击消息后进入程序的页面 data: data, templateId: '这里复制你的templateId', miniprogramState: 'developer' // 跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版 }); return result } catch(err){ return err } } function formatDate2 (time) {//时间处理函数 const date = new Date(time); const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); const hour = date.getHours(); const minute = date.getMinutes(); return year + '年' + [month, day].map(formatNumber).join('月') + '日 ' + [hour, minute].map(formatNumber).join(':'); } function formatNumber(n) { n = n.toString(); return n[1] ? n : '0' + n; } [代码] 操作步骤5:向用户推送消息的时间到,发出推送 就是,我们设定的推送时间,或者其他用户触发了向用户发送给推送消息的事,那么就是现在,发出推送. 这个例子中,就是管理员进行了操作,将用户的组队信息状态,从"wait"等待审核,变成了"accept",通过审核.状态发生改变的同时,我们向用户发出推送. [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() const _ = db.command const $ = db.command.aggregate // 云函数入口函数 exports.main = async (event, context) => { switch (event.action) {//通过传入action参数选择函数 case 'changeStatus': return changeStatus(event) default: return Promise.reject("unknown action") } } async function changeStatus(event){//管理员改变信息的状态 var {teamID,touser,status} = event var mp = {'reject':'拒绝','need':'通过'}//通过mp将英文转换为中文 var status2 = mp[status] var time = new Date() var createTime = event.createTime console.log(createTime) return new Promise((reslove,reject)=>{ db.collection('team').doc(teamID) .update({ data:{ status:status //更新信息状态 } }) .then(res=>{//成功之后发送订阅消息 cloud.callFunction({ name:'subscribe', data:{ action:"send3", data:{status:status2,touser:touser,now_db:time,createTime:createTime} }//这里的数据都需要和模板中的数据对应,touser就是发送给目标用户的openid }).then(res=>{ console.log(res) }).catch(err=>{ console.log(err) }) reslove({code:200,message:"转换成功",data:res}) }) .catch(err=>{ reject({code:300,message:"转换失败",data:err}) }) }) } [代码] 测试 这里因为开发者工具和真机调试弹出来的页面有所不同,所以推荐最好是使用真机进行调试. [图片] 用户点击允许之后,再触发发送函数,就可以收到消息了,一开始可以用自己的openid作为touser,来进行测试. !!!这里注意!!! 如果用户选择了"总是保持以上选择,不在询问",那么以后都不会调起这个界面,但是用户点击一次允许,我们才获得一次发送订阅消息的能力.所以这里会有一个BUG,目前还没有找到解决的方案.只能希望用户不选择这个了. 那么就到这里结束了,有什么问题欢迎留言交流,欢迎点赞关注收藏…好耶!
2021-11-16 - 数据结构折半查找判定树的画法(较简单易懂!)
复习数据结构做的笔记: 折半查找判定树的画法思路: 1. 先画出满足有序表长度的最大满二叉树,然后将剩下的结点个数一个个插入该树 2. 从上往下看,比较每个结点的左右子树结点个数,如果左右子树结点个数相同优先放右边,左边比右边少就放左边,直到往下塞到二叉树底部成为叶子结点。 对于步骤1和2的具体做法,见下列实例分析: 长度为12的有序表画出折半查找判定树 12>2^3,即最大能画出3层的满二叉树,接着将剩余5个结点插入该树 [图片] 先插入h,a的左右子树结点个数都为3,则到c,c的左右子树结点个数都为1,接着到g,g的左右子树都为0,最后h到了g的右边 [图片] 先插入i,a的左子树结点个数为3小于右子树的4,则到b,b的左右子树结点个数都为1,接着到e,e的左右子树都为0,最后i到了e的右边 [图片] 同理后面插入J,K,L [图片] [图片] [图片] 折半查找判定树就完成了 如果要求各元素查找概率相同的情况下平均查找长度,则 n=(1[代码]*[代码]1+2[代码]*[代码]2+4[代码]*[代码]3+5[代码]*[代码]4)/12=37/12 觉得该篇文章有用的请不要忘记忘记点击左下角的大拇指~
2021-11-15 - 浏览器实现cookie的操作详解!!!推荐新手观看
博客主页:https://blog.csdn.net/weixin_53893220?spm=1001.2101.3001.5343 本文基于js文档简单介绍了什么是cookie,浏览器是如何获取cookie并存储,等到加载时获取的,有助于新手了解cookie的相关操作 JavaScript Cookies cookie介绍: Cookie 是在您的计算机上存储在小的文本文件中的数据。 当 web 服务器向浏览器发送网页后,连接被关闭,服务器会忘记用户的一切。 Cookie 是为了解决“如何记住用户信息”而发明的: 当用户访问网页时,他的名字可以存储在 cookie 中。 下次用户访问该页面时,cookie 会“记住”他的名字。 Cookie 保存在名称值对中,如: [代码]username = Bill Gates [代码] 当浏览器从服务器请求一个网页时,将属于该页的 cookie 添加到该请求中。这样服务器就获得了必要的数据来“记住”用户的信息。 创建 cookie: [代码]document.cookie = "username=Bill Gates"; [代码] 通过 path 参数,您可以告诉浏览器 cookie 属于什么路径。默认情况下,cookie 属于当前页。 [代码]document.cookie = "username=Bill Gates; expires=Sun, 31 Dec 2017 12:00:00 UTC; path=/"; [代码] document.cookie 会在一条字符串中返回所有 cookie,比如:cookie1=value; cookie2=value; cookie3=value; 删除cookie: 直接把 expires 参数设置为过去的日期即可://即expires为cookie过期的时间 [代码]document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; [代码] 您应该定义 cookie 路径以确保删除正确的 cookie。 cookie过程详解: 下面代码出现的操作cookie的三个函数作用: checkCookie()//加载该页面时运行该函数,检查本地浏览器记录有无cookie setCookie()//没有的话就设置cookie getCookie()//获取cookie中的名称 出现的js自带方法为: getTime()//返回距 1970 年 1 月 1 日之间的毫秒数。 setTime()//以毫秒设置 Date 对象。 toUTCString()//可根据世界时 (UTC) 把 Date 对象转换为世界时字符串,并返回结果。 split(";")//将字符串以;为间隔,分割成多个字符串,返回一个字符串数组 charAt(0)//返回数组下标为0处的字符 substring(1)//切割出从下标为1开始的字符串,第二个参数为切割到哪个位置,即下标 [代码]function setCookie(cname, cvalue, exdays) { var d = new Date(); d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));//为d设置一个形式为一串数字的cookie过期时间 var expires = "expires="+d.toUTCString();//转化为世界时 document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";//我们要的cookie主体 } function getCookie(cname) { var name = cname + "="; var ca = document.cookie.split(';');//分割记录中已存入的cookie,如果有的话 for(var i = 0; i < ca.length; i++) {//有三个,cname=cvalue;expires=~~~;path=/ var c = ca[i]; while (c.charAt(0) == ' ') { c = c.substring(1);//从下标为1开始,目的是清除字符串开头带有的空格 } if (c.indexOf(name) == 0) { return c.substring(name.length, c.length);//返回cvalue } } return ""; } function checkCookie() { var user = getCookie("username");//该字符串可随意设置,容易理解即可 if (user != "") { alert("Welcome again " + user); } else { user = prompt("Please enter your name:", ""); if (user != "" && user != null) { setCookie("username", user, 365); } } } [代码] 觉得该篇文章有用的请不要忘记忘记点击右下角的大拇指~
2021-11-15 - ES6实现简易抽奖模块
博客主页:https://blog.csdn.net/weixin_53893220?spm=1001.2101.3001.5343 本文代码将展示用Generator实现抽奖的可控制化,用yield来控制每次的抽奖结果输出 对抽奖按钮自定义后,每次点击按钮都调用person对象的内置next方法进行数值的输出,从而达到控制抽奖的进行,而不是一次性将所有得奖人物都输出,es6的妙用 js部分代码如下: [代码]function* chouJiang(firstPeople, SecondPeople, thirdPeople) { let first = ['1a', '1b', '1c', '1d'] let second = ['2a', '2b', '2c', '2d', '2d', '2e', '2f'] let third = ['3a', '3b', '3c', '3d', '3e', '3f', '3g', '3h'] let count = 0 let totalSum = 6 let random while (1) { if (count < firstPeople) { random = Math.floor(Math.random() * first.length) yield first[random] count++ first.splice(random, 1) } else if (count < firstPeople + SecondPeople) { random = Math.floor(Math.random() * second.length) yield second[random] count++ second.splice(random, 1) } else if (count < firstPeople + SecondPeople + thirdPeople) { random = Math.floor(Math.random() * third.length) yield third[random] count++ third.splice(random, 1) } else { return false } } } let person = chouJiang(1, 2, 3) // console.log(person.next().value) // console.log(person.next().value) // console.log(person.next().value) // console.log(person.next().value) // console.log(person.next().value) // console.log(person.next().value) // console.log(person.next().value) [代码] 在这里模仿的实际抽奖,first,second,third三个数组为参与对应奖项的待定人选, 给该抽奖函数传入的参数为(1,2,3),即一等奖一名,二等奖两名,三等奖三名 html代码如下: [代码]<!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>Document</title> <!-- <script src="异步test.js"></script> --> <script src="抽奖.js"></script> <style> button { width: 100px; height: 100px; font-size: 40px; text-align: center; } </style> </head> <body> <button onclick="console.log(person.next().value)">抽奖</button> <p id="demo"></p> </body> </html> [代码] 觉得该篇文章有用的请不要忘记忘记点击右下角的大拇指~
2021-11-15 - 如何用ES6将不可迭代对象变为可迭代?
CSDN博客主页:https://blog.csdn.net/weixin_53893220?spm=1001.2101.3001.5343 众所周知,在js中的某些类型是内置好的可迭代对象,比如:字符串、数组、类型数组、Map对象等。而Object类型不可迭代。这些内置可迭代对象可以进行迭代的原因是内部实现了@@iterator 方法,即在该对象或该对象的原型链上有Symbol.iterator属性实现了@@iterator 方法。 而在实际开发获取数据中,为了数据的方便读取保存,需要让拥有某个数据结构的对象可迭代,因此本文将讲述如何自定义Symbol.iterator属性实现对象的可迭代化,下面将介绍两种方式: 1.通过可迭代协议,用yield来控制返回值 2.通过迭代器协议,用返回值中的done判断该对象是否迭代完毕,用 value来返回所需值 本篇文章用作例子的对象数据结构如下: [代码]let authors = { allAuthors: { fiction: ['Agla', 'Skks', 'LP'], scienceFiction: ['Neal', 'Arthru', 'Ribert'], fantasy: ['J.R.Tole', 'J.M.R', 'Terry P.K'] }, Addres: [] } [代码] 1.可迭代协议实现 [代码]authors[Symbol.iterator] = function*() { let allAuthors = this.allAuthors; let keys = Reflect.ownKeys(allAuthors); let value = []; while (1) { if (!value.length) { if (keys.length) { value = allAuthors[keys[0]] keys.shift() yield value.shift() } else { return false } } else { yield value.shift() } } } let one = [] for (let key of authors) { // console.log(key) one.push(key) } console.log(one) [代码] 2.迭代器实现 [代码]authors[Symbol.iterator] = function() { let allAuthors = this.allAuthors; let keys = Reflect.ownKeys(allAuthors); let value = []; return { next() { if (!value.length) { if (keys.length) { value = allAuthors[keys[0]]; keys.shift(); } } return { done: !value.length, value: value.shift() } } } } let one = [] for (let key of authors) { // console.log(key) one.push(key) } console.log(one) [代码] 觉得该篇文章有用的请不要忘记忘记点击右下角的大拇指~
2021-11-15 - 关于vscode的prettier和eslint默认格式不兼容的解决方法
在配置vue脚手架调试过程中,遇到vscode自动保存格式prettier和eslint文件默认格式不兼容的问题 出现了以下几个常见问题,例如: 缩进符不匹配(规则是2字节但vscode的默认缩进符4字节) 行尾加不加空格,分号 js文件末尾要不要加一个空行等问题 下面贴出我调试好后的prettier规则和eslint文件rules设置,具体参数对应意思可以去eslint官方文档以及其他CSDN大佬博客进行查阅 package.json中 [代码]"prettier": { "singleQuote": true, "semi": true, "stylelintIntegration": true, "eslintIntegration": true, "insertPragma": false, "trailingComma": "all", "arrowParens": "avoid", "tabWidth": 4, "bracketSpacing": true } [代码] .eslintrc.js中 [代码]rules: { "no-tabs": "off", "indent": 0, "eol-last": ["error", "never"] } [代码]
2021-11-15 - 关于ES6新增的方法总结,包含了一些个人笔记的重点强调
ES6 创建数组: Array.prototype.from(伪数组,函数)//伪数组转换成真数组 Array(length) 设置初始值: Array.of(value) Array.fill(value,start,end)//数组的填充start-end-1都用value填充,可以用于数组值的更改 数组值限制 Array.filter(function)//满足条件的所有值并返回,数组形式。return true时将该数加入新数组,false就过滤掉 Array.map(function)//对数组中的值进行普遍操作,比如全部乘2,就return n*2 查找值 Array.find(function)//满足条件的第一个值就返回,数值形式 Array.findIndex(function)//下标 数组删减增加 Array.splice(index,length,[value])//1.开始下标 2.要删除多少长度 3.替换成后面的值,删除为空 可以改变原数组 数组求和 Array.reduce(function,initValue)//对数组进行合并缩减,最终缩减为一个值 [图片] 数组合并 Array.concat(secondArray) 字符串翻转 string.split("").reverse().join("") class类 原型链 ES5中 类的静态方法(只加到原型上,即Animal.eat=function) 实例对象的方法(要Animal.prototype.eat=function)要加到object的原型对象上,所有的实例才继承 ES6中 加static就是类的静态方法 不加static就是实例对象的方法 类的继承: ES5不管了过于复杂,直接上ES6 Dog继承于Animal class Dog extends Animal {} [图片] 子类中如果没有新构造,例如上图中的this.age=2,那么子类中不需要写constructor(已默认继承) 函数 arguments为传入的参数伪数组 函数形参可以设置默认值:f(x,y=1,z) 如果要用函数默认值,则调用console.log(f(1,undefined,2))//undefined即可,而不能0或空字符串 function sum (…nums)//调用时可以是 sum(1,2,3) data=[1,2,3] function sum(x,y,z)//调用时 sum(…data) 即数组遍历,一边参数散开,一边是数组形式的收 箭头函数 ()=>{} 箭头函数无this,箭头函数内部的this指针为外层 Set 声明时内为可遍历对象, let s=new Set([1,2,3]) s.add()//添加元素 s.has()//是否有某数据 s.size() s.forEach(item => {console.log(item)}) Map let map=new Map([[1,2],[3,4],[“zjc”,“shuaibi”]])//创建map对象同时初始化值 map.set(1,2)//新增 map.delete(1)//删除的是键为1的键值对 map.has()//找的是键值 map.get()//找的是某个键对应的值 map.forEach((value,key)=>{console.log(value,key)})中,是先值再键 对象拷贝 Object.assign(new,old)//直接覆盖,即深拷贝,直接将新的引用到旧的数据上 object和Map存储的都是键值对组合。但是: object的键的类型是 字符串; map的键的类型是 可以是任意类型; 另外注意,object获取键使用Object.keys(返回数组); Map获取键值使用 map变量.keys() (返回迭代器)。 正则表达式 const s=‘aaa_aa_a’ const r1=/a+/g const r2=/a+/y r1.exec(s)//对于对象r1调用该方法,目的是在s中找r1出现的首个下标 /g 全局匹配,从第一位开始 /y 从上一次匹配的位置开始 即第一次执行r2.exec(s)返回0,第二次执行r2.exec(s)返回4 字符串拼接 ``的妙用 es5中用+拼接 es6中用 ``表示字符串,里面的${}内部表示字面量 [图片] ${‘retail’}为传入参数 函数中的s1=string[0]代表传入参数位置的前面的字符串,type为传入的参数, 检查完type==='retail’后,返回传入参数处前面的字符串和函数内部计算后的字符串,即如下 [图片] 字符串换行: [图片] 解构赋值 let arr=[1,2,3] let [zjc,aaa]=arr//以一对应赋值 对于对象(键值对),可以用Object.entries(object)来遍历所有键值对 因此可以用for-of来迭代 for(let [k,v] of Object.entries(user)) {console.log(k,v)} [图片] 如何达到异步机制 Promise对象,then,resolve,reject(这俩为了快速创建promise对象),catch,all,race js是单线程,同步的 先执行完某个作用域中的所有语句,再执行这些语句中引发的另外的事情,下例中先1再2后3 Callback 回调函数 [图片] Promise 返回一个状态(改变同步) 处于pending挂起状态 当第一个文件正确执行后运行onload事件,即执行resolve函数 当第一个文件执行失败运行onerror事件,即执行reject函数 来改变挂起状态 [图片] then 传入两个参数,且返回的是Promise实例 (.then是promise原型的对象和方法) promise.then(onFulfilled,onRejected)//onFullfilled对应上图resolve 必选参数;第二个为可选 如果onFulfilled为非函数,则.then返回空的Promise对象 下例中,第一个return的意思是箭头函数要有一个返回值,将return后面的loadScript得到的promise对象返回给第一个.then,让他能继续作为一个promise继续执行下一个.then [图片] 用Promise对象提供的两个静态方法resolve && reject 实现异步操作 resolve和reject可以快速生成promise实例,而不用new Promise [图片] [图片] Catch 捕获错误 Promise对象的方法,代替reject [图片] 上面的分一个一个接着执行的,为串行 All 对于并行的异步操作 [图片] Race 竞争,异步中的先到先得 Promise.race(p1(),p2()) [图片] Rflect反射机制,未来可能代替object Reflect不用new,直接用 apply 在非反射中(es5中无反射),应用apply就得先指定方法再通过apply改变作用域 反射中可以先apply,再根据执行中的条件去指定要调用哪个方法 用法:Reflect.apply(function,作用域,以数组形式传入的参数) Reflect.apply(Math.floor,null,[4.72])//apply中的null指的是作用域,没指定作用域就默认全局 construct 用于实例化一个类,跟new关键字用法一样 let d=Reflect.construct(Date,[])跟 let d=new Date()一样 construct方法中,第一个参数为什么类,第二个参数为初始化填入的东西,都必填,可以是空数组 修改某对象的原型对象 Reflect.setPrototypeof(old,new)//例如将一个数组改为字符串 例如:Reflect.setPrototypeof(arr,String.prototype) 新增一个寻找某个对象其原型对象的方法 Reflect.getPrototypeof() 验证某个对象上是否有某个属性或方法 Reflect.has(object,属性或方法) 返回某对象的所有键或某数组所有数据的索引值 Reflect.ownKeys(object) Proxy 代理 let d=new Proxy(object,function)//object要代理谁,哪个对象;第二个参数为代理后干什么事,读写 有点像中介[图片] 代理对象禁止赋值(通过set返回false) [图片] 用处:某对象想只能自己修改而用户不能修改,那么将代理对象设置成只读后给用户即可 对代理的写进行限制且 对象结构不被破坏(has限制) [图片] 监控错误 最顶端加,且要捕获而不是冒泡 [图片] 对于错误的处理,可以return false,也可以throw new TypeError(’’) 对新类添加代理,让某个属性只读 [图片] 撤销代理 [图片] 对代理对象进行Proxy.revocable声明,将代理数据和撤销代理方法存到对象d中 接着可以通过d.proxy.price读取数据 d.revoke() 撤销代理 Generator 控制遍历的停止 要点: 控制的函数声明时加星号 function * name(){} 当运行函数时遇到 yield 就会停止,调用.next()让函数继续运行,传递yield后面的结果回去 通过l.next()控制函数执行 [图片] 加星号函数返回给l两个结果,一个是value,遍历的值,另一个是done,是否结束 yield后面加星号,代表后面为可遍历对象 .next(xxx)//next方法可以传参,将参数传给函数体内部yield的返回值,上图中是给val,value空时才能传 .return()//return方法可以控制函数的提前结束 函数外部抛出异常到函数,如何在函数内部捕获该异常? 内部try{}catch{},外部.throw(new Error(’’)) [图片] 可迭代对象 如何将拥有复杂的数据结构的不可迭代对象变为可迭代对象呢?即该对象能用for-of进行遍历 给对象挂载一个Symbol.iterator方法,该方法输入为this,输出为一个对象 该方法的返回值写法为return{next(){return{done:true,value:1}}} 返回值要有next方法,该next方法有两个字段,返回值为done和value, done代表遍历结束没,默认为false没结束,value为遍历开始的值 [图片] [图片] 以上方法为通过控制done是否结束来输出value 以下为通过yield来按节奏来输出 [图片] 可迭代协议:(for of循环) 允许对象定义他的可迭代行为,比如在for of结构中,哪些值可以遍历到。在js中的某些类型是内置好的可迭代对象,比如:字符串、数组、类型数组、Map对象、Get对象等。而Object类型不可迭代。这些内置可迭代对象可以进行迭代的原因是内部实现了@@iterator 方法,即在该对象或该对象的原型链上有Symbol.iterator属性实现了@@iterator 方法。 简单来说就是将不可迭代对象变为可迭代对象,上例手写该协议,用Generator实现,yield控制输出 迭代器协议:(Iterator对象) 定义了产生一系列值的标准方式。一个对象必须实现next()方法,才能成为迭代器。 next() 方法必须返回一个对象,该对象应当有两个属性: done 和 value done(boolean)如果迭代器可以产生序列中的下一个值,则为 false。 value 迭代器返回的任何 JavaScript 值 模块 导出 export {const one=‘name’} 导入 import {name} from ‘xxx文件,可以省略.js拓展名’ 对于默认导出和对导入的模块进行改名 export default 变量名//只能有一个默认导出 在导入时,对于默认导入变量不写在{}里,例如import name2,{name} from ‘src’ 如果想对模块改名,默认变量直接改,其他得加as 例如,原文件默认导出为name,导入时 import name2,{addr as addr2} from ‘src’ 如果导出的是对象,只能导出一个对象, 可以import对象后,进行结构化赋值取出该对象里涵盖的多个对象 默认导出类时,可以不声明类名 如果想一次性导入多个模块,可以用*关键字,例如 import * as Mod from ‘src’ 注意:默认导出的模块需要用Mod.default()//默认去查找 觉得该篇文章有用的请不要忘记忘记点击右下角的大拇指~ 欢迎大家关注我的公众号:Smooth前端成长记录 公众号同步更新CSDN博客内容,想方便阅读博客的C友可以来关注我的公众号以便获得更优良的阅读体验~ [图片]
2021-11-15 - 微信小程序性能优化入门指南(转载)
原文地址:https://segmentfault.com/a/1190000016901634 小程序从发布到现在也已经有将近两年的时间,越来越来多的公司开始重视小程序生态带来的流量,今年也由于小程序平台对外能力的越来越多的开放以及小程序平台的自身优化,越来越多的开发者也自主的投入到小程序的开发当中,现在,作为前端如果会写小程序,绝对是一个不折不扣的面试加分项。 相信不少人刚接触小程序时的感觉大都是小程序很简单,开发只要是会写html、css、js就可以了,但是当自己的第一个小程序开发完成上线时,却发现小程序体验非常糟糕,接下来就让我们一窥小程序优化之道。 加载流程要想给小程序做优化,对小程序的加载流程一定要有一定的了解,小程序是怎么加载的,让我们先来看一个图片: [图片] 这三个图片大家一定都不陌生,当你打开一个小程序的时候就会经历这三个过程: 资源准备,这个过程就是小程序在下载你的代码包的过程业务代码注入和渲染,这个过程就是小程序将的业务代码分别注入视图层和逻辑层,并在视图层做视图的渲染异步数据的请求,显示加载中的时候,其实就是在到达首页时,如果首页有异步数据请求,这个时候小程序就会执行异步数据请求上述就是对小程序的启动过程的一个简单概述,让我们再来看一个更加具体一点的图片,可能会更好理解小程序启动过程: [图片] 从这个图片可以看到,小程序在启动加载的时候,其实分为两部分,一部分是逻辑层的启动启动,另一部分是视图层的启动,逻辑层的启动就是加载小程序的js代码,视图层的启动webview对页面进行加载和渲染,那预加载又是什么时候执行的呢?其实在微信动的时候,小程序平台就开始静默执行与加载的过程,包括JS引擎初始化和WebView的初始化,然后会注入小程序自带的公共库,例如自带api、组件等,后面的小程序启动,就是上面说过的打开一个小程序具体的启动加载过程了,下载代码包,分别注入逻辑层和视图层,然后共同完成首屏渲染。 启动性能优化讲完小程序的启动过程,就可以开始介绍具体的性能优化方案了,让我们一起看看影响小程序性能的因素以及具体的解决办法 代码包大小代码包大小会直接影响小程序的启动速度,代码包越大,小程序的启动时间就越长,在小程序启动时,下载代码包和代码注入的时间和小程序代码包大小是成正比的,一般小程序的平均启动时间是2s左右,可以看看你的小程序有没有拖后腿,那么如何控制包大小呢? 资源控制开启开发工具”上传代码时自动压缩”,小程序开发工具有一个上传代码时自动压缩的功能,当开启时,会在你上传代码时为你做代码压缩,除了这个,我们也可以通过使用第三方打包工具做代码压缩,如webpack、grunt、grulp。及时清理无用代码和资源文件,无用的代码和资源也会占用一定的包大小。减少代码包中的资源文件,将资源存放在cdn上,小程序开发工具对资源文件的压缩比率非常低,资源有条件的可以尽量放在CDN上,因为小程序开发工具对资源文件的压缩比率非常低,只有10%左右,或者也可以用第三编译工具对资源文件自己进行压缩处理分包加载[图片] 分包加载是小程序提高加载启动性能的一个重要方法,如果有人还不了解,可以点开链接看官方介绍,那么如何做好分包加载呢? 将小程序中不常用的代码放在分包中,主包内只保留用户最常访问的页面,但是由于官方规定tab页面只能放在主包中,因为小程序启动时只会加载主包,使用时按需下载分包,不会在加载时一次将整个代码包下载,这样就能有效减少启动加载的时间。 但是分包加载也有它的局限性,用户首次打开分包页面时,需要先进行分包代码的加载和注入,会造成页面切换时产生一定的延时,因此在此基础上,官方又推出了分包预加载和独立分包。 分包预加载先来看一下之前分包加载时的流程是怎样的: [图片] 那么分包预加载是怎么干的呢?分包预下载:提前配置可能会跳到哪些分包,框架在进入页面后根据配置进行预下载,分包预加载会在你进入主包页面后,为你静默开启分包代码的下载和注入,这个过程是无感的,来看一下分包预加载的流程是怎样的: [图片] 分包预加载需要注意的是:同一个分包中的页面享有共同的预下载大小限额2M,限额会在工具中打包时校验,因此不能把所有的分包页面都配置到分包预加载的配置中,只配置主包页面会跳转的页面即可。 独立分包独立分包又是什么呢?由于从分包页面启动是,必须要依赖于主包的下载和注入,启动速度会受到主包大小的制约,因此这就有了独立分包,独立分包在启动分包页面时,可以独立启动而不需要依赖主包,这样就可以减少主包下载和注入的时间,通常情况下我们会将活动、广告一类的具有独立逻辑的功能代码标记为一个独立分包,在分包页面启动时,可以不依赖于主包启动,只下载分包代码进行注入。让我们来看一下独立分包的加载流程是怎样的: [图片] 首屏加载性能优化首屏加载的体验对小程序来说十分重要,那么如何提升首屏加载性能呢? 提前请求:异步数据数据请求不需要等待页面渲染完成利用缓存:利用storage API对异步请求数据进行缓存,二次启动时先利用缓存数据渲染页面,再进行后台更新避免白屏:先展示页面骨架和基础内容及时反馈:及时地对需要用户等待的交互操作给出反馈,避免用户以为小程序没有响应渲染性能优化要想提高渲染性能,就需要知道小程序如何做页面渲染的,让我们先来看一个页面渲染的流程图: [图片] js引擎和native都可以过js的计算或者data修改来对Webview发起绘制操作,但是对开发者来说最重要的就是js引擎和Webview之间的通信,这通信过程是一个跨进程通信,是非常耗时的一个过程,我们要提高渲染的性能,也就是减少这个跨进程通信的时间,那么怎么去减少跨进程通信的时间呢? 避免不当使用setData使用data在方法间共享数据,会增加setData传输的数据量,同时会增加页面重绘的概率data仅包括与页面相关的数据使用setData传输大量数据,通讯耗时与数据量正相关,页面更新延迟可能造成更新开销增加仅传输页面中发生变化的数据,使用setData的特殊key实现局部更新后台页面进行setData抢占前台页面的资源页面切入后台后的setData调用,延迟到页面重新展示的时候执行总结来说就是在data中只定义与页面渲染相关的数据,其他与页面渲染无关的数据都定义成普通变量,在做setData操作时,尽量只传输页面渲染需要的数据,当页面切换时,将后台执行的setData操作销毁,等到页面重新展示的时候再执行。 避免不当使用onPageScroll只在必要的时候监听pageScroll事件避免在onPageScroll中执行复杂逻辑避免在onPageSroll中频繁调用setData避免频繁查询节点信息(SelectQuery),部分场景使用节点布局相交状态监听(IntersectionObserver)替代由于onPageSroll事件监听在处理js引擎和webview之间的通信时也是一个跨进程通信,因此在使用onPageScroll事件时,要注意以上的几点内容,来进行相关的优化 使用自定义事件在需要频繁更新的场景下,自定组件的更新只在组件内部更新,不受页面其他部分内容复杂性影响,这样也可以在一定程度优化渲染性能 总结这篇文章简单的介绍了微信小程序性能优化的一些方法,还有很多我没有介绍到方法就需要大家自己去探索总结了。希望大家看完这篇文章能对小程序性能优化有一定的认识,如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞收藏。
2021-09-23 - 【笔记】小程序中的水平与垂直居中
在复习水平居中与垂直居中时发现居然有点忘了 因此写个笔记下来记录一下 只要涉及到CSS就绕不开定位问题,尤其是盒子居中。居中又分为水平居中和垂直居中,有多种实现方式,下面我便一一列出来。 水平居中 inline元素:text-align: center block元素:margin: 0 auto absolute元素:left: 50% + margin-left负值(值为该元素宽度的一半) absolute元素:left: 50% + transform: translateX(-50%) 垂直居中 inline元素:line-height的值等于height值 absolute元素:top:50%+margin-top负值(值为该元素高度的一半) absolute元素:top:50%+transform: translateY(-50%) absolute元素:有固定宽高度+top,left,bottom,right=0 + margin:auto 水平垂直居中 就是上述的水平居中+垂直居中合起来 这里我举几个常用的例子: 1. 放在定位是relative盒子内的absolute盒子: top: 50%;left: 50%;transform: translate(-50%,-50%); [图片] [图片] 2. 知道盒子的宽度和高度 top: 50%;left: 50%; 加上 margin-left负值(值为该元素宽度的一半);margin-top负值(值为该元素高度的一半) [图片] [图片] 3. 放在定位是relative盒子内的absolute盒子且该盒子有固定宽高: top,left,bottom,right=0 + margin:auto [图片] [图片] 4. CSS3弹性布局(flex) 如果使用CSS3的弹性布局(flex)的话,问题就会变得容易多了。使用CSS3的弹性布局很简单,只要设置父元素设置成 display:flex // flex布局 align-items:center;// 元素水平居中 justify-content:center;// 元素垂直居中 [图片] [图片] 欢迎大家在评论区提出问题 觉得文章对你有帮助的不妨点个赞~
2021-11-13 - 快进来看看简单易懂的分分钟提升小程序性能50%上下的性能优化
场景引入为什么需要用到高性能虚拟列表+节流+分页请求的优化?当有场景需求为需要将大量数据(10000条)呈现在一页上,我们不断下拉访问,页面中有大量的数据列表的时候,用户会不会有不好的体验?会不会出现滚动不流畅而卡顿的情况?会不会因卡顿而出现短暂的白屏现象(数据渲染不成功)? 通过微信开发者工具自带的调试器->Network页面,我们可以观察到当有长列表时如果不使用高性能虚拟列表+节流+分页请求的优化,会出现以下问题: FPS:每秒帧数,图表上的红色块表示长时间帧,很可能会出现卡顿。CPU:CPU消耗占用,实体图越多消耗越高。NET:网络请求效率低,一次性请求10000条的渲染效率远远低于分1000次,每次请求10条数据内存:滑动该列表时明显能看到内存消耗大。总结:如果需要将大量数据(10000条)呈现在一页上,可以通过高性能虚拟列表+节流+按需请求分页数据并追加显示。优化的具体实现可拆分为以下需求(将一个大问题拆分为一个个小问题并逐个去解决): 不把长列表数据一次性全部直接显示在页面上。截取长列表一部分数据用来填充屏幕容器区域。长列表数据不可视部分使用使用空白占位填充。监听滚动事件根据滚动位置动态改变可视列表。监听滚动事件根据滚动位置动态改变空白填充。分页从服务器请求数据,将一次性请求所有数据变为滚动到底部才再次向服务器发送获取数据的请求 更详细的实现方法请阅读我的下一篇文章:https://developers.weixin.qq.com/community/develop/article/doc/000a2618d34908e3610d5978856c13
2021-11-12 - 详解小程序如何引入使用computed计算属性
对于使用过前端框架的很多人来写小程序,会因为小程序官方并没自带computed计算属性而造成了一定程度的困扰,为了解决这个困扰,下面我将介绍如何引入官方推荐的自定义组件扩展computed PS:第三点以及文尾的内容非常重要!!一定要注意!!! 进入你的小程序后(小程序根目录需要命名为全英文,中文在后续安装中也会有介绍怎么办),进入微信开发者工具,打开下方的终端,并点击+号新建终端 [图片] 然后检查左边目录,如果根目录是如下图分为cloudfunctions(云函数目录)和miniprogram(主要代码部分),那么需在终端输入[代码]cd miniprogram[代码]先进入miniprogram目录,再进行后续导入包 [图片] 如果左边目录是如下图的形式那么不需要输入[代码]cd miniprogram[代码] [图片] 然后在终端输入按顺序输入以下三条指令: (1)npm init -y(如果你的小程序根目录不是全英文,那么输入npm init,然后输入任意一个英文名称,一直回车即可) (2)npm install --production (3)npm install --save miniprogram-computed (4)点击微信开发者工具上方工具栏的 工具 -> 构建npm -> 构建完成点击确定即可 然后在需要使用计算属性computed页面的JS部分 先在JS部分最开始加入以下代码 [代码]const computedBehavior = require("miniprogram-computed").behavior;[代码] 然后在Pages中加入 [代码]behaviors: [computedBehavior][代码] 如下图: [代码]const computedBehavior = require("miniprogram-computed").behavior; Pages({ behaviors: [computedBehavior], data: { a: 1, b: 1, }, computed: { sum(data) { // 注意: computed 函数中不能访问 this ,只有 data 对象可供访问 // 这个函数的返回值会被设置到 this.data.sum 字段中 return data.a + data.b; }, }, methods: { onTap() { this.setData({ a: this.data.b, b: this.data.a + this.data.b, }); }, }, }); [代码] 温馨提示:需要通过this.setData改变值,computed的计算属性的值才会因为对应的值发生改变而改变 例如上述代码,onTap()方法中如果改变a和b的值是如下: [代码]a = this.data.b; b = this.data.a+ this.data.b; [代码] 而不是 [代码]this.setData({ a: this.data.b, b: this.data.a + this.data.b, }); [代码] 那么computed中的sum属性是不会发生任何改变的。 道理很简单,对已有的 setData 进行二次封装,在每次 setData 的时候计算出 computed 里各字段的值,然后设到 data 中,以达到计算属性的效果,不用setData对a和b进行更新,那么计算属性sum便接收不到此更改。 文章到此结束啦,觉得有帮助的小伙伴可以点个赞~ 如果按照上述步骤仍无法正常使用computed的欢迎在评论区提出
2021-11-11 - 小程序实现高性能虚拟列表优化+节流+分页请求(固定高度)
场景引入 为什么需要用到高性能虚拟列表+节流+分页请求的优化? 当有场景需求为需要将大量数据(10000条)呈现在一页上,我们不断下拉访问,页面中有大量的数据列表的时候,用户会不会有不好的体验?会不会出现滚动不流畅而卡顿的情况?会不会因卡顿而出现短暂的白屏现象(数据渲染不成功)? 通过微信开发者工具自带的调试器->Network页面,我们可以观察到当有长列表时如果不使用高性能虚拟列表+节流+分页请求的优化,会出现以下问题: FPS:每秒帧数,图表上的红色块表示长时间帧,很可能会出现卡顿。 CPU:CPU消耗占用,实体图越多消耗越高。 NET:网络请求效率低,一次性请求10000条的渲染效率远远低于分1000次,每次请求10条数据 内存:滑动该列表时明显能看到内存消耗大。 总结:如果需要将大量数据(10000条)呈现在一页上,可以通过高性能虚拟列表+节流+按需请求分页数据并追加显示。 优化的具体实现可拆分为以下需求(将一个大问题拆分为一个个小问题并逐个去解决): 不把长列表数据一次性全部直接显示在页面上。 截取长列表一部分数据用来填充屏幕容器区域。 长列表数据不可视部分使用使用空白占位填充。 监听滚动事件根据滚动位置动态改变可视列表。 监听滚动事件根据滚动位置动态改变空白填充。 分页从服务器请求数据,将一次性请求所有数据变为滚动到底部才再次向服务器发送获取数据的请求 开始实战 此次实例我设定每个元素的固定高度为210rpx [图片] (1)首先计算屏幕内的容积最大容量(即屏幕一次性可以容纳多少个高度为210rpx的元素) 调用wx.createSelectorQuery()的api [图片] 上图是id为scrollContainer的组件,滚动时触发函数handleScroll() [代码]// 计算容器的最大容积,onReady中触发,即初次渲染时触发 getContainSize() { wx.createSelectorQuery().select('#scrollContainer').boundingClientRect(function (rect) { rect.id // 节点的ID rect.dataset // 节点的dataset rect.left // 节点的左边界坐标 rect.right // 节点的右边界坐标 rect.top // 节点的上边界坐标 rect.bottom // 节点的下边界坐标 rect.width // 节点的宽度 rect.height // 节点的高度 }).exec((option) => { // console.log(~~(option[0].height / this.data.oneHeight) + 2); this.data.containSize = ~~(option[0].height / this.data.oneHeight) + 2; }) }, [代码] 调用api后返回的option中的height就是小程序页面的视口高度,除以oneHeight(210rpx)就是能容纳的个数,用[代码]~~[代码]来对结果进行向下取整(实际能容纳的应+1),由于在滚动时会出现上一个元素在上边界还没完全消失,第四个元素就从下边界进入视口了,因此最大容纳量应再+1。即实际容纳量应该如图最后一行代码所示+2. [图片] (2)监听滚动事件动态截取数据 监听用户滚动、滑动事件,根据滚动位置,动态计算当前可视区域起始数据的索引位置 startIndex,再根据containsize,计算结束数据的索引位置 endIndex,最后根据 startIndex与endIndex截取长列表所有数据a11Datalist 中需显示的数据列表 showDatalist。 PS:下列代码中函数handleScroll()最下面的[代码]this.setDataStartIndex(data);[代码]才是滚动时真正进行的滚动事件动态截取数据,上面那些代码用途在文章后面部分再详细介绍(节流)。 [代码]// 定义滚动行为事件方法 handleScroll(data) { if (this.data.isScrollStatus) { this.data.isScrollStatus = false; // 节流,设置一个定时器,1秒以后,才允许进行下一次scroll滚动行为 let mytimer = setTimeout(() => { this.data.isScrollStatus = true; clearTimeout(mytimer); }, 17) this.setDataStartIndex(data); } }, [代码] [代码]// 执行数据设置的相关任务, 滚动事件的具体行为 setDataStartIndex(data) { // console.log("scroll active") this.data.startIndex = ~~(data.detail.scrollTop / this.data.oneHeight); // 通过scrollTop滑动后距离顶部的高度除以每个元素的高度,即可知道目前到第几个元素了 this.setData({ showDataList: this.data.allDatalist.slice(this.data.startIndex, this.data.endIndex) }) // 动态截取实际拥有10000条数据的数组中下标为startIndex到endIndex的数组出来呈现在前端页面上 // 容器最后一个元素的索引 if (this.data.startIndex + this.data.containSize <= this.data.allDatalist.length - 1) { this.setData({ endIndex: (!this.data.allDatalist[this.data.endIndex]) ? this.data.allDatalist.length - 1 : this.data.startIndex + this.data.containSize // 滚动到底部了吗,是的话那就将endIndex设置为9999,不然的话设置为startIndex+视口最大容量 }) } else { console.log("滚动到了底部"); this.data.pageNumNow++; // 例如一次性从数据库拿10条数据赋值到allDataList,如果滚动到底部(即allDataList所有数据都已经呈现了),那就再次向服务器发送请求获取数据库中的下10条数据 this.addMes(); // 该函数内就写你实际向数据库请求时的代码,请求成功后拼接到allDataList即可 console.log(this.data.allDatalist.length) } }, [代码] (3)使用计算属性动态设置上下空白占位 我们设置了根据容器滚动位移动态截取ShowDatalist 数据,现在我们滚动一下发现滚动2条列表数据后,就无法滚动了,这个原因是什么呢? 在容器滚动过程中,因为动态移除、添加数据节点丢失,进而强制清除了顶部列表元素DOM节点,导致滚动条定位向上移位一个列表元素高度,进而出现了死循环根据 startIndex和endIndex的位置,使用计算属性,动态的计算并设置,上下空白填充的高度样式blankFi11Sty1e,使用padding或者margin 进行空白占位都是可以的 PS:由于小程序没有computed,所以为了使用计算属性,得另外引入封装好computed的包,引入computed组件的教程我放在最后,我使用的是官方推荐的computed,且注意使用该插件时,不要加[代码]this -> this.data[代码],直接data即可 [代码]computed: { // 定义上空白高度 topBlankFill(data) { // console.log("change") return data.startIndex * data.oneHeight; }, // 定义下空白高度 BottomBlankFill(data) { return (data.allDatalist.length - data.endIndex) * data.oneHeight; }, // 定义一个 待显示的数组列表元素 showDataList(data) { // console.log(data.allDatalist.slice(data.startIndex, data.endIndex)) return data.allDatalist.slice(data.startIndex, data.endIndex) }, }, [代码] (4)下拉置底自动请求加载数据 下方代码中[代码]setDataStartIndex()[代码]函数末尾的if-else便是判断是否已经滚动到现有全部数据的allDataList数组是否已经滚动到底部,全部呈现完了。 如果是那就执行else部分,请求的页数pageNumNow+1,然后调用addMes()请求数据,然后将新请求到的数据进行拼接到allDataList上 [代码]// 执行数据设置的相关任务, 滚动事件的具体行为 setDataStartIndex(data) { // console.log("scroll active") this.data.startIndex = ~~(data.detail.scrollTop / this.data.oneHeight); this.setData({ showDataList: this.data.allDatalist.slice(this.data.startIndex, this.data.endIndex) }) // 容器最后一个元素的索引 if (this.data.startIndex + this.data.containSize <= this.data.allDatalist.length - 1) { this.setData({ endIndex: (!this.data.allDatalist[this.data.endIndex]) ? this.data.allDatalist.length - 1 : this.data.startIndex + this.data.containSize }) } else { console.log("滚动到了底部"); this.data.pageNumNow++; this.addMes(); console.log(this.data.allDatalist.length) } }, // 根据接口数据来给数组添加真实数据 addMes: function () { this.list() .then(res => { // console.log(res.result.data); // 将接口获取到得所有数据存储起来 this.data.allDatalist = this.data.allDatalist.concat(res.result.data); // 设置初始显示列表 this.setData({ showDataList: this.data.allDatalist.slice(0, 5) }) }) }, // 以下为获取数据 list: function () { let pageNum = this.data.pageNumNow; let pageSize = 20; // console.log('当前请求的页码为:' + pageNum); return new Promise((resolve, reject) => { wx.cloud.callFunction({ name: 'teacher', data: { action: 'list', pageNum, pageSize } }).then(res => { resolve(res) }).catch(err => { reject(err) }) }) }, [代码] 到此处,高性能虚拟列表+分页请求的优化已经搞定了,下面开始节流的优化 (5)滚动事件节流定时器优化 由于监听滚动事件触发对应函数方法的频率是极高的,因此页面节流优化是必须的。 方法:在data中声明一个属性scro11State用来记录滚动状态,只有scro11State值为true的时候才会具体执行 PS:下面代码中定时器设置为17ms的原因是,由于小程序没有web的requestAnimationFrame的api,无法作滚动事件节流请求动画帧优化,因此可以手动计算显示器的帧率,大概一帧在17ms,因此定时器设置为17ms [代码]// 定义滚动行为事件方法 handleScroll(data) { //节流部分代码 if (this.data.isScrollStatus) { this.data.isScrollStatus = false; // 节流,设置一个定时器,1秒以后,才允许进行下一次scroll滚动行为 let mytimer = setTimeout(() => { this.data.isScrollStatus = true; clearTimeout(mytimer); }, 17) //节流部分代码 this.setDataStartIndex(data); } }, [代码] 到这为止,节流也搞定啦,觉得从中学到了许多的小伙伴不妨点个赞。 小程序如何引入computed计算属性请参考我的这篇文章:https://developers.weixin.qq.com/community/develop/article/doc/000a4442bd44c84e740d6b6b051413 在后续我也会将高性能虚拟列表+节流的优化封装成一个插件,给小伙伴们直接使用,欢迎关注我以便及时获取到我文章的更新~ 在这里也推荐一篇防抖和节流的性能优化知识介绍的文章:https://segmentfault.com/a/1190000018428170 觉得有帮助的小伙伴欢迎点赞,有其他问题也欢迎在评论区提出
2021-11-11 - 关于移动端适配的几种常见解决方案
前言 当不同设备对应的屏幕不一样时,这时对于px,rpx来写的样式就会存在样式适配问题。 对于移动端设备进行适配,采用设置最大宽度,并且在meta标签中设置理想视口,可以保证在移动设备以及PC上面的整体布局效果。 由于小程序方面,微信官方已经将样式适配问题处理得非常完善了,不用我们操心,但如果开发的是移动端页面等等需要自己适配,或面试考到,那么这方面内容就需要再深入了解一下了。 可见视口 Visual Viewport其实很好理解,就是整个屏幕的可见区域大小。由于设备的物理像素,也就是CSS中的pt单位是固定的,页面在移动端被缩放了之后,页面中的CSS像素分布在设备上也发生了变化。 理想视口 Ideal Viewport其实是上面两者的结合,当我们将Layout Viewport的宽度设置成屏幕的宽度,就保证了页面中CSS像素点的恒定。 这里就用到了移动端适配常用的 [代码]<meta name="viewport" content="width=device-width"> [代码] 这个标签可以保证在移动端设备中,页面的宽度与屏幕宽度相同。 正题 目前对于移动端适配的内容布局解决方案大致可分为以下几种: (1)百分比,所有需要动态调整的元素宽高采用百分比,字号固定像素。 (2)rem,通过计算或者JavaScript获取到设备像素/CSS像素的比例,确定根元素的字体像素,然后所有单位根据根元素字体像素进行rem设置,确定大小。而基础rem会根据设备变化而变化。 (3)vw,根据当前设备的Visual Viewport宽度作为100vw,然后得出单位vw的宽度,所有元素按照vw为单位进行样式排布。 (4)Media Query:通过断点来进行不同宽度区间的设备样式适配。 以上几个方法各自都有各自的好处,我们可以看一下实际应用时候的效果: 💥 百分比 💥 使用百分比作为内容大小的标准,在大部分条件下是可行的,百分比可以很好地让元素乖乖呆在自己的位置,无论屏幕的宽度大小。 但是文字就存在非常大的问题了,由于文字是固定大小,在屏幕dpr变化的时候,文字的CSS像素不变,就导致了文字在页面中的占位发生了变化。这样的结果就是,文字过多或者屏幕dpr过小的时候,会发生溢出;但是如果按照小屏幕为基准,又会发生字体太小这种情况。 百分比在当前移动端适配排版的时候,更多地会作为section级别元素的兼容排版。这个也要和设计稿中的效果相关,如果设计稿中要求一个元素定宽,那么就直接用px来保证宽度就可以了。 💥 rem 💥 rem这个单位和之前常用的em有点类似,唯一的区别在于rem及基于根元素的font-size来进行计算的一个相对值。em存在很多缺点,比如层层嵌套之后,可能就会忘记了上一层的font-size到底是多大。或者比如像现在的模块化开发,一个路由套在另一个路由里面,甚至找父元素都需要到其他文件中去找。 为了解决em存在的问题,标准中还有rem这个单位来帮助排版。所有的元素大小都用rem来作为单位,然后在页面的根元素中,我们为根元素的font-size进行确定化地赋值,这样所有的rem单位都是同一个明确的基准了。当屏幕进行适配的时候,只需要调整这个基准值,就可以保证每个元素的大小自动按照比例调整。 阿里的lib-flexible解决方案实际上就是利用了这个方式,通过给<html>标签绑定font-size以及data-dpr属性来进行整个页面的适配。 方案将整个页面宽度分成100份,分成100份的原因可以看下面的另一个方案。每10个单位宽度作为1rem,也就是整个视觉稿的宽度会被分成10rem的100份,假如拿到的视觉稿是750px的,那么1rem就代表75px。这样得到的比例系数就是75/750,也就是每次在进行设计稿到CSS的转换的时候,只需要对设计稿的像素值/10就可以得到对应的rem值。 通过一个预先加载的JavaScript脚本,计算根节点的字体大小,document.documentElement.style.fontSize = window.innerWidth / 10 + ‘px’;,然后我们在写页面代码的时候只需要将原始的像素值/基准值就可以得到对应的rem单位了。当然每次都要按计算器肯定是不行的。如果想方便使用的话,可以用less或者sass这种预处理器来处理页面。 💥 vw/vh(是最终解决方案吗?)💥 首先看看vw的浏览器支持情况吧,can i use vw支持情况,使用这个单位意味着你放弃了IE11以下的PC用户,在现在一个主要兼容移动端的世界里,并没有太大的副作用(这里吐槽一句,其实PC端的兼容远远要比移动端来的方便。移动端奇奇怪怪的分辨率以及2x,3x的屏幕,还有苦逼的ipad、横屏让我每次做兼容的时候想一跃解千愁)。 vw自身将整个可见视口横向分成了100份,每一个单位就是1vw,这个单位最大的优势就是在移动端的时候,无论是竖屏或者横屏,vw永远都是针对于横向的,比rem的方案好在当屏幕大小发生变化(顺便兼容了以后的可调节屏幕大小的移动设备[手动斜眼])的时候,不会让页面崩掉。 对于移动设备来说,比如iphone6+的375pxCSS像素宽度,1vw就等于3.75px,通过这个单位可以解决上面的依赖于脚本绑定根元素font-size的问题,在竖屏和横屏下面都有比较好的效果。 在通过vw解耦了CSS和JS之后,那么vw是否可以独立解决所有问题呢? 💥 media query(媒介查询)💥 CSS响应式布局 ① 使用 @media 查询可以针对不同的媒体类型定义不同的样式。 ② @media 可以针对不同的屏幕尺寸设置不同的样式,特别是如果需要设置设计响应式的页面。 ③ 重置浏览器大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面。 [代码] @media 媒介类型and|not|only (媒介特征) { ... } [代码] 实例: 为不同页面宽度设置不同的CSS样式——页面宽度大于320px和页面宽度等于320px时分别为div设置不同的背景颜色。 [代码]#square{ width:100px; height:100px; } @media only screen and (min-width: 320px) { #square { background:red; } } @media only screen and (min-width: 320px) and (max-width: 320px) { #square { background:yellow; } } [代码] 结论 虽然根据实现出来的效果,几种方法可能没有什么太大的效果差别,但是最大的问题在于,需要实现这种效果需要多么复杂的代码呢。CSS的兼容性不在于解释器上,而是在于设备的屏幕上面。大部分时间不仅需要将页面展示在用户的面前,而是需要将页面稳定且优雅地展示给用户。无论是百分比,rem还是vw,都是进行局部容器元素定位的,作为最底层的叶子元素或者单元元素来说,更多时间还是会使用px来尽量还原视觉稿。 长远考虑这个问题,vw在仅进行移动端访问的情况下效果拔群,因为不考虑兼容,只需要考虑适配问题。工程中到底使用哪个方法进行,取决于大部分业务需要兼容的环境。
2021-11-15 - 小程序内实现较精美的登录页面
先上效果图: [图片] 实现过程: 在html网页中先定义一个表单以后,再定义一个Login标题,两个输入框,最后一个登录按键,主要功夫为css配置。 先定义页面主体,标准的消内外边距 [代码]body { margin:0; padding:0; font-family:sans-serif; background-color: #ecf0f1; } [代码] 定义表单整体样式 [代码] .box { width:300px; padding:40px; top:50%; left:50%; transform: translate(-50%,-50%); position:absolute; background-color: #34495e; text-align: center; } [代码] 定义该表单中两个文本输入框的样式,可以根据喜好调整transition的动画时间 [代码].box input[type="text"],.box input[type="password"] { background: none; display: block; text-align: center; border:0; margin:20px auto; border:2px solid #2980b9; padding:14px 10px; width:200px; outline:none; color:white; border-radius: 24px; transition: 0.5s; } [代码] 定义当光标放在两个输入框时的样式,这里我只是简单的变宽和蓝色变亮 [代码].box input[type="text"]:focus,.box input[type="password"]:focus { width:280px; border-color:#3498db; } [代码] 定义提交按钮的样式 [代码] .box input[type="submit"] { color:white; background: none; display: block; text-align: center; border:0; margin:20px auto; border:2px solid #2ecc71; padding:14px 10px; cursor:pointer; width:180px; outline:none; border-radius: 24px; transition: 0.25s; } [代码] 定义提交按钮的选中样式,这里我简单把背景调成对应绿色,就已经很好看了 [代码].box input[type="submit"]:hover{ background-color: #2ecc71; } [代码] 7.大概完成后,发现页面标题默认黑色与表单背景颜色不太符合,因此调成白色即可 [代码]h1 { color:#ecf0f1; } [代码] 本页面全部代码如下: html中: [代码]<!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>Document</title> <link href="style.css" type="text/css" rel="stylesheet"> </head> <body> <form class="box" method="post"> <h1>Login</h1> <input type="text" name="firstName" placeholder="账户名称"> <input type="password" name="firstPassword" placeholder="密码"> <input type="submit" name="firstSubmit" value="登录"> </form> </body> </html> [代码] CSS中: [代码] body { margin:0; padding:0; font-family:sans-serif; background-color: #ecf0f1; } .box { width:300px; padding:40px; top:50%; left:50%; transform: translate(-50%,-50%); position:absolute; background-color: #34495e; text-align: center; } .box input[type="text"],.box input[type="password"] { background: none; display: block; text-align: center; border:0; margin:20px auto; border:2px solid #2980b9; padding:14px 10px; width:200px; outline:none; color:white; border-radius: 24px; transition: 0.5s; } .box input[type="text"]:focus,.box input[type="password"]:focus { width:280px; border-color:#3498db; } .box input[type="submit"] { color:white; background: none; display: block; text-align: center; border:0; margin:20px auto; border:2px solid #2ecc71; padding:14px 10px; cursor:pointer; width:180px; outline:none; border-radius: 24px; transition: 0.25s; } .box input[type="submit"]:hover{ background-color: #2ecc71; } h1 { color:#ecf0f1; } [代码] 如果你喜欢该篇文章,请不要忘记点击右下角的大拇指进行点赞噢~
2021-11-07 - 浅谈javascript的原型和原型链(新手懵懂想学会原型链?看这篇文章就足够啦!!!)
由于小程序前端页面涉及到一些数组及对象的方法,当然也可以在原型链上新增自己所需的一些方法,因此本篇文章对于原型链做个大概介绍。 本篇文章我将从概念和对应题目知识点讲起,希望大家能有所收获 一、原型 ①所有引用类型都有一个_proto_(隐式原型)属性(类似链表中的next指针), 链表可以通过.next访问下个元素,原型中可通过._proto_访问上一级元素。 [图片] ②所有类都有一个prototype(原型)属性,例如:Object,Function,Array ③所有引用类型的_proto_属性指向它构造函数的prototype 例如:arr是一个数组实例,那么arr._proto_=Array.prototype <br/><br/> 二、原型链 当访问一个对象的某个属性时,会先在这个对象本身上查找,如果没有找到,则会去它的_proto_上查找,即它的构造函数的prototype,如果还没有找到就会继续在构造函数prototype的_proto_中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。 下面例子有助于你对原型链的理解: arr为Array数组的实例 arr._proto_=Array.prototype Array.prototype._proto_=Object.prototype 分析:arr这个数组实例,沿着原型链找,找到数组的原型对象,数组这个类沿着原型链找,找到对象的原型对象(最高级),因此也可以用arr._proto_._proto_来找到Object.prototype,类似链表中的next指针,只不过_proto_是往上找。 <br/><br/> 面试真题: 题目一: instanceof的原理,并用代码实现 <br/> 分析:如果A沿着原型链能找到B.prototype,那么A instanceof B 为true(用_proto_来找) 解法:遍历A的原型链,如果找到B.prototype,返回true,否则返回false [代码]const instanceof =(A,B)=>{ let p = A; while(p){ if(p === B.prototype){ return true; } p = p._proto_; } return false; } [代码] <br/> 题目二: var foo = {}, F = function(){}; Object.prototype.a = ‘value a’; Function.prototype.b = ‘value b’; console.log(foo.a); console.log(foo.b); console.log(F.a); console.log(F.b); <br/> 分析:如果在A对象上没找到x属性,那么会沿着原型链找x属性。(如果A为函数实例,那么A上面找不到,就去找Function这个类上有没有挂载x属性,如果没有就继续往上找到Object原型对象上有没有x属性) 解法:明确foo和F变量的原型链,沿着原型链找a属性和b属性 因此答案为: [代码]'value a' 'undefined' 'value a' 'value b' [代码] foo这个对象实例上没有b属性,是因为原型链不能往下找,只能一层一层往上找,即对象实例不能腆着脸问他的下级Function有没有挂载b这个属性 <br></br><br></br> 觉得本篇文章对你有帮助的请不要忘记一键三连加关注~~ 你的支持就是对我最大的动力!! 会继续努力码更多的精品文章!!! 欢迎大家关注我的公众号:Smooth前端成长记录 公众号同步更新CSDN博客内容,想方便阅读博客的C友可以来关注我的公众号以便获得更优良的阅读体验~ [图片]
2021-11-07 - 关于腾讯位置服务的地址解析与路径规划两个api的坑!!!debug了一个晚上终于找出来了
⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️ 小程序的地图定位与导航以及非常常见且在很多小程序上都已在使用, 下面让我讲讲当我在使用腾讯位置服务这款微信小程序JavaScript SDK时,遇到的天坑! 重要!!! 地址解析(地址转坐标) geocoder(options:Object) 与 路线规划 direction(options:Object) 当小程序要实现的功能是,先输入起点名称,再输入终点名称,然后进行路线规划时, 要经历的过程有通过将起点、终点名称解析为经纬度,再将两点的经纬度传入路线规划的函数,进行路线规划。 而我所要说的坑就是,地址解析与路线规划的先后顺序问题 当你先地址解析将page的data中的start和end分别赋值为起终点的经纬度,这个时候立即调用direction函数,会发现先执行direction函数,才进行上面的赋值,想要解决这个问题,必须要把direction函数设立时间间隔为至少 1s 后再运行,这时才能正常赋值。 bug出在腾讯服务提供这两个api时与对应执行时的生命周期有关。(官方文档中也没讲清楚api调用要设置异步且需要一定的时间间隔) 必须要把direction函数设立时间间隔为至少 0.1s 后再运行!! 如下图: [图片] [图片] 希望我的文章对你有所帮助
2021-11-15