huaiyuG

huaiyuG 查看完整档案

西安编辑西北工业大学  |  软件工程 编辑  |  填写所在公司/组织 www.fourstacks.codes 编辑
编辑

Front-end beginner.
Learning rust now.
University student.
Working on noting :)

个人动态

huaiyuG 发布了文章 · 9月5日

计算机网络知识图谱(一)-计算机网络的体系结构

赞 0 收藏 0 评论 0

huaiyuG 发布了文章 · 5月9日

如何在graphviz中画有重复节点的树

如果想用graphviz中画一棵树,并且树中有重复节点。如下图,
树图[1]
简单地用下面的代码是无法完成的。因为graphviz将string当作唯一id;

digraph G{
    A->B;
    A->C;
    B->C;
}

上述代码只能生成一个有向图,而不是树
有向图
要生成一颗有重复节点的树,可以引入label,对于重复的节点,定义一个新节点;

digraph G{
    A->B;
    A->C;
    node1[label=C];
    B->node1;
}

Screenshot_20200509_093807.png

查看原文

赞 0 收藏 0 评论 0

huaiyuG 关注了用户 · 4月16日

rxliuli @rxliuli

算了,还是不在这儿玩了,国内什么论坛大了都会变成泥潭。。。

关注 16

huaiyuG 赞了文章 · 4月3日

记一次 <keep-alive> 缓存及其缓存优化原理

缓存淘汰策略

由于 <keep-alive> 中的缓存优化遵循 LRU 原则,所以首先了解下缓存淘汰策略的相关介绍。

由于缓存空间是有限的,所以不能无限制的进行数据存储,当存储容量达到一个阀值时,就会造成内存溢出,因此在进行数据缓存时,就要根据情况对缓存进行优化,清除一些可能不会再用到的数据。所以根据缓存淘汰的机制不同,常用的有以下三种:

  1. FIFO(fisrt-in-fisrt-out)- 先进先出策略

    我们通过记录数据使用的时间,当缓存大小即将溢出时,优先清楚离当前时间最远的数据。

  1. LRU (least-recently-used)- 最近最少使用策略

    以时间作为参考,如果数据最近被访问过,那么将来被访问的几率会更高,如果以一个数组去记录数据,当有一数据被访问时,该数据会被移动到数组的末尾,表明最近被使用过,当缓存溢出时,会删除数组的头部数据,即将最不频繁使用的数据移除。(keep-alive 的优化处理)

  1. LFU (least-frequently-used)- 计数最少策略

    以次数作为参考,用次数去标记数据使用频率,次数最少的会在缓存溢出时被淘汰。

<keep-alive> 简单示例

首先我们看一个动态组件使用 <keep-alive>例子)。

<div id="dynamic-component-demo">
  <button v-on:click="currentTab = 'Posts'">Posts</button>
    <button v-on:click="currentTab = 'Archive'">Archive</button>
  <keep-alive>
    <component
      v-bind:is="currentTabComponent"
      class="tab"
    ></component>
  </keep-alive>
</div>
Vue.component('tab-posts', { 
  data: function () {
      return {
      count: 0
    }
  },
    template: `
      <div class="posts-tab">
     <button @click="count++">Click Me</button>
         <p>{{count}}</p>
    </div>`
})

Vue.component('tab-archive', { 
    template: '<div>Archive component</div>' 
})

new Vue({
  el: '#dynamic-component-demo',
  data: {
    currentTab: 'Posts',
  },
  computed: {
    currentTabComponent: function () {
      return 'tab-' + this.currentTab.toLowerCase()
    }
  }
})

我们可以看到,动态组件外层包裹着 <keep-alve> 标签。

<keep-alive>
  <component
    v-bind:is="currentTabComponent"
    class="tab"
  ></component>
</keep-alive>

那就意味着,当选项卡 PostsArchive 在来回切换时,所对应的组件实例会被缓存起来,所以当再次切换到 Posts 选项时,其对应的组件 tab-posts 会从缓存中获取,计数器 count 也会保留上一次的状态。

<keep-alive> 缓存及优化处理

就此,我们看完 <keep-alive> 的简单示例之后,让我们一起来分析下源码中它是如何进行组件缓存和缓存优化处理的。

首次渲染

vue模板 -> AST -> render() -> vnode -> 真实Dom 这个转化过程中,会进入 patch 阶段,在patch 阶段,会调用 createElm 函数中会将 vnode 转化为真实 dom

function createPatchFunction (backend) {
  ...
  //生成真实dom
  function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
    // 返回 true 代表为 vnode 为组件 vnode,将停止接下来的转换过程
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return;
    }
    ...
  }
}

