champyin

champyin 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织 champyin.com 编辑
编辑

远离喧嚣,静心钻研

个人动态

champyin 赞了文章 · 11月2日

微前端(singleSpa + React )试玩

前言

我们团队正在做一个XX系统,技术栈是React,目前该系统日渐庞大,开发及维护成本加大,且每次必须把整个项目一起打包,费时费力。经考虑后决定将其拆分成多个项目,由它们组合成一个完整系统,微前端架构是非常好的选择。

微前端差不多有以下几个好处:

  1. 单项目维护:比如将商品模块单拉出来形成一个项目,它可以由一个小组单独维护,实现良好解耦
  2. 复杂度降低:不需要在整个集成式的庞大系统内开发,避免巨大的代码量,开发时编译速度快,提高开发效率
  3. 容错性:单独项目发生错误不会影响整个系统
  4. 技术栈灵活:vue、react、angular 等包括其他前端技术栈都可以使用,会 vue 的不需要再学 react

对我们来说最大的好处是单项目维护

展示

UI示例图

我们将整个微前端分为两个部分:

  1. 主项目(Main):红色框部分,作为整个项目的父级,负责展示菜单模块、头部模块
  2. 子项目(Sub-apps):蓝色框部分,子项目的作用是具体的业务展示

动图展示

注意看地址栏变化,其中包含 /app1/xxx/app2/xxx,乍一看这是一个项目中两个页面的切换,实际上是来自两个独立的项目,app1 和 app2 来自不同的 git 仓库。

微前端架构图

整个流程大概为:用户访问 index.html, 此时运行模块加载器Js,加载器会根据整个系统的配置文件(project.config) 去注册各个项目,系统会先加载主项目(Main),然后会根据路由前缀动态加载对应的子项目

我们这个架构也参考了网上很多好的文章,其中核心文章可参考 https://alili.tech/archive/11...

关于 project.config

大概如下

[
 {
    isBase: false,
    name: 'app1',
    version: '1.0.0',
    //通过该路由前缀匹配加载当前入口文件
    hashPrefix: '/app1',
    //入口文件
    entry: 'http://www.xxxx.com/app1/dist/singleSpaEntry.js',
    //顶级Store
    store: 'http://www.xxxx.com/main/dist/store.js'
  }
  ......
]

技术细节

single-spa

我们找了些实现微前端的仓库,对比后决定使用single-spa

我们技术栈是 react,在子项目入口中需要使用 single-spa-react 来构建,关键代码如下:

import singleSpaReact from 'single-spa-react';

const reactLifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Root,
  domElementGetter
});

export function bootstrap(props) {
  return reactLifecycles.bootstrap(props);
}

export function mount(props) {
  return reactLifecycles.mount(props);
}

export function unmount(props) {
  return reactLifecycles.unmount(props);
}

如果你使用 vue,可以使用 single-spa-vue

然后在系统入口文件中,把所有的项目注册进来:

import * as singleSpa from 'single-spa';

singleSpa.registerApplication(
    'app1',
    () => SystemJS.import('app1-entry.js'),
    () => location.hash.startsWith(`#/app1`),
    props
  );

具体可参考 single-spa 官网 https://single-spa.js.org 这里有很多例子

Webpack 与 SystemJs

我们使用的 lerna 统一管理所有项目的依赖包,所有依赖包的版本统一,这样非常方便维护。

使用 webpack 的 dll 功能,将所有项目的公用依赖包抽离,比如 react、react-dom、react-router、mobx等

为了方便项目动态加载,我们也参考网上大佬的想法,使用了systemjs,只不过我们使用的是 0.20.19 版本,配合 systemjs ,在 Webpack 中需要改一下 libraryTarget:

output: {
    publicPath: 'http://www.xxxxx.com/',
    filename: '[name].js',
    chunkFilename: '[name].[chunkhash:8].js',
    path: path.resolve(__dirname, 'release'),
    libraryTarget: 'amd', //注意 这里使用 amd 的规范
    library: 'app1'
  },

我们没有使用 umd 规范,也没有使用 systemjs 里的 Import Maps
功能,而是直接通过 project.config 来动态加载模块入口。

app之间通信

关于这个也看了一些大佬的方案,大概就是所有的项目里有个 store,在注册入口时将所有 store 放进队列,需要更新 store 里的状态时,调用 dispatch 将所有 store 同步。

我的做法和传统单页应用一样,一个系统应该只有一个顶级 Store,由于顶级 Store 里存的一般是整个系统的公用状态 比如菜单、用户信息等,我把它放在 Main项目里,但打包时这个Store是单独抽离的:

entry: {
    singleSpaEntry: './src/singleSpaEntry.js',
    store: './src/store' //单独一个入口
  },

在注册时,将这个 Store 传入每个项目中:

//顶级Store
const mainStore = await SystemJS.import(storeURL);

singleSpa.registerApplication(
    'app1',
    () => SystemJS.import('http://www.x.com/app1/entry.js'),
    hashPrefix('/app1'),
    { mainStore }
);
singleSpa.registerApplication(
    'app2',
    () => SystemJS.import('http://www.x.com/app2/entry.js'),
    hashPrefix('/app1'),
    { mainStore }
);

这样就可以达到只管理这一个 Store 就可以,非常方便。
注意:我使用的是 Mobx 作为状态管理

前端部署

我们部署的方式非常简单,我自己写了一个 webpack 插件用于把打包后的 dist 传到 OSS 然后将项目信息传给服务端,服务端根据我传入的项目信息组织成 project.config,然后用户在访问 index.html 时会获取 project.config,此时 single-spa 根据配置注册所有项目,然后根据路由来拉取对应的项目入口文件js文件。

把子项目的挂载 DOM 放在 Main 项目里

