随手记

7 阅读12分钟

腾讯IM实时通讯

开发指引:有体验版和开发版可以免费试试基础功能。文档很详细,api很多。

数字千位符分割,正则

'1234567.89'.replace(/\B(?=(\d{3})+(?!\d))/g, ',')

  • /\B/g: 匹配以下几种情况。正则中 单词字符指的是 字母、数字、下划线, 在这里,匹配数字和数字之间的位置。 '1234567.89'.replace(/\B/g, ',')输出结果是'1,2,3,4,5,6,7.8,9'。 数字7,8是单词字符,而小数点不是单词字符,7和小数点,小数点和8之间匹配不到。

    • 字符串第一个字符为非“字”字符
    • 字符串最后一个字符为非“字”字符
    • 两个单词字符之间
    • 两个非单词字符之间
    • 空字符串
  • x(?=y): 正向先行断言。 匹配x 仅当x后面是y时匹配;

  • (\d{3})+: 匹配三位数字一次或者多次

  • x(?!y): 正向否定查找, 匹配 x 仅当x后面不跟着 y的时候。所以 (?!\d)指的是仅当后面不跟着数字的时候

  • (?=(\d{3})+(?!\d)): 指的是 匹配位置后有n个连续三位数字,且n个连续三位数字后不是数字,n>=1

匹配过程

  • 位置0(字符12之间)

    • \B:前后均为数字,匹配。

    • 断言验证

      • 后面是 234567.89,可分割为 234 和 567(两组三位数)。
      • 三位数组后是小数点(非数字),满足 (?!\d)
    • 结果:在此处插入逗号 ,

  • 位置1(字符23之间)

    • \B:匹配。

    • 断言验证

      • 后面是 34567.89,仅能分割为 345(剩余 67 不足三位)。
      • 匹配后紧跟数字 6,不满足 (?!\d)
    • 结果:不匹配。

  • 位置3(字符45之间)

    • \B:匹配。

    • 断言验证

      • 后面是 567.89,可分割为 567(一组三位数)。
      • 三位数组后是小数点,满足 (?!\d)
    • 结果:在此处插入逗号 ,

  • 其他位置
    后续位置无法满足断言条件(剩余数字不足三位或后面紧跟数字)。

path

  • path.resolve: 方法是把一个路径或路径片段的序列解析为一个绝对路径。 如果传入的第一个参数是非根路径,会在基于当前脚本所在目录的基础上,拼接后续传入的路径片段。
    • 注意点: path.resolve如果传入了 以 / 开头的片段,会直接跳转到脚本所在磁盘根部 path.resolve('a', '/b', 'c')===> E://b/c
    • 若是要拼接其他盘的绝对路径,第一个参数需要传入其他盘的绝对路径
  • path.join:用于连接路径, 主要用于连接相对路径。会把全部给定的 path 片段连接到一起,并规范化生成的路径。只是拼接传入路径片段
  • __dirname 是被执行的js 文件的地址 ——文件所在目录.当前执行脚本所在的目录
  • process.cwd() 是当前Node.js进程执行时的文件夹地址-工作目录。 如 node file.js,则这个时候process.cwd()就是file.js文件所在的目录地址

pnpm