在转化节点的过程中,因为 <keep-alive>vnode 会视为组件 vnode,因此一开始会调用 createComponent() 函数,createComponent() 会执行组件初始化内部钩子 init(), 对组件进行初始化和实例化。

function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    var i = vnode.data;
    if (isDef(i)) {
      // isReactivated 用来判断组件是否缓存
      var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        // 执行组件初始化的内部钩子 init()
        i(vnode, false /* hydrating */);
      }
      // after calling the init hook, if the vnode is a child component
      // it should've created a child instance and mounted it. the child
      // component also has set the placeholder vnode's elm.
      // in that case we can just return the element and be done.
      if (isDef(vnode.componentInstance)) {
        initComponent(vnode, insertedVnodeQueue);
        // 将真实 dom 添加到父节点,insert 操作 dom api
        insert(parentElm, vnode.elm, refElm);
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
        }
        return true
      }
    }
  }

<keep-alive> 组件通过调用内部钩子 init() 方法进行初始化操作。

注:源码中通过函数 installComponentHooks() 可追踪到内部钩子的定义对象 componentVNodeHooks
// inline hooks to be invoked on component VNodes during patch
var componentVNodeHooks = {
  init: function init (vnode, hydrating) {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      var mountedNode = vnode; // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode);
    } else {
      // 第一次运行时,vnode.componentInstance 不存在 ,vnode.data.keepAlive 不存在
      // 将组件实例化,并赋值给 vnode 的 componentInstance 属性
      var child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      );
      // 进行挂载
      child.$mount(hydrating ? vnode.elm : undefined, hydrating);
    }
  },
  // prepatch 是 patch 过程的核心步骤
  prepatch: function prepatch (oldVnode, vnode) { ... },
  insert: function insert (vnode) { ... },
  destroy: function destroy (vnode) { ... }
};

第一次执行时,很明显组件 vnode 没有 componentInstance 属性,vnode.data.keepAlive 也没有值,所以会调用 createComponentInstanceForVnode() 将组件进行实例化并将组件实例赋值给 vnodecomponentInstance 属性,最后执行组件实例的 $mount 方法进行实例挂载。

createComponentInstanceForVnode()是组件实例化的过程,组件实例化无非就是一系列选项合并,初始化事件,生命周期等初始化操作。

缓存 vnode 节点

<keep-alive> 在执行组件实例化之后会进行组件的挂载(如上代码所示)。

...
// 进行挂载
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
...

挂载 $mount 阶段会调用 mountComponent() 函数进行 vm._update(vm._render(), hydrating); 操作。

Vue.prototype.$mount = function (el, hydrating) {
  el = el && inBrowser ? query(el) : undefined;
  return mountComponent(this, el, hydrating)
};

function mountComponent (vm, el, hydrating) {
  vm.$el = el;
    ...
  callHook(vm, 'beforeMount');
  var updateComponent;
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    ...
  } else { 
    updateComponent = function () {
      // vm._render() 会根据数据的变化为组件生成新的 Vnode 节点
      // vm._update() 最终会为 Vnode 生成真实 DOM 节点
      vm._update(vm._render(), hydrating);
    }
  }
  ...
  return vm
}

vm._render() 函数最终会调用组件选项中的 render() 函数,进行渲染。

function renderMixin (Vue) {
  ...
  Vue.prototype._render = function () {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    ...
    try {  
      ...
      // 调用组件的 render 函数
      vnode = render.call(vm._renderProxy, vm.$createElement);
    }
    ...
    return vnode
  };
}

由于keep-alive 是一个内置组件,因此也拥有自己的 render() 函数,所以让我们一起来看下 render() 函数的具体实现。