我们的需求是 Main 作为整个项目的 Layout,其中子项目的挂载 Dom 也在 Main项目里,这就必须等到 Main 项目完全渲染完成后,才能挂载子项目。我参考了网上有些微前端的实现,把 domElementGetter 方法借鉴了过来:

function domElementGetter() {
  let el = document.getElementById('sub-module-wrap');
  if (!el) {
    el = document.createElement('div');
    el.id = 'sub-module-wrap';
  }
  let timer = null;
  timer = setInterval(() => {
    if (document.querySelector('#content-wrap')) {
      document.querySelector('#content-wrap').appendChild(el);
      clearInterval(timer);
    }
  }, 100);

  return el;
}

demo

demo地址:https://github.com/Vibing/mic...

结束语

这是我们第一次玩微前端,可能有很多地方不完美,还望各位大佬多多包涵

查看原文

赞 8 收藏 4 评论 0

champyin 发布了文章 · 6月14日

Deno

前言

Deno 已经被前端圈子提及有很长一段时间了,上个月 Deno 发布了 1.0 版本,又掀起了一小股 Deno 热。Deno 到底是什么?它可以用来做什么呢?它好用吗?带着一直以来的好奇心,趁着周末的时间,认真地接触了一次 Deno。

一、什么是Deno?

Deno 是一个更安全的 JavaScript 和 TypeScript 运行时,作者 Ryan Dahl 同时也是 Node.js 的创建者。

什么是运行时?

运行时是一个运行环境,也叫运行平台,开发者可以使用指定的语言,基于这个环境开发应用。可以认为运行时就是一个舞台,能做什么事情,取决于舞台能提供什么道具。比如浏览器就是一个运行时,我们可以在浏览器上通过 JS 调用浏览器提供的 API 操作 DOM。

Deno 的作用

Deno 的作用,是让开发者可以使用 JavaScript 语言开发后端服务。

二、为什么会有Deno?

我们知道 Node.js 也是一个让开发者可以使用 JavaScript 语言开发后端服务的 JavaScript 运行时。那既然已经有一个 Node.js,而且已经非常成功,为什么还要造另一个 JavaScript 运行时 Deno ?

两年前(2018年6月),Ryan Dahl 在德国柏林召开的 JSConf EU 会议上发表了名为 “10 Things I Regret About Node.js” 的演讲,有兴趣可以到这里下载 PPT

deno-Ryan-Dahl.jpg

在分享中,Ryan 回顾了在他看来当初开发 Node.js 时留下的10大遗憾。但由于Node.js 现在已经广泛应用于各个领域,为了保证兼容性,对 Node.js 底层进行大规模改造已经不现实。会上,Ryan 宣布了他决定开发一个全新的 JavaScript Runtime 以解决当初的种种缺陷,这个项目就是 Deno。

deno-logo.png

Deno 的命名很有意思,就是把 node(no de) 倒过来 deno(de no),颇有颠覆 Node 的意味。

BTW,上个月(2020年5月15日),Deno 发布了1.0版本。

三、走近 Deno

Deno 的开发语言

相比 Node.js 使用 C++ 开发,Deno 起初使用的开发语言是 GoLang,后来改为了 Rust。并且随后把 C++ 写的 libdeno 换成了 Rust 编写的 V8 绑定:denoland/rusty_8。

Deno 目前是建立在 V8 引擎、Rust 、Tokio、TypeScript 的基础之上。

  • V8 是 chrome 浏览器内的 JavaScript 运行时。
  • Rust 是一门系统编程语言,专注于安全,尤其是并发安全。它的性能和标准C++ 不相上下。
  • Tokio 是一个给 Rust 语言使用的异步运行时,提供 event loop 和具体的 I/O 类型。
  • TypeScript 是 JavaScript 的超集。

Deno 的特性

  • 默认支持 ES Modules
  • 默认支持 TypeScript
  • 尽可能兼容 Web 标准 APIs
  • 默认采用沙箱模式运行代码,更安全
  • 去中心化第三方模块机制
  • 提供标准库

与 Node.js 的比较

  • 使用 ES 模块,不支持 require()
  • Deno 不使用 package.json
  • Deno 不使用 npm
  • Deno 中的所有异步操作返回 promise,因此 Deno 提供与 Node 不同的 API
  • Deno 需要显示指定文件、网络和环境权限
  • 第三方模块通过 URL 或者文件路径导入
  • 当未捕获的错误发生时,Deno 总是会异常退出
  • 兼容 Web 的运行时 APIs,更利于前后端的代码同构。

四、如何使用 Deno

Deno 能够在 macOS、Linux 和 Windows 上运行。Deno 是一个单独的可执行文件,它没有额外的依赖。

1.安装

在 macOS 下可以通过Shell命令安装:

curl -fsSL https://deno.land/x/install/install.sh | sh

这个方式在国内安装会很慢,慢到下不下来。。。so,不推荐。

也可以通过HomeBrew 安装:

brew install deno

deno-install-brew.jpg

这个方式可以安装下来,但是,安装的版本是 v0.20.0,很低的版本:

deno-version-low.jpg

并且这个版本不带 upgrade 命令,升级 deno 的时候很不方便。so,也不推荐。

