• 记录工作中开发遇到的问题(持续更新)


    1. IOS App进入后台之后,超过了后台运行时间(默认5S,可进行后台申请有3分钟),便进入了挂起状态,无法执行代码,但是内存并没有清除。

    做定时器任务时,如果进程被切到后台后,秒数还继续读取,要用 new Date().getTime()

    const tick = () => {  
        timer = setTimeout(() => {
          if (second > 0) {
            let newSecond: number = totalTime - parseInt((new Date().getTime() - startTime) / 1000);
            newSecond = newSecond > 0 ? newSecond : 0;
            setSecond(newSecond);
          } else if (current < list.length - 1) {
            startTime = new Date().getTime();
            setCurrent(current + 1);
            setAnswerNum(answerNum + 1);
            localStorage.answerNum = answerNum+1;
            setSecond(totalTime);
            setSelectAnswer('');
          } else {
            handleEnd();
          }
        }, 1000);
      };
    

    2.防止用户快速点击,调用多次接口

    (1)用户点击一次后,置灰,等返回数据后可再次点击 或 倒数5S后可再次点击

    (2)用户2s内多次点击,都只调用一次接口

    export function throttle(callback, time = 2000) {
      const startTime = new Date();
      if (startTime - endTime > time || !endTime) {
        callback();
        endTime = startTime;
      }
    }
    

    3.前端canvas转base64图片问题。

    (1)dom-to-img 在ios中没办法转为png/ipeg,只能转为svg。

    (2)html2canvas 可能会转换不完整

    解决生成图片不清晰问题

    html2canvas": "^1.0.0-rc.5 在ios10.2.x下 用img标签引入的图片会不显示,这个版本在修改清晰度是需要页面dom进行缩放
    "html2canvas": "1.0.0-alpha.12", 这个版本的ios10不会有问题,清晰度时也不需要缩放dom

    4. IOS时间戳转换问题. 要把 - 转换成 /  new Date('2018/4/26 9:24'); 

    5. ios上只有用户交互触发的focus事件才会起效,而延时回调的focus是不会触发的。

    例如:(1)调用setTimeout开始一个新的调用堆栈,IOS的安全机制开始阻止你触发input元素的focus事件.

    (2)focus()放在callback异步回调函数里

    6. z-index只有在设置了position属性之后才有效。

    z-index有一个从父原则。当两个position:fixed;的div嵌套,内层div的z-index会失效。

    7.身份证号正则:

    const identityNo = /(^[1-9]d{5}(18|19|([23]d))d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)d{3}[0-9Xx]$)|(^[1-9]d{5}d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)d{3}$)/;

     8. 微信小程序将rpx转为px

    px与rpx之间转换的公式:px = rpx / 750 * wx.getSystemInfoSync().windowWidth;

    9. 用css实现信封下面的条纹

    10.taro 根据 编译环境来按需打包

    let html2canvas;
    if (process.env.TARO_ENV === 'h5') {
      html2canvas = require('html2canvas');
    }
    

    11. 目前微信小程序不支持 下载文件到手机本地,只可以下载到微信临时路径 然后打开预览

    https://developers.weixin.qq.com/community/develop/doc/000ae6e1f1cd381b9bf6c86a558400?_at=1559535442703

    12. 对象数组按照字母顺序排序

    const arr = [{
       "A":"选项A",
    },{
       "B":"选项B",
    },{
        "C":"选项C",
    },{
        "D":"选项D",
    }];
    
    arr.sort( (a,b) => Object.keys(a)[0].localeCompare(Object.keys(b)[0]) )
    

    13.关于支付:

    1.支付宝

    (1)支付宝PC端扫码支付,接口会返回一段html片段,一个form表单的提交,直接跳转到支付宝支付即可

    (2)支付宝移动端h5支付,接口会返回一段html片段,一个form表单的提交,直接跳转到支付宝支付即可

    <form name="punchout_form" method="post" action="https://openapi.alipay.com/gateway.do?charset=utf-8&method=alipay.trade.wap.pay&sign=aCegTNCKXD25l80POEK1w9kxAftnnM2iQkxsBBexwzO9LGQYTSGYrPdxflGTmrJMS%2Bf%2FVhFF3qr9YrXdFLsBECd1xHE5OkmD1BQCzZu7Y%2B42Nfw0N%2BNF9zTQMM3uYWCXshzwezEkTjDvRQYG%2FrKwR%2BPVWCtX5yvAk55nheoDmhLkEtVf%2Big0VckdSWk2iwa0%2B9p%2BjllDiCRnnzJlH7cPaI6v%2FCZ40M%2BcVjUhnh7ijCKrFUCnrvzXTHrq6lMyUB6ObwIpHDEQ2TwZ%2F92uc5tYEErMoQDhQKfaaFKDBEHSfZU25oNbKByyb21k2qBChIFcy9Rmp1nVQmGjBrZdCqKjNA%3D%3D&return_url=http%3A%2F%2F192.168.86.40%3A10086%2Fpages%2Forder%2Fdetail%3ForderCode%3D201906171054333533&notify_url=http%3A%2F%2Fhsz-test.lai-ai.com%2Fapi%2Fnotify%2FzfbPayNotify&version=1.0&app_id=2019061265523269&sign_type=RSA2&timestamp=2019-06-17+18%3A03%3A07&alipay_sdk=alipay-sdk-java-3.7.26.ALL&format=json">
    <input type="hidden" name="biz_content" value="{"out_trade_no":"RL3717180307C3D6","total_amount":0.01,"subject":"支付","product_code":"FAST_INSTANT_TRADE_PAY","body":"支付"}">
    <input type="submit" value="立即支付" style="display:none" >
    </form>
    <script>document.forms[0].submit();</script>
    

      

    2 微信支付

    (1)微信PC端扫码支付,接口会返回一个二维码链接 code_url,用qrcode这个库将链接转为二维码图片在前端显示;

    (2)微信移动端H5支付:

      实际操作中。redirect_url并没有回调成功。所以支付成功后在支付页面弹起一个确认框,由用户自己去确认是否支付结束

    (3)微信内h5支付(JSAPI支付,公众号支付)

      有两种方式:

      A. 不用引入sdk包,直接调用 WeixinJSBridge.invoke()方法  WeixinJSBridge内置对象在其他浏览器中无效。(微信支付官方文档

      B.  需要引入sdk包,配置 wx.config(), 然后再调用为微信支付wx.chooseWXPay()(微信公众号官方文档)

       注意:(i)后端返回的字段 signType (签名方式)与调用sdk的方法返回的 signnature 有所不同;

            (ii)支付的时候需要传openId,所以手机号登录也需要获取微信openId保存使用

     14: taro组件 onInput返回 [object promise]

    // 原因是由 async 导致,删除 async就可以了
    onChange = async(e) {
       this.setState({
         val: r.target.value,
       })  
    }  
    
    // 分析: 因为 async返回的是一个promise对象
    // 看taro的Input组件的源码知道 onInput 方法返回的是 onInput(e), 将函数返回,所以会把promise对象返回  
    

      

     15. no-form 自定义校验

    treeStrBack: [
          {
            validator: (_, value: string) => {
              return String(value).split('-').length === 4;
            },
            message: '请选择全部后台分层',
          },
        ],
    

    16.关于setTimeOut

    修改以下 print 函数,使之输出 0 到 99,或者 99 到 0
    要求:
    
    1、只能修改 setTimeout 到 Math.floor(Math.random() * 1000 的代码
    
    2、不能修改 Math.floor(Math.random() * 1000
    
    3、不能使用全局变量
    
    function print(n){
      setTimeout(() => {
        console.log(n);
      }, Math.floor(Math.random() * 1000));
    }
    for(var i = 0; i < 100; i++){
      print(i);
    }  

     分析:首先n还是 0-99,只是因为延时输出,且delay是随机数,所以输出顺序打乱

    (1)直接用code语句,相当于同步函数,会立即执行
    function print(n){
      setTimeout(console.log(n), Math.floor(Math.random() * 1000));
    }
    for(var i = 0; i < 100; i++) {
      print(i);
    }
    
    (2)利用setTimeOut可以传入多个参数
    function print(n){
      setTimeout(() => {
        console.log(n);
      },1, Math.floor(Math.random() * 1000));
    }
    for(var i = 0; i < 100; i++){
      print(i);
    }
    
    (3)修改this指向
    function print(n){
      setTimeout((() => {
        console.log(n)
      }).call(n,[]), Math.floor(Math.random() * 1000));
    }
    for(var i = 0; i < 100; i++){
      print(i);
    }
    

    17. flux,redux,vuex,redux-saga 总结对比;

     

    flux(View  Action  Dispatcher  Store)

    redux(view store action reducer)

    数据触发流程 view或者测试用例等地方触发action,dispatcher会接收到所有的action,然后转发到所有的store,store的改变只能通过action  view触发action,然后 Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。State 一旦有变化,Store 就会调用监听函数,得到当前状态,重新渲染view
    特点 一个应用可以拥有多个 Store,多个Store之间可能有依赖关系;Store 封装了数据还有处理数据的逻辑。 只有一个store
      单向数据流 单向数据流
      可以多个数据源 单一数据源
      对应的store修改对应的view 每个reducer都由跟reducer统一管理

    redux:

    1. redux只有一个store,整个应用的数据都在store里,store的state不能直接修改,每次只能返回一个新的state;

    import { createStore } from 'redux';
    const store = createStore(reducer);
    

    2. Action 必须要有type属性,代表action名称,是个纯对象

    const action = {
      type: 'ADD_TODO',
      payload: 'Learn Redux'
    };

    3. redux用reducer纯函数(相同的输入都会导致相同的输出,不会依赖外部环境变量) 来处理state的计算过程

    createStore接受reducer作为参数 生成新的store,以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

    createStore内部 主要是 subscribe, listeners, getState

    多个 Reducer 可以通过 combineReducers 方法合成一个根 Reducer,这个根 Reducer 负责维护整个 State。

      

    中间件

    Redux 引入了中间件 Middleware 的概念来处理在action异步操作后自动执行reducer(异步的操作发送action的时候,store.dispatch)

    简单来说,就是对 store.dispatch方法进行一些改造

    Redux 提供了一个 applyMiddleware 方法来应用中间件: 把所有的中间件组成一个数组,依次执行

    const store = createStore(
      reducer,
      applyMiddleware(thunk, promise, logger)
    );

    18. 在微信小程序中 跳转外部链接时 需要另外开启一个页面,用webview包起来 

    19. 微信小程序,(taro) 回到顶部 有专门的方法

    wx.pageScrollTo(Object object)

    20. 小程序里  props.children 是打印不出来的  所以 注意不能讲 props.children 作为判断条件  

      

     21. ![小程序自定义导航栏的问题](https://developers.weixin.qq.com/community/develop/article/doc/00068aec7941f8f57509794be54413

      注意: 在微信 7.0.5以下的版本,getMenuButtonBoundingClientRect 这个方法 获取的值都为0;

    22.  onShareAppMessage 这个方法只能在Page页面使用,在自定义组件内不可。

    解决: 对于自定义组件中需要传参的场景,可以通过 

    <Button type="share" data-{自定义变量名}={传的参数}></Button>
    
    //在page页面
    this.onShareAppMessage = (res) => {
      const {} = res.target.dataset.自定义变量名;
    }

     23. 微信小程序授权登录流程图   (文章

    (1)wx.login()这个API的作用就是为当前用户生成一个临时的登录凭证,这个临时登录凭证的有效期只有五分钟。我们拿到这个登录凭证后就可以进行下一步操作:获取openidsession_key

    (2)session_key就保证了当前用户进行会话操作的有效性,这个session_key是微信服务端给我们派发的。可以用这个标识来间接地维护我们小程序用户的登录态

    session_key需要在自己的服务端请求微信提供的第三方接口https://api.weixin.qq.com/sns/jscode2session

     1》 session_key和微信派发的code是一一对应的,同一code只能换取一次session_key。

     2》session_key是有时效性的,即便是不调用wx.login,session_key也会过期,过期时间跟用户使用小程序的频率成正相关,但具体的时间长短开发者和用户都是获取不到的

    24. 前端点击下载图片(不同域名,先配置跨域)

     1 const onDownload = (attUrl, attName) => {
     2     fetch(attUrl)
     3       .then(res => res.blob())
     4       .then(blob => {
     5         const a = document.createElement('a');
     6         const url = window.URL.createObjectURL(blob);
     7         const filename = attName;
     8         a.href = url;
     9         a.download = filename;
    10         a.click();
    11         window.URL.revokeObjectURL(url);
    12       });
    13   };

     25. 保存图片: h5只能调用浏览器 长按保存

            小程序 有特定的api

    微信小程序,想要长按图片 识别小程序码 Image需要添加一个属性

    26. taro 原生canvas画图

    1. canvas h5 画图的时候要创建对象  const imgs = new Image(); 但是在小程序的时候会报错  Image is undefine 

    import Taro, { useScope} from '@tarojs/taro';
    const scope = useScope();
    
    Taro.createSelectorQuery()
                .in(scope)
                .select('#canvas')
                .fields({
                  node: true,
                  size: true,
                })
                .exec(async res => {
                  const dpr = Taro.getSystemInfoSync().pixelRatio;
                  const canvas = res[0].node;
                  const { width } = res[0];
                  const { height } = res[0];
                  canvas.width = width * dpr;
                  canvas.height = height * dpr;
    
                  const ctx = canvas.getContext('2d');
                  ctx.scale(dpr, dpr);
    
                  const img = canvas.createImage();
                  img.onload = async () => {
                    ctx.drawImage(img, 0, 0, width, height);
    
                    await drawData(ctx, result);
    
                   ctx.drawImage(img2, 25, 180, 55, 55);
                   const fileRes = await Taro.canvasToTempFilePath({    // 需要放在回调函数中
                          canvas,
                           width * dpr,
                          height: height * dpr,
                        });        
                  };
                  img.src = posterBg;
                });

    特别注意:⚠️ ctx.font = '12px sans-serif';   必须写完整 如果只写 12px 部分安卓机会报错闪退

     27. 把一个数组对象按照另一个数组来进行顺序重排

    const chartTextArr = [
      '气虚质',
      '阴虚质',
      '阳虚质',
      '湿热质'
    ];
    
    const arr  = [
    {name: '气虚质', sort:20},
    {name: '阴虚质', sort:210},
    {name: '阳虚质', sort:60},
    {name: '湿热质', sort:50},
    ];
    
    (arr || []).sort((a, b) => {
      return chartTextArr.indexOf(a.name) - chartTextArr.indexOf(b.name);
    });

     28,防止将整个项目域名封了,所以将支付操作放在另外一个域名下,实现单点登录

    思路:创建一个iframe, iframe的src 携带一个namespace参数和动态时间戳。 在A域名下 调用postMessage方法 postMessage的参数里会传递一个method方法 第三方页面 中通过监听 window.addEventListener('message') 事件,针对 method进行 对应的存储操作 (注意:ios 真机上localStorage会存储不了,所以在ios上可以用cookie)在B域名下 我们可以调用 crossStorage.get 从第三方页面拿到acessToken数据

    import { getEnv } from '@/utils/mobile';
    
    const { isRelease } = getEnv();
    
    type SendMessageParams = {
      method: 'set' | 'get' | 'remove';
      key: string;
      data?: any;
    };
    
    type InitParams = {
      isRelease: boolean;
    };
    
    let origin = '';
    let isLoad = false;
    const iframe = window ? document.createElement('iframe') : '';
    
    const messageList = [];
    const callbackMap = {};
    
    function encode(data) {
      return window.btoa(encodeURIComponent(JSON.stringify(data)));
    }
    
    function decode(data) {
      return JSON.parse(decodeURIComponent(window.atob(data)));
    }
    
    function receiveMessage(e) {
      if (origin.includes(e.origin) && e.data && typeof e.data === 'string') {
        const { id, data } = JSON.parse(e.data);
        if (callbackMap[id]) {
          callbackMap[id].resolve(data);
        }
      }
    }
    
    function postMessage(message) {
      iframe.contentWindow.postMessage(message, origin);
    }
    
    function sendMessage<T>(params: SendMessageParams) {
      return new Promise<T>((resolve, reject) => {
        const id = new Date().getTime().toString();
        callbackMap[id] = {
          resolve,
          reject,
        };
    
        const message = JSON.stringify({
          id,
          ...params,
        });
        if (isLoad) {
          postMessage(message);
        } else {
          messageList.push(message);
        }
      });
    }
    
    if (window) {
      window.addEventListener('message', receiveMessage, false);
    }
    
    function init() {
      if (!isLoad) {
        const url = isRelease ? 'h5.lai-ai.com' : 'h5.fe.lai-ai.com';
        origin = `https://${url}/storage/index.html?namespace=umart-mobile&t=${new Date().getTime()}`;
        iframe.src = origin;
        iframe.style.display = 'none';
        // iframe.setAttribute('style', 'position: fixed; left: 0; top: 0;  100vw; height: 100vh');
        iframe.id = 'cross-iframe';
        iframe.onload = () => {
          isLoad = true;
          if (messageList.length) {
            messageList.forEach(message => {
              postMessage(message);
            });
          }
        };
    
        document.body.appendChild(iframe);
      } else {
        isLoad = true;
      }
    }
    
    async function remove(key: string): Promise<void> {
      return sendMessage({
        key,
        method: 'remove',
      });
    }
    
    async function get<T = any>(key: string, defaultValue?: T, isMix?: boolean): Promise<T> {
      let res = await sendMessage<T>({
        key,
        method: 'get',
      });
    
      try {
        // @ts-ignore
        res = res && isMix ? decode(res) : res;
        return res || defaultValue;
      } catch (e) {
        await remove(key);
        window.location.reload();
        return null;
      }
    }
    
    async function set<T = any>(key: string, data: T, isMix?: boolean): Promise<void> {
      return sendMessage({
        key,
        method: 'set',
        data: isMix ? encode(data) : data,
      });
    }
    
    export default {
      init,
      get,
      set,
      remove,
    };
    

     

    <script>
            function getUrlParam(name) {
                if (!name) {
                    return ''
                }
                var url = location.search;
                var reg = new RegExp('(?:[?&]|^)' + name.replace(/(?=[\^$*+?.():|{}])/, '\') + '=([^?&#]*)', 'i');
                var match = url.match(reg);
                return decodeURIComponent(!match ? '' : match[1]);
            }
    
            var namespace = getUrlParam('namespace');
    
            window.addEventListener('message', function(e) {
                if (e.data && typeof e.data === 'string') {
                    var current = JSON.parse(e.data);
                    if (current.method) {
                        var id = current.id;
                        var method = current.method;
                        var key = namespace + '-' + current.key;
                        var data = current.data;
                        var resData = '';
                        switch (method) {
                            case 'set': {
                                var str = JSON.stringify(data);
                                localStorage.setItem(key, str);
                                Cookies.set(key, str);
                                break;
                            }
                            case 'get': {
                                var strData = localStorage.getItem(key) || Cookies.get(key);
                                if (strData) {
                                    try {
                                        resData = JSON.parse(strData);
                                    } catch (e) {}
                                }
                                break;
                            }
                            case 'remove': {
                                localStorage.removeItem(key);
                                Cookies.remove(key);
                                break;
                            }
                            default: {}
                        }
                        e.source.postMessage(JSON.stringify({
                            id: id,
                            method: method,
                            data: resData,
                        }), e.origin);
                    }
                }
            }, false);
        </script>
    

    29. Taro v2.x  canvas

    (1)h5环境下,可以直接调用  Taro.createCanvasContext

          为了处理跨域,可以将图片转为base64; 为了解决图片模糊不高清,可将所有数据 画布缩放2倍

    (2)小程序环境下, 直接调用  Taro.createCanvasContext('canvas') 画图不成功;

      注意:img.onload的处理 和 img.src的位置

    const dpr = Taro.getSystemInfoSync().pixelRatio;
                Taro.createSelectorQuery()
                  .in(scope)
                  .select('#canvas')
                  .fields({
                    node: true,
                    size: true,
                  })
                  .exec(async res => {
                    const canvas = res[0].node;
                    const { width } = res[0];
                    const { height } = res[0];
                    canvas.width = width * dpr;
                    canvas.height = height * dpr;
    
                    const ctx = canvas.getContext('2d');
                    ctx.scale(dpr, dpr);
    
                    const img = canvas.createImage();
                    img.onload = async () => {
                      ctx.drawImage(img, 0, 0, width, height);
    
                      await drawData(ctx, result);
    
                      const img2 = canvas.createImage();
                      img2.onload = async () => {
                        ctx.drawImage(img2, 25, 180, 55, 55);
                        const fileRes = await Taro.canvasToTempFilePath({
                          canvas,
                           width * dpr,
                          height: height * dpr,
                        });
                        if (props.onCreateImg) {
                          props.onCreateImg(fileRes.tempFilePath);
                        }
                        setPic(fileRes.tempFilePath);
                      };
                      img2.src = result.infraUserLogoUrl || avartar;
                    };
                    img.src = posterBg;
                  });
    

      

  • 相关阅读:
    JavaScript 闭包
    JavaScript for循环
    JavaScript switch语句
    JavaScript if...else 语句
    JavaScript流程控制语句脑图
    JavaScript比较和逻辑运算符
    JavaScript运算符
    记录一下获取浏览器可视区域的大小的js
    20181016记录一次前端布局
    20181015记录一个简单的TXT日志类
  • 原文地址:https://www.cnblogs.com/hsprout/p/10405384.html
Copyright © 2020-2023  润新知