var KeepAlive = {
  ...
  props: {
    include: patternTypes,  // 名称匹配的组件会被缓存,对外暴露 include 属性 api
    exclude: patternTypes,  // 名称匹配的组件不会被缓存,对外暴露 exclude 属性 api
    max: [String, Number]  // 可以缓存的组件最大个数,对外暴露 max 属性 api
  },
  created: function created () {},
  destroyed: function destroyed () {},
    mounted: function mounted () {},
  
  // 在渲染阶段,进行缓存的存或者取
  render: function render () {
    // 首先拿到 keep-alve 下插槽的默认值 (包裹的组件)
    var slot = this.$slots.default;
    // 获取第一个 vnode 节点
    var vnode = getFirstComponentChild(slot); // # 3802 line
    // 拿到第一个子组件实例
    var componentOptions = vnode && vnode.componentOptions;
    // 如果 keep-alive 第一个组件实例不存在
    if (componentOptions) {
      // check pattern
      var name = getComponentName(componentOptions);
      var ref = this;
      var include = ref.include;
      var exclude = ref.exclude;
      // 根据匹配规则返回 vnode 
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      var ref$1 = this;
      var cache = ref$1.cache;
      var keys = ref$1.keys;
      var key = vnode.key == null
        // same constructor may get registered as different local components
        // so cid alone is not enough (#3269)
        // 获取本地组件唯一key
        ? componentOptions.Ctor.cid + (componentOptions.tag ? ("::" + (componentOptions.tag)) : '')
        : vnode.key;
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        // 使用 LRU 最近最少缓存策略,将命中的 key 从缓存数组中删除,并将当前最新 key 存入缓存数组的末尾
        remove(keys, key); // 删除命中已存在的组件
        keys.push(key); // 将当前组件名重新存入数组最末端
      } else {
        // 进行缓存
        cache[key] = vnode;
        keys.push(key);
        // prune oldest entry
        // 根据组件名与 max 进行比较
        if (this.max && keys.length > parseInt(this.max)) { // 超出组件缓存最大数的限制
          // 执行 pruneCacheEntry 对最少访问数据(数组的第一项)进行删除
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }
      // 为缓存组件打上标志
      vnode.data.keepAlive = true;
    }
    // 返回 vnode 
    return vnode || (slot && slot[0])
  }
};

从上可得知,在 keep-alive 的源码定义中, render() 阶段会缓存 vnode 和组件名称 key 等操作。

  • 首先会判断是否存在缓存,如果存在,则直接从缓存中获取组件的实例,并进行缓存优化处理(稍后会介绍到)。
  • 如果不存在缓存,会将 vnode 作为值存入 cache 对象对应的 key 中。还会将组件名称存入 keys 数组中。
if (cache[key]) {
  vnode.componentInstance = cache[key].componentInstance;
  // make current key freshest
  // 使用 LRU 最近最少缓存策略,将命中的 key 从缓存数组中删除,并将当前最新 key 存入缓存数组的末尾
  remove(keys, key); // 删除命中已存在的组件
  keys.push(key); // 将当前组件名重新存入数组最末端
} else {
  // 进行缓存
  cache[key] = vnode;
  keys.push(key);
  // prune oldest entry
  // 根据组件名与 max 进行比较
  if (this.max && keys.length > parseInt(this.max)) { // 超出组件缓存最大数的限制
    // 执行 pruneCacheEntry 对最少访问数据(数组的第一项)进行删除
    pruneCacheEntry(cache, keys[0], keys, this._vnode);
  }
}

缓存真实 DOM

回顾之前提到的首次渲染阶段,会调用 createComponent()函数, createComponent()会执行组件初始化内部钩子 init(),对组件进行初始化和实例化等操作。

function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  var i = vnode.data;
  if (isDef(i)) {
    // isReactivated 用来判断组件是否缓存
    var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
    if (isDef(i = i.hook) && isDef(i = i.init)) {
        // 执行组件初始化的内部钩子 init()
      i(vnode, false /* hydrating */);
    }
    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue);
      // 将真实 dom 添加到父节点,insert 操作 dom api
      insert(parentElm, vnode.elm, refElm);
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
      }
      return true
    }
  }
}

createComponet() 函数还会我们通过 vnode.componentInstance 拿到了 <keep-alive> 组件的实例,然后执行 initComponent()initComponent() 函数的作用就是将真实的 dom 保存再 vnode 中。

...
if (isDef(vnode.componentInstance)) {
  // 其中的一个作用就是保存真实 dom 到 vnode 中
  initComponent(vnode, insertedVnodeQueue);
  // 将真实 dom 添加到父节点,(insert 操作 dom api)
  insert(parentElm, vnode.elm, refElm);
  if (isTrue(isReactivated)) {
      reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
  }
  return true
}
...
function initComponent (vnode, insertedVnodeQueue) {
    if (isDef(vnode.data.pendingInsert)) {
      insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);
      vnode.data.pendingInsert = null;
    }
    // 保存真是 dom 节点到 vnode 
    vnode.elm = vnode.componentInstance.$el;
    ...
}

缓存优化处理

在文章开头,我们介绍了三种缓存优化策略(它们各有优劣),而在 vue 中对 <keep-alive> 的缓存优化处理的实现上,便用到了上述的 LRU 缓存策略 。

上面介绍到,<keep-alive> 组件在存取缓存的过程中,是在渲染阶段进行的,所以我们回过头来看 render() 函数的实现。