安利通过国内加速器(镜像源 https://x.deno.js.cn )来安装:

curl -fsSL https://x.deno.js.cn/install.sh | sh

也可以指定版本:

curl -fsSL https://x.deno.js.cn/install.sh | sh -s v1.0.0 

deno-install-x.jpg

首次安装,可以看到提示,需要手动配置一下环境变量,配置语句也已经给出:

$ touch ~/.bash_profile # 创建用户环境变量文件
$ vim ~/.bash_profile # 打开文件,将刚才命令行提示给出的配置语句粘贴进去,保存退出。

让配置立即生效:

$ source ~/.bash_profile 

环境变量就设置好了,现在在任何一个新打开的命令行里面都可以使用 deno 命令了。

注意:如果之前使用 brew 安装过低版本的 deno,请使用 brew uninstall deno 卸载 deno 之后,再使用加速器安装高版本,不卸载直接安装高版本不会生效。(别问我为什么知道。。。都是泪。

其他操作系统环境的安装可参考 https://github.com/denoland/d...

如果要升级本地的 Deno,可以运行

deno upgrade

还可以安装指定的版本:

deno upgrade --version 1.1.0

这个命令会从 github.com/denoland/deno/releases 获取最新的发布版本(一个可执行的二进制文件 zip 压缩包),然后解压并替换现有的版本。而 github release 的文件使用的是 aws,在国内访问不稳定。

So,升级也推荐使用国内加速器安装指定版本来达到目的。

2.测试安装

deno --version

deno-version-high.jpg

如果打印出 Deno 版本,说明安装成功。

到这里,我们就安装好 Deno ,并且可以基于 Deno 进行开发了。

3.运行一个远程的项目

跑一个远程项目(官方的demo)

deno run https://deno.land/std/examples/welcome.ts

可以看到在控制台输出"Welcome to Deno 🦕”。

deno-run-remote.jpg

4.运行一个本地的项目

起一个最简单的本地服务

// http.js
import { serve } from "https://deno.land/std@0.57.0/http/server.ts";

const s = serve({ port: 8000 });
console.log("http://localhost:8000/");

for await (const req of s) {
  req.respond({ body: "Hello World\n" });
}

可以看到 Deno 在引用第三方模块的方式为 ES6 的 import 语法,并且直接通过 URL 来引入,版本号也被锁定在了 URL 中。

另外,Deno 支持 顶层的 await 语法,不用与 async 语法配对使用了。

运行:

deno run http.js

deno-run-local.jpg

首次引入第三方包,Deno 会去下载这个包和它的依赖,这些包会被缓存到全局,下次再引入的时候,将直接读取缓存。

这里报了一个缺少网络权限的错,这是因为 Deno 采用沙箱模式运行代码,网络权限必须通过手动添加 flag (--allow-net)来开启。

带上网络权限运行:

deno run --allow-net http.js

deno-run-with-net.jpg

打开localhost://8000

deno-server.jpg

可以看到一个简单的本地服务就跑起来了。

5.其他相关配置

如果我们要高效地使用 Deno,最好还需要设置一些开发环境,比如环境变量、命令行自动补全、编辑器等。

  • 环境变量

    DENO_DIR:
    这是 Deno 在本地存放生成的代码和缓存下载的模块的路径,默认为 $HOME/Library/Caches/deno。

    NO_COLOR:
    这个会关闭输出的文字颜色。

    HTTP_PROXY 和 HTTPS_PROXY :
    这两个变量用来设置 HTTP 和 HTTPS 的代理地址。

  • 命令自动补全
    通过 deno completions <shell> 命令可以生成补全脚本。他会输出到 stdout,应该将它重定向到适当的文件。

    Deno 支持的 shell 有 zsh、bash、fish、powershell、elvish。

  • 编辑器插件

我们可以给 VS Code 配置 Deno 的插件: vscode_deno

如果你是其他编辑器/IDE,可以参考官网推荐的插件

Deno 将来会取代 Node.js 吗?

这也是很多前端者关心的话题,网络上两种声音都有,我的看法是:会共存,但不会取代。

首先,Node.js 的作者之所以开发 Deno 只是为了兑现他心目中对 JavaScript Runtime 的一个理想实现,并不是为了取代 Node.js;

其次,Node.js 经过十多年的发展,已经很成熟了(虽然在 Ryan 的眼里不那么完美),并且已经被广泛应用。个人认为,将来 Deno 要做的事情,Node.js 都能做,如果没有特别的因素(比如潜在的安全隐患等),已经使用了 Node.js 的应用,不大会改用 Deno 重构。

所以,以我目前的认知,我认为 Deno 如果能发展起来,应该会与 Node.js 共生,而不会取代 Node.js。

不管怎样,我很钦佩 Ryan,在 Node.js 获得如此成功之后,仍然怀揣对作品的理想追求,大胆分享自己在 Node.js 中犯的“错误”,开始 Deno 的征程,并且现在 Deno 正在以飞快的速度在迭代。就在昨天,Deno 又发布了 V1.1.0。

deno-release-v1.1.0.jpg

结语

以上是我对 Deno 的一个初探,解答了什么是 Deno,它有什么作用,有哪些特点,与 Node.js 有什么不同,以及如何使用 Deno(虽然只浅浅地跑了最简单的程序,但足以让我感觉到 Deno 与 Node.js 在使用上的不同)。现在,总算对 Deno 的有了一个比较清晰的了解。

有兴趣交流的小伙伴可以在这里留言讨论:https://github.com/yc111/yc11...
Deno 交流QQ群:698469316


参考
Deno Manual:https://deno.land/manual
Deno Doc:https://doc.deno.land/https/g...
Deno中文社区:https://denocn.org/
Deno中文开发者社区:https://deno.js.cn/
Deno中文手册:https://nugine.github.io/deno...
Futures 和 Tokio 项目的前世今生:https://rustcc.cn/article?id=...


文章首发于于公众号「前端手札」,喜欢的话可以关注一下哦。

qianduanshouzha-gzh.png


本文作者:ChampYin
转载请注明出处:https://champyin.com/2020/06/14/Deno-初探/
查看原文

赞 8 收藏 3 评论 2

champyin 关注了标签 · 4月26日

nginx

nginx(发音同engine x)是一款由俄罗斯程序员Igor Sysoev所开发轻量级的网页服务器、反向代理服务器以及电子邮件(IMAP/POP3)代理服务器。

关注 6464

champyin 关注了标签 · 4月26日

关注 4227

champyin 关注了标签 · 4月26日

react.js

React (sometimes styled React.js or ReactJS) is an open-source JavaScript library for creating user interfaces that aims to address challenges encountered in developing single-page applications. It is maintained by Facebook, Instagram and a community of individual developers and corporations.

关注 36601

champyin 发布了文章 · 4月24日

彻底弄懂GMT、UTC、时区和夏令时

前言

格林威治时间、世界时、祖鲁时间、GMT、UTC、跨时区、夏令时,这些眼花缭乱的时间术语,我们可能都不陌生,但是真正遇到问题,可能又不那么确定,不得不再去查一查,处理完可能过段时间又忘记。今天,我们彻底来梳理一下它们。

一、GMT

什么是GMT

GMT(Greenwich Mean Time), 格林威治平时(也称格林威治时间)。

它规定太阳每天经过位于英国伦敦郊区的皇家格林威治天文台的时间为中午12点。

GMT的历史

格林威治皇家天文台为了海上霸权的扩张计划,在十七世纪就开始进行天体观测。为了天文观测,选择了穿过英国伦敦格林威治天文台子午仪中心的一条经线作为零度参考线,这条线,简称格林威治子午线。

1884年10月在美国华盛顿召开了一个国际子午线会议,该会议将格林威治子午线设定为本初子午线,并将格林威治平时 (GMT, Greenwich Mean Time) 作为世界时间标准(UT, Universal Time)。由此也确定了全球24小时自然时区的划分,所有时区都以和 GMT 之间的偏移量做为参考。

1972年之前,格林威治时间(GMT)一直是世界时间的标准。1972年之后,GMT 不再是一个时间标准了。

二、UTC

什么是UTC

UTC(Coodinated Universal Time),协调世界时,又称世界统一时间、世界标准时间、国际协调时间。由于英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC。

UTC 是现在全球通用的时间标准,全球各地都同意将各自的时间进行同步协调。UTC 时间是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以秒为单位的国际原子时所综合精算而成。

在军事中,协调世界时会使用“Z”来表示。又由于Z在无线电联络中使用“Zulu”作代称,协调世界时也会被称为"Zulu time"。

UTC 由两部分构成:

  • 原子时间(TAI, International Atomic Time):
    结合了全球400个所有的原子钟而得到的时间,它决定了我们每个人的钟表中,时间流动的速度。
  • 世界时间(UT, Universal Time):
    也称天文时间,或太阳时,他的依据是地球的自转,我们用它来确定多少原子时,对应于一个地球日的时间长度。

UTC的历史

1960年,国际无线电咨询委员会规范统一了 UTC 的概念,并在次年投入实际使用。

“Coordinated Universal Time”这个名字则在1967年才被正式采纳。

1967年以前, UTC被数次调整过,原因是要使用闰秒(leap second)来将 UTC 与地球自转时间进行统一。

三、GMT vs UTC

GMT是前世界标准时,UTC是现世界标准时。
UTC 比 GMT更精准,以原子时计时,适应现代社会的精确计时。
但在不需要精确到秒的情况下,二者可以视为等同。
每年格林尼治天文台会发调时信息,基于UTC。

四、时区

随着火车铁路与其他交通和通讯工具的发展,以及全球化贸易的推动,各地使用各自的当地太阳时间带来了时间不统一的问题,在19世纪催生了统一时间标准的需求,时区由此诞生。

时区是如何定义的

从格林威治本初子午线起,经度每向东或者向西间隔15°,就划分一个时区,在这个区域内,大家使用同样的标准时间。

但实际上,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分,而是按自然条件来划分。另外:由于目前,国际上并没有一个批准各国更改时区的机构。一些国家会由于特定原因改变自己的时区。

全球共分为24个标准时区,相邻时区的时间相差一个小时。

time-timezone.png

在不同地区,同一个时区往往会有很多个不同的时区名称,因为名称中通常会包含该国该地区的地理信息。在夏令时期间,当地的时区名称及字母缩写会有所变化(通常会包含“daylight”或“summer”字样)。

例如美国东部标准时间叫:EST,Estern Standard Time;而东部夏令时间叫:EDT,Estern Daylight Time。

想查看世界所有时区的名字可以访问这个网站:
https://www.timeanddate.com/t...

四、夏令时

什么是夏令时

DST(Daylight Saving Time),夏令时又称夏季时间,或者夏时制。

它是为节约能源而人为规定地方时间的制度。一般在天亮早的夏季人为将时间提前一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。

全球约40%的国家在夏季使用夏令时,其他国家则全年只使用标准时间。标准时间在有的国家也因此被相应地称为冬季时间。

在施行夏令时的国家,一年里面有一天只有23小时(夏令时开始那一天),有一天有25小时(夏令时结束那一天),其他时间每天都是24小时。

time-daylight-time.jpg

绿色部分为2019年统计的在全球施行冬夏令时的国家和地区。

夏令时的历史

1784年,美国驻法国大使本杰明·富兰克林(Benjamin Franklin)提出“日光节约时间制”。1908年,英国建筑师威廉·维莱特(William Willett)再次提出,但当时该提案并未被采纳。

1916年,处于一战时期的德国政府下令将时钟推至一个小时后,通过获得额外一小时的日光来节省战争所需的煤炭,成为第一个实行夏时制的国家。随后,英法俄美四个一战参战国纷纷效仿。

美国在一战结束后于1919年取消夏时制,但在1942年二战时,美国重新启动夏令时制,1966年正式立法确定永久使用。1973至1975年石油危机爆发期间,美国连续两年延长夏令时制,以节省石油。

欧洲大部分国家则是从1976年——第四次中东战争导致首次石油危机(1973年)的3年后才开始施行夏令时制。

1986年4月,中国国务院办公厅发出《在全国范围内实行夏时制的通知》,要求全民早睡早起节约能源:每年4月中旬的第一个星期日2时,将时钟拨快一小时;10月中旬第一个星期日的2时,再将时钟拨慢一小时。但此夏令时只实行了6年,在1992年停止施行,主因是中国东西地域广阔却只奉行一个北京时间,实时夏令时制带来很多不切实际的反效果。

夏令时的争议

从过去的100多年来看,夏令时往往是在国家发生严重危机(如战争和能源短缺)的情况下才会受到青睐。而在相对和平的近10年里,这种时间制度则变得越来越不受欢迎。

它会使得人们的生物钟被扰乱,常常陷入睡眠不足的情况,不仅对人体健康有害、导致车祸,还会对旅游、航空领域造成极大的混乱。

另外,冬、夏令时究竟能否起到节能的作用,也仍有待商榷。美国一项截至2014年3月的研究表明,这种时间转换制度最多能在3、4月帮助美国减少1%的用电量,而美国国家标准局则认为,夏令时对用电量没有丝毫影响。

在俄罗斯,此前的一份报告也显示,夏令时帮助俄罗斯每年节约的电量,仅相当于两三个火力发电厂的发电量,十分的“鸡肋”。

去年(2019年)3月26日,作为全世界第一个提出并实行夏令时的国家,德国,在欧洲议会上以410比192的赞成票通过了取消冬、夏令时转换制提案,拟定于2021年4月起,所有欧盟国家不再实行冬、夏令时转换。待各成员国形成最终法案后,将选择永久使用夏令时时间或是冬令时时间。

五、本地时间

在日常生活中所使用的时间我们通常称之为本地时间。这个时间等于我们所在(或者所使用)时区内的当地时间,它由与世界标准时间(UTC)之间的偏移量来定义。这个偏移量可以表示为 UTC- 或 UTC+,后面接上偏移的小时和分钟数。

六、JavaScript中的Date

得到本地时间,在不同时区打印 new Date() ,输出的结果将会不一样:

new Date();

得到本地时间距 1970年1月1日午夜(GMT时间)之间的毫秒数:

new Date().getTime();

返回本地时间与 GMT 时间之间的时间差,以分钟为单位:

new Date().getTimezoneOffset();

如何在任何地方都能正确显示当地时间(只要知道该地的timezone):

//目标表时间,东八区
let timezone = 8;
//获取本地时间与格林威治时间的时间差(注意是分钟,记得转换)
const diff = new Date().getTimezoneOffset();
//根据本地时间和时间差获得格林威治时间
const absTime = new Date().getTime() + diff * 60 * 1000;
//根据格林威治时间和各地时区,得到各地时区的时间
let localTime = new Date(absTime + timeZone * 60 * 60 * 1000);
//处理夏令时(isDST为自己封装的处理方法)
if(isDST(localTime, country)) {
  localTime = new Date(absTime + (timeZone + 1) * 60 * 60 * 1000);
}
return localTime;

结语

以上分别从定义、来源等维度解释和扩展说明了GMT、UTC、时区和夏令时的概念、历史、意义,并在最后列举了这些概念在JS项目中的一个非常实用的应用。

简单地讲, GMT 是以前的世界时间标准;UTC 是现在在使用的世界时间标准;时区是基于格林威治子午线来偏移的,往东为正,往西为负;夏令时是地方时间制度,施行夏令时的地方,每年有2天很特殊(一天只有23个小时,另一天有25个小时)。

从源头上彻底了解了这些概念,将会让我们在处理与时间相关的问题时如虎添翼。


文章同时发表于公众号「前端手札」,喜欢的话可以关注一下哦。

qianduanshouzha-gzh.png

本文作者:ChampYin
转载请注明出处:https://champyin.com/2020/04/24/彻底弄懂GMT、UTC、时区和夏令时
查看原文

赞 11 收藏 9 评论 2

champyin 赞了回答 · 4月23日

解决mongoose save时,如果数据库不存在同样的数据才执行

確實是使用Collection.update

update(selector, document, options, callback)
Updates documents.

NameTypeDegaultDescription
selectorobjectThe selector for the update operation.
documentobjectThe update document.
optionsobjectnull[optional] Optional settings.
callbackCollection~writeOpCallback[optional] The command result callback

options中有一個upsert屬性(Type: boolean),用於確定這是否是一個 upsert 操作。即但不存在selectop匹配的文檔時,是否插入文檔


name相同則更新,不同則插入:

代碼應該是這樣的:

var urDocument = { // 你想插入的文檔
  name: "joasn",
 other: "ohaedoaduantoduntaodutaod"
};

var MongoClient = require('mongodb').MongoClient,
MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {

  var collection = db.collection('collocation');

  collection.update({name: {$ne: urDocument.name}, {upsert: true}, urDocument);

});

name相同則不作為,不同則插入:

var UrDocument = { // 你想插入的文檔
  name: "joasn",
 other: "ohaedoaduantoduntaodutaod"
};

var MongoClient = require('mongodb').MongoClient,
MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {

  var collection = db.collection('collocation');

  collection.findOne({name: urDocument.name })
      .then(doc => doc ?? collection.insertOne(urDocument));
});

关注 2 回答 2

champyin 收藏了文章 · 4月17日

不再羡慕python,nodejs爬虫撸起袖子就是干,爬取宅男女神网大姐姐的高清图片!

年前无心工作,上班刷知乎发现一篇分享python爬虫的文章。

感觉他爬取的网站里的妹子都好好看哦,超喜欢这里的,里面个个都是美女。

无小意丶:自我发掘爬虫实战1:宅男女神网妹子图片批量抓取,分类保存到本地和MongoDB数据库

无奈python虽然入门过但太久没用早已荒废,最近在用nodejs重构后台接口,遂尝试用nodejs实现个爬虫。

先上几张图:
图片描述

爬几个相册示范一下
图片描述

都是高清无码大图哦
图片描述

好了,开始准备工作吧,少年!

喂!我说的是准备工作环境!你,你,还有你,你们把手上的纸巾放下!

准备工作:

系统环境:mac (作为一个前端,应该不用我解释了吧?)
运行环境:node (作为一个前端,应该不用我解释了吧??)
所需模块:request-promise、cheerio、fs
编辑器:vscode (谁用谁知道)

简单了解一下这几个nodejs的模块:

   var request = require('request-promise');
    request('http://www.google.com')
    .then(function (htmlString) {
    console.log(htmlString)
    })
    .catch(function (err) {
    });

任何响应都可以输出到文件流:

request('http://google.com/doodle.png').pipe(
  fs.createWriteStream('doodle.png')
)
  • cheerio----为服务器特别定制的,快速、灵活、实施的jQuery核心实现:

api类似jQuery,使用超简单

const cheerio = require('cheerio')
const $ = cheerio.load('<h2 class="title">Hello world</h2>')

$('h2.title').text('Hello there!')
$('h2').addClass('welcome')

$.html()
//=> <h2 class="title welcome">Hello there!</h2>
  • fs----Node.js 文件系统

可以创建目录,创建文件,读取文件等。

网页分析:

分析目标网站的相册网页地址
因为python文章的作者已经很详细的分析了网站,所以我就简单分析一下。

随便打开几个相册,可以看到都是这样的规则:

"https://www.****.com/g/****/"

所以我们就可以确定要爬取的基本url:

const base_url = 'https://www.****.com/g/';//爬取相册网页的基本网址

然后再看几乎每个相册底部都有页码,而我们要抓取的是整个相册,所以就要考虑分页的情况,点开分页,我们看到分页的url是这样的:

"https://www.****.com/g/****/*.html"


业务逻辑:

图片描述

实战代码:

app.js 轮询及代码结构

const nvshens = require('./co');
const base_url = 'https://www.nvshens.com/g/';//爬取相册网页的基本网址

let index = 1;
let start = 25380;
const end = 30000;

const main = async (URL) => {
  //1.请求网址
  const data = await nvshens.getPage(URL);
  //2.判断是否存在相册
  if (nvshens.getTitle((data.res))) {
    //3.下载照片
    await nvshens.download(data.res);
    //4.请求分页
    index++;
    const new_url = `${base_url}${start}/${index}.html`;
    main(new_url);
  } else {
    index = 1;
    console.log(`${base_url}${start}页面已完成`)
    start++;
    if (start < end) {
      //5.请求下一个网址
      main(base_url + start);
    } else {
      console.log(`${base_url}${end}所有页面已完成`)
    }
  }
};

main(base_url + start);

co.js //业务代码

var request = require('request-promise'); //网络请求
const cheerio = require("cheerio");//操作dom
const fs = require("fs");//读写文件

const headers = {
  "Referer": "https://www.nvshens.com/g/24656/"
}
//因为一些网站在解决盗链问题时是根据Referer的值来判断的,所以在请求头上添加Referer属性就好(可以填爬取网站的地址)。
//另外Referer携带的数据 是用来告诉服务器当前请求是从哪个页面请求过来的。

const basePath = "/Users/用户名/Desktop/mm/";
//自定义mac本地下载目录,需预先创建,windows路径可参考评论
let downloadPath;
let pageIndex = 1;


module.exports = {

  //请求页面
  async getPage(url) {
    const data = {
      url,
      res: await request({
        url: url
      })
    }
    return data;
  },

  //判断页面是否存在相册
  getTitle(data) {
    const $ = cheerio.load(data);
    if ($("#htilte").text()) {
      downloadPath = basePath + $("#htilte").text();
      //创建相册
      if (!fs.existsSync(downloadPath)) {
        fs.mkdirSync(downloadPath);
        console.log(`${downloadPath}文件夹创建成功`)
      }
      return true;
    } else {
      return false;
    }
  },

  //下载相册照片
  async download(data) {
    if (data) {
      var $ = cheerio.load(data);
      $("#hgallery").children().each(async (i, elem) => {
        const imgSrc = $(elem).attr('src');
        const imgPath = "/" + imgSrc.split("/").pop().split(".")[0] + "." + imgSrc.split(".").pop();
        console.log(`${downloadPath + imgPath}下载中`)
        const imgData = await request({
          uri: imgSrc,
          resolveWithFullResponse: true,
          headers,
        }).pipe(fs.createWriteStream(downloadPath + imgPath));
      })
      console.log("page done")
    }
  },

}

跑起来

node app.js

图片描述

几个函数就可以实现,是不是很简单呢?

查看原文

champyin 发布了文章 · 4月16日

开发一个时间小程序

前言

跟异国他乡的朋友们微信聊天的时候,经常面临时差的问题。我每次想要确定对方现在是几点,总是要口算一下,有时忘记具体时差,或者涉及跨天,还得打开浏览器查一下,很不方便。有什么方法可以把朋友们所在城市的时间集中起来随时供自己查看呢?于是想到了微信小程序。找了找市面上的时间小程序,不是功能太杂就是小广告太多,不满意。

为什么不自己动手量身打造一个呢?

行动起来。

首先快速明确需求

很简单:

  1. 需要展示时间的城市初定:加州、纽约,再加北京做对比
  2. 需要显示具体的时分秒,和年月日
  3. 需要实时变化
  4. 在其他国家也能正确展示时间

然后创建项目开撸

怎么创建和前期的准备就不在这里展开了,相信不少人都熟悉。如果不熟悉小程序开发的可以参考官网 或者我的另一篇文章如何开发微信小程序 ,上面有对如何开发小程序的简明扼要的的介绍。

关键逻辑

这个小程序的核心是时间的处理。如何得到其他地区的时刻信息?

这还不简单?
先获取本地时刻,然后加上或者减去另外一个地点与国内(北京时间)的时差(小时),最多再处理一下跨天的情况,不就得到其他地点的时刻了?

我一开始也是这么想的,做完觉得还挺美,准备提交的时候,突然意识到问题:我时差全是基于北京时间计算的,换在其他国家访问,获取的本地时间已经不是北京时间了,时差应该变才对,写死了时差可还行?!发布一个只能在国内使用的鸡肋时间工具,可不是我的风格!

捣鼓一阵,新方案出炉:

  1. 想办法获得零时区的时间
  2. 获取不同地区与零时区的时差(时区)
  3. 用零时区的时间加减与零时区的时差(时区),得到各地的绝对时间

1. 获得零时区的时间

零时区,也叫中时区,位于英国格林威治本初子午线上。该时区的地方时,叫做格林威治时间,也叫世界时。

我们不能直接获得格林威治时间,但是我们可以获得本地与格林威治的时间差:

const diff = new Date().getTimezoneOffset() // 单位为分钟

然后根据本地时间和时间差获得格林威治时间:

const absTime = new Date().getTime() + diff * 60 * 1000;

2. 查询各地时区

格林威治本初子午线将地球划分为东西两个半球,格林威治本初子午线为零时区,往西依次为西一区到西十一区,往东依次为东一区到东十一区,西十二区和东十二区重合成为东西十二区,一共划分了24个时区,每个时区相差正好是1个小时。

北京是东八区,纽约是西五区,加州是西八区。

完整时区地图:

timezone-map.jpg

3. 计算各地的绝对时间

东时区的时刻比零时区快,西时区的时刻比零时区慢,所以东时区为正,西时区为负,所有时间计算记得转换为毫秒。

let localTime = new Date(absTime + timeZone * 60 * 60 * 1000);

获取任何时区的绝对时间的完整核心代码:

/**
 * timeZone: 东n区为正,西n区为负, 单位为小时
 */
const getFullTimeInfo = (timeZone, country, spliter) => {

  //获取本地时间与格林威治时间的时间差(注意是分钟,记得转换)
  const diff = new Date().getTimezoneOffset();

  //根据本地时间和时间差获得格林威治时间
  const absTime = new Date().getTime() + diff * 60 * 1000;

  //根据格林威治时间和各地时区,得到各地时区的时间
  let localTime = new Date(absTime + timeZone * 60 * 60 * 1000)

  return {
    time: formatTime(localTime, spliter)
  };
}

发布

很快,第一版就完成了。

world-time-v1.0.0

刚开始这个样子略丑,有点裸奔的赶脚。不过第一版最主要是核心功能,简陋的界面只是暂时的。

给当地的朋友检验确定时间展示正确后,提交代码、提交审核,2天后收到审核通过的通知(吐槽腾讯的审核效率😓),然后在小程序管理平台点击发布,哦了。

扫描二维码,打开小程序,然后收藏。以后要看时间了,微信主界面向下一拉,打开我的时间工具,一眼就看到想要知道的时间信息,确实比之前便捷多了。功能虽然简单,界面虽然简陋,但是妥妥滴满足我的需求。

迭代

用了一阵子,觉得样式啥的还是得丰富丰富,于是花了一些时间做了一次改版,实时时间以时钟效果展示,并且修改了布局,顺便重构了一下代码,便于新增地区。

world-time-v2.0.0

嗯,效果似乎还行~

改BUG

前几天跟澳洲的朋友聊天,聊着聊着居然发现了我的程序的一个潜在BUG。

那天是4月4日的早晨(北京时间),我跟朋友吐槽我的一个疑惑:查询悉尼时区为东十区(即与北京相差2小时),但是为啥查询悉尼时间却与北京相差3小时(所以我当时程序中是把悉尼作为东十一区来计算的)。朋友说:是的没错,我们这里现在在使用夏令时,等夏令时结束就恢复2个小时时差了。然后一查,今年澳洲夏令时将在4月5号凌晨3点结束。。。

也就是说,距离这个BUG发作还有不到一天的时间。。。

马上打开电脑,改BUG。。。

根据资料,获得美国和澳大利亚的夏令时规则:

  • 美国

每年的3月第二个星期日02:00:00,时钟向前调整1小时,变为03:00:00,开始夏令时。
每年的11月第一个星期日02:00:00,时钟向后调整1小时,变为01:00:00,结束夏令时。

  • 澳大利亚

每年的10月第一个星期日02:00:00,时钟向前调整1小时,变为03:00:00,开始夏令时。
每年的4月第一个星期日03:00:00,时钟向后调整1小时,变为02:00:00,结束夏令时。

关于夏令时,也挺有意思,有空我会另开一个篇幅来专门讲述。

将夏令时的判断逻辑加上:

/**
 * timeZone: 东n区为正,西n区为负, 单位为小时
 */
const getFullTimeInfo = (timeZone, country, spliter) => {

  //获取本地时间与格林威治时间的时间差(注意是分钟,记得转换)
  const diff = new Date().getTimezoneOffset();

  //根据本地时间和时间差获得格林威治时间
  const absTime = new Date().getTime() + diff * 60 * 1000;

  //根据格林威治时间和各地时区,得到各地时区的时间
  let localTime = new Date(absTime + timeZone * 60 * 60 * 1000)

+  // 考虑夏令时
+  // judgeDST是我封装好的一个判断夏令时的方法
+  const isDST = judgeDST(localTime, country);
+  if (isDST) {
+    localTime = new Date(absTime + (timeZone + 1) * 60 * 60 * 1000)
+  }

  return {
    time: formatTime(localTime, spliter).split(':').slice(0,2).join(':'), 
    isDST
  };
}

有了现在的版本:

world-time-v2.1.0

以后对这个小工具我还会不断优化,会越来越灵活,比如支持地区选择,这样每个人都可以定制自己的时差表了。可以期待一下哦~

最后附上小程序二维码,扫一扫即可体验。

world-time-qr-code.jpg

--
还是毛爷爷说得好:自己动手丰衣足食。

Happy coding :)


