小程序面试题

news/2024/7/20 4:07:36 标签: 小程序

小程序双线程模型架构的理解?

小程序分为视图层和逻辑层,视图层的相关任务全都在WebView里执行。一个小程序存在多个界面,所以视图层存在多个WebView线程。而逻辑层采用JsCore线程运行JS脚本。他们之间通过系统层的WeixinJsBridge进行通信,也就是逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。

所以小程序双线程模式主要解决体验和管控的问题

  • 体验:web页面开发渲染线程和脚本线程是互斥的,长时间的脚本运行可能会导致页面失去响应或者白屏。而双线程模式是不会有这个问题的。而且这个模式下,强制使用了MVVM框架的数据驱动,即让视图状态和视图绑定在一起,同时也使用了虚拟dom优化体验
  • 管控:阻止开发者使用浏览器的开发性接口,通过提供一个沙盒环境来运行开发者的js代码,只能使用微信提供开放的方法来获取元素的一些信息。这样就避免开发者的操作不在管控范围。除了JS用沙盒环境管控,html也改用了封装过的wxml(WeiXin Markup language) ,css改为wxss(WeiXin Style Sheet),为了管控,同时也是为了提供更多功能,例如封装了播放直播的live-player、滚动选择器picker-view。另外,也提供了wxs(WeiXin Script)让wxml在渲染的时候也可以做一些逻辑处理。

小程序更新视图数据的通信流程

每当小程序视图数据需要更新时,逻辑层会调用小程序宿主环境提供的 setData 方法将数据从逻辑层传递到视图层,经过一系列渲染步骤之后完成UI视图更新。完整的通信流程如下:

  1. 小程序逻辑层调用宿主环境的 setData 方法。
  2. 逻辑层执行 JSON.stringify 将待传输数据转换成字符串并拼接到特定的JS脚本,并通过evaluateJavascript 执行脚本将数据传输到渲染层。
  3. 渲染层接收到后, WebView JS 线程会对脚本进行编译,得到待更新数据后进入渲染队列等待 WebView 线程空闲时进行页面渲染。
  4. WebView 线程开始执行渲染时,待更新数据会合并到视图层保留的原始 data 数据,并将新数据套用在WXML片段中得到新的虚拟节点树。经过新虚拟节点树与当前节点树的 diff 对比,将差异部分更新到UI视图。同时,将新的节点树替换旧节点树,用于下一次重渲染。

主要目录和文件的作用?

  • project.config.json 项目配置文件,做一些个性化配置,例如界面颜色、编译配置等等
  • app.json 当前小程序的全局配置,包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等
  • sitemap 配置小程序及其页面是否允许被微信索引
  • pages 里面包含一个个具体的页面
  • wxss 页面样式,app.wxss 作为全局样式,会作用于当前小程序的所有页面,局部页面样式 page.wxss 仅对当前页面生效。
  • app.js 小程序的逻辑
  • js 页面逻辑
  • json 页面配置
  • wxml 页面结构

配置文件

  • sitemap.json
    • 微信会爬取你的页面内容, 当用户在自己的微信中搜索时可以搜索到你开发的小程序
  • project.private.config.json:一些配置信息
    • 比如:项目名字,是否开启热重载, 是否开启地址检查,当前版本库的版本号
    • 这个文件中设置的内容会覆盖掉project.config.json文件中的相同设置
    • 与project.config.json配置不同时会改变这个文件中的配置
  • project.config.json:一些基础配置
    • 比如项目名称、appid
    • 这个文件一般不会发生变化
  • app.json:全局配置
    • pages: 页面路径列表
      • 用于指定小程序由哪些页面组成,每一项都对应一个页面的 路径(含文件名) 信息
      • 小程序中所有的页面都是必须在pages中进行注册
    • window: 全局的默认窗口展示
      • 用户指定窗口如何展示, 其中还包含了很多其他的属性
    • tabBar: 底部tab栏的展示
  • page.json:页面的单独配置
    • 每一个小程序页面也可以使用 .json 文件来对本页面的窗口表现进行配置
    • 页面中配置项在当前页面会覆盖 app.json 的 window 中相同的配置项

