Zhili's blog Zhili's blog
首页
关于
GitHub (opens new window)

Zhili

转型中的前端老兵
首页
关于
GitHub (opens new window)
  • webpack 区分环境使用CDN以及HtmlWebpackPlugin插件的编写
  • 使用 Mpvue 开发小程序总结
  • 前端知识汇总-持续更新
    • webGL知识汇总-持续更新
    • React思想要点
    • Mapbox 入门初试
    • 构建自己的 GLSL 绘图器 - 2d 版
    • webGL二维有向距离场(SDF)及布尔运算
    • webGL入门:绘制一个三角形
    • 使用 GLSL 绘图尝试:绘制正弦曲线
    • 简单解析虚拟 DOM
    • 自己动手开发一个 markdown 转微信文章工具
    • Vue组件扩展及权限管理的实现技巧
    • 使用 CSS 绘制三角形
    • Nginx 前端配置和使用
    • webpack按需加载配置和babel编译
    • 面试总结39题
    • 使用node反向代理接口改造旧项目
    • 可视化表单搭建系统的开发与思考
    • 前端
    zhili
    2019-01-11

    前端知识汇总-持续更新

    持续更新工作中的一些经验教训

    # 1. JS 中的 this 是由函数调用者调用的时候决定的。

    this 是在函数被调用的时候决定的,如果没有显示的调用者,this 指向的是全局对象,在浏览器中指向的是 window,在 node 指向的是 global。但是在严格模式下指向的是 undefined。

    # 2.如果你使用了 ES6 的 class 语法,所有在 class 中声明的方法都会自动地使用严格模式

    # 3.箭头函数中的 this 永远绑定了定义箭头函数所在的那个对象

    # 4.React 为什么需要手动绑定 this?

    以 1-3 条知识为基础,react 的 render 函数中事件绑定原写法为

    <button onClick={this.onDismiss}>删除</button>
    
    1

    相当于:

    class App {
      onDismiss() {
        console.log(this)
      }
    
      render() {
        const onDismiss = this.onDismiss
        onDismiss()
      }
    }
    
    const app = new App()
    app.render() // 输出结果为:undefined
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    实例 app 的 render 方法里面,将 App 对象的方法 onDismiss 方法赋值给 onDismiss,然后调用 onDismiss(),onDismiss 在调用时并没有显示的调用者,因此调用者会为全局对象,根据上面第 2 条,class 中使用的为严格模式,所有 this 指向的为 undefined.

    以上,react 中事件调用需要手动绑定 this,在构造函数中手动 bind 或者使用箭头函数都可以

    # 5.requestAnimationFrame 回调函数中的时间?

    requestAnimationFrame 回调函数的时间是 DOMHighResTimeStamp,它是自当前文档生命周期开始以来经过的毫秒数,而不是调用 requestAnimationFrame 开始的时间戳,如果中途调用 cancelAnimationFrame 取消了循环,在下次再调用 requestAnimationFrame 的时候时间并不会重置,如果需要重置,需要自己维护时钟。

    # 6 数组循环中使用 await async

    在循环中使用 await 有 2 中方法,一种是使用 for in 语法,如下:

    async function start() {
      const array = [1, 2, 3, 4, 5]
    
      for (const a in array) {
        const result = await Promise.resolve(array[a])
        console.log(result)
      }
      console.log('end')
    }
    start()
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    另外一种是使用 Promise.all 结合 map 方法,如下:

    async function start() {
      const array = [1, 2, 3, 4, 5]
    
      await Promise.all(
        array.map(async a => {
          const result = await Promise.resolve(a)
          console.log(result)
        })
      )
      console.log('end')
    }
    start()
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    2 种方式都有着相同的输出:

    1
    2
    3
    4
    5
    end
    
    1
    2
    3
    4
    5
    6

    # 7 Vue 页面刷新不触发 beforeDestroy

    Vue生命周期是Vue对象的生命周期,生命周期函数是Vue对象在创建、挂载和销毁中调用的一些钩子函数,浏览器刷新重载是浏览器触发,与 Vue 对象无关,因此,在页面刷新的时候不会触发 Vue 对象的beforeDestroy与destroyed生命周期函数。如果我们需要在页面刷新的时候处理某些逻辑的话,需要监听 window 对象的beforeunload事件。

    我们可以在 Vue 组件的created函数中添加beforeunload监听:

    created () {
        window.addEventListener('beforeunload', handler)
    },
    
    1
    2
    3

    在 Vue 组件的beforeDestroy中移除监听:

    beforeDestroy () {
        window.removeEventListener('beforeunload', handler)
    }
    
    1
    2
    3

    # 8 时间格式化在IOS上面的问题。

    在IOS端,时间字符串分隔符为/,需要将其他分隔符如-转为/ 再进行初始化,否则会提示NAN。

    // 例如 time = '2020-08-25'
    // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
    time = time.replace(new RegExp(/-/gm), '/')
    
    1
    2
    3

    # 9.Axios 在 GET 请求中传递数组问题

    今天在重构之前写的代码的时候,发现一个接口请求数据有问题,传递给后端的是一个数组,但是在控制台里面却变了样,参数名称后面诡异的加了一个'[]',回忆了一下自身并没有对这个参数进行了格式化操作,Google 一番发现这是 axios 的锅

    经过实验,axios 的 get 方法中使用 params 时对于 js 数组类型的参数的默认操作比较诡异,会使得参数名后带上'[]'字符串,原因是 axios 对 params 格式化采用的是qs (opens new window)这个库,因此可以使用 qs 自带的 arrayFormat 参数配置解决这个问题,大致配置如下:

    const qs = require('qs')
    
    axios.get(url, {
      params: {
        arr: [1, 2, 3]
      },
      paramsSerializer: function(params) {
        return Qs.stringify(params, { arrayFormat: 'repeat' })
      }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    后端接收为 url-query 数组,即'id=1&id=2&id=3'

    # 10. vue 组件没有 prototype

    如果是在组件中需要挂载全局变量的话,没法直接使用 this.prototype,组件实例没有这个属性,只有proto,但是这个属性是被废弃的,不建议使用。在组件中使用 this.$options._base 即为 Vue 构造函数,在构造函数原型上挂载属性即可挂载到 Vue 全局中,在所有组件中使用。 参考:Vue 技术揭秘:构造子类构造函数 (opens new window)

    # 11.在项目中组件的封装

    项目中的组件常分为基础组件和业务组件,基础组件为对一些页面基本构成部分的封装,如 element-ui 等属于这一类,业务组件则为适应业务要求而对基础组件进行的再封装,一般基础组件的适应性更强。

    • 在组件封装中不要过度封装,组件应该着重于展示部分,接收数据,根据数据展现出应有的形态,应本着高内聚,低耦合原则。prop 尽量只传递一些基本数据类型,并做数据验证。不要将非展示的逻辑强行封装到组件中,导致组件适应性差,不应把父组件应处理的逻辑放到组件中处理,组件只接受数据,返回渲染结果

    • 业务组件可以适当的将业务逻辑封装进组件内部,有时候业务性质高度统一,但是又是不同的页面只是数据接口改变而已,这样可以将整个页面进行封装,实现复用。

    示例图 (opens new window)

    如上图所示,在产品中已对 QueryForm 和 table 进行了一次封装,但是现在有 2 个类似的页面,都有对表格数据的增删改查需求,表格操作按钮的弹窗和逻辑都高度相似,UI 部分和业务逻辑部分都基本不变,只是接口名改变了。简单的方式就是新建一个文件,代码复制一遍。如果封装的话,需要把各自的增删改查接口通过 prop 传进组件里面才能实现最大的复用。因此我选择了后者,如果后面再出一个类似的页面就可以直接使用了。但是问题是需要增删改查接口传递的参数一致才行,不然需要针对每个接口做数据格式化,写一堆 if else 语句来进行判断,因此业务组件的耦合性相当高,扩展性不强。如果把页面所需要处理的事件都冒泡到父组件来处理的话,那也就失去了组件化的意义。如何对组件进行封装以及封装到哪种地步都需要根据情况来考虑。

    我封装的组件使用方式如下所示:

    <template>
      <div>
        <div v-if="showIndex" class="flex-1-box">
          <service-list
            service-type="mysql"
            :get-table-list-api="getMysqlList"
            :init-api="init"
            :update-api="update"
            :rest-api="resetPsw"
          />
        </div>
        <router-view v-else></router-view>
      </div>
    </template>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    # 12.alert,notification 组件

    这类组件的封装,其实内部维护一个数组,将新加的 message 添加进数组,渲染一个列表,然后根据设置 duration 设置一个延迟函数,移除添加进去的 message,message 一条一条的渲染出来并在一定时间后消失。

    编辑 (opens new window)
    #前端#总结
    上次更新: 2022/03/14, 06:54:51
    使用 Mpvue 开发小程序总结
    webGL知识汇总-持续更新

    ← 使用 Mpvue 开发小程序总结 webGL知识汇总-持续更新→

    最近更新
    01
    可视化表单搭建系统的开发与思考
    03-09
    02
    使用node反向代理接口改造旧项目
    11-17
    03
    面试总结39题
    10-11
    更多文章>
    Theme by Vdoing | Copyright © 2022-2023 zhili | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式