文章同时发表于公众号「前端手札」,喜欢的话可以关注一下哦。

qianduanshouzha-gzh.png

本文作者:ChampYin
转载请注明出处:http://champyin.com/2020/04/08/开发一个时间小程序/
查看原文

赞 14 收藏 9 评论 3

champyin 赞了文章 · 3月9日

phpspider简单快速上手的php爬虫框架

前言

前段时间接到一个开发采集网站数据的项目,从事php开发的我立刻想到使用php做爬虫。虽然python爬虫方便,但是php在这方面也不弱,谁让php是世界上最好的语言!这里推荐一款php的爬虫框架phpspider。不建议自己写爬虫,因为效率太低。使用框架爬虫真的要高效许多

官方文档:
https://doc.phpspider.org/

1、下载

官方github下载地址:
https://github.com/owner888/p...

下载地址可能无法访问,这里提供一个网盘下载地址:
https://pan.baidu.com/s/10n9Z...
提取码:b2zc

2、文件结构

下载解压后,phpspider的文件结构如图所示:


其中demo文件夹放的是phpspider的一些案例,如图所示:

3、创建爬虫并且运行

在demo文件夹下创建爬虫文件。需要注意的是,phpspider有两种运行爬虫文件的方式,一种是在命令行下运行;另外一种是可视化操作(在浏览器下运行)