performant npm 高性能的 npm ,是由`npm/yarn'衍生出来的,解决了'npm/yarn'潜在的一些bug。

  • 特点: 速度快,节省磁盘空间,支持monorepo,安全性高

monorepo

用一个仓库管理多个项目项目代码的管理策略。

lerna

monorepo 可以利用 lerna 统一包管理 lerna 可以帮助解决 monorepo 因为多个子项目放在一个代码仓库,并且子项目之间又相互依赖时带来的两个棘手问题:

  • 在多个子目录执行相同的命令时需要手动进入各个目录,并执行命令;
  • 子项目更新后只能手动追踪依赖该项目的其他子项目,并升级其版本。
  • vue组件 本组件内通过name可以自己调用自己做子组件, 树形结构

simplest-mock-server

一个开箱即用的搭建本地 mock 接口的工具

其他

  1. canonical 和 alternate 标签:
  • canonical:当一个网站有多个版本的内容时,例如同一个页面有多个URL,或者内容存在多种语言或版本,这些URL之间的内容可能会被搜索引擎认为是重复的,从而降低网站的排名。在这种情况下,使用canonical标签可以指示搜索引擎哪个URL应该被认为是原始的或主要的版本,避免因为重复内容而被搜索引擎降权

  • alternate:pc做简单的响应式可能不太满足需求,单独设置移动端站点。PC站点加alternate指向移动端站点

  • 不同的URL对应分别对应pc和移动端的同一套内容网站时

 <!-- PC端页面head标签内对应添加如下代码:(以职位百科详情页为例) -->
 <meta name="applicable-device" content="pc">     
 <link rel=“alternate” media=“only screen and(max-width: 640px)”   href="https://mbaike.51job.com/zhiwei/04021/" >
<!-- 移动端页面head标签内对应添加如下代码:    -->
<meta name="applicable-device"   content="mobile"> 
<link rel="canonical" href="https://baike.51job.com/zhiwei/04021/">

  1. webpack-dev-server

webpack-dev-server: 是一个本地开发服务器,会自动监听变化,自动打包构建,自动更新刷新浏览器

  • 特点: 不会产生dist文件,将打包结果暂时存在内存中,内部的http-sever访问这些文件并读取数据,发送给浏览器 减少磁盘的读取,提高构建效率
  • 写法:在webpack.config.js文件中,通过属性devServer: { } 设置与 webpack-dev-server相关的配置
  1. bin 目录是一个存放可直接运行文件的目录,里面有很多能够帮助我们提升效率的操作
  2. process 模块是 Node 环境的全局模块,属于全局对象,它好比浏览器环境上的 window 对象,不同的是它存在于 Node 环境中而已,它身上有很多方法与属性。
  • process.argv: 能获取到命令行输入的一些参数,以空格分割
  1. file-save: 和 fs.writeFileSync(path, content)类似,创建和写入文件的,对写入进行封装,可以链式调用
   var fileSave = require('file-save');
//  the first line will create a writeStream to the file path
    fileSave('sample/test')
        .write('this is the first line', 'utf8')
        .write('this is the second line', 'utf8', function() {
            console.log('writer callback')
        })
        .end('this is the end')
        .error(function() {
            console.log('error goes here')
        })
        .finish(function() {
            console.log('write finished.')
        })
  1. uppercamelcase 模块是将破折号/点/下划线/空格分隔的字符串转换为驼峰形式。
  2. JSON.stringify()第三个参数
  • 只有一个参数,就是将数据转换成字符串
obj = {
  name:'wxz',
  age:78,
  birth:'2020-02-15'
}
console.log(JSON.stringify(obj)); // {"name":"wxz","age":18,"birth":"2020-02-15"}
  • 第二个参数:过滤对象中属性,需要转换返回的属性. 可以是一个数组或者一个函数
console.log(JSON.stringify(obj, ['name', 'age'])); // {"name":"wxz","age":18}
  • 第三个参数:用于控制结果字符里的间距
console.log(JSON.stringify(obj, ['name','age'], '~')); // '{\n~"name": "wxz",\n~"age": 78\n}'
console.log(JSON.stringify(obj, ['name','age'], ' ')); // '{\n "name": "wxz",\n "age": 78\n}'
  1. json-templater 模块是一个字符串模板生成器,它能帮助我们更加快速、便捷的生成好字符串模板。
  2. charCodeAt 和 fromCharCode
// charCodeAt() 方法可返回指定位置的字符的 Unicode 编码,返回值是 0 - 65535 之间的整数
"HELLO WORLD".charCodeAt(0) // 72
// fromCharCode() 可接受一个指定的 Unicode 值,然后返回一个字符串。
String.fromCharCode(98) // b
  1. 经常碰到 Nginx: 301 Moved Permanently Nginx负责设置301 Moved Permanently状态码。但nginx.conf控制Nginx如何处理301 Moved Permanently状态码! 换句话说,要不要进行页面重定向,和怎么重定向,完全是用户配置的结果! Nginx主动设置301 Moved Permanently状态码只有一种情况,当用户在浏览器输入了一个url地址,末尾部分是一个文件目录且不以斜杠”/“结尾,比如 “www.test.com/index” 。 Nginx没有找到index这个文件,但发现了index是个目录。于是本次访问的返回状态码就会被设置成301 Moved Permanently。

  2. GraphQL vs REST APIs

  • REST: 它侧重于分配 http请求方法(get,post,patch,delete,put) 和 url端点之间的关系
    • 缺点
      • 获取过度:客户端收到了很多不必要的数据。比如:客户端请求获取用户名和头像,但返回了除这两个之外的 地址,年龄,电话,职业,状态等
      • 获取不足: 向服务器发出大量额外请求以检索必要的数据。比如:客户端需要 用户名和头像外还需要 朋友列表。这可能需要两个接口才能拿到.
    • 有点: 更易于理解,且有更简单和更可预测的数据结构。服务器响应速度相对好
  • GraphQL:
    • 查询(query): 查询语言发出请求来定义必要数据的结构。这些请求由一系列字段组成,这些字段定义了所请求数据的形状,每个字段都包含自己的一组子字段。然后,服务器使用与查询形状匹配的 JSON 对象响应请求
        query {
        user(id: 1) {
          id
          name
          address
          friends {
            id
            name
            address
          }
        }
      }
    
    
    • 变更(Mutations): 更改服务器数据,通常用于创建、更新和删除操作
        mutation {
          createUser(input: { name: "Alice", address: "123 Main St" }) {
            id
            name
            address
          }
        }
    
    
    • 相比于REST API v3,它最强大的优势在于,你能够精确的定义所需要的数据,并且毫无冗余。通过GraphQL,你只需要一次请求就能取到通过多个REST请求才能获得的数据。
  1. cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js
    • cross-env: 能跨平台设置和使用环境变量
    • cross-env BABEL_ENV=utils 作用: 设置 process.env.BABEL_ENV 的值为 utils
    • babel src --out-dir lib --ignore src/index.js: 运行Babel指令编译src目录下文件,输出到 lib文件夹下,编译时忽略 src/index.js文件

Vue 相关

Props

  1. props属性申明时,如果是对象或者数组,设置默认值时,必须用函数返回,该函数接收组件所接收到的原始 prop 作为参数
  2. props 自定义类型校验函数
// 自定义类型校验函数
  propF: {
    validator(value) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // 所有 prop 默认都是可选的,除非声明了 required: true。 
  // 除 Boolean 外的未传递的可选 prop 将会有一个默认值 undefined, 未传递的 Boolean 类型默认 false。
  1. 一个属性被允许是多个类型,且其一种是 Boolean时
<MyComponent disabled />
// disabled 将被转换为 true
defineProps({
  disabled: [Boolean, Number]
})
  
// disabled 将被转换为 true
defineProps({
  disabled: [Boolean, String]
})
  
// disabled 将被转换为 true
defineProps({
  disabled: [Number, Boolean]
})
  
// disabled 将被解析为空字符串 (disabled="")
defineProps({
  disabled: [String, Boolean]
})
  1. 使用一个对象绑定多个props
const data = {
  id: 1,
  title: 'My Journey with Vue'
}
<MyComponent  v-bind="data" />
<!-- 相当于 -->
<MyComponent  :id="data.id" :title="data.title" />

Vue.extend, 扩展Vue实例,以js的方式调用组件

import Vue from 'vue';
import store from '@/store';
import router from '@/router';
import shopVip from './shopVip.vue';
import chatAdd from './chatAdd.vue';
// 使用extend方法创建vue的子类
function globalExtend(data, Modal, callback) {
  let instance = null;
  if (!instance) {
    const PoupDom = Vue.extend(Modal);
    instance = new PoupDom({ store, router }).$mount();
  }
  document.body.appendChild(instance.$el);
  Vue.nextTick(() => {
    instance.showModalFun(data);
  });
  if (callback && typeof callback === 'function') {
    callback();
  }
}

export const vipModalDialog = function (data) {
  globalExtend(data, shopVip);
};
export const chatAddModal = function (data) {
  globalExtend(data, chatAdd);
};

Vue.prototype.$showVipModal = vipModalDialog; //vip购买弹框
Vue.prototype.$showChatAddModal = chatAddModal; //聊天增补包

其他

  1. 在双大括号或者指令中使用方法时,方法在组件每次更新时都会被重新调用,因此不应该产生任何副作用,比如改变数据或触发异步操作。
  2. vue模板使用表达式时,可用的内置全局对象
import { makeMap } from './makeMap'

const GLOBALS_ALLOWED =
  'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' +
  'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' +
  'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console'

export const isGloballyAllowed = /*#__PURE__*/ makeMap(GLOBALS_ALLOWED)

/** @deprecated use `isGloballyAllowed` instead */
export const isGloballyWhitelisted = isGloballyAllowed
  1. 指令的动态参数,比如v-bind,v-on的 (指令|参数|修饰符=值)
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>

<a v-on:[eventName]="doSomething"> ... </a>
<!-- 简写 -->
<a @[eventName]="doSomething">

<!-- attributeName 和 eventName 即是变量:它们的值应当是一个字符串或者null,为null即意味着移除该绑定 -->
<!-- 如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式 -->
  1. Vue中initState()是在beforeCreate和created之间
    if (opts.props) initProps(vm, opts.props)//初始化Props
    if (opts.methods) initMethods(vm, opts.methods)//初始化methods
    if (opts.data) {
    initData(vm)} else {
    observe(vm._data = {}, true /* asRootData */)}//初始化data
    if (opts.computed) initComputed(vm, opts.computed)//初始化computed

手机网站支付功能

  1. 支付页面点 立即支付 后, 调后台接口创建订单
// 新增vip用支付接口  params, AndoroParams, iosParams, fromPage
export function createPayOrderVip(payParams) {
  store.commit('payOrder/SET_LOADING', true);
  const fromModule = payParams.fromModule;
  if (equipmentSystem() == 9) {
    // 安卓
    const AndoroParams = { itemId: payParams.itemId, qty: payParams.qty, tag: payParams.tag, fromModule };
    store
      .dispatch('payOrder/createOrder', AndoroParams)
      .then((data) => {
        // 判断地址栏是否有pageType参数
        let pageType = getQueryString('pageType');
        const fromPage = payParams.fromPage || '';
        router.push({
          path: `/pay?orderId=${data}&pageType=${pageType}&fromModule=${fromModule}&fromPage=${fromPage}`,
        });
      })
      .finally(() => {
        store.commit('payOrder/SET_LOADING', false);
      });
  } else if (equipmentSystem() == 8) {
    // 此处唤起苹果内购
    const iosParams = { productid: payParams.productid, tag: payParams.tag, fromModule };
    window.webkit.messageHandlers.model.postMessage(['buyProduct:', iosParams]);
    setTimeout(() => {
      store.commit('payOrder/SET_LOADING', false);
    }, 1000);
  }
}

createOrder(event) {
      this.$emit('createOrder', event);
      if (!isWeixin) {
        //非小程序环境
        const fromModule = getQueryString('fromModule') || '';
        // 之前存在订单并且是安卓机器
        if (this.checkedPop.tradeOrderCode && equipmentSystem() == 9) {
          // 直接跳转支付页,不需要创建订单
          this.$router.push({
            path: `/pay?orderId=${this.checkedPop.tradeOrderCode}&fromModule=${fromModule}`,
          });
        } else {
          /* 如果有赠品选择带赠品的商品Id, 否则取checked.id */
          let params = {
            fromModule: fromModule,
            itemId: this.orderInfo.itemId,
            qty: 1,
            tag: this.orderInfo.tag,
            productid: this.orderInfo.iosProductId,
            fromPage: this.fromPage,
          };
          createPayOrderVip(params);
        }
      } else {
        //小程序环境
        this.appletPay();
      }
    },
    //小程序支付
    appletPay() {
      this.$store.commit('payOrder/SET_LOADING', true);
      this.$store
        .dispatch('payOrder/createOrder', {
          itemId: this.orderInfo.itemId,
          qty: 1,
          tag: this.orderInfo.tag,
          fromModule: getQueryString('fromModule') || '',
        })
        .then((orderCode) => {
          wx.miniProgram.navigateTo({
            url: `/src/pages/pay/index?orderCode=${orderCode}`,
          });
        })
        .finally(() => {
          this.$store.commit('payOrder/SET_LOADING', false);
        });
    },

  // 支付页面,确认支付调用方法
    // 安卓支付
    androidpay() {
      let param = {
        orderCode: this.orderId,
        payMethod: this.payMethod,
      };
      this.$store.dispatch('payOrder/getPayInfo', param).then((res) => {
        let url = '';
        if (this.payMethod == 1) {
          // 支付宝支付
          // url = 'ali://wap/pay?' + 'orderId=' + this.orderId + '&' + 'orderInfo=' + res.vendor_data;
          url = `ali://wap/pay?orderId=${this.orderId}&orderInfo=${res.vendor_data}`;
        } else if (this.payMethod == 2) {
          // 微信支付
          /* let data = JSON.parse(res.vendor_data);
          url = url + 'weixin://wap/pay?';
          for (let item in data) {
            url = url + item + '=' + data[item] + '&';
          }
          url = url + 'orderId=' + this.orderId; */
          let data = JSON.parse(res.vendor_data);
          let weixin = '';
          for (let item in data) {
            weixin = `${weixin}${item}=${data[item]}&`;
          }
          url = `weixin://wap/pay?${weixin}orderId=${this.orderId}`;
        }
        window.location = url;
      });
    },

    // 支付页面:还需要轮询 调接口查订单状态,确认支付成功后跳转