小程序的生命周期函数

应用的生命周期

生命周期说明
onLaunch小程序初始化完成时触发,全局只触发一次
onShow小程序启动,或从后台进入前台显示时触发
onHide小程序从前台进入后台时触发
onError小程序发生脚本错误或 API 调用报错时触发
onPageNotFound小程序要打开的页面不存在时触发
onUnhandledRejection()小程序有未处理的 Promise 拒绝时触发
onThemeChange系统切换主题时触发

页面的生命周期

生命周期说明作用
onLoad生命周期回调—监听页面加载发送请求获取数据
onShow生命周期回调—监听页面显示请求数据
onReady生命周期回调—监听页面初次渲染完成获取页面元素(少用)
onHide生命周期回调—监听页面隐藏终止任务,如定时器或者播放音乐
onUnload生命周期回调—监听页面卸载终止任务

组件的生命周期

生命周期说明
created生命周期回调—监听页面加载
attached生命周期回调—监听页面显示
ready生命周期回调—监听页面初次渲染完成
moved生命周期回调—监听页面隐藏
detached生命周期回调—监听页面卸载
error每当组件方法抛出错误时执行

描述下相关文件类型

微信小程序项目结构主要有四个文件类型

  • WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。内部主要是微信自己定义的一套组件
  • WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式
  • js 逻辑处理,网络请求
  • json 小程序设置,如页面注册,页面标题及tabBar

wxss和css不一样的地方

WXSS 和 CSS 类似,不过在 CSS 的基础上做了一些补充和修改

  • 尺寸单位 rpx

rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。
如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

换算成px 就是实际尺寸/2 = px;

  • 使用 @import 标识符来导入外联样式。@import 后跟需要导入的外联样式表的相对路径,用;表示语句结束
@import '../plugins/wxParse/wxParse.wxss';

什么是rpx

可以根据屏幕宽度进行自适应,规定屏幕宽度为750rpx,建议开发中将 iPhone6 作为视觉稿的标准

  • iPhone6 屏幕宽度为375px 750物理像素 所以 750rpx = 375px = 750物理像素
  • 1rpx = 0.5px
  • 因此如果想定义一个100px宽度的view 则需要设置width为 200rpx

小程序关联微信公众号确定用户的唯一性

如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过 unionid 来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的 unionid 是唯一的。

换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid 是相同的

bindtap和catchtap的区别

相同点:首先他们都是作为点击事件函数,就是点击时触发。在这个作用上他们是一样的,可以不做区分

不同点:他们的不同点主要是bindtap是不会阻止冒泡事件的,catchtap是阻值冒泡的

canvas自适应屏幕画海报并保存图片

先利用wx.getSystemInfo (获取系统信息)的API获取屏幕宽度

// 在onLoad中调用
const that = this
wx.getSystemInfo({
  success: function (res) {
    console.log(res)
    that.setData({
      model: res.model,
      screen_width: res.windowWidth/375,
      screen_height: res.windowHeight
    })
  }
})

在绘制方法中将参数乘以相对单位即可实现自适应