var KeepAlive = {
  ...
  props: {
    include: patternTypes,  // 名称匹配的组件会被缓存,对外暴露 include 属性 api
    exclude: patternTypes,  // 名称匹配的组件不会被缓存,对外暴露 exclude 属性 api
    max: [String, Number]  // 可以缓存的组件最大个数,对外暴露 max 属性 api
  },
  // 创建节点生成缓存对象
  created: function created () {
    this.cache = Object.create(null); // 缓存 vnode 
    this.keys = []; // 缓存组件名
  },
 
  // 在渲染阶段,进行缓存的存或者取
  render: function render () {
    // 首先拿到 keep-alve 下插槽的默认值 (包裹的组件)
    var slot = this.$slots.default;
    // 获取第一个 vnode 节点
    var vnode = getFirstComponentChild(slot); // # 3802 line
    // 拿到第一个子组件实例
    var componentOptions = vnode && vnode.componentOptions;
    // 如果 keep-alive 第一个组件实例不存在
    if (componentOptions) {
      // check pattern
      var name = getComponentName(componentOptions);
      var ref = this;
      var include = ref.include;
      var exclude = ref.exclude;
      // 根据匹配规则返回 vnode 
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      var ref$1 = this;
      var cache = ref$1.cache;
      var keys = ref$1.keys;
      var key = vnode.key == null
        // same constructor may get registered as different local components
        // so cid alone is not enough (#3269)
        // 获取本地组件唯一key
        ? componentOptions.Ctor.cid + (componentOptions.tag ? ("::" + (componentOptions.tag)) : '')
        : vnode.key;
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        // 使用 LRU 最近最少缓存策略,将命中的 key 从缓存数组中删除,并将当前最新 key 存入缓存数组的末尾
        remove(keys, key); // 删除命中已存在的组件
        keys.push(key); // 将当前组件名重新存入数组最末端
      } else {
        // 进行缓存
        cache[key] = vnode;
        keys.push(key);
        // prune oldest entry
        // 根据组件名与 max 进行比较
        if (this.max && keys.length > parseInt(this.max)) { // 超出组件缓存最大数的限制
          // 执行 pruneCacheEntry 对最少访问数据(数组的第一项)进行删除
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }
      // 为缓存组件打上标志
      vnode.data.keepAlive = true;
    }
    // 返回 vnode 
    return vnode || (slot && slot[0])
  }
};

<keep-alive> 组件会在创建阶段生成缓存对象,在渲染阶段对组件进行缓存,并进行缓存优化。我们重点来看下段带代码。

if (cache[key]) {
  ...
  // 使用 LRU 最近最少缓存策略,将命中的 key 从缓存数组中删除,并将当前最新 key 存入缓存数组的末尾
  remove(keys, key); // 删除命中已存在的组件
  keys.push(key); // 将当前组件名重新存入数组最末端
} else {
  // 进行缓存
  cache[key] = vnode;
  keys.push(key);
  // 根据组件名与 max 进行比较
  if (this.max && keys.length > parseInt(this.max)) { // 超出组件缓存最大数的限制
    // 执行 pruneCacheEntry 对最少访问数据(数组的第一项)进行删除
    pruneCacheEntry(cache, keys[0], keys, this._vnode);
  }
}

从注释中我们可以得知,当 keep-alive 被激活时(触发 activated 钩子),会执行 remove(keys, key) 函数,从缓存数组中 keys 删除已存在的组件,之后会进行 push 操作,将当前组件名重新存入 keys 数组的最末端,正好符合 LRU

LRU:以时间作为参考,如果数据最近被访问过,那么将来被访问的几率会更高,如果以一个数组去记录数据,当有一数据被访问时,该数据会被移动到数组的末尾,表明最近被使用过,当缓存溢出时,会删除数组的头部数据,即将最不频繁使用的数据移除。
remove(keys, key); // 删除命中已存在的组件
keys.push(key); // 将当前组件名重新存入数组最末端