trtc

rtc-detect 用来检测当前环境对 TRTC SDK 的支持度。 trtc-js-sdk是腾讯云实时音视频通讯解决方案的 Web 端 SDK,它是通过 HTML 网页加载的 JavaScript 库。Web 开发者可以使用 TRTC Web SDK 提供的 API,在您的业务网站上实现实时音视频通话、直播等功能。

js创建事件的原生方法 document.createEvent

// 创建事件
const event = document.createEvent('Event');
// 初始化事件
event.initEvent('myEvent', true, true);
// 监听事件
document.addEventListener('myEvent', () => {
  console.log('myEvent 触发了');
});
// 触发事件
document.dispatchEvent(event);

创建自定义事件

function CustomEvent(event, params) {
  params = params || { bubbles: false, cancelable: false, detail: undefined }
  var evt = document.createEvent('CustomEvent')
  evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
  return evt
}
// 触发事件
window.dispatchEvent(new CustomEvent('modeChange', { detail: { name: 'wxz', age: 28 } }))
// 可以监听这个事件
window.addEventListener('modechange', (ev) => {console.log('wxz', ev)})

app原生和H5的通信

android 添加方法给 H5 调用

主要通过WebView的addJavascriptInterface实现

  1. 定义类
// 定义类
public class WebAppInterface {
    private Context context;
    private WebView webView;

    public WebAppInterface(Context context, WebView webView) {
        this.context = context;
        this.webView = webView;
    }

    @JavascriptInterface
    public void showToast(String message) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }
}
  1. 配置WebView并注入对象: 在Activity中启用JavaScript并将对象注入WebView。
WebView webView = findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
// 注入对象,H5通过window.Android访问
webView.addJavascriptInterface(new WebAppInterface(this, webView), "Android");
// addJavascriptInterface该方法:第一个参数是注入的对象实例,第二个参数是对象在H5中调用的名称。
webView.loadUrl("file:///android_asset/index.html"); // 加载本地或远程H5页面
  1. H5调用原生方法: H5页面通过window.Android调用暴露的方法。
// 调用显示Toast
window.Android.showToast('Hello from H5!');

ios 添加方法给 H5 调用

使用的WKScriptMessageHandler

// ios手机,h5调用类似下面: 其中 nativeBridge以及传参形式 是app的同学定义的
window.webkit.messageHandlers.nativeBridge.postMessage({
    action: 'showToast',
    message: 'Hello from H5!'
});