<canvas  canvas-id="PosterCanvas" style="width:{{screen_width*375+'px'}}; height:{{screen_height*1.21+'px'}}"></canvas>
drawPoster(){
    let ctx = wx.createCanvasContext('PosterCanvas'),that=this.data;
    console.log('手机型号' + that.model,'宽'+that.screen_width*375,'高'+ that.screen_height)
    let rpx = that.screen_width
    //这里的rpx是相对不同屏幕宽度的相对单位,实际的宽度测量,就是实际测出的px像素值*rpx就可以了;之后无论实在iPhone5,iPhone6,iPhone7...都可以进行自适应。
    ctx.setFillStyle('#1A1A1A')
    ctx.fillRect(0, 0, rpx * 375, that.screen_height * 1.21)
    ctx.fillStyle = "#E8CDAA";
    ctx.setFontSize(29*rpx)
    ctx.font = 'normal 400  Source Han Sans CN';
    ctx.fillText('Hi 朋友', 133*rpx,66*rpx)
    ctx.fillText('先领礼品再买车', 84*rpx, 119*rpx)
    ctx.drawImage('../../img/sell_index5.png', 26*rpx, 185*rpx, 324*rpx, 314*rpx)
    ctx.drawImage('../../img/post_car2x.png', 66 * rpx, 222 * rpx, 243 * rpx, 145 * rpx)
    ctx.setFontSize(16*rpx)
    ctx.font = 'normal 400 Source Han Sans CN';
    ctx.fillText('长按扫描获取更多优惠', 108*rpx, 545*rpx)
    ctx.drawImage('../../img/code_icon2x.png', 68 * rpx, 575 * rpx, 79 * rpx, 79 * rpx)
    ctx.drawImage('../../img/code2_icon2x.png', 229 * rpx, 575 * rpx, 79 * rpx, 79 * rpx)
    ctx.setStrokeStyle('#666666')
    ctx.setLineWidth(1*rpx)
    ctx.lineTo(187*rpx,602*rpx)
    ctx.lineTo(187*rpx, 630*rpx)
    ctx.stroke()
    ctx.fillStyle = "#fff"
    ctx.setFontSize(13 * rpx)
    ctx.fillText('xxx科技汽车销售公司', 119 * rpx, 663 * rpx)
    ctx.fillStyle = "#666666"
    ctx.fillText('朝阳区·望京xxx科技大厦', 109 * rpx, 689 * rpx)
    ctx.setFillStyle('#fff')
    ctx.draw()
  },

保存到相册很简单,就是在画完图片之后的draw回调函数里调用canvasToTempFilePath()生产一个零时内存里的链接,然后在调用saveImageToPhotosAlbum()就可以了;其中牵扯到授权,如果你第一次拒绝了授权,你第二次进入的时候在iphone手机上是不会再次提醒你授权的,这时就需要你手动调用了;以下附上代码!

ctx.draw(true, ()=>{
        // console.log('画完了')
        wx.canvasToTempFilePath()({
          x: 0,
          y: 0,
          width: rpx * 375,
          height: that.screen_height * 1.21,
          canvasId: 'PosterCanvas',
          success: function (res) {
            // console.log(res.tempFilePath);
            wx.saveImageToPhotosAlbum({
              filePath: res.tempFilePath,
              success: (res) => {
                console.log(res)
              },
              fail: (err) => { }
            })

          }
        }) 
      })

拒绝授权后再次提醒授权的代码

mpvue.saveImageToPhotosAlbum({
        filePath: __path,
        success(res) {
          mpvue.showToast({
          title: '保存成功',
          icon: 'success',
          duration: 800,
          mask:true
          });
         },
        fail(res) {
            if (res.errMsg === "saveImageToPhotosAlbum:fail:auth denied" || res.errMsg === "saveImageToPhotosAlbum:fail auth deny" || res.errMsg === "saveImageToPhotosAlbum:fail authorize no response") {

          mpvue.showModal({
                title: '提示',
                content: '需要您授权保存相册',
                showCancel: false,
                success:modalSuccess=>{
                  mpvue.openSetting({
                    success(settingdata) {
                      // console.log("settingdata", settingdata)
                      if (settingdata.authSetting['scope.writePhotosAlbum']) {
                        mpvue.showModal({
                          title: '提示',
                          content: '获取权限成功,再次点击图片即可保存',
                          showCancel: false,
                        })
                      } else {
                        mpvue.showModal({
                          title: '提示',
                          content: '获取权限失败,将无法保存到相册哦~',
                          showCancel: false,
                        })
                      }
                    },
                    fail(failData) {
                      console.log("failData",failData)
                    },
                    complete(finishData) {
                      console.log("finishData", finishData)
                    }
                  })
                }
              })
          }
         }
      });

