⌚️Intl.DateTimeFormat.format 与 Date.toLocaleString

两种方法使用方法基本一致,第一个参数可以接受BCP 47 标准的字符串,或者一个包含区域,时间格式的字符串数组。
第二个参数接受一个格式化的配置项,包含时间样式,时区,格式匹配器。下面是一个格式化utc时间戳的例子。

const DateTimeFormat = new Intl.DateTimeFormat('zh-CN', {
    year: 'numeric', month: 'numeric', day: 'numeric',
    hour: 'numeric', minute: 'numeric', second: 'numeric',
    hour12: false,
});
function formatTime(utc) {
    return DateTimeFormat.format(utc);
}

formatTime(new Date)
// "2019/8/24 02:29:38"

Intl.DateTimeFormat.prototype.format 不需要每次都设置区域设置和选项,所以它的性能更好,但是Intl是ecma402中添加的内容,兼容没有toLocaleString好。

在Vue组件可见时异步加载

对于单页应用,我们通常用路由来做代码分割,但在一个页面,对于一个首屏不可见的组件或者说需要用户交互才会触发显示的组件,我们还可以用延迟加载组件的方法,来减少首页体积,更细粒度的控制加载内容。vue 提供了 asyncComponent 工厂函数,我们可以在这个基础上做可见时异步加载。

const AsyncComponent = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: import('./MyComponent.vue'),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})

上面为文档上的例子,我们需要在其基础上进行扩展,首先timeout不能指定时间,因为如果组件不可见的话一直不会加载。此外由于组件为异步加载,所以我们不能直接判断组件可见,但是我们可以通过异步组件的loading是否出现在视窗内来判断。component参数接受一个Promise对象,所以这里我们可以直接构造一个promise,然后保存其resolve函数,然后在loading组件挂载后使用intersectionObserver api监听其是否出现在视窗内,在intersectionObserver回调函数中使用保存的resolve函数,将 componentpending 变为 resolved状态。

export default function lazyLoadComponent({
  componentFactory,
  loading,
  loadingData,
}) {
  let resolveComponent;

  return () => ({
    component: new Promise((resolve) => {
      resolveComponent = resolve;
    }),
    loading: {
      mounted() {
        const observer = new IntersectionObserver(([entry]) => {
          if (!entry.isIntersecting) return;
          observer.unobserve(this.$el);
          componentFactory().then(resolveComponent);
        });
        observer.observe(this.$el);
      },
      render(h) {
        return h(loading, loadingData);
      },
    },
  });
}

示例

Foo: lazyLoadComponent({
   componentFactory: () => import(`./components/Foo.vue`),
}),

在nuxt中使用,我们需要借助Vue动态组件方式来延迟加载。

<template>
    <div class="post">
        <div style="height: 110vh"></div>
        <div :is="content"></div>
    </div>
</template>

<script>
    export default {
        components: {},
        data() {
            return {content: ''}
        },
        mounted() {
            this.content = this.$loadComponent(() => import(`...`))
        }
    }
</script>

参考文章

Lazy Load Vue.js Components When They Become Visible

前端状态管理——Redux与有限状态机(FSM)

Redux

Redux和Vuex都是基于Flux的思想实现的,这里我们以Redux为例。

核心概念

  • 所有的状态存放在 Store。组件每次重新渲染,都必须由状态变化引起。
  • 用户在 UI 上发出 action。
  • 纯函数 reducer 接收 action,然后根据当前的 state,计算出新的 state。

特点

数据的单一来源:即整个APP尽量不使用内部state,所有的状态全部源自于Redux
纯函数reducer:处理action并生成新的state,其形式为 newState = f(state,action),不允许任何副作用
不可变state:只有当state对象被整体替换时候,才会触发view层的更新。

副作用

在函数式编程中,最大的特点就是纯函数,一个输入一个结果,这个过程想数学函数一样,透明可控,但是异步操作等给应用程序带来了不确定性和可变性,这种被称为副作用。直接用Redux处理这些副作用比较复杂也难以测试,所以出现了redux-saga,mobx等工具。

有限状态机(FSM)

有限状态机(finite-state machine),是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
比如TCP连接,Promise都可以用状态机来表示他们工作方式。
捕获.PNG

特点

  • 状态总数是有限的。
  • 任一时刻,只处在一种状态之中。
  • 某种条件下,会从一种状态转变(transition)到另一种状态。

与有限状态机差异

  • 都是一个状态容器,但FSM将将有限状态与无限状态或者上下文状态区分开,也正因如此有限状态机可以可视化状态之间的转换
  • redux 的 reducer 就是一个函数,而状态机是“带规则的reducer” ,你可以定义由于事件导致的有限状态之间的合法转换,以及应该在转换中(或在状态的进入/退出时)执行哪些操作
  • redux 没有内置处理副作用
  • Redux 需要使用单一的全局原子性的 Store,状态机使用类似角色模型的方式,其中可以存在许多彼此通信的分层状态图。
  • Redux 单向数据流动,状态机并没约束数据流。
  • 状态机可以适应任何框架

如何选择

关键要看你主要关注应用的最终状态还是约束状态变化状态转换的过程

参考文章:

What is an actual difference between redux and a state machine (e.g. xstate)?

【译】使用 WebStorm 调试 Nuxt.js