3.1 在命令行下运行爬虫文件

要爬取的对象链接:
https://www.douban.com/photos...

要爬取的内容如图所示:

爬取id为wrapper的div所包含的内容

3.1.1 在demo文件夹下新建文件spider.php,代码如下:

<?php  
require_once__DIR__ . '/../autoloader.php';  
usephpspider\core\phpspider;  
  
/* Do NOT delete this comment */  
/* 不要删除这段注释 */  
  
$configs = array(  
 'name' => '豆瓣',//定义当前爬虫名称  
 'log_show' => true, //显示日志调试信息  
 'input_encoding' => 'UTF-8',//输入编码  
  
//定义爬虫爬取哪些域名下的网页, 非域名下的url会被忽略以提高爬取速度  
'domains' => array(  
  'www.douban.com'  
 ),  
  
//定义爬虫的入口链接, 爬虫从这些链接开始爬取,同时这些链接也是监控爬虫所要监控的链接  
'scan_urls' => array(  
   'https://www.douban.com/photos/album/1616649448/'  
 ),  
  
//爬虫爬取数据导出  
'export' => array(   
   'type' => 'csv', //type:导出类型 csv、sql、db  
   'file' => '../data/abc.csv', //file:导出 csv、sql 文件地址,如果不存在文件自动创建  
  ),  
  
  
//定义内容页的抽取规则  
'fields' => array(  
   array(  
    'name' => "wrapper",  
    'selector' => "//div[@id='wrapper']",  
     )  
   )  
);  
  