function remove (arr, item) {
  if (arr.length) {
    var index = arr.indexOf(item);
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

至此,我们可以回过头看我们上边的 <keep-alive> 示例,示例中包含 tab-poststab-archive 两个组件,通过 componentis 属性动态渲染。当 tab 来回切换时,会将两个组件的 vnode 和组件名称存入缓存中,如下。

keys = ['tab-posts', 'tab-archive']
cache = {
    'tab-posts':   tabPostsVnode,
    'tab-archive': tabArchiveVnode
}

假如,当再次激活到 tabPosts 组件时,由于命中了缓存,会调用源码中的 remove()方法,从缓存数组中 keys tab-posts 删除,之后会使用 push 方法将 tab-posts 推到末尾。这时缓存结果变为:

keys = ['tab-archive', 'tab-posts']
cache = {
    'tab-posts':   tabPostsVnode,
    'tab-archive': tabArchiveVnode
}

现在我们可以得知,keys 用开缓存组件名是用来记录缓存数据的。 那么当缓存溢出时, <keep-alive>又是如何 处理的呢?

我们可以通过 max 属性来限制最多可以缓存多少组件实例。

在上面源码中的 render() 阶段,还有一个 pruneCacheEntry(cache, keys[0], keys, this._vnode) 函数,根据 LRU 淘汰策略,会在缓存溢出时,删除缓存中的头部数据,所以会将 keys[0] 传入pruneCacheEntry()

if (this.max && keys.length > parseInt(this.max)) { // 超出组件缓存最大数的限制
  // 执行 pruneCacheEntry 对最少访问数据(数组的第一项)进行删除
  pruneCacheEntry(cache, keys[0], keys, this._vnode);
}

pruneCacheEntry() 具体逻辑如下:

  • 首先,通过cached$$1 = cache[key]` 获取头部数据对应的值 `vnode`,执行 `cached$$1.componentInstance.$destroy() 将组件实例销毁。
  • 其次,执行 cache[key] = null 清空组件对应的缓存节点。
  • 最后,执行 remove(keys, key) 删除缓存中的头部数据 keys[0]

至此,关于 <keep-alive> 组件的首次渲染、组件缓存和缓存优化处理相关的实现就到这里。

最后

最后记住这几个点:

  • <keep-alive>vue 内置组件,在源码定义中,也具有自己的组件选项如 datamethodcomputedprops 等。
  • <keep-alive> 具有抽象组件标识 abstract,通常会与动态组件一同使用。
  • <keep-alive> 包裹动态组件时,会缓存不活动的组件实例,将它们停用,而不是销毁它们。
  • <keep-alive> 缓存的组件会触发 activateddeactivated 生命周期钩子。
  • <keep-alive> 会缓存组件实例的 vnode 对象 ,和真实 dom 节点,所以会有 max 属性设置。
  • <keep-alive> 不会在函数式组件中正常工作,因为它们没有缓存实例。
查看原文

赞 14 收藏 11 评论 0

huaiyuG 赞了文章 · 4月1日

Deno 并不是下一代 Node.js

这几天前端圈最火的事件莫过于 ry(Ryan Dahl) 的新项目 deno 了,很多 IT 新闻和媒体都用了标题:“下一代 Node.js”。这周末读了一遍 deno 的源码,特意写了这篇文章。长文预警(5000字,11图)。

0. 为什么开发 Deno?

这是我上周做的一张图,介绍了 JavaScript 的发展简史。刚才修改了一下,添加了对 Node.js 和 Deno 发布时间的标注。
Node.js 和 Deno 分别是 Ryan Dahl 在 2009 年和 2018 年,基于当年最新的前端技术开发的非浏览器 JavaScript 运行时

JavaScript 历史(Node &amp; Deno)

Ryan Dahl 开发 deno 并不是因为 “just for fun”,也不是为了取代 node。下面慢慢解释。

1. 目前 deno 只是一个 demo

这两天花时间看了 deno 的源码(好在是初级阶段,源码很少,也很容易理解),顺带看了所有的 issue 和 pr。不知道“从官方介绍来看,可以认为它是下一代 Node”是如何脑补出来的。

既然是 Node.js 之父的新作,在讨论中自然离不开 Node.js。而作者很皮的回复到:

The main difference is that Node works and Deno does not work : )

最大的区别就是:Node 可以工作,而 Deno 不行 : )

目前 Deno 只是一个 Demo,甚至连二进制发行版都没有。好在从源码编译比较简单(如果你使用的不是 Windows 系统)。

在 high-level 层面,Deno 提供了一个尽可能简单的 V8 到系统 API 的绑定。为什么使用 Golang 替代 C++ 呢,因为相比 Node 而言,Golang 让我们更加容易的添加新特性,比如 http2 等。

至于为什么不选择 Rust,作者没有回答。

我们再对比一下两者的启动性能。分别运行:

console.log('Hello world')

node vs deno

我之前写过一篇文章:Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍,那如果我们使用 --without-snapshot 参数编译 Node.js 呢?

deno vs node(without-snapshot)

依然是相差悬殊,毕竟 deno 需要加载一个 TypeScript 编译器。毕竟是一个 demo 版本,希望以后用力优化。