上拉刷新下拉加载

const app = getApp()

Page({
  data: {
    list: 30
  },
  onLoad(options) {
    this.setData({
      list: 30
    })
  },
  onPullDownRefresh() {
    setTimeout(() => {
      this.setData({
        list: 30
      })
      wx.stopPullDownRefresh()
    }, 1000)
  },
  onReachBottom() {
    this.setData({
      list: this.data.list + 30
    })
  }
})
{
  "usingComponents": {},
  "enablePullDownRefresh": true,
  "onReachBottomDistance": 0
}

wx:if和hidden属性有什么区别

wx:if是 组件是否渲染

hidden指的是hidden属性是否添加

开发中选择:

  • 如果操作很频繁 则使用hidden
  • 如果不频繁 则使用 wx:if

wx:for为什么需要绑定key

为什么要绑定key:

  • 当我们希望处于同一层的VNode 进行插入 删除 新增 节点时 可以更好的进行节点的复用 就需要key属性来判断

绑定key的方式有哪些:

  • 字符串: 表示 for循环array中item的某个属性(property) 该property是列表中的唯一的字符串或数字
  • 保留关键字 *this 表示item本身 此时item本身是唯一的字符串或数字

事件传递参数的方法

小程序中常用传递参数的方式是通过 data- 属性来实现,可以在逻辑代码中通过 “el.currentTarget.dataset.属性名称” 获取

target和currentTarget的区别?

target和currentTarget的区别 :

· target指触发事件的元素

· currentTarget指的是处理事件的元素,两者作用在同一个元素上无差别,小程序中常用currentTarget

页面和组件进行数据传递

页面和组件如何进行数据传递 :

· 向组件传递数据可以通过 properties 属性,支持String、Number、Boolean、Object、Array、null等类型

· 向组件传递样式可以通过定义externalClasses属性来实现

· 组件向外传递事件可以在组件内部通过this.triggerEvent将事件派发,页面可以通过bind绑定

网络请求的封装

class Class_Request {
  request(options) {
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,
        success: (res) => {
          resolve(res.data)  //网络请求成功时回调
        },
        fail: reject  //失败时回调
       })
    })
  }

  get(options) {  //get方法
    return this.request({...options, method: "get"})
  }
  post(options) {  //post方法
    return this.request({...options, method: "post"})
  }
}

// 导出
export const API = new Class_Request()

小程序页面跳转

小程序中实现页面跳转有两种方式 :

通过navigator组件

通过wx的API进行页面跳转,常用 :

  • wx.navigateTo():保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
  • wx.redirectTo():关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
  • wx.switchTab():跳转到 abBar 页面,并关闭其他所有非 tabBar 页面
  • wx.navigateBack()关闭当前页面,返回上一页面或多级页面。可通过
  • getCurrentPages() 获取当前的页面栈,决定需要返回几层
  • wx.reLaunch():关闭所有页面,打开到应用内的某个页面

页面跳转数据传递

// Navigate
wx.navigateTo({
  url: '../pageD/pageD?name=raymond&gender=male',
})

// Redirect
wx.redirectTo({
  url: '../pageD/pageD?name=raymond&gender=male',
})


// pageB.js
...
Page({
  onLoad: function(option){
    console.log(option.name + 'is' + option.gender)
    this.setData({
      option: option
    })
  }
})

获取修改别的页面的数据

const pages = getCurrentPages() //获取实例方法
const prevPage = pages[pages.length - 2]  //具体实例
prevPage.setData({info: "my name is wzl"})  //修改数据

小程序的登录流程

1.通过wx.login()获取code

2.将这个code发送给后端,后端会返回一个token,这个token将作为你身份的唯一标识

3.将token通过wx.setStorageSync()保存在本地存储