$spider = new phpspider($configs);  
$spider->start();

3.1.2 在demo文件夹中直接打开cmd命令面板,输入命令行 php -f spider.php 回车,代码跑起来,如图所示:

3.1.3 查看爬取下来的数据

在phpspider文件结构中找到data文件夹下的abc.csv文件,打开文件可看到爬取下来的数据,如图所示:

3.2 可视化操作(在浏览器下运行爬虫文件)

要爬取的对象链接:
https://movie.douban.com/subj...

要爬取的内容如图所示:

爬取class为nav-items的div所包含的内容

3.2.1 在demo文件夹下新建另外一个文件test.php,代码如下:

<?php  
  
header("Content-Type: text/html;charset=utf-8");  
date_default_timezone_set("Asia/Shanghai");  
ini_set("memory_limit", "10240M");  
  
require_once__DIR__ . '/../autoloader.php';  
usephpspider\core\phpspider;  
usephpspider\core\requests;  
usephpspider\core\selector;  
  
/* Do NOT delete this comment */  
/* 不要删除这段注释 */  
  
$html = requests::get('https://movie.douban.com/subject/26588308/?from=showing');  
$data = selector::select($html, "//div[@class='nav-items']");  
echo $data;

3.2.2 打开浏览器输入文件地址

结语

以上只是简单的爬虫例子,还可以进行多进程爬取,代理爬虫,很多好玩的,更多操作参考官方文档
https://doc.phpspider.org/

最后

觉得文章不错的,给我点个赞哇,关注一下呗!
技术交流可关注微信公众号【GitWeb】,加我好友一起探讨
微信交流群:加好友(备注思否)邀你入群,抱团学习共进步

查看原文

赞 39 收藏 4 评论 2

认证与成就

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

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

  • 旅行足迹

    一个可定制的足迹应用 根据是否到过以及熟悉程度提供4个等级着色 已支持:世界地图、中国地图、美国地图,中国所有省、市地图 实现地图下钻上钻 支持一键存图

注册于 2017-10-30
个人主页被 734 人浏览