你之前一直只用控制台调试吗?你是否对其输出的顺序头疼不已?用很长时间才发现丢失了对象?让我们面对现实...几乎每个人都不得不重复这种方法,包括我自己。

console.log 不是且永远不是调试的银弹

调试器是一个伴随我们多年但因为种种原因人们不再Node世界中使用。我们来自NodeJS,VSCode和Jetbrains的朋友创建了大量工具,帮助我们“停止”应用程序,并在那个时刻获得应用程序的当前状态。NuxtJS,另一方面,尝试启动和运行调试器一直很痛苦,难以搞定,因此人们就倾向于放弃并开始使用console.log。

好吧,恐惧不是我的朋友。事实上我有一个快速,安全并且好用的方案来解决你所有的问题!其实,NuxtJS的调试比大家想象中的更容易,我想让你了解它,因为几乎没有关于这个主题的文档,希望这让你的生活更轻松。

项目配置

打开你的nuxt.config.js并转到build属性,因为我们要修改extend方法。

extend(config, ctx) {
      // Added Line
      config.devtool = ctx.isClient ? 'eval-source-map' : 'inline-source-map'

      // Run ESLint on save
      if (ctx.isDev && ctx.isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
      }
}

这行什么意思?

config.devtool是Webpack的一个属性,它允许我们配置如何生成JS的SourceMap(参考

eval-source-map是一个与行号完全匹配的SourceMap,这有助于我们在客户端进行调试。(更多信息

inline-source-map与上一个非常相似,但有一个例外,即在bundle中添加为DataUrl。 它帮助我们在服务器中调试我们的NuxtJS应用程序。 (更多信息

注意:判断开发环境,不建议在生产中使用它。

使用nodemon运行NodeJS调试器

我们将使用一个名为nodemon的优秀开发工具,它基本上让我们可以观察项目中的任何变化并自动重启服务器。

要使用nodemon运行NodeJS调试器,只需添加标志--inspector即可!

{
  "scripts": {
    "dev": "nodemon --inspect node_modules/.bin/nuxt",
  }
}

WebStorm

配置服务端

正常配置npm run dev项目即可

配置客户端

添加 Javascript Debug,URL应该是Nuxt将运行的URL,但我建议使用Chrome并启用“确保在加载脚本时检测到断点”选项并保存!

如何同时运行

首先以调试模式🐞运行服务端,项目正确加载后,选择客户端运行配置并单击相同的图标。它应该打开一个新的chrome实例。

现在我们完全准备好调试我们的程序了!🎉

原文地址:Nuxt.js Debugging with WebStorm

JS 实现LRU缓存

为什么不直接用对象这种k-v结构,因为内存空间很宝贵而且是有限的。
在浏览器我们无所谓,但服务端就不一样了,例如nuxt就是用了LRU算法缓存接口数据或者渲染后的dom对象。

LRU(Least Recently Used)把最近没用过的一个置换出去,意思就是如果最近用到了就放到前面,如果不够了最后一个就是最近没使用的,将其删除掉就好。使用双链表就能满足这种需求。

/*
 * Illustration of the design:
 *
 *       entry             entry             entry             entry
 *       ______            ______            ______            ______
 *      | tail |.newer => |      |.newer => |      |.newer => | head |
 *      |  A   |          |  B   |          |  C   |          |  D   |
 *      |______| <= older.|______| <= older.|______| <= older.|______|
 *
 */
class Node {
    prev = null;
    next = null;
    constructor(k, v) {
        if (valid(k)) this.key = k;
        if (valid(v)) this.value = v;
    }

    valid(e) {
        return typeof key != "undefined" && key !== null
    }
}

class LRU {
    size = 0;
    head = null;
    tail = null;
    map = {};
    constructor(limit = 1000) {
        this.limit = limit
    }

    setHead(node) {
        node.next = this.head;
        node.prev = null;
        if (this.head !== null) this.head.prev = node;
        if (this.tail === null) this.tail = node;
        this.size++;
        this.map[node.key] = node;
    }

    set(k, v) {
        const node = new Node(k,v);
        if (this.map[key]) {
            this.map[key].value = node.value;
            this.remove(node.key);
        } else {
            if (this.size >= this.limit) {
                delete this.map[this.tail.key];
                this.size--;
                this.tail = this.tail.prev;
                this.tail.next = null;
            }
        }
        this.setHead(node);
    }

    get(k) {
        if (this.map[k]) {
            const value = this.map[k].value;
            const node = new Node(k, v);
            this.remove(k);
            this.setHead(node);
            return value;
        } else {
            console.log("Key " + k + " does not exist in the cache.")
        }
    }

    remove(key) {
        const node = this.map[key];
        if (node.prev !== null) {
            node.prev.next = node.next;
        } else {
            this.head = node.next;
        }
        if (node.next !== null) {
            node.next.prev = node.prev;
        } else {
            this.tail = node.prev;
        }
        delete this.map[key];
        this.size--;
    }
}

插入删除时间复杂度都是O(1)

现在为止我们实现了最为核心部分,但是缓存也不能无限的停留在内存中,像接口这些数据有时会发生改变,所以还需要为缓存加上有效期,在get时判断是否过期。

LRU能带给我们一定的收益,但是他也让我们的程序拥有了状态,也就不能做分布式来多实例负载均衡。