个人案例
- 前端XR
小程序元宇宙~
前端XR扫码体验
- ELSE IF
国庆头像挂国旗
ELSE IF扫码体验
- ELSE前端ELSE前端
扫码体验
- 第一个小游戏做完了,源码已分享,另附完整开发教程
从4月27号,我发布了“微信小游戏实战系列”的第一篇文章,也是我的第一个小游戏正式开始制作的时间,之前它只是一个脑袋中的想法,大概类似于这样:我能不能试着做这样的一系列的游戏呢?它们有着经典的玩法,不会过时,仍然有着很多的受众,如果我把一个经典的小游戏做成一个精品的小游戏,是否是有价值的,甚至能产生商业价值? 当你有了一个待要去验证的想法后,就很难再无视它。这个想法每天都在我的脑海里游荡,时不时的叮咚一下:嗨!你打算什么时候开始做呀?与其长此以往的被脑袋中的这个小闹铃打扰,我知道最好的方式就是把第一个小游戏做出来,然后它就会安静了。所以我就开始谋划第一个小游戏的题材,那些游戏史上的经典:俄罗斯方块,太空侵略者,贪吃蛇,吃豆人….. 最终我选择了“1010”,虽然它不见的会被载入游戏经典史,但是它简单的规则和玩法,确实有被载入的潜质。我的第一款小游戏就是它了,我要将它制作成一款精品。 在确定了要做的游戏后,我开始思考,市面上已经有如此多的“1010”的类型的游戏,我到底要做一款怎样的“1010”呢?以我的能力可以把它做成什么样的呢? 既然是要做一款微信小游戏,当然是先看一下微信小游戏平台上现在已有的“1010”类型的游戏都是什么样子?在试玩了所有的1010类型的游戏后,我有了这样的想法:这里所有的1010都无法让我满意,都不够精致,看来我必须而且一定要自己做一个了。一个极其自恋但是对我来说却有着极大动力的想法。 我要做一款这样的游戏,它小而美,简洁,精致,不会过时,它的名字叫“精致1010”。 于是有了下面这张草图,是我对它初识的印象。 [图片] 而它最终的样子是这样。 [图片] 可以看出,在界面上基本延续了初识时的样子,只是看起来更好看了一些。 虽然不论是从简洁性还是配色上,看上去还不错,但是这只能算是万千小游戏中的一个而已,只有这样并不足以让人能够记住它。于是我又陷入了深深的思考:一款游戏想要被记住,想要深入人心,只有在情感上与玩家产生共鸣和连接才行,这样的一款简单的小游戏如何才能产生情感共鸣呢? 于是我有了这样的方案:将游戏的四个主题与春,夏,秋,冬的四季相联系,然后在每个季节里增加一点儿那个季节里所独有的有可能与某些记忆相联系的东西,春天的樱花飘落,夏天的细雨绵绵,秋天缓缓落下的枫叶以及冬天安静飘洒的雪花。 最终有了这样的效果: 春天的樱花飘落。 [图片] 夏季的绵绵细雨。 [图片] 秋天缓缓落下的枫叶。 [图片] 冬天静静飘洒的雪花。 [图片] 每种季节的特定效果并不会在一开始就出现,而是在游戏中不经意间出现一小会儿。我希望它能传达出这样的感觉:当你行走在春天的路上,不经意间,几片樱花花瓣从眼前飘落,你停下脚步,抬起来头,看见了美丽的樱花树,也看见了整个春天。 为了在游戏中模拟出樱花的飘落的感觉,我设置了非常多的随机数据,用了整整一天的时间,才调整出了想要的感觉:一阵微风吹过,伴随着片片花瓣向斜下方掉落,驻足抬头,你看见了整棵樱花树(这棵樱花树能美成什么样完全基于你的想象)。 游戏中的音效不多,我没有为其增加背景音乐,因为我认为背景音乐会让它显得嘈杂。我希望游戏中的音效能够简单,清澈。起初,找了一些开源的音效素材库,但是并没有找到能够让我满意的音效。后来也试着使用了一些音效制作软件来制作音效,但是仍旧不太满意。最终,从女儿的音乐小玩具中找到了我想要的声音。 [图片] 游戏中的所有的音效都是用这个小东西创造的,原声的录制未加任何的修饰。 旋律也是极其的简单,如果你在游戏中仔细听一下,大概能够听出来: 消除音效用的是:哆瑞咪发梭拉西哆(12345671)游戏结束音效用的是:梭发咪瑞哆(54321)形状拾起时用的是:咪(3)形状放下时用的是: 哆(1)所有的按钮点击用的是:瑞(2) 以上是以作为一个游戏创作者的视角描述的这个游戏的产生和制作的过程和细节。对于一个普通玩家来说,可以算作看个热闹,了解一下游戏是怎么做出来的,都包含了哪些东西。 下面是完成这款游戏后给我的感想和思考。 在游戏做完后,我把它发给了老婆玩,我老婆的感受大概是这样的: 老公,你这个游戏是不是太简单了。 哎呦,还会掉花瓣呢!有点儿意思。 当她玩到了两千多分,登顶排行榜之后...... 老公,你这个游戏挺不错的,配色也挺好看,看着简单,东西还蛮多的。 在游戏上线后,我把它分享给了老妈和丈母娘,想看看她们对于这样一款游戏的感受,没有做过多的解释,对于她们来说似乎也没有什么上手的难度,看着她们在排行榜中的得分,我知道这款游戏完成了,做了这么多年的游戏,这是第一个分享给她们玩的游戏。 每当女儿看到我玩这个游戏时都会跑过来说:爸爸,我玩。然后就用她的小手在屏幕上拖拖拽拽的,她并不能理解游戏规则,只知道只要把下方的图形放到上面去就很高兴,在多次尝试放不上去时就会对我说:爸爸,把我手,一起玩…..然后我就握着她的小手一个一个的将下方的图形拖拽上去。 对于我来说这也是第一个让我每天都会打开的游戏,游戏制作者通常不会玩自己的游戏,这是事实。但是我却经常的打开它,我喜欢游戏里的夏天,打开它有时候只是为了等待那场不知何时会下起的雨。 做了多年的游戏开发,也做了很多款游戏,这是唯一的一款让我觉得异常满足的可以分享给家人玩的游戏,它包含了很多的我对于游戏的想法和思考,并且让我觉得做出了一些不一样的东西。 在这个一切都讲求快的时代,在所有的游戏都在想尽一切办法赚取玩家注意力,保持玩家留存率的环境下,我想通过这款游戏表达一些不一样的东西,正如我在游戏中写给玩家一封信上所说。 我希望它是这样的一款游戏: 简洁而精致,让人看了感觉舒服。它不会去抢夺你的注意力,你可以随时打开玩几下也可以随时的放下。它就像你的一位老朋友,无需多言,也无需时刻保持联络,它就静静的守候在那里,伴你度过每一个春夏秋冬。 当我们玩游戏时,通常都会处于一种“躁”的状态,而我希望它能够传达出一种“静”的感受,它是一位沉默寡言的老友,即使从不说话,它也会一直在那里陪伴着你。 虽然,我个人对它觉得满意,但是,这样的一款游戏是否具有商业价值呢?这是个不得不思考的问题。任何的一件事,只凭借着热爱是很难长久的,对于这种小众的游戏是否有可能实现盈利,持续发展下去呢? 我相信是的,因为我认为这是一件正确的事,长期坚持做下去定会产生价值的事。 说到这里不得不介绍一个人,以及他的一个产品。 此人我叫他“波哥”,因为我写的小游戏开发教程而结实成为朋友,至今尚未谋面。他做了一个个人的博客网站,叫做“刻意周刊”,每周分享互联网上的优质内容。 自从认识后,他的“刻意周刊”几乎就承包了我每个周的阅读内容,各种的人文,社科,行业,趣闻,影视,书籍推荐等等,每期的周刊内容之丰富即使我用一个周的闲余时间也涉猎不完。做这样的一个周刊,每个周得阅读多少的东西才能精选出来呀?他背后一定有一个团队在做这样的事情,我这样想。 于是我就好奇的问他:波哥,你这个“刻意周刊”是一个人做的还是有团队在做啊? 波哥回复:我自己做的,就是把每个周看到的感觉有价值的可以分享的内容整理出来,这种事就得坚持长期价值。 于是乎,我断定此乃牛人也。 你可以看一下这个“刻意周刊”,至今为止出了26期了,看一下每期的内容和质量,大概也就能明白为什么在知道这是一个人做的之后,我会觉得震惊了。 就在今天,基于刻意周刊发现与推荐优质内容的初心,孵化了一个新的产品“Superdaily”,开始正式发挥它的商业价值了。 波哥所做的事证明了这样的一个事实,坚持长期做有价值的事情,是会产生商业价值的。所以,我对自己的小游戏开发之路也有了这样的初心和愿景: - 致力于制作优质的微信小游戏,可以分享给父母,分享给朋友,分享给小朋友的游戏,希望当玩家看到这个游戏是小蚂蚁出品的,就知道这个游戏是用心制作的,靠谱的。 - 继续写出更好的游戏开发教程,帮助更多想要做游戏又不知从何处下手的朋友能够做出自己的游戏,实现自己的想法和创意。 把正确的有价值的事情长期做下去,坚持长期价值。 在制作这个游戏的过程中,我写了一个“微信小游戏开发实战系列”的教程,教你如何从0开始开发出一款这样的游戏。 如果你从未有过游戏开发经验,但是又对游戏开发感兴趣,想实现自己的游戏创意,做出自己的游戏,可以学习一下我的“人人都能做游戏”系列教程,手把手的带你进入游戏开发世界的大门。 差点儿忘了介绍本文的主角:精致1010。一款精致而温暖的游戏送给你,第一个作品,欢迎大家支持鼓励一下,十分感谢。 [图片] 游戏已开源到社区,项目地址:精致1010 欢迎学习,交流,改编分享,请不要直接用于商业用途。 欢迎关注我的公众号:小蚂蚁游戏开发,我会继续致力于分享与小游戏开发有关的教程和内容。如果你遇到任何的问题,我也会尽可能的为你提供帮助。 [图片]
2021-06-19 - 教你 3 分钟搭建 AI 助手(无需编码)
前言 今天给大家带来的是云开发AI智能体,上篇《云开发之云模版CMS体验》文章提到了期待云模版有更多实用模板,这周云模版就更新了AI智能体应用,无需代码一句话搞定一个 AI 助手。 步骤 进入「微信开发者工具」-「云开发」控制台 [图片] 选择「云后台」-「去使用」 [图片] 进入云模板控制台选择「模板中心」 [图片] 进入「AI智能体应用」点右上角「安装」 [图片] 安装成功后切换到「关联资源」菜单「访问」管理后台 [图片] 默认会内置一些AI助手 [图片] 选择「分享」可以直接对话体验 [图片] 如果没有满意的AI助手可以通过「从模版创建」 [图片] 模板中还有 10 多AI助手供你选择 [图片] 选择后可以进入AI助手编辑详情页面 左边区域可以设置人设提示词 右边区域可以直接对话调试效果 [图片] 确认没问题点击「提交」就创建成功了 接下来再来一个自定义一个AI助手,选择「创建智能体」 [图片] 输入AI助手名称点击「确认」 [图片] 进入AI助手详情页,不会写人设提示词可以点击「获取AI建议」 [图片] AI会根据名称来生成一套专业的人设提示词,点击「使用AI建议」即可 [图片] 设定好AI助手人设后,可以在右边区域对话调试 [图片] 除此之外还可以设置AI助手的基础信息: 对外展示信息 头像 名称 简介 背景 开场白 欢迎语 推荐问题 [图片] [图片] 确认无误后点击「提交」就创建好了,可以「分享」给你的好友使用了 [图片] 电脑端效果: [图片] 手机端效果: [图片] 还支持在「对话」菜单中查看所有智能体的对话记录 [图片] 总结 整个体验下来,创建流程非常简单,只需要输入你要创建的AI助手名称即可生成,全程无需代码,赶紧去试试吧~ 发布到小程序/公众号已经在来的路上了,后续开放后我会更新相关体验文章。
05-17 - 微信对话开放平台《实验室》邀请测试
微信对话开放平台为微信AI研发的低门槛、零费用的便捷对话机器人工具,多年来一直为微信生态的开发者与商家提供智能对话能力。 近两年来,随着大语言模型的涌现,人工智能领域迎来了历史性的突破,也引发了一股ChatBot的热潮。随着相关话题的探讨热度不断增加,我们也开始思考,是否可以在微信对话开放平台原有的能力基础上,融入大语言模型技术,从而更好地解决知识库构建成本高、无法回答复杂问题等痛点。为此,我们将进行一个新的尝试,开放“实验室”功能并邀请用户加入测试。 [图片] LLM的优点在于能够处理大量信息,理解复杂语境,并生成自然、流畅的回答。利用LLM的能力,我们可以更好地处理开放式问题、复杂问题和模糊问题。但是,在实际应用场景,LLM也存在一定局限性,为此我们尝试了两方面的调整。 大语言模型文档问答 虽然LLM有着强大的推理理解能力,但其潜在的偏离实际的预测以及知识更新延迟问题仍然值得关注。为了在实际应用中加强"准确性"、"可控性",我们使用RAG(Retrieval Augmented Generation)检索增强生成技术,让大模型可以处理各种复杂的知识密集型任务。 同时,我们针对问答场景对模型进行了微调(Fine-tune),进一步提升了拒识、推理和多轮对话等能力,确保了输出效果的优质。这使得大型语言模型在细分应用场景中具有真正的落地价值。 平台不仅支持Word、TXT、PDF等文件格式的上传,还支持网页、公众号文章等数据格式。 [图片] 文档抽取问答对 由于LLM的知识库回答是从大量的文本数据中提取和总结的,且它可能包含一些错误或不准确的信息。FAQ问答知识库,是一种将信息以问题和答案的形式组织起来的形式。这种结构化知识库通优点是信息准确、可靠,易于管理和更新。 为此平台实现了从上传的文档中自动抽取问答对功能,并允许开发者审核并添加到知识库中。此功能可以有效的提高知识库的创建效率和质量。 [图片] 想要加入测试的用户可以按照如下方式进行申请: 申请方式 1. 发送邮件到:wechatopenai@tencent.com 2. 邮件主题:申请实验室(机器人ID) 3. 邮件内容: - 机器人信息:(包含:机器人ID、机器人名称) - 当前应用情况:(包含:每日消息量、当前召回率) - 应用场景:(如:“微信公众号”、“小程序”) - 应用简介:(如:xxxx 公司xxx 产品的售后客服) - 开发者微信号:xxxxx
01-22 - 留言功能开通了?
21年新创建的公众号,昨天发了一篇图文,发现有了留言功能,之前都是没有的,突然开通了留言功能,是后台有什么规则修改了嘛
02-20 - 5秒教你如何获取公众号主页url
要点: 找到唯一标识:__biz 方法一: step1: [图片] step2: 在新开的页面上,从地址栏中找到“__biz”,如下所示: https://mp.weixin.qq.com/s?__biz=MzA3OTc4MTQ5NQ==&mid=yyyy&&idx=1&sn=111&chksm=222&token=333&lang=zh_CN#rd __biz后面值就是当前公众号的一个唯一标识,此处为MzA3OTc4MTQ5NQ== 也就是__biz后面的=之后、下一个&符号之前的这部分 [图片] [图片] step3: 找到biz的值,把biz的值加到模版地址后面,得到一个链接: https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzA3OTc4MTQ5NQ==#wechat_redirect step4: 把step3中拼好的链接通过微信给自己,然后在微信中点击此链接,就可以了。 说明:这个链接需要在微信中打开 方法二: https://developers.weixin.qq.com/community/develop/article/doc/0008a48410cf207994ef2c6125b413
01-09 - 400元迁移微信公众号留言功能,迁移全过程
背景:2021年5月27日晚8点30分开始策划迁移公众号,迁移的目的为了是为了【留言】功能,因为从2018年开始注册的公众号都没有留言功能。 [图片] 为了不浪费朋友时间,我直接把他的管理员转移给我微信小号了,方便我来回各种扫码。进入主题,开始迁移公众号,先看官方流程图: [图片] 迁移流程总耗时5天(可以缩短到3天) 开始发起迁移: [图片] 管理员(我的微信小号)扫码验证后出现迁移协议,就是下面这个图 [图片] 输入目标账号原始ID(本公众号的原始ID)发送验证后,在目标账号管理员微信端(我的微信大号)允许即可 [图片] 同意后下一步下载迁移公函 [图片] 下载下来我们需要填写迁移双方基本信息,迁移公函需要双方盖章 [图片] 继续准备资料办理公证书:原公众号营业执照、原公众号后台截图、原法人身份证正反面、目标公众号营业执照、目标公众号后台截图、目标法人身份证正反面,公众号后台截图。 [图片] 准备好后开始办理公函公证书,可以在网上办理某宝1-2百左右。线下的话地图搜索:公证处,提前打电话问问能不能做公众号迁移公函公证书,问好了再去,大约3-5百左右。(下面是我的公证书) [图片] 然后我们上传这些资料去提交即可,最后支付300元。(如果资料传错了,有两次免费修改的机会,两次都错了那继续交300) [图片] 然后我们等着就好了,一般情况2、3天(工作日)就搞定了,期间会打电话确认,然后双方公众号管理员同意迁移,约1个工作日迁移成功。 [图片] 至此公众号迁移功能已完成,整理一下我准备的资料。 [图片] 最后做个timeline 2021年5月27日:和朋友沟通拿号 2021年5月28日:把朋友公众号管理员签到我的微信 2021年5月29日:开始准备迁移所有资料 2021年5月30日:TB线上办理公函公证书100,上传后支付300迁移费 2021年5月31日:迁移电话确认,双方管理员同意,迁移成功
2021-06-01 - 手机号授权处罚永久,并扣除运营分0分。申诉2次都没有通过,还有办法吗?
违规信息 [图片] 官方截图 违规1 存在违规收集用户手机号的行为 2023-5-23 [图片] 违规2 存在违规收集用户手机号的行为 2023-6-8 [图片] 目前小程序内获取手机号提示: 由于小程序违规,该功能暂时无法使用 [图片] 申诉 [图片] 申诉了都没有通过,已经永久封禁了吗? 请问各位大佬们还有解决办法吗?? 在线等!急急急!!
2023-10-31 - “小程序直播”接入指引
各位微信开发者: “小程序直播” 功能正在公测中,具体接入指引请参考《小程序直播产品介绍及操作指引》。 一、功能简介 小程序直播是微信官方在2020年2月公测推出的产品能力,帮助商家在自有小程序中实现直播互动与商品销售的闭环。 [图片] 二、商家准入要求 满足以下条件,即可开通小程序直播: ①属于小程序直播开放类目,具体见《微信小程序直播功能准入要求》 ②主体下小程序近半年没有严重违规; ③小程序近90天内,有过支付行为; 三、具体产品功能及操作指引 具体接入指引请参考《小程序直播产品介绍及操作指引》,以下为商家操作步骤: 1. 开通权限 1) 登录“小程序后台”(mp.weixin.qq.com),在左侧导航栏找到“小程序—功能—直播”,点击开通。 小程序直播需要基于小程序,如若开发者还未创建小程序,可按照《小程序接入指南》流程指引创建小程序并完成开发。 2) 符合上述开放范围的即可开通。 2.功能开发 小程序直播需要实现【直播组件】与【后台配置】两个部分,其中组件部分需要在小程序中进行配置开发。 具体开发文档,请参考《小程序直播开发文档》。 2.直播间配置 开发完成后,商家可通过小程序后台设置直播计划、开通、设置抽奖等操作。具体操作指引,请参考《小程序直播产品介绍及操作指引》。
2020-08-26 - 小程序地图个性化样式组件要收费了!
地图个性化样式组件 自2023年6月29日0点起,该能力需要先购买再使用。若未购买,届时将无法使用该能力。具体购买方式见付费管理。 自2023年6月29日0时起,个性化地图配置界面的入口统一为微信公众平台-付费管理,请从此入口进入,腾讯位置服务官网入口不再使用。已经在小程序生效的个性化样式配置,将于2023年6月29日0时变更为默认样式,如有个性化样式配置需求,请于6月29日0时前,前往微信公众平台-付费管理进行相关能力的开通和配置。 [图片] 地图个性化样式组件是腾讯位置服务为开发者提供的地图高级能力,开发者可以在法律允许的范围内,定制背景面、背景线、道路、POI等多种地图元素,灵活地设计心仪的地图样式。 购买该能力后,您可以在MP平台「管理->付费管理->概览->地图个性化样式->去使用」中创建配置您的地图个性化样式,您可以选择我们提供的基础及高级模版,也可以通过在线编辑平台,对多种地图元素的样式进行自定义设置,以满足在不同场景下的个性化需求。 微信公众平台-付费管理 [图片]
2023-07-17 - 公众号运营:如何文章内跳转公众号主页(不是历史文章页面)
近期社区中运营公众号的朋友咨询,如何文章内跳转到公众号主页 [图片] 不是历史文章页面 [图片] 这点是可以通过文章内的图片实现的。 接下来教大家如何实现图片跳转公众号主页: 1、首先获取历史文章链接 打开该公众号任意一篇内容,复制链接,在浏览器打开 [图片] 右击鼠标查看源代码 [图片] 快捷键ctrl+F开启全局搜索“ __biz”然后把该公众号卡版ID复制,记得后面两个“==”号必须都复制上。 [图片] 2、这个功能要通过SVG排版来实现。官方编辑器是不支持的,所以在这里我们使用某排版中的SVG编辑器。 [图片] 打开某排版SVG编辑器后,在组件这里搜索公众号或10214 [图片] 找到公众号——图片(免费的) 组件 [图片] 然后添加图片,粘贴公众号卡片ID [图片] 设置好后,先保存,后同步到微信后台 [图片] 这时候就可以去公众号预览了。 当然要注意的是,这种SVG排版同步到公众号后是不能修改的。所以建议在某排版编辑好所有内容之后,再同步到微信公众号后台。 而且这种排版可以跳转任何公众号主页。 还有一点这种跳转没有任何提示,直接跳到公众号主页,大家快去试试吧。 当然,还有朋友不知道如何跳转历史文章,可以参考:公众号运营:公众号菜单如何跳转历史文章? https://developers.weixin.qq.com/community/develop/article/doc/000e2e5dca09589f9f6fc985456013 我是立十,非官方人员💍公众号💍运营资深忠实粉丝,专注回答社区中关于公众号的问题。
2023-06-21 - 微信:小程序获取手机号要开始收费了!
[图片] 收费说明 自2023年8月26日起,手机号实时验证组件将需要[代码]付费使用[代码]。 [图片] 手机号快速验证组件 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html 新版本组件不再需要提前调用wx.login进行登录。 代码示例 [代码]<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button> Page({ getPhoneNumber (e) { console.log(e.detail.code) } }) [代码] 返回参数说明 code,动态令牌。可通过动态令牌换取用户手机号。使用方法详情phonenumber.getPhoneNumber接口。 请注意: 目前该接口针对非个人开发者,且完成了认证的小程序开放(不包含海外主体); 该能力使用时,用户可选择绑定号码,或自主添加号码。平台会基于中国三大运营商提供的短信等底层能力对号码进行验证,但不保证是实时验证; 请开发者根据业务场景需要自行判断并选择是否使用,必要时可考虑增加其他安全验证手段。 开发者需合理使用,若被发现或用户举报开发者不合理地要求用户提供手机号等个人信息,中断了正常的使用流程,影响了用户的使用体验,微信有权依据《微信小程序平台运营管理规范》对该小程序进行处理。常见违规事例和具体解析; 自2023年8月26日起,手机号快速验证组件将需要付费使用。标准单价为:每次组件调用成功,收费0.03元。 手机号快速验证组件(旧版):https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/deprecatedGetPhoneNumber.html 注意 目前该接口针对非个人开发者,且完成了认证的小程序开放(不包含海外主体)。需谨慎使用,若用户举报较多或被发现在不必要场景下使用,微信有权永久回收该小程序的该接口权限。 该能力使用时,用户可选择绑定号码,或自主添加号码。平台会对号码进行验证,但不保证是实时验证; 请开发者根据业务场景需要自行判断并选择是否使用,必要时可考虑增加其他安全验证手段。 手机号实时验证组件 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getRealtimePhoneNumber.html 代码示例 [代码]<button open-type="getRealtimePhoneNumber" bindgetphonenumber="bindgetrealtimephonenumber"></button> Page({ getRealtimePhoneNumber (e) { console.log(e.detail.code) } }) [代码] 该能力与手机号快速验证组件的区别为: 手机号实时验证组件,在每次请求时,平台均会对用户选择的手机号进行实时验证; 手机号快速验证组件,平台会对号码进行验证,但不保证是实时验证。 请注意: 3. 目前该接口针对非个人开发者,且完成了认证的小程序开放(不包含海外主体); 4. 该能力使用时,用户可选择绑定号码,或自主添加号码。每次请求时,平台均会基于中国三大运营商提供的短信等底层能力对号码进行实时验证; 5. 请开发者根据业务场景需要自行判断并选择是否使用,必要时可考虑增加其他安全验证手段。 6. 开发者需合理使用,若用户举报或被发现开发者不合理地要求用户提供手机号等个人信息,中断了正常的使用流程,影响了用户的使用体验,微信有权依据《微信小程序平台运营管理规范》对该小程序进行处理。常见违规事例和具体解析; 7. 该能力的bindgetrealtimephonenumber 事件回调中,仅会返回 code,不会返回 encryptedData,开发者仅可通过消费 code的方式换取用户手机号; 自2023年8月26日起,手机号实时验证组件将需要付费使用。标准单价为:每次组件调用成功,收费0.04元。 付费管理 https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/paymentManage.html 修改建议 手机号快速验证组件旧版本接口依然可以使用,只是需要收费而已,不想做改动的交钱就行了。如果想用新接口就去改吧,多花一分钱(增强小程序安全性)。
2023-06-30 - 【集合】花了 3 个月,写了 40 篇小程序文章
前言 花了3个月,一共输出 40 篇文章,这也算是一个阶段性的总结。在此做个文章分类集合,希望对大家有所帮助。 小程序前端 《专治按钮效果不明显(扩散动画效果)》 《小程序开发必备,这 5 款超实用开源插件!》 《仿抽奖助手奖品详情页面向上翻页效果》 《推荐 5 款高仿知名应用的开源项目!》 《生成海报很复杂?有它轻松搞定!》 《推荐一个自定义导航栏开源库》 《前端开发,必备的学习网站!》 《情侣券-领取动画分析》 《通过玩游戏来学习CSS》 《CSS不规范导致的布局显示问题》 《微信小程序如何引入npm包?》 《情侣券-选中卡片翻转动画》 《CSS:实现卡片洗牌效果》 《情侣券 v2.0 使用的 4 款开源组件》 小程序云开发 《使用聚合函数实现打卡排行榜》 《使用云开发做内容安全检查》 《云开发-实现分页功能》 《云开发-实现维护用户表》 《云开发-实现模糊搜索》 《云开发实战:实现订阅消息推送》 《如何优雅的调用云函数?》 《云开发实战-如何维护用户表?(优化版)》 《推荐 10 款使用云开发的开源项目》 《云开发:CloudBase CMS 实战使用指南》 小程序产品 《如何利用小程序提高10倍活动效果?》 《实战:让数据说话之自定义埋点分析》 《#小程序云开发挑战赛#-情侣券》 《小程序运营必备的 3 款官方小程序》 《小程序云开发挑战赛:情侣券 v1.1 版本迭代》 《云开发挑战赛复赛:情侣券介绍PPT》 《参加#小程序云开发挑战赛#复赛收获》 《云开发挑战赛决赛:情侣券介绍PPT》 通用知识 《如何重构?》 《如何高效学习?》 《如何看懂时序图?》 《为什么优秀的程序员都写博客?》 《我从 Android 转到 微信小程序 的思考》 最后 后续计划会写更多云开发相关的文章以及小程序基础系列学习文章。
2020-11-24 - 新年模板小程序个人快速体验
开端:在朋友圈看到有个小姐姐说动动手指,轻松制作了自己的拜年小程序。[图片] 2,打开了这位小姐姐说的文章,看到了是微信开发者工具的新年模板https://mp.weixin.qq.com/s/1GPSip5SKkRJRInynj2agQ [图片] 3,尝试自己做一个,创建小程序时,可以选择微信云开发或不使用,都会显示新年模板的。[图片] 4,打开后是现成的新年祝福小程序,可以改一下自己微信头像,改一下里面的微信昵称以及祝福语。[图片] 5,直接上传提审,然后发布。[图片] [图片] [图片] [图片] [图片] 结尾:感觉还可以添加更多设置,比如显示打开的人微信头像,然后他可以用自己的名字和头像转发给亲友。感谢阅读!
2022-01-28 - 前端XR
[图片]
2023-06-28 - 别在@微信官方了,5分钟10行代码教你制作带红旗的微信头像,多种样式随便选(含视频和源码)
昨天发了微信头像挂红旗的讲解文章,应大家要求,今天录制一套视频,免费送给大家。 我们先来看下效果图 [图片] [图片] [图片] 原理讲解 其实原理很简单,我们用canvas把自己的头像画在最下面,然后把对应的相框模版画到我们头像上面。然后使用小程序自带的api把我们画出来的图片,保存成本地图片文件,然后把这个图片作为我们的微信头像就可以了。 [图片] 免费视频链接 https://www.ixigua.com/i6740973288706540040/ B站免费视频链接 https://www.bilibili.com/video/av69074127/ 腾讯视频免费视频链接 https://v.qq.com/x/page/f3001t402da.html
2019-09-27 - ELSE IF
[图片]
2023-06-28 - ELSE前端
ELSE前端个人主页
2022-10-22 - 微信云开发管理工具入门教程
前言 微信云开发管理工具是是什么? 提供了一套云开发的后台管理工具,并且提供低代码开发工具,开发者可基于低代码工具,连接到业务数据库,拖拽组件生成前端UI,从而定制各类管理端应用。 在这里肯定有同学会问它与 云开发内容管理CMS 有什么区别? 可以理解为更加灵活,可随意定制的内容管理,结合了微搭使用起来更简单更灵活。 如果还不知道微搭的同学可以看下我之前写过的低代码平台微搭入门教程 体验 目前微信云开发管理工具还在内测当中,如需申请内测权限,戳这里申请入口 开通主页 当我们开通权限成功后 下载/更新最新版微信开发者工具 进入云开发 IDE 控制台 [图片] 选择「更多」中的「管理工具」 [图片] 打开后会提示是否打开微搭低代码插件提示,选择「允许」 [图片] 模板体验 从这一步开始就已经正式进入管理工具了,首先可以看到的就是模板页面,目前已经内置了常用的模板,需要那个模板点击「查看/安装工具」即可。 这让我感觉这就像手机系统上的App市场,需要什么就安装什么,只要模板足够多那么开发者使用起来能提升不少效率,开发成本可以得到极大的提升。如果这个模板市场可以支持开发者接入发布,类似 App 开发者自由发布 App 市场一样,还可以做付费模板,那么想象空间还是很大的。 [图片] 我先选择一个轮播图管理测试下效果。 [图片] 提示:首次加载会比较慢,需要耐心等待下 安装成功后,可以获得管理后台地址和管理员账号密码 [图片] 复制链接输入账号密码即可进入后台管理 [图片] 管理后台有个简单的 banner 管理后台案例数据 菜单分别为:轮播图管理、轮播图图片管理 [图片] 如果需要自定义轮播图需要现在图片管理上传图片 [图片] 然后再到轮播图管理添加 [图片] 那么小程序如何获取数据呢?我们可以回到云开发 IDE 控制台看到数据库多了一张表 cloudbase-sample-banner 里面有三条数据 [图片] 小程序获取数据代码 [代码]wx.cloud .database() .collection("cloudbase-sample-banner") .where({ status: "online", }) .get({ success: (res) => { this.setData({ banner: res.data, }); }, }); [代码] 编辑模版 那么如果轮播图模版无法满足我们的需求怎么办? 如:轮播图需要点击可以跳转显示公众号文章,这个时候需要加文章路径字段 基于以上需求我们来修改一下,首先回到管理工具首页 [图片] 点击轮播图模版查看详情,选择最下方的「编辑工具」 [图片] 在这里可以对管理页面进行「页面设计」 [图片] 所有页面的数据的显示当然离不开数据,第二个菜单就是「数据源」 [图片] 剩下两个菜单分别是:素材、应用设置,这两个菜单相对比较简单就不做过多介绍。 新增字段需要在「数据源」中找到「添加轮播图」然后点击「编辑」 [图片] 添加一个入参,path 参数就代表文章路径(编辑轮播图信息操作类似) [图片] 然后还要修改代码,在获取参数和添加参数的地方加上 path 这个属性 [图片] 最后点击「方法测试」添加一条数据看下效果 [图片] 可以通过后台管理页面或云开发 IDE 数据库看到数据已经添加成功 [图片] [图片] 添加方法已经改造完成,接下来就是修改查询方法,选择「查询轮播图列表」然后在「出参」进行添加子集,因为查询数据是多条所以是一个数组,我们要查询显示的是数据里面的对象。(查询轮播图信息操作类似,区别是添加入参而不是子集) [图片] 添加是什么字段显示就是什么样的字段 [图片] 数据源部分搞定了! 接下来就是修改后台管理页面「添加」和「查询」,切换到「页面设计」菜单 点击「添加轮播图」可以看到它的布局结构一个表单容器里面装了很多组件 [图片] 我们文章路径需要输入,那么可以从上方拖拽一个单行输入组件到布局里面来 [图片] 然后修改下显示标题和绑定字段 [图片] 当我们编辑完成后可以点击右上角「预览」然后点击「实时预览」 [图片] 这样就可以单独打开 一个窗口进行功能测试,添加修改完成后我们再来修改「列表显示」,选中「数据表格」组件在「列管理」添加 path 路径 [图片] 列表显示效果 [图片] 修改完成需要点击右上角「发布」即可同步线上版本后台。 自定义模版 还有一种情况就是目前的模版无法满足业务需求,比如下面这个「云数据库管理」模版,为了通用只能显示json,查询也没办法模糊查询,那么这个时候就需要自定义。 [图片] 接下来我们自己做个活动列表显示,然后再做个模糊查询,这个需求可以说是最常用的操作了。 接入数据 我们基于「云数据库管理」模版新增一个查询活动列表页面,首先切换到「数据源」点击+号选择「自定义代码」 [图片] 输入名称和标识点击创建 [图片] 添加方法 [图片] 查询代码,其他操作详细可见 cloudbase node sdk 文档 [代码]const cloudbase = require("@cloudbase/node-sdk"); const envId = "<云开发环境ID>"; const collectionName = "<云数据库集合名>"; const app = cloudbase.init({ env: envId, }); const db = app.database(); module.exports = async (event, context) => { let { pageNo, pageSize } = event; if (pageNo < 1) pageNo = 1; // 查询条件先固定为空,即查询集合内的所有数据 const query = db.collection(collectionName).where({}); const recordsRes = await query .skip((pageNo - 1) * pageSize) .limit(pageSize) .get(); const totalRes = await query.count(); return { records: recordsRes.data, total: totalRes.total, }; }; [代码] 入参配置:pageSize、pageNo 出参配置:使用「方法测试」运行测试后再使用出参数映射即可 [图片] 数据显示 切换到「页面设计」点击右上角+号 [图片] 新增空白页面 [图片] 拖拽一个数据表格组件到布局中 [图片] 设置数据表格数据来源 [图片] 列表中数据就显示出来了,但是我们会发现其中时间字段显示重复了,以及表头是字段名,使用者不一定能看懂。 [图片] 我们可以在属性中列管理对不需要的字段进行删除 [图片] 还可以修改属性标题,效果如下: [图片] 建议 1.模版需要更丰富,满足更多开发者场景 2.自带模版业务思考不够全面,过于简单无法直接 3.自定义模板操作过于复杂,需要简化,如:自动生成基础增删查改代码 相关教程 官方云开发管理工具教程 云开发Node.js SDK API 微搭组件列表
2023-06-14 - 2023前端面试上岸手册——JavaScript
JavaScript 有哪些数据类型,它们的区别? JavaScript 共有八种数据类型,分别是 Undefined、Null、Boolean、 Number、String、Object、Symbol、BigInt。 其中 Symbol 和 BigInt 是ES6 中新增的数据类型: Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。 BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。 这些数据可以分为原始数据类型和引用数据类型: 栈:原始数据类型(Undefined、Null、Boolean、Number、String) 堆:引用数据类型(对象、数组和函数)两种类型的区别在于存储位置的不同: 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储; 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中: 在数据结构中,栈中数据的存取方式为先进后出。 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。 在操作系统中,内存被分为栈区和堆区: 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。 数据类型检测的方式有哪些 typeof [图片] 其中数组、对象、null 都会被判断为object,其他判断都正确。 instanceof instanceof 可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。 [图片] 3. constructor constructor 有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor 就不能用来判断数据类型了: Object.prototype.toString.call() Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型: [图片] toString 方法(function 类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用 Object 上原型 toString 方法(返回对象的具体类型),所以采用 obj.toString()不能得到其对象类型,只能将 obj 转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object 原型上的toString 方法。 null 和undefined 区别 首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。 undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null 主要用于赋值给一些可能会返回对象的变量,作为初始化。 undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 如何获取安全的 undefined 值? 因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。 Object.is() 与比较操作符 “两等” 、“三等” 的区别? 使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。 使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。 使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN是相等的。 什么是 JavaScript 中的包装类型? 在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台隐式地将基本类型的值转换为对象,如: [图片] 在访问’abc’.length 时, JavaScript 将’abc’ 在后台转换成 String(‘abc’),然后再访问其length 属性。 JavaScript 也可以使用Object 函数显式地将基本类型转换为包装类型: [图片] 也可以使用valueOf 方法将包装类型倒转成基本类型: [图片] 看看如下代码会打印出什么: [图片] 答案是什么都不会打印,因为虽然包裹的基本类型是 false,但是 false 被包裹成包装类型后就成了对象,所以其非值为 false,所以循环体中的内容不会运行。 为什么会有 BigInt 的提案? JavaScript 中 Number.MAX_SAFE_INTEGER 表示最⼤安全数字,计算结果是 9007199254740991,即在这个数范围内不会出现精度丢失(⼩数除外)。但是⼀旦超过这个范围,js 就会出现计算不准确的情况,这在⼤数计算的时候不得不依靠⼀些第三⽅库进⾏解决,因此官⽅提出了BigInt 来解决此问题。 如何判断一个对象是空对象 使用JSON 自带的.stringify 方法来判断: [图片] 使用ES6 新增的方法Object.keys()来判断: [图片] const 对象的属性可以修改吗 const 保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。 但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const 只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。 如果 new 一个箭头函数的会怎么样 箭头函数是ES6 中的提出来的,它没有prototype,也没有自己的this 指向,更不可以使用arguments 参数,所以不能New 一个箭头函数。 new 操作符的实现步骤如下: 创建一个对象 将构造函数的作用域赋给新对象(也就是将对象的 proto 属性指向构造函数的prototype 属性) 指向构造函数中的代码,构造函数中的 this 指向该对象(也就是为这个对象添加属性和方法) 返回新的对象 所以,上面的第二、三步,箭头函数都是没有办法执行的。 箭头函数的this 指向哪⾥? 箭头函数不同于传统JavaScript 中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this 是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的 this,所以是不会被 new调⽤的,这个所谓的this 也不会被改变。 可以⽤Babel 理解⼀下箭头函数: [图片] 转化后: [图片] 扩展运算符的作用及使用场景 对象扩展运算符 对象的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。 [图片] 上述方法实际上等价于: [图片] Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.assign 方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。 同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。 [图片] 利用上述特性就可以很方便的修改对象的部分属性。在 redux 中的 reducer 函数规定必须是一个纯函数,reducer 中的 state 对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。 需要注意:扩展运算符对对象实例的拷贝属于浅拷贝。 数组扩展运算符 数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。 [代码]console.log(...[1, 2, 3]) // 1 2 3 console.log(...[1, [2, 3, 4], 5]) //1 [2, 3, 4] 5 [代码] 下面是数组的扩展运算符的应用: 将数组转换为参数序列 [图片] 复制数组 [图片] [代码]要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。[代码] 合并数组 如果想在数组内合并数组,可以这样: [图片] 扩展运算符与解构赋值结合起来,用于生成数组 [代码]const [first,.rest],5]; first // 1 rest // [2, 3, 4, 5] [代码] 需要注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一 位,否则会报错。 [代码]const [...rest, last] = [1, 2, 3, 4, 5];// 报错 const [first, ..rest, last]=[1, 2, 3, 4, 5]; // 报错 [代码] 将字符串转为真正的数组 [代码][...'hello'] //[ "h", "e", "l", "l", "o" ] [代码] 任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组 比较常见的应用是可以将某些数据结构转为数组: [图片] 用于替换es5 中的Array.prototype.slice.call(arguments)写法。 使用Math 函数获取数组中特定的值 [图片] Proxy 可以实现什么功能? 在 Vue3.0 中通过 Proxy 来替换原本的 Object.defineProperty来实现数据响应式。 Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。 [图片] 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。 下面来通过 Proxy 来实现一个数据响应式: [图片] 在上述代码中,通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。 当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。 常用的正则表达式有哪些? [图片] 对JSON 的理解 JSON 是一种基于文本的轻量级的数据交换格式。它可以被任何的编程语言读取和作为数据格式来传递。 在项目开发中,使用 JSON 作为前后端数据交换的方式。在前端通过将一个符合 JSON 格式的数据结构序列化为JSON 字符串,然后将它传递到后端,后端通过 JSON 格式的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。 因为 JSON 的语法是基于 js 的,因此很容易将 JSON 和 js 中的对象弄混,但是应该注意的是 JSON 和 js 中的对象不是一回事,JSON中对象格式更加严格,比如说在 JSON 中属性值不能为函数,不能出现 NaN 这样的属性值等,因此大多数的 js 对象是不符合 JSON 对象的格式的。 在 js 中提供了两个函数来实现 js 数据结构和 JSON 格式的转换处理,JSON.stringify 函数,通过传入一个符合 JSON 格式的数据结构,将其转换为一个 JSON 字符串。如果传入的数据结构不符合 JSON 格式,那么在序列化的时候会对这些值进行对应的特殊处理,使其符合规范。在前端向后端发送数据时,可以调用这个函数将数据对象转化为 JSON 格式的字符串。 JSON.parse() 函数,这个函数用来将 JSON 格式的字符串转换为一个 js 数据结构,如果传入的字符串不是标准的 JSON 格式的字符串的话,将会抛出错误。当从后端接收到 JSON 格式的字符串时,可以通过这个方法来将其解析为一个 js 数据结构,以此来进行数据的访问。 JavaScript 脚本延迟加载的方式有哪些? 延迟加载就是等页面加载完成之后再加载 JavaScript 文件。js 延迟加载有助于提高页面加载速度。 一般有以下几种方式: defer 属性:给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。 async 属性:给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。 动态创建 DOM 方式:动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。 使用 setTimeout 延迟方法:设置一个定时器来延迟加载 js 脚本文件 让 JS 最后加载:将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。 什么是 DOM 和 BOM? DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。 BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM 的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器 窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页 中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方 法存在。window 对象含有 location 对象、navigator 对象、screen对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM的 window 对象的子对象。 escape、encodeURI、encodeURIComponent 的区别 encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行转义。 encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。 escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为 0xff 之外字符的时候会有区别,escape 是直接在字符的 unicode编码前加上 %u,而 encodeURI 首先会将字符转换为 UTF-8 的格式,再在每个字节前加上 %。 对AJAX 的理解,实现一个 AJAX 请求 AJAX 是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。 创建AJAX 请求的步骤: 创建一个 XMLHttpRequest 对象。 在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。 在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通 过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。 当对象的属性和监听函数设置完成后,最后调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。 [图片] 使用Promise 封装AJAX: [图片] 什么是尾调用,使用尾调用有什么好处? 尾调用指的是函数的最后一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。 ES6 模块与 CommonJS 模块有什么异同? ES6 Module 和CommonJS 模块的区别: CommonJS 是对模块的浅拷⻉,ES6 Module 是对模块的引⽤,即 ES6 Module 只存只读,不能改变其值,也就是指针指向不能变,类似 const; import 的接⼝是 read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对 commonJS 对重新赋值(改变指针指向),但是对ES6 Module 赋值会编译报错。 ES6 Module 和CommonJS 模块的共同点: CommonJS 和ES6 Module 都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变。 for…in 和for…of 的区别 for…of 是 ES6 新增的遍历方式,允许遍历一个含有 iterator 接口 的数据结构(数组、对象等)并且返回各项的值,和ES3 中的for… in 的区别如下 for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名; for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链; 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值; [代码]总结:for...in 循环主要是为了遍历对象而生,不适用于遍历数组; for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。[代码] ajax、axios、fetch 的区别 AJAX Ajax 即“AsynchronousJavascriptAndXML”(异步 JavaScript 和 XML),是指一种创建交互式[代码]网页[代码]应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。其缺点如下: 本身是针对MVC 编程,不符合前端MVVM 的浪潮 基于原生XHR 开发,XHR 本身的架构不清晰 不符合关注分离(Separation of Concerns)的原则 配置和调用方式非常混乱,而且基于事件的异步模型不友好。 Fetch fetch 号称是 AJAX 的替代品,是在 ES6 出现的,使用了 ES6 中的 promise 对象。Fetch 是基于 promise 设计的。Fetch 的代码结构比起ajax 简单多。fetch 不是ajax 的进一步封装,而是原生 js,没有使用XMLHttpRequest 对象。 fetch 的优点: 语法简洁,更加语义化 基于标准 Promise 实现,支持 async/await 更加底层,提供的API 丰富(request, response) 脱离了XHR,是ES 规范里新的实现方式 fetch 的缺点: fetch 只对网络请求报错,对 400,500 都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。 fetch 默认不会带 cookie , 需要添加配置项: fetch(url,{credentials: ‘include’}) fetch 不支持 abort , 不支持超时控制, 使用 setTimeout 及 Promise.reject 的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费 fetch 没有办法原生监测请求的进度,而XHR 可以 Axios Axios 是一种基于Promise 封装的HTTP 客户端,其特点如下: 浏览器端发起XMLHttpRequests 请求 node 端发起 http 请求 支持Promise API 监听请求和返回 对请求和返回进行转化 取消请求 自动转换json 数据 客户端支持抵御XSRF 攻击 对原型、原型链的理解 在JavaScript 中是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。一般来说不应该能够获取到这个值的,但是现在浏览器中都实现了_proto_ 属性来访问这个属性,但是最好不要使用这个属性, 因为它不是规范中规定的。ES5 中新增了一个 Object.getPrototypeOf() 方法,可以通过这个方法来获取对象的原型。 当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是 Object.prototype 所以这就是新建的对象为什么能够使用 toString() 等方法的原因。 特点:JavaScript 对象是通过引用来传递的,创建的每个新对象实体中并没有一份属于自己的原型副本。当修改原型时,与之相关的对象也会继承这一改变。 由于Object 是构造函数,原型链终点 Object.prototype. proto ,而Object.prototype. proto === null // true,所以,原型链的终点是null。原型链上的所有原型都是对象,所有的对象最终都 是由Object 构造的,而Object.prototype 的下一级是 Object.prototype. proto 。 [图片] 对作用域、作用域链的理解 全局作用域和函数作用域 全局作用域 最外层函数和最外层函数外面定义的变量拥有全局作用域 所有未定义直接赋值的变量自动声明为全局作用域 所有window 对象的属性拥有全局作用域 全局作用域有很大的弊端,过多的全局作用域变量会污染全局命名空间,容易引起命名冲突。 函数作用域 函数作用域声明在函数内部的变零,一般只有固定的代码片段可以访问到 作用域是分层的,内层作用域可以访问外层作用域,反之不行 块级作用域 使用ES6 中新增的let 和const 指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中的创建(由{ }包裹的代码片段) let 和const 声明的变量不会有变量提升,也不可以重复声明 在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部。 作用域链: 在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到 window 对象就被终止,这一层层的关系就是作用域链。 作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。 作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。 当查找一个变量时,如果当前执行环境中没有找到,可以沿着作用域链向后查找。 对this 对象的理解 this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。 第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。 第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时, this 指向这个对象。 第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。 第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显 示的指定调用函数的 this 指向。其中 apply 方法接收两个参数: 一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举 出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对 象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其 他情况下都不会改变。 这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。 call() 和 apply() 的区别? 它们的作用一模一样,区别仅在于传入参数的形式的不同。 apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类 数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。 call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数。 异步编程的实现方式? JavaScript 中的异步机制可以分为以下几种: 回调函数 的方式,使用回调函数的方式有一个缺点是,多个回调函数嵌套的时候会造成回调函数地狱,上下两层的回调函数间的代码耦合度太高,不利于代码的可维护。 Promise 的方式,使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确。 generator 的方式,它可以在函数的执行过程中,将函数的执行权转移出去,在函数外部还可以将执行权转移回来。当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权给转移回来。因此在 generator 内部对于异步操作的方式,可以以同步的顺序来书写。使用这种方式需要考虑的问题是何时将函数的控制权转移回来,因此需要有一个自动执行 generator 的机制,比如说 co 模块等方式来实现 generator 的自动执行。 async 函数 的方式,async 函数是 generator 和 promise 实现的一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。 对Promise 的理解 Promise 是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。 Promise 的实例有三个状态: Pending(进行中) Resolved(已完成) Rejected(已拒绝) 当把一件事情交给promise 时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。 Promise 的实例有两个过程: pending -> fulfilled : Resolved(已完成) pending -> rejected:Rejected(已拒绝) [代码]注意:一旦从进行状态变成为其他状态就永远不能更改状态了。[代码] Promise 的特点: 对象的状态不受外界影响。promise 对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是 promise 这个名字的由来——“期约”; 一旦状态改变就不会再变,任何时候都可以得到这个结果。promise对象的状态改变,只有两种可能:从 pending 变为 fulfilled,从 pending 变为 rejected。这时就称为 resolved(已定型)。如果改变已经发生了,你再对 promise 对象添加回调函数,也会立即得到这个结果。这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。 Promise 的缺点: 无法取消Promise,一旦新建它就会立即执行,无法中途取消。 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。 当处于pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 总结: Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态, 分别是 pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。 状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。 注意:在构造 Promise 的时候,构造函数内部的代码是立即执行的 Promise 解决了什么问题 在工作中经常会碰到这样一个需求,比如我使用ajax 发一个A 请求后,成功后拿到数据,需要把数据传给 B 请求;那么需要如下编写代码: [图片] 上面的代码有如下缺点: 后一个请求需要依赖于前一个请求成功后,将数据往下传递,会导致多个ajax 请求嵌套的情况,代码不够直观。 如果前后两个请求不需要传递参数的情况下,那么后一个请求也需要前一个请求成功后再执行下一步操作,这种情况下,那么也需要如上编写代码,导致代码不够直观。 Promise 出现之后,代码变成这样: [图片] 这样代码看起了就简洁了很多,解决了地狱回调的问题。 对async/await 的理解 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X3jHJs2R-1682168150270)(media/image8.png)]async/await 其实是 Generator 的语法糖,它能实现的效果都能用 then 链来实现,它是为优化then 链而开发出来的。从字面上来看, async 是“异步”的简写,await 则为等待,所以很好理解async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。当然语法上强制规定 await 只能出现在asnyc 函数中,先来看看async 函数返回了什么: [图片] [图片] 所以,async 函数返回的是一个 Promise 对象。async 函数(包含函数语句、函数表达式、Lambda 表达式)会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。 async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,当然应该用原来的方式:then() 链来处理这个 Promise 对象,就像这样: [图片] 那如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)。 联想一下 Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。 注意:Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的简写,可以用于快速封装字面量对象或其他对象,将其封装成 Promise 实例。 async/await 的优势 单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。 假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。仍然用 setTimeout 来模拟异步操作: [图片] 现在用 Promise 方式来实现这三个步骤的处理: [图片] 输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。 如果用 async/await 来实现呢,会是这样: [图片] 结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样 async/await 对比 Promise 的优势 代码读起来更加同步,Promise 虽然摆脱了回调地狱,但是 then 的链式调⽤也会带来额外的阅读负担 Promise 传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅 错误处理友好,async/await 可以⽤成熟的 try/catch,Promise 的错误捕获⾮常冗余 调试友好,Promise 的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then 代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then 代码块,因为调试器只能跟踪同步代码的每⼀步。 对象创建的方式有哪些? 一般使用字面量的形式直接创建对象,但是这种创建方式对于创建大量相似对象的时候,会产生大量的重复代码。但 js 和一般的面向对象的语言不同,在 ES6 之前它没有类的概念。但是可以使用函数来进行模拟,从而产生出可复用的对象创建方式,常见的有以下几种: 第一种是工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系。 第二种是构造函数模式。js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么就可以把它称为构造函数。执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数的 prototype 属性,然后将执行上下文中的 this 指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因为 this 的值指向了新建的对象,因此可以使用 this 给对象赋值。构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此可以通过原型来识别对象的类型。但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建,因为在 js 中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。 第三种模式是原型模式,因为每一个函数都有一个 prototype属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。 第四种模式是组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。 第五种模式是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。这一种方式很好地对上面的混合模式进行了封装。 第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,我对这个模式的理解是,它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函数,也达到了扩展对象的目的。它的一个缺点和工厂模式一样,无法实现对象的识别。 对象继承的方式有哪些? 第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。 第二种方式是使用借用构造函数的方式,这种方式是通过在子 类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不 能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函 数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。 第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。 第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。 第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是自定义类型时。缺点是没有办法实现函数的复用。 第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。 哪些情况会导致内存泄漏 以下四种情况会造成内存的泄漏: 意外的全局变量:由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。 被遗忘的计时器或回调函数:设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。 脱离 DOM 的引用:获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。 闭包:不合理的使用闭包,从而导致某些变量一直被留在内存当中。 下节更新vue
2023-06-09 - 微信:把元宇宙装进小程序
[图片] 作为月活13.09亿的国民级应用,微信的每次小升级都很容易形成现象级。2023开年,微信放大招,试图把元宇宙装进小程序。 不久前,微信官方在开放社区贴出了“XR-FRAME”开发指南,这是一套为小程序定制的XR(扩展现实)/3D应用解决方案。简单来说,该方案上线后将从底层赋予小程序扩展现实和3D能力,让未来小程序的人机交互方式由2D向更立体化的3D转变。 之前,XR-FRAME还处于测试阶段,根据官方发布的Demo看,该组件可以更好地呈现3D效果,并提供AR换脸、AR游戏等体验。据透露,微信率先将XR小程序的试水场景瞄准了电商领域,落地AR 试穿试戴、AR 家装等不同类型案例。 如今,框架 XR-FRAME 发布正式版,曾进行了一系列的更新,且一些功能还在开发中。 [代码]xr-frame[代码]在基础库[代码]v2.32.0[代码]开始基本稳定。 [图片] 限制: 最低要求客户端iOS8.0.29、安卓8.0.30及以上,推荐稳定版在iOS8.0.36、安卓8.0.35及以上。 基础库最低2.27.1及以上,推荐2.32.0及以上。 开发工具需要最新版本,建议Nightly版本。 小程序全局同一时刻只能存在一个[代码]xr-frame[代码]组件,否则可能会发生异常。 同一个[代码]xr-frame[代码]组件只能存在一个[代码]xr-scene[代码],并且必须为顶层。 目前不支持和小程序传统标签比如[代码]<view>[代码]混写。 目前不支持[代码]wxml[代码]自动补全,真机调试需要特别注意,见真机调试文档。 同时未来还会追加更多的能力,在未来的规划中,我们还会着重致力于: XR-FRAME内置特色的UI组件,让开发者可以在XR-FRAME组件中写UI,来实现一套酷炫的UI系统。 AR/VR能力持续增强,支持眼睛设备。 交互手段进一步强化,物理碰撞、触发等功能(已完成,待发布)。 工具能力强化,包括标签属性自动补全等。 从战略角度,把元宇宙装进微信小程序,腾讯居安思危。经济学家朱嘉明预测,未来人们社交的基本形态将在元宇宙中进行,倘若这一切真的发生,微信等传统的社交模式将被颠覆。打败微信的可能并非“抖音”社交,而是元宇宙应用。 小程序要3D化 还记得在微信风靡一时的小游戏“跳一跳”吗?玩家通过长按屏幕让小人蓄力跳跃到前方的盒子上得分,小人不慎掉落则游戏结束。这款游戏在2017年12月登陆微信小程序,引来了无数玩家比拼较量。几个月前,消除闯关游戏“羊了个羊”也以微信小程序为载体火爆全网。 自2017年1月微信小程序上线以来,多款现象级应用在其中诞生。依托庞大的微信用户群,小程序为开发者们提供了一片新的创作土壤。6年来,小程序的平台能力和底层框架也在不断升级。2023年刚开年,小程序曝出大动作——正在内测XR框架。 XR即扩展现实,是VR(虚拟现实)、AR(增强现实)、MR(混合现实)的合称,可为受众带来真实与虚拟结合、人机交互的环境。不久前,微信官方在微信开放社区贴出了“XR-FRAME”开发指南,根据描述,这是一套小程序官方提供的XR/3D应用解决方案,基于混合方案实现,性能逼近原生、效果好、易用、强扩展、渐进式、遵循小程序开发标准。 现在这一底层框架还处于测试阶段,小程序官方在开放社区贴出了详细的教程,指导开发者们如何从头构建一个XR小程序。 比起当前小程序采用的Canvas(画布)组件,xr-frame带来了更多能力。据介绍,其提供xml(可扩展标记语言)的方式来描述3D场景,并集成了AR、物理、动画、粒子、后处理等等系统,上手简单。同时,内置完整的PBR(基于物理的渲染)效果、环境光照、阴影,可以快速通过全景图生成环境数据。此外还有渲染性能逼近原生、扩展性强等优势。 [图片] XR小程序开发效果示例 简单理解,xr-frame的上线将从底层赋予小程序扩展现实和3D能力,将让未来小程序的人机交互方式由2D向更立体化的3D转变。 根据官方发布的Demo来看,获得xr-frame支持后,虚拟3D 人、3D物、3D场景都可以在小程序里更好地呈现,此外AR换脸、扫描平面获得AR游戏、扫描特定图片获得AR交互等功能也可实现。这意味着,以后的微信小程序将有更多具有交互性的应用出现。 按照测试节奏,XR小程序功能很可能在中国农历新年后全量上线,业界普遍将其视为下一个微信大版本升级的核心功能。 让元宇宙发生在微信上? 微信内测XR小程序,恰处于元宇宙浪潮激荡之时。这个动作释放了一个信号,微信小程序将一定程度担起腾讯布局元宇宙的使命。 虽然诸如Meta、百度等互联网巨头都开发了元宇宙全景应用,但体验和人气都十分有限。目前与元宇宙相关的应用场景主要体现在人机交互游戏、沉浸式电商购物、虚拟人直播、虚拟会议等方面。没有自建元宇宙平台,腾讯试水元宇宙,选了个轻巧做法,借助微信流量入口,用小程序先接入虚拟现实场景。 据透露,目前微信率先将XR小程序的试水场景瞄准了电商,并与多个不同品类品牌小程序合作,落地 AR 试穿试戴、AR 家装等不同类型案例。 现阶段,XR技术最普遍应用于电商领域。在传统的电商购物体验中,消费者只能依赖商家展示的图片/视频进行购物决策,最终购买后往往会出现买家秀与卖家秀差距过大的情况。而在引入AR技术后,传统电商平台的商品展示模式逐渐被颠覆,尤其在服装试穿、珠宝穿戴、美妆试用等特定场景下,AR能够帮助用户做出更正确的决策。 尽管微信一直不是电商的主战场,但诸如NIKE、Adidas、香奈儿等消费品牌都上线了相关的微信小程序,当XR功能上线后,这些品牌可以更好地展示商品,或许会带动微信电商生态的增长。 另外,可以想象的是,未来微信小程序中也将诞生更多3D建模以及现实交互感更强的小游戏,也许,下一款爆款小游戏将是截然不同的3D形态。 事实上,作为微信生态重要的组成部分,小程序也进入了发展瓶颈。为了实现无需下载、用完即走的效果,微信小程序在性能上做出巨大的牺牲,同样的应用在微信小程序上的表现往往不如APP。这也导致,小程序没有如人们预想一样成为颠覆性的应用,相反,某些功能性较强的应用如果通过小程序使用,显得鸡肋。 XR功能的上线能否为小程序打开增长空间尚未可知。有开发者担忧地表示,xr-frame能否达到预期还需要谨慎乐观。毕竟小程序的设计初衷就是要求快速,这里的“快”指的是加载以及渲染,所以导致微信小程序一直是以webview渲染为主、原生渲染为辅的混合渲染方式,也使得小程序的开发主要是以前端技术为主。然而xr-frame的3D化开发则需要完全不同的技术栈,对于渲染的开销也成倍提升,这就要求开发者要有更为优秀的优化水平。此外,在优质3D建模动辄占用较大内存的情况下,小程序提供的 XR 体验是否会有折扣也要打个问号。 当然,微信作为月活13.09亿的“国民级应用”,流量优势显著,只要有优异的小程序出现,一定不缺用户。这也会吸引大量的品牌方、开发者在上面进行尝试,所以xr-frame的上线将会带动小程序开发生态的繁荣。 从战略角度而言,把元宇宙装进小程序也是腾讯在社交领域的进一步探索,海外的社交巨头Meta已经开始了。 经济学家、横琴数链数字金融研究院学术与技术委员会主席朱嘉明曾认为,Facebook之所以改名为Meta,是因为Facebook本来就是元宇宙,只不过在此前的时代,数字技术发展还有局限,社交体验只能以图文、视频的方式呈现。他预测,未来人们社交的基本形态将在元宇宙中进行,打破时空、地理界限,带来更沉浸式的体验。 倘若这一切真的发生,微信、QQ等传统的社交应用模式将被颠覆,打败微信的可能并非“抖音”这样的视频社交应用,而是元宇宙。 扫码体验 [图片] 你认为XR能否催生现象级小程序? 注:“现象级小程序”是指在短时间内突然爆红而被众所周知和使用
2023-06-13 - XR-FRAME上手须知那些细节
春节假期,终于有时间研究了小程序 XR-FRAME的情况。尽管现在XR-FRAME 还是Beta 版本,但可以说这是小程序进入元宇宙的一个关键节点(希望尽快转正式)。上手入门教程和说明都十分平易近人,基本就是手把手的带入门,这个的确要赞一下。不过呢,可能这文档都比较早,之后都没怎么更新了,我就说说一下我这边入门的关键点吧。 1、真机调试,入门教程文档里忘记提到一个关键点就是需要在APP.JSON建立一个分包。 代码如下: "subpackages": [{ "root": "packageA", "pages": ["pages/cat"] }], 没这分包真机调试怎么都不能起来会报错,但预览可以调用;之前没发现这个费了我不少时间进行测试和找原因。 2、看社区不少同学问调用前置摄像头,其实就加个在xr-scene加给参数camera:Front。 代码如下: 这样就能顺利通过xr-camera进行调用了。 3、xr-frame与小程序view的通信,其实我的理解就是通过原来的view再加载xr-frame的组件应用,所以他们间可以通过父子组件进行通信,这个我没有太深入研究,就用了this.triggerEvent()完事,万幸也成功了。。 最后,一个的确没法搞掂的事情就是,video-texture加载的MP4视频死活都没声音和只能播放一段,我个人觉得因为video-texture是一个简单的视频图像渲染,没有将声音带过来,所以就没有声音了。这个目前我也没深研了,不过这个是AR的一个关键点,望尽快处理吧。最后的最后,以上是本人实战研究的一下小经验,也给后来者一点提醒吧,不用走太多弯路。
2023-01-30 - 关于XR-Frame中clear-color颜色设置的说明
在使用XR-Frame时,可能经常会使用到 clear-color="0.4 0.6 0.7 1" 这样的颜色设置,如下: <xr-camera position="0 1 4" clear-color="0.4 0.6 0.7 1" background="skybox" target="target" camera-orbit-control="" /> 这个颜色代码具体是什么意思呢? 首先,前三个参数代表颜色代码,第四个参数代表透明度(0-1)。 其次,对于前三个参数来说,这不是RGB的颜色方法,因为RGB的颜色方法是表示红、绿和蓝三原色的相对亮度值。例如,(255,0,0) 表示的是红色,它的RGB颜色方法的值是(1,0,0),表示红色是由100%红色,0%绿色和0%蓝色组成。 而(0.1, 0.1, 0.1) 是RGB颜色模型中使用的RGB颜色空间值的归一化版本(从0到1)。它并没有实际的颜色含义,但它可以被用来表示非常暗的灰色。 运算方法: rgb颜色值 除以 255即可! 如:RGB(227,176,242) 227/255 = 0.89 176/255 = 0.69 242/255 = 0.94 (保留两位小数) 归一化处理后即 为(0.89 0.69 0.94) 保持不透明(0.89,0.69,0.94,1)
2023-03-24 - 如何实现加入购物车的抛物线效果
一、场景分析 在一些如商城、点餐小程序中实现购物车抛物线效果可以提升界面趣味性增加小程序用户体验。 二、效果预览 效果图压缩后速度有点快,请下载代码片段预览 [图片] 三、实现原理 当用户点击物品时记录当前触摸点,根据触摸点计算抛物线运动的顶点位置,通过触摸点、顶点、购物车的位置计算出抛物线运动轨迹,然后控制 icon 运动。 计算购物车在当前手机内的位置 [代码]/** 设置购物车的坐标位置 **/ wx.getSystemInfo({ success: (res) => { let busPos = {} // x y 坐标分别取屏幕百分之八十的位置 busPos['x'] = res.windowWidth * 0.8 busPos['y'] = res.windowHeight * 0.8 this.setData({ busPos }) } }) [代码] 商品点击事件的处理 点击物品后记录点击的位置,然后根据点击位置计算出抛物线的顶点位置,计算方式为点击位置的上方+150,右边+150(需要根据点击位置是否在购物左边还是右边进行判断)。 根据点击,顶点,购物车三个位置计算出抛物线运动轨迹 以3个控制点为例,点A、B、C、AB 上设置点 D、BC 上设置点 E、DE 连线上设置点 F,则最终的贝塞尔曲线是点F的坐标轨迹; 计算相邻控制点间距; 根据完成时间,计算每次执行时 D 在AB方向上移动的距离,E 在 BC 方向上移动的距离; 时间每递增 100ms,则 D、E 在指定方向上发生位移,F 在 DE 上的位移则可通过 AD/AB = DF/DE 得出; 根据 DE 的正余弦值和 DE 的值计算出F的坐标。 开启定时器,依次按照贝塞尔曲线位置做动画位移 使用定时器将抛物线运动轨迹做动画位移。 定时器执行完动画后将购物车角标+1 老规矩,结尾放代码片段 https://developers.weixin.qq.com/s/PnYfitmG7Hxv
2022-03-04 - 如何彻底解决小程序滚动穿透问题
背景 俗话说,产品有三宝:弹窗、浮层加引导,足以见弹窗在产品同学心目中的地位。对任意一个刚入门的前端同学来说,实现一个模态框基本都可以达到信手拈来的地步,但是,当模态框里边的内容滚动起来以后,就会出现各种各样的让人摸不着头脑的问题,其中,最出名的想必就是滚动穿透。 什么是滚动穿透? 滚动穿透的定义:指我们滑动顶层的弹窗,但效果上却滑动了底层的内容。 具体解决方案分析如下: 改变顶层:从穿透的思路考虑,如果顶层不会穿透过去,那么问题就解决了,所以我们尝试给蒙层加catchtouchmove,但是发现部分场景无效果,那么就不再赘述了。 改变底层:既然是顶层影响了底层,要是底层不会滚动,那就没这个问题了。 如何改变底层解决该问题呢? 不成熟方案: 底部页面最外层view设置position: fixed;页面不可滚动,但是这个时候会导致页面回到顶部。 滚动时监听滚动距离,弹窗时记录滚动位置,关闭弹窗后使用wx.pageScrollTo回滚到记录的位置。 成熟方案 使用page-meta组件,通过该组件我们可以操作Page的style样式,类似于h5里body设置overflow: hidden; 控制页面不可滚动。文档地址:https://developers.weixin.qq.com/miniprogram/dev/component/page-meta.html 使用wx.setPageStyle设置overflow: hidden, 也可以实现给Page组件设置样式。) page-meta组件: 通过该组件我们可以直接操作[代码]Page[代码]组件 ,我们给它的wxss样式overflow动态设置[代码]hidden[代码]or[代码]visible[代码]or[代码]auto[代码] 就可以控制整个页面是否可以滚动。 [图片] wx.setPageStyle方法: 调用这个api,动态设置它为hidden/auto,用于控制页面是否可滚动,主要用于页面组件内使用,比如封装好的弹窗组件,就不用单独写page-meta组件了。。 [代码]wx.setPageStyle({ style: { overflow: 'hidden' // ‘auto’ } }) [代码] 老规矩,结尾放代码片段: https://developers.weixin.qq.com/s/U6ItgQmP7upQ 拓展 支付宝小程序虽然存在page-meta组件,但是由于内核为69版本,给page设置overflow: hidden 也无法控制底部元素不可滚动,目前已联系支付宝的底层开发同学提供API控制页面disableScroll,目前正在封装Appx,近期开放。
08-06 - 小程序获取手机号,getPhoneNumber回调中获取不到code
[图片] [图片] 不是代码问题!微信开发工具切换基础库版本或者让用户手动更新微信版本即可解决! 1)确认当前基础库版本是否为 2212及以上。 2)目前开发者工具稳定版未兼容此表现 (近期应该会同步)。 3)正常情况下,基础库2.212对应的是客户端版本号为 8.0.16,如果你的版本低于 8.0.16并且是通过开发者工具向客户端推送的 2.21.2 基础库,大几率不会返回 code 参数,请手动更新微信版本
2023-06-07 - “分享监听”能力调整
近期我们收到了很多用户对小程序/小游戏中分享功能的投诉:在某些小程序/小游戏中,分享并非是用户主动自发的行为,而是受到了某类利益的诱惑,或是被迫分享。这样的内容充斥在群里、小程序里,对用户造成了骚扰。 分享功能,旨在帮助用户更流畅地与好友分享内容和服务,应是用户自发的行为。在原来的分享接口中,用户发起分享动作之后,可以通过 [代码]success[代码] 、[代码]fail[代码]、[代码]complete[代码]等回调来判断用户是否完成了最后的分享动作。通过这个能力,开发者可以将产品交互在分享这个能力上做得比较自然和顺畅。现在为鼓励用户自发分享喜爱的内容,减少“强制分享至不同群”等滥用分享能力,破坏用户体验的行为,在我们权衡了分享功能带来的利弊后,分享功能将进行以下调整: 10月10日起新提交发布的版本,不再支持分享回调参数 [代码]success[代码] 、[代码]fail[代码] 、[代码]complete[代码],即用户从小程序/小游戏中分享消息给好友时,开发者将无法获知用户是否分享完成,也无法在分享后立即获得分享成功后的回调参数[代码]shareTicket[代码]。该调整可以在基础库 2.3.0及以上版本体验。 此次调整可能影响到三种分享功能的用法。 第一种:判断用户是否分享成功,进而给予用户奖励。 例如:小程序提示用户“分享到5个群,可以获得一张20元的优惠券”。 这类诱导用户分享的行为是我们平台所不倡导的,后续将没有办法实现。 第二种:分享完成后变更当前的页面状态 例如:赠送礼品场景下,用户点击“赠送”按钮,将礼品分享出去,分享成功后,界面展示“等待领取”。 这类场景,我们建议可以适当调整交互方案。例如在分享后继续保留“赠送”按钮,但在页面上提示用户一个礼品只能被一人领取,重复赠送无效。 第三种:通过用户分享之后的 [代码]shareTicket[代码] 获取群唯一标识 [代码]openGId[代码] ,以显示对应群的相关信息。 例如:通过分享小程序到某个群里,可以查看该群内成员的排行榜。 此次调整后,用户分享完成后无法立刻显示该群的排行榜信息,但仍可在用户从群消息点击进入小程序时显示该群的排行榜信息。 10月10日起新提交发布的版本将会受到此调整的影响。 需要各位开发者注意,10月10日起新提交发布的版本将会受到此策略的影响,请及时调整分享相关能力,考虑兼容上述调整带来的影响。 调整策略在基础库 2.3.0 及以上版本生效,该基础库版本对应微信客户端6.7.2版本。另外,考虑到兼容性等问题,在基础库版本 2.3.0 以下的环境中不受此策略影响,小程序/小游戏可继续获取分享回调事件。
2018-09-13 - 缺陷修改实践——replace函数的运用 | 思考?
介绍 大家好,我是清风。今天给大家分享一个项目中遇到问题解决问题的案例,编程其实就是一个思考的过程,缺少思考就没有灵魂,遇到问题先静下心去思考,想到方法后再去实践。我们要学会灵活变通去解决问题,掌握方法,这样才能举一反三,临危不乱,遇到所有问题都能很好地去解决。 事情是这样的,做的一个答题小程序,要求在后台编辑题目,编辑题目是用的[代码]textarea[代码]文本域,即输入的是文本内容,配置之后小程序里查询显示后台配置的题目列表。 [图片] 当然在测试时后台去配置文本内容,小程序显示是没有问题的。因为配置的题目是纯文本,小程序里也是直接在[代码]text[代码]里显示的,没有问题。 后台配置题目 [图片] 小程序显示题目 [图片] 问题出现 问题往往只有在用的时候才会发现,用户或管理员的各种操作才能显现出各种问题。现在小程序里已经不能正常显示了,有些显示的是html格式富文本格式,有些不显示。显然和需求大相径庭,已经出现问题了。 问题分析 [图片] 如上图,在用的时候因为是从[代码]word[代码]文档里直接复制过去的题目(事先在word里整理好了题目,由于各部门工作对接,先是在word文档进行审核),直接复制会把富文本内容也携带过去,携带过去的当然也有[代码]style[代码]样式,小程序里不显示的原因是富文本的字体颜色是黑色字体,和小程序面背景都是黑色,导致看不到文字。打开控制台调试,问题确实是这么出现的。 [图片] word文档编辑的题目会出现不同的富文本的样式格式,这都会导致小程序的不正常显示。 [图片] 富文本的文字颜色是黑色,这导致的小程序页里题目的不显示。 解决方法 既然后台有可能出现富文本,我们直接把小程序题目的渲染方式换成富文本渲染,这样就算是纯文本也可以显示的。而第二个问题,由于小程序页面背景颜色是黑色,所以题目的字体颜色不能是黑色,我们做一个查找替换,把style样式里的字体样式替换掉就可以了。 优化 样式可能是各种各样的,通过查找style样式里的字体样式考虑的匹配太多了,比如说color属性值可以是rgb形式,也可是rgba形式,也可能是#号颜色值形式,还有可能是英文形式颜色,所以不可取。我们换一种思路,直接把style属性替换掉,即查找到style,换成一个无效的属性名。案例中我们替换成了[代码]c[代码],[代码]c[代码]是一个无效的属性,不会触发节点的查找和计算。 replace函数 我们用到replace函数,[代码]replace()[代码]方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。 参数 regexp/substr,必需。规定子字符串或要替换的模式的 RegExp 对象。 replacement,必需。一个字符串值。规定了替换文本或生成替换文本的函数。 [代码]请注意,regexp/substr 是一个字符串,则将它作为要检索的直接量文本模式,而不是首先被转换为 RegExp对象[代码],返回值是一个新的字符串,是用 replacement 替换了 regexp 的第一次匹配或所有匹配之后得到的。 实现 replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。 [代码] that.setData({ _list: res.data }) let data = that.data._list data.forEach((element, index) => { var stemContent = '_list[' + index + '].stemContent' that.setData({ [stemContent]: element.stemContent.replace(/style/g, 'c') }) element.choiceTopicVos.forEach((item, i) => { var listCheck = '_list[' + index + '].choiceTopicVos[' + i + '].optionDes'; that.setData({ [listCheck]: item.optionDes.replace(/style/g, 'c') }) }) }) [代码] 用到的replace函数,进行了字符串的查找替换,查找到style属性,把它替换到了无效的属性c,没有了样式属性,从而达到了去除内联样式的效果,经过调试,完美解决问题。 [图片] 总结 [图片] 这是一个小问题,我为什么要拿出来说呢?抛去开发的测试流程不说,我认为它能引发一些思考。写代码的过程必须是一个思考的过程,怎么样更好地去实现效果,怎么样更简单有效地解决问题,怎么样优化,要去考虑一个大的方面。善于发现问题,解决问题,并不是说用了什么高级语言就鄙弃了思考,机器语言是相通的,开发中各种各样地应用才是魅力所在。编程是一门是艺术,最重要的是你怎样去巧妙地运用,就像解一道数学题,精绝巧妙的解法会让人眼前一亮,增色太多。同样都做出来了,但是你做的就显得十分优雅! 方法都是思考出来的,能高效解决问题的方法就是好方法,大家如果还有更好的解决方法欢迎评论区留言
2022-11-23 - JavaScript小技巧【建议收藏】
在JavaScript世界里,有些操作会让你无法理解,但是却无比优雅! 有时候读取变量属性时,他可能不是Ojbect。这个这个你就要判断这个变量是否为对象,如果是在如引用 [代码]var obj; if(obj instanceof Object){ console.log(obj.a); }else{ console.log('对象不存在'); } [代码] ES6中有简便写法,可以帮我们简短代码,从而更加明确 [代码]var obj; console.log(obj?.a || '对象不存在'); [代码] 1,2,3,4,5,6…10,11,12 小于10的前面补0 其实我们用slice函数可以巧妙解决这个问题 slice(start,end); start:必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。 end :可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。 [代码]var list=[1,2,3,4,5,6,7,8,9,10,11,12,13]; list=list.map(ele=>('0' + ele).slice(-2)); console.log(list); [代码] [图片] 打印可视化console.table() [代码]var obj = { name: 'Jack' }; console.table(obj); obj.name= 'Rose'; console.table(obj); [代码] [图片] 获取数组中的最后的元素 数组方法slice()可以接受负整数,如果提供它,它将从数组的末尾开始截取数值,而不是开头。 [代码]let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; console.log(array.slice(-1)); // Result: [9] console.log(array.slice(-2)); // Result: [8, 9] console.log(array.slice(-3)); // Result: [7, 8, 9] [代码] es6模板字符串 [代码]let name = "Charlse" let place = "India"; let isPrime = bit =>{ return (bit === 'P'? 'Prime' : 'Nom-Prime') } let messageConcat = `Mr.name' is form ${place} .He is a ${isPrime('P')} member`; [代码] H5语音合成 在网页端实现将指定的文字进行语音合成并进行播报。 使用HTML5的Speech Synthesis API就能实现简单的语音合成效果。 [代码]<input id="btn1" type="button" value="点我" onclick="test();" /> <script> function test() { const sos = `阿尤!不错哦`; const synth = window.speechSynthesis; let msg = new SpeechSynthesisUtterance(sos); synth.speak(msg) } </script> [代码] 然后点击按钮,就会触发test方法的执行实现语音合成 这里推荐使用Chrome浏览器,因为HTML5的支持度不同 数字取整 [代码]let floatNum = 100.5; let intNum = ~~floatNum; console.log(intNum); // 100 [代码] [图片] 字符串转数字 [代码]let str="10000"; let num = +str; console.log(num); // 10000 [代码] 交换对象键值 [代码]let obj = { key1: "value1", key2: "value2" }; let revert = {}; Object.entries(obj).forEach(([key, value]) => revert[value] = key); console.log(revert); [代码] [图片] 数组去重 [代码]let arrNum = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 ]; let arrString = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" ]; let arrMixed = [ 1, "1", "2", true, false, false, 1, 2, "2" ]; arrNum = Array.from(new Set(arrNum)); arrString = [...new Set(arrString)]; arrMixed = [...new Set(arrMixed)]; console.log(arrNum); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] console.log(arrString); // ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] console.log(arrMixed); // [1, "1", "2", true, false, 2] [代码] 数组转化为对象 [代码]const arr = [1,2,3] const obj = {...arr} console.log(obj) [代码] 合并对象 [代码]const obj1 = { a: 1 } const obj2 = { b: 2 } const combinObj = { ...obj1, ...obj2 } console.log(combinObj) [代码] 也就是通过展开操作符(spread operator)来实现。 获取数组中的最后一项 [代码]let arr = [0, 1, 2, 3, 4, 5]; const last = arr.slice(-1)[0]; console.log(last); [代码] 一次性函数,适用于初始化的一些操作 [代码]var sca = function() { console.log('msg')//永远只会执行一次 sca = function() { console.log('foo') } } sca() // msg sca() // foo sca() [代码] 提高工作效率,减少代码量,降低键盘磨损程度 我希望你喜欢它并学到了一些新东西 感谢你的阅读,编程快乐!
2022-10-25 - uniapp|微信小程序获取当前城市名称--逆地址解析
问题 uniapp开发的小程序需要获取当前城市名称 解决步骤 看文档 当然是看uniapp文档,我们查到有提供相关的API,即[代码]uni.getLocation(OBJECT)[代码],获取当前的地理位置、速度。 [图片] 我们试试吧 [代码]uni.getLocation({ type: 'wgs84', geocode:true, success: function (res) { console.log('当前位置的经度:' + res.longitude); console.log('当前位置的纬度:' + res.latitude); } }); [代码] 我们发现只能返回经纬度信息,并不会返回城市信息。 [图片] 原来是只有app才支持geocode 哈哈事情没有想象中那么简单~ [图片] 思考ing… 查阅了相关资料,[代码]原来是位置详细信息的一些保密协议,并不能直接获取到[代码]。那么我们就需要通过经纬度,利用腾讯地图JavaScript SDK逆地址解析,即输入坐标返回地理位置信息。 有方法了,开整 逆地址解析 1. 创建应用 打开腾讯地图开放平台,创建应用 [图片] 然后 控制台 ->应用管理 -> 我的应用 ->添加key-> 勾选 [图片] [代码]小程序SDK需要用到webserviceAPI的部分服务,所以使用该功能的KEY需要具备相应的权限[代码] [代码]授权ip即当前连接服务的IP地址(注意:上线后这个一定要换成上线IP地址哦)[代码] [代码]填入微信小程序appid[代码] [图片] 现在有了地图秘钥key 2. uniapp配置 pages.json配置 加入以下配置项,用于申请获得位置权限 [代码]"permission": { "scope.userLocation": { "desc": "你的位置信息将用于小程序位置接口的效果展示" } } [代码] uniapp配置 [图片] [代码]这里填写的描述信息就是微信弹起信息微信授权弹框的描述信息[代码] [图片] 3. 代码部分 下载微信小程序JavaScriptSDK [代码]var QQMapWX = require('@/utils/qqmap-wx-jssdk.min.js'); // 实例化API核心类 var qqmapsdk = new QQMapWX({ key: '' // 必填 }); [代码] [代码]key即申请的腾讯地图秘钥key[代码] [代码]uni.getLocation({ type: 'gcj02', geocode: true, success: function (res) { //逆地址解析 坐标转地址信息 qqmapsdk.reverseGeocoder({ //Object格式 location: { latitude: res.latitude, longitude: res.longitude }, success: function(res) {//成功后的回调 const mapdata=res.result.ad_info; that.city = mapdata.city; },fail: function(error) { console.error(error); }, complete: function(res) { //console.log(res); } }); } }); [代码] 编译 [图片] 我们看返回信息 [图片] 现在是有返回当前位置详细信息了,当前也包括城市名称。 问题解决! 总结 遇到问题并不可怕,可怕的是停滞不前,知难而退。只有发现问题才能解决问题,才会得到成长和锻炼。人生的路总是一波三折,代码也是,风雨之后总会见到彩虹的,加油! 七年代码两茫茫,不思量,自难忘 🌈7年资深前端主管一枚,只分享技术干货,项目实战经验,面试指导 关注博主不迷路~
2022-10-21 - 微信小程序生成海报图片导出相册
前言 小程序内通过静态模板和样式绘制 canvas ,导出图片,可用于生成分享图等场景 一、效果预览 点击生成海报 [图片] 这个是生成的保存到相册的图片 [图片] 二、使用步骤 1.安装引入wxml-to-canvas Step1.运行小程序npm安装命令 [代码]npm install --save wxml-to-canvas [代码] Step2.JSON 组件声明 [代码]{ "usingComponents": { "wxml-to-canvas": "wxml-to-canvas", } } [代码] 文章最后会有wxml-to-canvas的其它详细教程 2.创建js 创建的js放到页面文件夹即可 [图片] 代码如下(demo.js): [代码]var calendar = require('../../../utils/calendar'); var d=new Date(); let Year = d.getFullYear(); let Month = (d.getMonth()+1).toString().padStart(2,'0'); let Day = (d.getDate()).toString().padStart(2,'0'); let time = Month+ '/' + Day; let xq = "星期"+"天一二三四五六".charAt(d.getDay()); const wxml = (hb_describe,hb_bg,hb_wx2code)=>{ return ` <view class="container" > <view class="itembox1"> <text class="time">`+time+`</text> <view class="xqview"> <text class="xq">`+xq+`</text> <text class="xq2">`+calendar.default.solar2lunar(Year,Month,Day).gzYear+'年'+calendar.default.solar2lunar(Year,Month,Day).IMonthCn+calendar.default.solar2lunar(Year,Month,Day).IDayCn+`</text> </view> </view> <view class="itembox2"> <image class="img" mode="aspectFit" src="`+hb_bg+`"></image> </view> <view class="itembox3"> <text class="text">`+hb_describe+`</text> <image class="imgcode" src="`+hb_wx2code+`"></image> </view> </view> ` } let width=wx.getSystemInfoSync().windowWidth*0.8; let widthCenter=width*0.9; let width6=widthCenter*0.6;//描述占60% let width4=widthCenter*0.4; const style = { container: { width: width, height: 450, flexDirection: 'column', backgroundColor: '#FFFFFF', alignItems: 'center' }, itembox1: { width: widthCenter, height: 130 }, itembox2: { width: widthCenter, height: 200 }, itembox3: { width: widthCenter, height: 120, flexDirection: 'row', justifyContent:'space-around', alignItems: 'center', //paddingTop:20, //paddingBottom:20 }, time:{ fontSize:88, color:'#0222AC', textAlign:'center', fontStyle:'italic' }, xqview:{ fontSize:16, color:'#0222AC', marginTop:106 }, xq:{ textAlign:'left' }, xq2:{ textAlign:'right' }, img: { width: widthCenter, minHeight: 190, maxHeight: 200, }, imgcode: { width: 80, height: 80 }, text: { width: width6, height: 60, textAlign: 'center', verticalAlign: 'middle' } } module.exports = { wxml, style } [代码] 3.小程序页面 wxml: [代码]<!-- 海报 --> <view class="hbbox" wx:if="{{maskHidden}}"> <wxml-to-canvas class="widget" height='{{canvasH}}'></wxml-to-canvas> </view> <view class="padding-sm margin-top flex flex-direction renderToCanvas" wx:if="{{hexiao}}"> <button bindtap="renderToCanvas" class="cu-btn">活动打卡</button> </view> <view class='hbmask' hidden="{{maskHidden == false}}"> <button class='hbbaocun' hidden="{{maskHidden == false}}" bindtap='extraImage'>保存相册</button> </view> <!-- 海报end --> [代码] js: 博主这里是打开一个弹窗 在弹窗里展示生成海报,js代码全部放出来,可自行取用。 [代码]const { wxml, style } = require('./demo.js') Page({ data: { //海报 hb_wx2code: "", hb_bg: "", hb_describe: '', maskHidden: false, canvasH: 0, number:0, flag:'' }, //生成海报 makeHB(){ let that = this; that.setData({ maskHidden: true, canvasH:570 }) setTimeout(()=>{ const _wxml = wxml(that.data.hb_describe,that.data.hb_bg,that.data.hb_wx2code) wx.nextTick(()=>{ that.widget = that.selectComponent('.widget') const p1 = that.selectComponent('.widget').renderToCanvas({ wxml:_wxml , style }) p1.then((res) => { wx.hideToast(); that.container = res; }).catch(err=>{ }) }) },500) }, renderToCanvas() { let that = this; if(that.data.hb_wx2code){ wx.showToast({ title: '海报生成中...', icon: 'loading', duration: 1000 }); that.makeHB(); } else{ wx.showToast({ title: '海报生成中...', icon: 'loading', duration: 1000 }); const data={ open_id : wx.getStorageSync('openId'), type: '2', video_id:that.data.id } mycomm.ajaxDataGet("/inviteFriends/ordinary", data, function (res) { that.setData({ hb_wx2code: res.val.qr_code, hb_bg:res.val.background_img, hb_describe:res.val.describe }); that.makeHB(); }, true); } }, extraImage() { const p2 = this.widget.canvasToTempFilePath() p2.then(res => { // this.setData({ // src: res.tempFilePath, // width: this.container.layoutBox.width, // height: this.container.layoutBox.height // }) this.baocun(res.tempFilePath); }) }, //点击保存到相册 baocun: function (imagePath) { var that = this wx.saveImageToPhotosAlbum({ filePath: imagePath, success(res) { wx.showModal({ content: '海报已保存到相册', showCancel: false, confirmText: '确定', confirmColor: '#333', success: function (res) { if (res.confirm) { /* 该隐藏的隐藏 */ that.setData({ maskHidden: false }) } }, fail: function (res) {} }) } }) }, }) [代码] 4.农历日期js 这个是海报上根据当前日期对应的农历日期,不需要可以自行删除掉。 [代码]var calendar = { /** * 农历1900-2100的润大小信息表 * @Array Of Property * @return Hex */ lunarInfo:[0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,//1900-1909 0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,//1910-1919 0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,//1920-1929 0x06566,0x0d4a0,0x0ea50,0x16a95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,//1930-1939 0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,//1940-1949 0x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,0x052b0,0x0a9a8,0x0e950,0x06aa0,//1950-1959 0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,//1960-1969 0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b6a0,0x195a6,//1970-1979 0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,//1980-1989 0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x05ac0,0x0ab60,0x096d5,0x092e0,//1990-1999 0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,//2000-2009 0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,//2010-2019 0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,//2020-2029 0x05aa0,0x076a3,0x096d0,0x04afb,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,//2030-2039 0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0,//2040-2049 /**Add By JJonline@JJonline.Cn**/ 0x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50, 0x06b20,0x1a6c4,0x0aae0,//2050-2059 0x092e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4,//2060-2069 0x052d0,0x0a9b8,0x0a950,0x0b4a0,0x0b6a6,0x0ad50,0x055a0,0x0aba4,0x0a5b0,0x052b0,//2070-2079 0x0b273,0x06930,0x07337,0x06aa0,0x0ad50,0x14b55,0x04b60,0x0a570,0x054e4,0x0d160,//2080-2089 0x0e968,0x0d520,0x0daa0,0x16aa6,0x056d0,0x04ae0,0x0a9d4,0x0a2d0,0x0d150,0x0f252,//2090-2099 0x0d520],//2100 /** * 公历每个月份的天数普通表 * @Array Of Property * @return Number */ solarMonth:[31,28,31,30,31,30,31,31,30,31,30,31], /** * 天干地支之天干速查表 * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"] * @return Cn string */ Gan:["\u7532","\u4e59","\u4e19","\u4e01","\u620a","\u5df1","\u5e9a","\u8f9b","\u58ec","\u7678"], /** * 天干地支之地支速查表 * @Array Of Property * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"] * @return Cn string */ Zhi:["\u5b50","\u4e11","\u5bc5","\u536f","\u8fb0","\u5df3","\u5348","\u672a","\u7533","\u9149","\u620c","\u4ea5"], /** * 天干地支之地支速查表<=>生肖 * @Array Of Property * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"] * @return Cn string */ Animals:["\u9f20","\u725b","\u864e","\u5154","\u9f99","\u86c7","\u9a6c","\u7f8a","\u7334","\u9e21","\u72d7","\u732a"], /** * 阳历节日 */ festival: { '1-1': {title: '元旦节'}, '2-14': {title: '情人节'}, '5-1': {title: '劳动节'}, '5-4': {title: '青年节'}, '6-1': {title: '儿童节'}, '9-10': {title: '教师节'}, '10-1': {title: '国庆节'}, '12-25': {title: '圣诞节'}, '3-8': {title: '妇女节'}, '3-12': {title: '植树节'}, '4-1': {title: '愚人节'}, '5-12': {title: '护士节'}, '7-1': {title: '建党节'}, '8-1': {title: '建军节'}, '12-24': {title: '平安夜'}, }, /** * 农历节日 */ lfestival: { '12-30': {title: '除夕'}, '1-1': {title: '春节'}, '1-15': {title: '元宵节'}, '5-5': {title: '端午节'}, '8-15': {title: '中秋节'}, '9-9': {title: '重阳节'}, }, /** * 返回默认定义的阳历节日 */ getFestival(){ return this.festival }, /** * 返回默认定义的内容里节日 */ getLunarFestival(){ return this.lfestival }, /** * * @param {Object} 按照festival的格式输入数据,设置阳历节日 */ setFestival(param={}){ this.festival = param }, /** * * @param {Object} 按照lfestival的格式输入数据,设置农历节日 */ setLunarFestival(param={}){ this.lfestival = param }, /** * 24节气速查表 * @Array Of Property * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"] * @return Cn string */ solarTerm:["\u5c0f\u5bd2","\u5927\u5bd2","\u7acb\u6625","\u96e8\u6c34","\u60ca\u86f0","\u6625\u5206","\u6e05\u660e","\u8c37\u96e8","\u7acb\u590f","\u5c0f\u6ee1","\u8292\u79cd","\u590f\u81f3","\u5c0f\u6691","\u5927\u6691","\u7acb\u79cb","\u5904\u6691","\u767d\u9732","\u79cb\u5206","\u5bd2\u9732","\u971c\u964d","\u7acb\u51ac","\u5c0f\u96ea","\u5927\u96ea","\u51ac\u81f3"], /** * 1900-2100各年的24节气日期速查表 * @Array Of Property * @return 0x string For splice */ sTermInfo:['9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e','97bcf97c3598082c95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f','b027097bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f','97bd0b06bdb0722c965ce1cfcc920f','b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e','97bcf97c359801ec95f8c965cc920f','97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa','9778397bd19801ec9210c965cc920e','97b6b97bd19801ec95f8c965cc920f', '97bd09801d98082c95f8e1cfcc920f','97bd097bd097c36b0b6fc9210c8dc2','9778397bd197c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e','97bd09801d98082c95f8e1cfcc920f','97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa','97b6b97bd19801ec95f8c965cc920e','97bcf97c3598082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2','9778397bd097c36c9210c9274c91aa','97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f','97bd097bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e','97bcf97c3598082c95f8c965cc920f','97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e','97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f','97bd097bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e','97bcf97c359801ec95f8c965cc920f','97bd097bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9210c8dc2','9778397bd19801ec9210c9274c920e','97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722','7f0e397bd097c36b0b6fc9210c8dc2','9778397bd097c36c9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f','97bd07f5307f595b0b0bc920fb0722','7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa','97b6b97bd19801ec9210c965cc920e','97bd07f1487f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2','9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e','97bcf7f1487f595b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e','97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722','7f0e397bd07f595b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c9274c920e','97bcf7f0e47f531b0b0bb0b6fb0722','7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c91aa','97b6b97bd197c36c9210c9274c920e','97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722','9778397bd097c36b0b6fc9210c8dc2','9778397bd097c36c9210c9274c920e', '97b6b7f0e47f531b0723b0b6fb0722','7f0e37f5307f595b0b0bc920fb0722','7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b70c9274c91aa','97b6b7f0e47f531b0723b0b6fb0721','7f0e37f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc9210c8dc2','9778397bd097c36b0b6fc9274c91aa','97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f595b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa','97b6b7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa','97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722','7f0e397bd07f595b0b0bc920fb0722','9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0787b0721','7f0e27f0e47f531b0b0bb0b6fb0722','7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c91aa','97b6b7f0e47f149b0723b0787b0721','7f0e27f0e47f531b0723b0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722','9778397bd097c36b0b6fc9210c8dc2','977837f0e37f149b0723b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722','7f0e37f5307f595b0b0bc920fb0722','7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b0721','7f07e7f0e47f531b0723b0b6fb0721','7f0e37f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc9210c8dc2','977837f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722','977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722','977837f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722','7f0e397bd07f595b0b0bc920fb0722','977837f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721','7f0e27f0e47f531b0b0bb0b6fb0722','7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0723b06bd','7f07e7f0e37f149b0723b0787b0721','7f0e27f0e47f531b0723b0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722','977837f0e37f14898082b0723b02d5','7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722','7f0e37f1487f595b0b0bb0b6fb0722','7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721','7f07e7f0e47f531b0723b0b6fb0722','7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5','7ec967f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f531b0b0bb0b6fb0722','7f0e37f0e37f14898082b072297c35','7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722','7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35','7ec967f0e37f14998082b0787b06bd','7f07e7f0e47f149b0723b0787b0721', '7f0e27f1487f531b0b0bb0b6fb0722','7f0e37f0e366aa89801eb072297c35','7ec967f0e37f14998082b0723b06bd', '7f07e7f0e47f149b0723b0787b0721','7f0e27f0e47f531b0723b0b6fb0722','7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd','7f07e7f0e37f14998083b0787b0721','7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35','7ec967f0e37f14898082b0723b02d5','7f07e7f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722','7f0e36665b66aa89801e9808297c35','665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721','7f07e7f0e47f531b0723b0b6fb0722','7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b0723b02d5','7ec967f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721', '7f0e36665b66a449801e9808297c35','665f67f0e37f14898082b072297c35','7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721','7f0e26665b66a449801e9808297c35','665f67f0e37f1489801eb072297c35', '7ec967f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722'], /** * 数字转中文速查表 * @Array Of Property * @trans ['日','一','二','三','四','五','六','七','八','九','十'] * @return Cn string */ nStr1:["\u65e5","\u4e00","\u4e8c","\u4e09","\u56db","\u4e94","\u516d","\u4e03","\u516b","\u4e5d","\u5341"], /** * 日期转农历称呼速查表 * @Array Of Property * @trans ['初','十','廿','卅'] * @return Cn string */ nStr2:["\u521d","\u5341","\u5eff","\u5345"], /** * 月份转农历称呼速查表 * @Array Of Property * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊'] * @return Cn string */ nStr3:["\u6b63","\u4e8c","\u4e09","\u56db","\u4e94","\u516d","\u4e03","\u516b","\u4e5d","\u5341","\u51ac","\u814a"], /** * 农历年份数字称呼速查表 * @Array Of Property * @trans ['零','一','二','三','四','五','六','七','八','九','十'] * @return Cn string */ nStr4:["\u96f6","\u4e00","\u4e8c","\u4e09","\u56db","\u4e94","\u516d","\u4e03","\u516b","\u4e5d","\u5341"], /** * 返回农历y年一整年的总天数 * @param lunar Year * @return Number * @eg:var count = calendar.lYearDays(1987) ;//count=387 */ lYearDays:function(y) { var i, sum = 348; for(i=0x8000; i>0x8; i>>=1) { sum += (this.lunarInfo[y-1900] & i)? 1: 0; } return(sum+this.leapDays(y)); }, /** * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0 * @param lunar Year * @return Number (0-12) * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6 */ leapMonth:function(y) { //闰字编码 \u95f0 return(this.lunarInfo[y-1900] & 0xf); }, /** * 返回农历y年闰月的天数 若该年没有闰月则返回0 * @param lunar Year * @return Number (0、29、30) * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29 */ leapDays:function(y) { if(this.leapMonth(y)) { return((this.lunarInfo[y-1900] & 0x10000)? 30: 29); } return(0); }, /** * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法 * @param lunar Year * @return Number (-1、29、30) * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29 */ monthDays:function(y,m) { if(m>12 || m<1) {return -1}//月份参数从1至12,参数错误返回-1 return( (this.lunarInfo[y-1900] & (0x10000>>m))? 30: 29 ); }, /** * 返回公历(!)y年m月的天数 * @param solar Year * @return Number (-1、28、29、30、31) * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30 */ solarDays:function(y,m) { if(m>12 || m<1) {return -1} //若参数错误 返回-1 var ms = m-1; if(ms==1) { //2月份的闰平规律测算后确认返回28或29 return(((y%4 == 0) && (y%100 != 0) || (y%400 == 0))? 29: 28); }else { return(this.solarMonth[ms]); } }, /** * 农历年份转换为干支纪年 * @param lYear 农历年的年份数 * @return Cn string */ toGanZhiYear:function(lYear) { var ganKey = (lYear - 3) % 10; var zhiKey = (lYear - 3) % 12; if(ganKey == 0) ganKey = 10;//如果余数为0则为最后一个天干 if(zhiKey == 0) zhiKey = 12;//如果余数为0则为最后一个地支 return this.Gan[ganKey-1] + this.Zhi[zhiKey-1]; }, /** * 公历月、日判断所属星座 * @param cMonth [description] * @param cDay [description] * @return Cn string */ toAstro:function(cMonth,cDay) { var s = "\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf"; var arr = [20,19,21,21,21,22,23,23,23,23,22,22]; return s.substr(cMonth*2 - (cDay < arr[cMonth-1] ? 2 : 0),2) + "\u5ea7";//座 }, /** * 传入offset偏移量返回干支 * @param offset 相对甲子的偏移量 * @return Cn string */ toGanZhi:function(offset) { return this.Gan[offset%10] + this.Zhi[offset%12]; }, /** * 传入公历(!)y年获得该年第n个节气的公历日期 * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起 * @return day Number * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春 */ getTerm:function(y,n) { if(y<1900 || y>2100) {return -1;} if(n<1 || n>24) {return -1;} var _table = this.sTermInfo[y-1900]; var _info = [ parseInt('0x'+_table.substr(0,5)).toString() , parseInt('0x'+_table.substr(5,5)).toString(), parseInt('0x'+_table.substr(10,5)).toString(), parseInt('0x'+_table.substr(15,5)).toString(), parseInt('0x'+_table.substr(20,5)).toString(), parseInt('0x'+_table.substr(25,5)).toString() ]; var _calday = [ _info[0].substr(0,1), _info[0].substr(1,2), _info[0].substr(3,1), _info[0].substr(4,2), _info[1].substr(0,1), _info[1].substr(1,2), _info[1].substr(3,1), _info[1].substr(4,2), _info[2].substr(0,1), _info[2].substr(1,2), _info[2].substr(3,1), _info[2].substr(4,2), _info[3].substr(0,1), _info[3].substr(1,2), _info[3].substr(3,1), _info[3].substr(4,2), _info[4].substr(0,1), _info[4].substr(1,2), _info[4].substr(3,1), _info[4].substr(4,2), _info[5].substr(0,1), _info[5].substr(1,2), _info[5].substr(3,1), _info[5].substr(4,2), ]; return parseInt(_calday[n-1]); }, /** * 传入年份返回汉语通俗表示法 * @param {lunar} year * @return CN {string} * @eg:let cnYear = calendar.toChinaYear(1960);//cnYear = '一九六零' */ toChinaYear:function(y){ //年 let year = y.toString().split(""); return `${this.nStr4[year[0]]}${this.nStr4[year[1]]}${this.nStr4[year[2]]}${this.nStr4[year[3]]}`; }, /** * 传入农历数字月份返回汉语通俗表示法 * @param lunar month * @return Cn string * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月' */ toChinaMonth:function(m) { // 月 => \u6708 if(m>12 || m<1) {return -1} //若参数错误 返回-1 var s = this.nStr3[m-1]; s+= "\u6708";//加上月字 return s; }, /** * 传入农历日期数字返回汉字表示法 * @param lunar day * @return Cn string * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一' */ toChinaDay:function(d){ //日 => \u65e5 var s; switch (d) { case 10: s = '\u521d\u5341'; break; case 20: s = '\u4e8c\u5341'; break; break; case 30: s = '\u4e09\u5341'; break; break; default : s = this.nStr2[Math.floor(d/10)]; s += this.nStr1[d%10]; } return(s); }, /** * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春” * @param y year * @return Cn string * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔' */ getAnimal: function(y) { return this.Animals[(y - 4) % 12] }, /** * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON * @param y solar year * @param m solar month * @param d solar day * @return JSON object * @eg:console.log(calendar.solar2lunar(1987,11,01)); */ solar2lunar:function (y,m,d) { //参数区间1900.1.31~2100.12.31 y = parseInt(y) m = parseInt(m) d = parseInt(d) //年份限定、上限 if(y<1900 || y>2100) { return -1;// undefined转换为数字变为NaN } //公历传参最下限 if(y==1900&&m==1&&d<31) { return -1; } //未传参 获得当天 if(!y) { var objDate = new Date(); }else { var objDate = new Date(y,parseInt(m)-1,d) } var i, leap=0, temp=0; //修正ymd参数 var y = objDate.getFullYear(), m = objDate.getMonth()+1, d = objDate.getDate(); var offset = (Date.UTC(objDate.getFullYear(),objDate.getMonth(),objDate.getDate()) - Date.UTC(1900,0,31))/86400000; for(i=1900; i<2101 && offset>0; i++) { temp = this.lYearDays(i); offset -= temp; } if(offset<0) { offset+=temp; i--; } //是否今天 var isTodayObj = new Date(), isToday = false; if(isTodayObj.getFullYear()==y && isTodayObj.getMonth()+1==m && isTodayObj.getDate()==d) { isToday = true; } //星期几 var nWeek = objDate.getDay(), cWeek = this.nStr1[nWeek]; //数字表示周几顺应天朝周一开始的惯例 if(nWeek==0) { nWeek = 7; } //农历年 var year = i; var leap = this.leapMonth(i); //闰哪个月 var isLeap = false; //效验闰月 for(i=1; i<13 && offset>0; i++) { //闰月 if(leap>0 && i==(leap+1) && isLeap==false){ --i; isLeap = true; temp = this.leapDays(year); //计算农历闰月天数 } else{ temp = this.monthDays(year, i);//计算农历普通月天数 } //解除闰月 if(isLeap==true && i==(leap+1)) { isLeap = false; } offset -= temp; } // 闰月导致数组下标重叠取反 if(offset==0 && leap>0 && i==leap+1) { if(isLeap){ isLeap = false; }else{ isLeap = true; --i; } } if(offset<0) { offset += temp; --i; } //农历月 var month = i; //农历日 var day = offset + 1; //天干地支处理 var sm = m-1; var gzY = this.toGanZhiYear(year); // 当月的两个节气 // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year` var firstNode = this.getTerm(y,(m*2-1));//返回当月「节」为几日开始 var secondNode = this.getTerm(y,(m*2));//返回当月「节」为几日开始 // 依据12节气修正干支月 var gzM = this.toGanZhi((y-1900)*12+m+11); if(d>=firstNode) { gzM = this.toGanZhi((y-1900)*12+m+12); } //传入的日期的节气与否 var isTerm = false; var Term = null; if(firstNode==d) { isTerm = true; Term = this.solarTerm[m*2-2]; } if(secondNode==d) { isTerm = true; Term = this.solarTerm[m*2-1]; } //日柱 当月一日与 1900/1/1 相差天数 var dayCyclical = Date.UTC(y,sm,1,0,0,0,0)/86400000+25567+10; var gzD = this.toGanZhi(dayCyclical+d-1); //该日期所属的星座 var astro = this.toAstro(m,d); var solarDate = y+'-'+m+'-'+d var lunarDate = year+'-'+month+'-'+day var festival = this.festival var lfestival = this.lfestival var festivalDate = m+'-'+d var lunarFestivalDate = month+'-'+day return { date: solarDate, lunarDate: lunarDate, festival: festival[festivalDate] ? festival[festivalDate].title : null, lunarFestival: lfestival[lunarFestivalDate] ? lfestival[lunarFestivalDate].title : null, 'lYear':year, 'lMonth':month, 'lDay':day, 'Animal':this.getAnimal(year), 'IMonthCn':(isLeap?"\u95f0":'')+this.toChinaMonth(month), 'IDayCn':this.toChinaDay(day), 'cYear':y, 'cMonth':m, 'cDay':d, 'gzYear':gzY, 'gzMonth':gzM, 'gzDay':gzD, 'isToday':isToday, 'isLeap':isLeap, 'nWeek':nWeek, 'ncWeek':"\u661f\u671f"+cWeek, 'isTerm':isTerm, 'Term':Term, 'astro':astro }; }, /** * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON * @param y lunar year * @param m lunar month * @param d lunar day * @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可] * @return JSON object * @eg:console.log(calendar.lunar2solar(1987,9,10)); */ lunar2solar:function(y,m,d,isLeapMonth) { //参数区间1900.1.31~2100.12.1 y = parseInt(y) m = parseInt(m) d = parseInt(d) var isLeapMonth = !!isLeapMonth; var leapOffset = 0; var leapMonth = this.leapMonth(y); var leapDay = this.leapDays(y); if(isLeapMonth&&(leapMonth!=m)) {return -1;}//传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同 if(y==2100&&m==12&&d>1 || y==1900&&m==1&&d<31) {return -1;}//超出了最大极限值 var day = this.monthDays(y,m); var _day = day; //bugFix 2016-9-25 //if month is leap, _day use leapDays method if(isLeapMonth) { _day = this.leapDays(y,m); } if(y < 1900 || y > 2100 || d > _day) {return -1;}//参数合法性效验 //计算农历的时间差 var offset = 0; for(var i=1900;i<y;i++) { offset+=this.lYearDays(i); } var leap = 0,isAdd= false; for(var i=1;i<m;i++) { leap = this.leapMonth(y); if(!isAdd) {//处理闰月 if(leap<=i && leap>0) { offset+=this.leapDays(y);isAdd = true; } } offset+=this.monthDays(y,i); } //转换闰月农历 需补充该年闰月的前一个月的时差 if(isLeapMonth) {offset+=day;} //1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点) var stmap = Date.UTC(1900,1,30,0,0,0); var calObj = new Date((offset+d-31)*86400000+stmap); var cY = calObj.getUTCFullYear(); var cM = calObj.getUTCMonth()+1; var cD = calObj.getUTCDate(); return this.solar2lunar(cY,cM,cD); } }; export default calendar [代码] 附其它: wxml-to-canvas的使用 wxml 引入组件 [代码]<video class="video" src="{{src}}"> <wxml-to-canvas class="widget"></wxml-to-canvas> </video> <image src="{{src}}" style="width: {{width}}px; height: {{height}}px"></image> [代码] js 获取实例 [代码]const {wxml, style} = require('./demo.js') Page({ data: { src: '' }, onLoad() { this.widget = this.selectComponent('.widget') }, renderToCanvas() { const p1 = this.widget.renderToCanvas({ wxml, style }) p1.then((res) => { this.container = res this.extraImage() }) }, extraImage() { const p2 = this.widget.canvasToTempFilePath() p2.then(res => { this.setData({ src: res.tempFilePath, width: this.container.layoutBox.width, height: this.container.layoutBox.height }) }) } }) [代码] wxml模板支持 view、text、image 三种标签,通过 class 匹配 style 对象中的样式。 样式对象属性值为对应 wxml 标签的 class 驼峰形式。需为每个元素指定 width 和 height 属性,否则会导致布局错误。 存在多个 className 时,位置靠后的优先级更高,子元素会继承父级元素的可继承属性。 元素均为 flex 布局。left/top 等 仅在 absolute 定位下生效。
2023-06-07