对于性能提升还有一个思路就是,可以使用 LLVM 作为后端编译器把 TypeScript 代码编译为 WebAssembly 然后在 V8 里面运行,甚至可以直接把源码编译成二进制代码运行。Ryan Dahl 表示 deno 只需要一个编译器,那就是 TS。但是既然 deno 要兼容浏览器,那么 WebAssembly 应该也会被支持。

Deno 可以对 ts 的编译结果进行缓存(~/.deno/cache),所以目前关注的就是启动速度和初次编译速度。

要么就是在发布前先行编译,如此一来 deno 就脱离了开发的初衷了。deno 是一个 ts 的运行时,那么就应该可以直接运行 ts 代码,如果提前把 ts 编译成 js,那么 deno 就回退到 js 运行时了。

2. 初学者应该学习 Node.js 还是 Deno?

对于这个问题,Ryan Dahl 的回答干净利落:

Use Node. Deno is a prototype / experiment.

使用 Node。Deno 只是一个原型或实验性产品。

从介绍可以看到,Deno 的目标是不兼容 Node,而是兼容浏览器。

所以,Deno 不是要取代 Node.js,也不是下一代 Node.js,也不是要放弃 npm 重建 Node 生态。deno 的目前是要拥抱浏览器生态。

不得不说这个目标真伟大。Ryan Dahl 开发了 Node.js,社区构建出了整个 npm 生态。我在另一个回答 justjavac:纯前端开发眼里nodejs到底是什么? 里面写到“Node.js 是前端工程化的重要支柱之一”。

虽然后来 Ryan Dahl 离开 Node.js 去了 Golang 社区,但是现在 Ryan Dahl 又回来了,为 JavaScript 社区带来了 Golang,开发出了 Deno,然后拥抱浏览器生态。👍

我们看看 deno 的关于 Web API 的目标:

  • High level

    • Console √
    • File/FileList/FileReader/Blob
    • XMLHttpRequest
    • WebSocket
  • Middle level

    • AudioContext/AudioBuffer
    • Canvas

甚至还会包括 webGL 和 GPU 等的支持。

3. Deno 的架构

Parsa Ghadimi 绘制了一张关于 Deno 的架构图

Deno‘s architecture

底层使用了作者开发的 v8worker2,而 event-loop 则基于 pub/sub 模型。关于 v8worker 可以看看这个 PPT:https://docs.google.com/prese...

我比较好奇的是 deno 使用了 protobuf,而没有使用 Mojo。既然目标是要兼容浏览器,却不使用 Mojo,而是要在 protobuf 上重新造轮子,可见 Ryan Dahl 是真正的“轮子哥”啊。但是从 issue 中可以看出,Ryan Dahl 之前是没有听说过 Mojo 的,但是他看完 mojo 之后,依然觉得 protobuf 的选择是正确的。

Mojo 是 Google 开发的新一代 IPC 机制,用以替换旧的 Chrome IPC。目前 Chrome 的最新版本是 67,而 Google 的计划是在 2019 年的 75 版本用 mojo 替换掉所有的旧的 IPC。

Mojo 的思路确实和 protobuf 毕竟像,毕竟都是 Google 家的。旧的 IPC 系统是基于在 2 个进程(线程)之间的命名管道(IPC::Channel)实现的。这个管道是一个队列,进程间的 IPC 消息按照先进先出的顺序依次传递,所以不同的 IPC 消息之间有先后次序的依赖。相比之下,Mojo 则为每一个接口创建了一个独立的消息管道,确保不同接口的 IPC 是独立的。而且为接口的创建独立的消息管道的代价也并不昂贵,只需分配少量的堆内存。

Mojo 的架构设计:

我们可以看一下 Chrome 引入 Mojo 之后的架构变化。

之前:

之后:

是不是有点微服务的感觉。

熟悉 Java 的 Spring 的可以明显看出这个依赖倒置。Blink 本来是浏览器最底层的排版引擎,通过 Mojo,Blink 变成了要给中间模块。最近大热的 Flutter 也是基于 Mojo 架构的。

4. TypeScript VS JavaScript

deno 的介绍是一个安全的 TypeScript 运行环境。但是我们看源码就会发现,deno 集成进了一个 TypeScript 编译器,而入口文件中 ry/deno:main.go

// It's up to library users to call 
// deno.Eval("deno_main.js", "denoMain()") 
func Eval(filename string, code string) { 
    err := worker.Load(filename, code) 
    exitOnError(err) 
} // It's up to library users to call
// deno.Eval("deno_main.js", "denoMain()")
func Eval(filename string, code string) {
    err := worker.Load(filename, code)
    exitOnError(err)
}