4.用户下次进入页面时,会先通过wx.getStorageSync() 方法判断token是否有值,如果有值,则可以请求其它数据,如果没有值,则进行登录操作

小程序常见的系统API

小程序常见系统API :

· 展示弹窗API : showToast、showModal、showLoading、showActionSheet

· 分享功能 :通过onShareAppMessage()实现

· 获取设备信息 : 通过wx.getSystemInfo()实现

· 获取用户位置信息 : 通过wx.getLocation()获取

· 本地数据存储 (常用两个):

  • 同步存储数据 : wx.setStorageSync()
  • 同步获取数据 : wx.getStorageSync()

云数据库的增删改查操作

// 1 获取数据库
const db = wx.cloud.database();
// 2 获取到操作的集合
const studentsColl = db.collection("students");

// 添加
studentsColl
      .add({
        data: {
          name: "wmm",
          age: 18,
          height: 1.88,
          address: {
            name: "sx",
            code: "033000",
          },
          hobbies: ["联盟", "吃鸡"],
        },
      })
 // 删除数据
     studentsColl
     .doc("6d85a2b962fefabe1a635e252c570b60")
       .remove()
       .then((res) => {
         console.log(res);
       });

// 根据条件删除
 const _ = db.command;
    studentsColl
      .where({
        age: _.gt(25),
      })
      .remove()
      .then((res) => {
        console.log(res);
      });
 // 修改 某一条数据
    studentsColl
      .doc("0a4ec1f962ff32731a5056974fac5b24")
      .update({
        data: {
          hobbies: ["c", "t", "lq"],
          age: 30,
        },
      })
      .then((res) => {
        console.log(res);
      });
// 2 set 新增 将原来的字段全部替换掉
studentsColl
     .doc("8f75309d62ff32721546b5852c0431e6")
      .set({
        data: {
          age: 31,
        },
      })
      .then((res) => {
        console.log(res);
      });
  // update 更新多条数据
const _ = db.command;
    studentsColl
      .where({
        age: _.gt(25),
      })
      .update({
        data: { age: 10 },
      })
      .then((res) => {
        console.log(res);
      });
    // 1 方式一 根据id查询某条数据
   lolColl
      .doc("b69f67c062ff0a6311e0f21f02fd1047")
      .get()
      .then((res) => {
        console.log(res);
    	});
    // 2 方式二 查询多条数据
 lolColl
      .where({
        nickname: "天才辅助杨小杨",
      })
      .get()
      .then((res) => {
        console.log(res);
      });
// 3 方式三 查询指令, gt/lt
   const _ = db.command;
    lolColl
      .where({
        rid: _.gte(5000000),
      })
      .get()
      .then((res) => {
        console.log(res);
      });

    // 4 正则表达式
 lolColl
      .where({
        nickname: db.RegExp({
          regexp: "z",
          options: "i",
        }),
      })
      .get()
      .then((res) => {
        console.log(res);
      });

    // 5 方式五 获取整个集合中的数据
  lolColl.get().then((res) => {
      console.log(res);
    });
 // 6 分页 skip(offset)/ limit
 let page = 1;
    lolColl
      .skip(page * 5)
      .limit(5)
      .get()
      .then((res) => {
        console.log(res);
      });
 // 7 排序 orderBy("rid")
// 升序 asc
    // 降序 desc
    lolColl
      .skip(page * 5)
      .limit(5)
      .orderBy("rid", "asc")
      .get()
      .then((res) => {
        console.log(res);
    	});
  // 8 过滤字段
 lolColl
      .field({
        _id: true,
        hn: true,
        nickname: true,
        roomName: true,
        rid: true,
      })
      .skip(page * 5)
      .limit(5)
      .orderBy("rid", "desc")
      .get()
      .then((res) => {
        console.log(res);
      });