使用 V8 运行的 deno_main.js 文件。是 JavaScript 而不是 TypeScript 。

在前面的分析中我们知道这会影响 deno 的初次启动速度。那么对于执行速度呢?从理论上,TypeScript 作为一种静态类型语言,编译完成的 JavaScript 代码会有更快的执行速度。我之前在《前端程序员应该懂点V8 知识》曾经提到过 V8 对于 JavaScript 性能提升有一项是 Type feedback

当 V8 执行一个函数时,会基于函数传入的实参(注意是实参,而不是形参,因为 JavaScript 的形参是没有类型的)进行即时编译(JIT):

但是当后面再次以不同的类型调用函数时,V8 会进行去优化(Deopt)操作。

(将之前优化完的结果去掉,称为“去优化”)

但是如果我们使用 TypeScript ,所有的参数都是由类型标注的,因此可以防止 V8 引擎内部执行去优化操作。

5. 对 deno 性能的展望和猜想

虽然 TypeScript 可以避免 V8 引擎的去优化操作,但是 V8 执行的是 ts 编译后的结果,我们通过字节码或者机器码可以看到,V8 依然生成了 Type Check 的代码,每次调用函数之前,V8 都会对实参的类型进行检查。也就是说,虽然 TypeScript 保证了函数的参数类型,但是编译成 JavaScript 之后,V8 并不能确定函数的参数类型,只能通过每次调用前的检查来保证参数的类型。

其次,当 V8 遇到函数定义时,并不知道参数的类型,而只有函数被调用后,V8 才能判断函数的类型,才对函数进行 Typed 即时编译。这里又有一个矛盾了,typescript 在函数定义时就已经知道了形参的类型,而 V8 只有在函数调用时才根据实参的类型进行优化。

所以,目前 deno 的架构还存在很多问题,毕竟只是一个 demo。未来还有很多方向可以优化。

V8 是一个 JavaScript 运行时,而 deno 如果定义为“安全的 TypeScript 运行时”,至少在目前的架构上,性能是有很大损失的。但是目前还不存在一个 TypeScript 运行时,退而求其次只能在 V8 前面放一个 TypeScript 编译器了。

执行流程是这样的:

虽然我在项目中没有使用过 TypeScript ,但是基本上我在项目里面写的第三方库都会提供一d.ts 文件。目前 TypeScript 最大的用途还是体现在开发和维护过程中。

我们想到的一个方式就是 fork 一份 V8 的源码,然后把编译流程整合进去。TypeScript 在编译为 JavaScript 的过程中也需要一份 AST,然后生成 js 代码。V8 执行 js 代码是再 parse 一份 AST,基于 AST 生成中间代码(ByteCode)。如果 TypeScript 可以直接生成对用的字节码则会提升运行时的性能。

不过 Ryan Dahl 大概不会这么干。但是也未必,毕竟社区已经把 TypeScript 的一个子集编译为 WebAssembly 了。

之前微软的 JScript 和 VBScript 在和 JavaScript 的竞争中败下阵来,而现在 TypeScript 势头正猛。虽然对 ES 规范的兼容束缚了 TypeScript 的发展,但很期待微软可以提供一个 TS 运行时,或者在 Chakra 引擎增加对 TS 运行时的支持。

6. 总结

不论如何,deno 是一个非常伟大的项目,但却不是“下一代 Node.js”。


PS:昨天 Ryan Dahl 在 JS Conf 做了《Design Mistakes in Node》的演讲,目前只有 PPT,还没有 Youtube 视频。而 8 年前的 2009 年,Ryan Dahl 也在 JS Conf 做了一次演讲,这次演讲诞生了 Node.js。

查看原文

赞 101 收藏 71 评论 29

huaiyuG 发布了文章 · 4月1日

为你的vscode添加一个图片背景吧~

先看一个效果展示~

coding page

welcome page

方法

为了可随时修改,你可以在你的~/.vscode文件夹下创建一个vscode_style的目录,
在里边放入你自己的css文档和一些图片。以笔者为例,我的vscode的style是

/**vscode_style.css*/
body{
   pointer-events: auto !important;
   background-size: 100% !important;
   opacity: 0.90 !important;
   background-position: 0 0 !important;
   /**change your image url here*/
   background-image: url('./bg.png');
   content: '';
   position: absolute;;
   z-index: 999;
   width: 100%;
   background-repeat: no-repeat;
 }

然后找到vscode程序文件夹,linux上一般在'/opt'目录下。在{vscode_dir}/resources/app/out/vs/workbench下修改workbench.desktop.main.css就可以修改vscode的样式啦。

@import "/home/huaiyu/.vscode/vscode_style/vscode_style.css";

重启vscode就好了,当你重启后发现vscode提醒你your code installation appears to be corrupt,直接忽略就它~

  • 另,如果觉得丑丑的title bar不好看,可以在settings.json添加
"window.titleBarStyle": "custom",

可以自己定制标题栏的样式。

查看原文

赞 4 收藏 1 评论 0

huaiyuG 关注了用户 · 3月22日

justjavac @justjavac

会写点 js 代码

关注 14445

huaiyuG 发布了文章 · 3月19日

[codewars] Factorial tail 题解 rust

题目链接
大意就是有两个参数base,number,求number!在base进制下的数末尾有多少个零。

例如
base:10,number:10 则:10! = 3628800,末尾有2个零
base:16,number:16 则:16!=>转换为16进制=>0x130777758000,末尾有三个零
思路

找到base所有素数因子,并且找到包含该素数因子的数量,存为hash表base_hash
找到(1..number+1)找到所有素数因子,以及他们的数量和,存为hash表number_hash
再遍历hash表,找到min(number_hash.get(key)/base_hash(key))
则这个最小值则为我们的anser

代码

激情打码,存在一些冗余

use std::collections::HashMap;
use std::cmp::*;

fn zeroes(base: i32, number: i32) -> i32 {
    let prime_tabel: Vec<i32> = get_prime_table(base);
    let mut divisor_hash: HashMap<i32, u32> = HashMap::new();

    let divisor_table: Vec<i32> = prime_tabel
        .iter()
        .filter(|&a| base % a == 0)
        .cloned()
        .collect();


    for i in 1..number + 1 {
        let mut mut_i = i;
        for divisor_num in &divisor_table {
            while mut_i % *divisor_num == 0 {
                mut_i /= divisor_num;
                *divisor_hash.entry(*divisor_num).or_insert(0) += 1;
            }
        }
    }

    let mut base_hash:HashMap<i32,u32>=HashMap::new();
    let mut mut_base=base;
    for num in &divisor_table{
        while mut_base%num==0{
            mut_base/=num;
            *base_hash.entry(*num).or_insert(0)+=1;
        }
    }

    let mut answer:i32=i32::max_value();
    for (&key,&value) in &divisor_hash{
        let base_value=match base_hash.get(&key){
            Some(num)=>num,
            None=>&0,
        };
        match value.cmp(&base_value) {
            Ordering::Less=>{
               answer=0
            },
            _=>{ answer=min(answer, (value/base_value) as i32)}
        }
    }
    if divisor_hash.is_empty(){
         0
    }else {
        answer
    }
}

fn get_prime_table(number: i32) -> Vec<i32> {
    let mut prime_tabel: Vec<i32> = vec![2];
    for num in 2..number+1 {
        let mut prime_flag = true;
        for prime_num in &prime_tabel {
            if num % prime_num == 0 {
                prime_flag = false;
            }
        }
        if prime_flag {
            prime_tabel.push(num);
        }
    }
    prime_tabel
}
大佬的代码
fn zeroes(base: i32, number: i32) -> i32 {
    let mut b = base;
    (2..base + 1).filter_map(|x| {

        let mut n = 0;
        while b % x == 0 {
            b /= x;
            n += 1;
        }
        if n == 0 {
            None
        } else {
            Some((x, n))
        }
    }).map(|(p, n)| {
        (1..number + 1).fold(0, |acc, x| {
            let mut c = 0;
            let mut x = x;
            while x % p == 0 {
                x /= p;
                c += 1;
            }
            acc + c
        }) / n
    }).min().unwrap_or(0)
}

大佬的代码主要思路和我的差不多,也是先找到base因子及其数目再找到2..number+1的因子和再相除求最小值。
我的代码可以简化的部分有:

  • 没必要求prime表,再filter,在求因子的cnt,完全可以一步搞定
  • 就是求和那里太罗嗦,不简介

收获就是见识到fold的这个method,还有就是再次被大佬简介的代码震撼。

查看原文

赞 0 收藏 0 评论 0

huaiyuG 关注了用户 · 3月16日

吃不完的土豆番茄 @jainghupiao

关注 5

huaiyuG 赞了回答 · 3月7日

解决vue.js 的el是什么意思呢?

为实例提供挂载元素。值可以是 CSS 选择符,或实际 HTML 元素,或返回 HTML 元素的函数。

关注 8 回答 5

认证与成就

  • 获得 7 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-09-16
个人主页被 173 人浏览