云存储的上传、下载、删除

    const imageRes = await wx.chooseMedia({
      type: "image",
    });

    const imagePath = imageRes.tempFiles[0].tempFilePath;
    const timestamp = Date.now();
    const openid = "open_xx";
    const extension = imagePath.split(".").pop();
    const filename = `${timestamp}_${openid}_${extension}`;
    const uploadRes = await wx.cloud.uploadFile({
      filePath: imagePath,
      cloudPath: `images/${filename}`,
    });

下载

 const result = await wx.cloud.downloadFile({
      fileID:
        "	cloud://cloud1-0gs04p81a1eb23de.636c-cloud1-0gs04p81a1eb23de-1313399766/images/1660901337462_open_xx_png",
    });
    this.setData({
      tempFilePath: result.tempFilePath,
    });

删除

const res = await wx.cloud.deleteFile({
      fileList: [
        "cloud://cloud1-0gs04p81a1eb23de.636c-cloud1-0gs04p81a1eb23de-1313399766/21.png",
      ],
    });

组件插槽

<!--第一步:封装组件,components/music/index.wxml-->
<view>
  <view>默认内容</view>
  <slot></slot>
</view>

<!--第二步:引入组件,pages/index/index.wxml-->
<f-music></f-music>
<f-music>
  <view>我是定制的内容</view>
</f-music>
<!--第一步:封装组件,components/music/index.wxml-->
<view>
  <view>默认内容</view>
  <slot name="custom1"></slot>
  <slot name="custom2"></slot>
</view>

<!--第三步:引入组件,pages/index/index.wxml-->
<f-music></f-music>
<f-music>
  <view slot="custom1">我是定制的内容1</view>
  <view slot="custom2">我是定制的内容2</view>
</f-music>

多个组件插槽需要进行配置

// 第二步:启用插槽,components/music/index.js
Component({
  // 启用插槽
  options: {
    multipleSlots: true
  }
})

插槽默认值

在使用小程序组件插槽的时候,我们发现这个插槽是不能给默认值的。

<view>
    <view class="slot">
        <slot></slot>
    </view>
    <!-- 插槽不传递值的时候,则作为默认值显示 默认情况下 我们不让其显示 -->
    <view class="default">
        <view>默认内容</view>	
    </view>
</view>

最简单的方式当然是使用一个布尔类型的变量,通过wx:ifwx:else来控制是显示插槽的值,还是显示组件内部的默认值,但是除此之外我们可以使用一个empty伪类来解决。

  // 默认插槽是否显示 如果默认插槽组件内是空的,也就是没有传组件,此时<slot/>
  // 标签在渲染的时候,会消失,则slot标签的父容器此时为空
  .slot:empty + .default {
    // 插槽是空 则显示默认插槽
    display: block;
  }

  .right {
    // 默认情况 我们认为插槽会传值 则不显示
    display: none;
  }
}

分包加载

https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html

开发者通过在 app.json subpackages 字段声明项目分包结构:

{
  // 主包也可以有自己的 pages,即最外层的 pages 字段。
  "pages":[
    // `tabBar` 页面必须在主包内
    "pages/index",
    "pages/logs"
  ],
  "subpackages": [
    // 声明 `subpackages` 后,将按 `subpackages` 配置路径进行打包,`subpackages` 配置路径外的目录将被打包到主包中
    {
      "root": "packageA",
      "pages": [
        "pages/cat",
        "pages/dog"
      ]
    }, {
      "root": "packageB",
      "name": "pack2",
      "pages": [
        "pages/apple",
        "pages/banana"
      ]
    }
  ]
}

https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/preload.html

开发者可以通过配置,在进入小程序某个页面时,由框架自动预下载可能需要的分包,提升进入后续分包页面时的启动速度。

{
  "pages": ["pages/index"],
  "subpackages": [
    {
      "root": "important",
      "pages": ["index"],
    },
    {
      "root": "sub1",
      "pages": ["index"],
    },
    {
      "name": "hello",
      "root": "path/to",
      "pages": ["index"]
    },
    {
      "root": "sub3",
      "pages": ["index"]
    },
    {
      "root": "indep",
      "pages": ["index"],
      "independent": true
    }
  ],
  "preloadRule": {
    "pages/index": {
      "network": "all",
       // 进入页面后预下载分包的 root 或 name。
      "packages": ["important"]
    },
    "sub1/index": {
      "packages": ["hello", "sub3"]
    },
    "sub3/index": {
      "packages": ["path/to"]
    },
    "indep/index": {
       // __APP__ 表示主包。
      "packages": ["__APP__"]
    }
  }
}

http://www.niftyadmin.cn/n/462940.html

相关文章

HTML 教程:学习如何构建网页||HTML 简介

HTML 简介 HTML 简介 现在您可以通过如下的一个 HTML 实例来建立一个简单的 HTML 页面&#xff0c;以此来简单了解一下 HTML 的结构。 HTML 实例 <!DOCTYPE html> <html> <head> <title>页面标题(w3cschool.cn)</title> </head> <…

vue3+vite+ts配置

文章目录 1. src别名配置2. svg配置2.1 封装svg全局组件 3. 集成scss与css完成主题定制3.1 在vite中配置scss 4. 环境变量配置5. 移动端适配6. 自动按需加载7. mock配置 1. src别名配置 下载path模块 npm i types/path 在vue.config.ts 配置别名路径 // vite.config.ts impor…

5. 一线大厂高并发缓存架构实战与性能优化

分布式缓存技术Redis 本文是按照自己的理解进行笔记总结&#xff0c;如有不正确的地方&#xff0c;还望大佬多多指点纠正&#xff0c;勿喷。 课程内容&#xff1a; 1中小公司Redis缓存架构以及线上问题分析 2、大厂线上大规模商品缓存数据冷热分离实战 3、实战解决大规模缓存…

PHP后端开发的学习路线

PHP后端开发的学习路线。以下是一个基本的学习路线&#xff0c;供你参考&#xff1a; 1. 基础知识 学习PHP的基本语法和语义了解PHP的数据类型、变量和常量学习控制结构&#xff08;如条件语句、循环语句&#xff09;和函数 2. Web开发基础 学习HTML和CSS&#xff0c;了解前…

Go语言基础-基础语法

前言&#xff1a; \textcolor{Green}{前言&#xff1a;} 前言&#xff1a; &#x1f49e;这个专栏就专门来记录一下寒假参加的第五期字节跳动训练营 &#x1f49e;从这个专栏里面可以迅速获得Go的知识 本文主要是根据今天所学&#xff08;链接放在了最后&#xff09;总结记录的…

第三方库介绍——Protobuf库(更高效的协议)

文章目录 protobuf综述传输协议与指令创建协议编译协议介绍addressbook.pb.h文件序列化与反序列化的接口 利用soctet实现客户端与服务端传输协议Linux&#xff08;Ubuntu&#xff09;安装protoc步骤编写案例代码Cartoon.prototcpsocket.hMyTcpsocket.hclient.cppserver.cppCMak…

【数据库六】存储过程

存储过程 1.存储过程概述1.1 存储过程定义1.2 存储过程优点1.3 创建存储过程 2. 存储过程参数2.1 输入参数2.2 输出参数2.3 输入输出参数2.4 存储过程参数总结 3. 删除存储过程4.存储过程的控制语句4.1 条件语句if --else4.2 循环语句4.3 存储过程控制语句总结 1.存储过程概述 …

产品设计.从用户体验五要素出发,谈如何设计产品

用户调研--产品定位---产品方案---视觉设计 作者 | 渐渐见减减简https://www.zcool.com.cn/article/ZMTEyNDA2NA.html 用户体验五要素是一种产品分析与设计的方法论&#xff0c;帮助我们以正确方式从0到1设计一款产品。 1 战略层 企业做一个产品前&#xff0c;都要明确几个问题…