Giter Site home page Giter Site logo

blog's Introduction

大深海的技术博客

我的开源书

博客日志精选

其他

  • 本博客内容首发会在公众号显示,如果想第一时间知道博客内容更新
    • 可以 watch 本项目
    • 关注我公众号 大海码DeepSeaCode

qrcode_for_gh_959d1c4d729a_258

blog's People

Contributors

chenshenhai avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

休息3个月,基于Canvas写了个简单的前端绘图JavaScript库

前言

不知不觉,有一年没发布公众号文章了,这几天刚好有时间,就抽空写了这篇文章。本文主要讲解我裸辞3个月以来,利用在家休息的空隙时间,开发了一个前端绘图的JavaScript库 iDraw.js。

为啥要开发这个东西

  • 为了实现用纯Canvas能力结合图片、HTML和SVG作为素材 来做绘图工作。
  • 为了试试看单独用 Canvas的2D(二维)API 能作出怎样的多种素材绘图工作。
  • 最后是为了试试看尽量用 Canvas做素材的操控操作。
  • 目前还没发现类似的可以操控素材绘图的Canvas开源框架,所以就想自己开发一个。

iDraw.js简介

  • 网站: https://idraw.js.org/
  • 一个基于纯Canvas来实现绘图和操控素材能力的JavaScript库。
  • 可以基于 iDraw.js 进行扩展自定义开发各种可视化操控应用,这里可以参考 idraw.js.org/studio/案例
  • 可以绘制文字、矩形、圆形、图片、HTML片段和SVG文件,并且都可以作为绘图元素生成指定格式的数据。

iDraw.js有哪些功能

  • 支持绘制文字、矩形、圆形、图片、HTML片段和SVG片段 绘图元素

  • 绘制文字
    element-text

  • 绘制矩形
    element-rect

  • 绘制圆形
    element-circle

  • 绘制图片
    element-image

  • 绘制HTML片段
    element-html

  • 绘制SVG片段
    element-svg

支持基于Canvas的可视化操作

iDraw.js有哪些特点

  • 可以绘制文字、矩形、圆形、图片、HTML片段和SVG文件,并且作为绘图元素。
  • 可以直接在Canvas操控以上绘图元素,不用担心CSS和DOM变化的污染问题。
  • Canvas操控绘制,并且是所见即所得可以直接导出绘制的图片结果。
  • 由于可视化操控和图片生成都是基于Canvas,可以尽量减少绘图的浏览器兼容问题。

原理介绍

  • 基于数据驱动来绘制Canvas的图画
  • 基于 requestAnimationFrame 来控制数据变化时候,Canvas的重绘处理
  • 内置一个前端并发队列来处理 图片、HTML和SVG的图片化转换渲染

实际使用案例

  • 一个基于 iDraw.js 实现的UI可视化绘图
  • @idraw/studio 的实现

snapshot-001

snapshot-002

snapshot-003

snapshot-004

snapshot-005

其他

socket.io简单使用

安装socket.io

npm install --save socket.io

demo目录

├── index.js
├── node_modules
├── package.json
└── views
    └── index.html

服务端代码

./index.js

const http = require('http');
const fs = require('fs');
const socketIO = require('socket.io');
const server = http.createServer((req, res) => {
  let html = fs.readFileSync('./views/index.html'); 
  res.end(html);
}).listen(3000,'127.0.0.1', () => {
  console.log('socket.io server is running at http://127.0.0.1:3000/');
});


// socket io 操作
const io = socketIO.listen(server);
io.sockets.on('connection', (socket) => {
  console.log('socket.io connected');

  // 监听socket断开事件
  socket.on('disconnect',() => {
    console.log('socket.io disconnected');
  });

  // 轮询进行 服务端推送 自定义事件
  let serverNum = 0;
  setInterval(() => {
    serverNum ++;
    socket.emit('server.message', { serverNum: serverNum });
  }, 1000);

  // 监听客户端推送 自定义事件
  socket.on('client.message', (data) => {
    console.log('[client.message]', data)
  });

});

客户端代码

./views/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>demo</title>
  </head>
  <body>
    <h1>socket.io demo</h1>
    <script src="/socket.io/socket.io.js"></script>

    <script>
        const socket = io.connect('http://127.0.0.1:3000');
        // 监听 socket 连接事件
        socket.on('connection', () => {
            console.log('socket.io connected');
            socket.open();
        });

        // 监听 socket 断开事件
        socket.on('disconnect',() => {
            console.log('socket.io disconnected');
            socket.close();
        });


        let clientNum = 0;
        // 轮询 推送客户端信息 到服务端
        setInterval(() => {
            clientNum ++;
            socket.emit('client.message', { clientNum: clientNum });
        }, 1000);

        // 监听服务端 自定义信息事件
        socket.on('server.message', (data) => {
            console.log('[server.message]', data)
        });
            
    </script>
  </body>
</html>

启动服务

node index.js

ubuntu环境下开发配置

ubuntu 环境下开发配置

安装git

sudo apt-get install git

初始化SSH key

ssh-keygen -t rsa -b 4096 -C "[email protected]"

初始化git配置

git config --global user.name  "xxx"
git config --global user.email "xxx"

sublime中文编辑支持配置

git clone https://github.com/lyfeyaj/sublime-text-imfix.git

cd ~/sublime-text-imfix

sudo cp ./lib/libsublime-imfix.so /opt/sublime_text/

sudo cp ./src/subl /usr/bin/

# 启动方法1
LD_PRELOAD=./libsublime-imfix.so subl

# 启动方法2
## 修改/usr/share/applications/sublime_text.desktop文件
### 将Exec=/opt/sublime_text/sublime_text %F修改为
Exec=bash -c 'LD_PRELOAD=/usr/lib/libsublime-imfix.so /opt/sublime_text/sublime_text' %F

### 将Exec=/opt/sublime_text/sublime_text -n修改为
Exec=bash -c 'LD_PRELOAD=/usr/lib/libsublime-imfix.so /opt/sublime_text/sublime_text' -n

安装atom编辑器

sudo apt-get install atom 

node.js开发环境设置

  • 去官网下载node.js linux下的tar安装包 (下载编译好的安装包) https://nodejs.org/en/
  • 安装node.js环境
//解压安xz装包成tar
 xz -d node-v6.3.1-linux-x64.tar.xz

//解压tar包
 tar -zxvf node-v6.3.1-linux-x64.tar

//将解压后的文件重命名为nodejs,再复制到 /opt/目录下

 sudo cp -a nodejs /opt/


//配置全局环境变量
 sudo gedit /etc/profile

//配置个人账户环境变量 
gedit ~/.bashrc


//在文件的最末尾加上环境变量配置
export NODE_HOME=/opt/nodejs/bin
export NODE_PATH=/opt/nodejs/lib/node_modules
export PATH=$NODE_HOME:$PATH


//保存配置全局变量
sudo source /etc/profile

//保存个人账户环境变量 
gedit ~/.bashrc

//配置国内镜像源
npm config set registry https://registry.npm.taobao.org

安装chrome浏览器

#!/bin/sh

sudo wget https://repo.fdzh.org/chrome/google-chrome.list -P /etc/apt/sources.list.d/

wget -q -O - https://dl.google.com/linux/linux_signing_key.pub  | sudo apt-key add -

sudo apt-get update

sudo apt-get install google-chrome-stable

/usr/bin/google-chrome-stable

Linux添加用户

初始化新用户和密码

# 新增用户
adduser   newuser

# 新用户密码
passwd newuser

赋予新用户root权限

  • 编辑 /etc/sudoers
  • 添加 newuser ALL=(ALL) ALL
## Allow root to run any commands anywhere
root    ALL=(ALL)     ALL
newuser    ALL=(ALL)     ALL
  • 验证权限 su -

iDraw.js的v0.4重构,大深海的2023年度总结

前言

2023年已经结束,是时候对过去一年做个总结。

话说熟悉我公众号的朋友们,应该有印象,就是我已经有两年没写任何文章了。在此,简单唠嗑一下这两年历程,两年前(2021年)裸辞休息,在写完 iDraw.js 第一版并开源后,我就回归职场了。在回归职场后,由于业余时间有限,加上对 iDraw.js 定位理解飘忽不定,经历过怀疑、弃坑、不舍和重构这四个过程,最终在2023年上半年开始了对 的重构。

重构iDraw.js是好玩的事情

在过去2023年里,利用业余零碎的时间,对iDraw.js重写、推翻、再重写,让 iDraw.js 的功能更上一个层次。至于回归职场后的工作内容,其实和普通程序员工作内容大同小异,乏善可陈,还不如自己业余时间沉淀的 iDraw.js 更好玩,更有成就感。

为啥iDraw.js对于我来说更好玩,这是因为iDraw.js是在我人生第一次裸辞长假中创作的开源产品,全凭技术兴趣驱动,没任何KPI压力,没掺杂任何职场气息。更重要是没有职场哪种“指指点点PUA”、“技术价值怀疑”和“方案选型撕逼”。

在开发和开源iDraw.js过程中,主打的就是“我开心,我就乐意这么写代码”。因此,我的2023总结,就是总结我开心的事情,有成就感的事情,也就是2023年的业余时间下,对iDraw.js进行v0.4版本的重构。

这是2023年五月份错峰出游,面朝大海写代码拍摄的海景,在假期里,在好玩的地方,面对好玩的景色,写好玩的代码,就是最好玩的事情。

也就是在这次出游后,加速了iDraw.js的重构步骤,到了年底,iDraw.js基本重构完毕,v0.4版本也初步完成,目前进入beta阶段。

iDraw.js v0.4 来了,让Web绘图更简单

按照惯例,宣传产品需要带个口号,iDraw.js就不免俗,也来个口号,就是“让Web绘图更简单”,可能有些新朋友不知道iDraw.js是什么,那我也来简单介绍一下。

一个普通的Web绘图JavaScript框架,可以用于图片设计,Web网页设计等二维平面设计。

也只是一个正在并长期处于发展中的开源工具,从2021年5月提交第一次代码开始至今2年半里,持续迭代和优化。

为什么要重构iDraw.js

为什么要重构,主要是v0.3的设计缺陷,导致GitHub和邮箱收到以下几类

  • 放大模糊,比如一像素内容放大后会出现模糊
  • 画布受限,比如无法类似Figma进行无限画布操作
  • 没有标尺,没有标尺可以知道元素的目测位置
  • 缺少群组,导致所有元素平铺,管理元素不方便
  • 没动画效果,支持绘制动画效果
  • 元素匮乏,比如二维码元素、gif动图元素支持等

在此反馈的基础上,我也不是全量接受,毕竟开源只是“用爱发电”,况且时间精力有限,我还都是周末等业余空闲时间在维护。因此,我只挑选了符合iDraw.js定位“二维平面设计”的内容进行重构,也就是后续iDraw.js新增的内容。

iDraw.js v0.4带来什么?

  • 无限画布,数据层面上的“无限”,只要在JS的数值范围内,都可以作为尺寸数值。
  • 矢量绘制,只要是非图片元素,绘制的内容都可以无损放大,也是iDraw.js前几个版本中,社区反馈的方法模糊问题,在v0.4版本中已经得到解决。
  • 新增元素类型,新增了路径元素,作为兜底画图操作,基于SVG路径规范,可以绘制出任何形状和内容。
  • 新增群组类型,更加方案绘图的元素组织操作。
  • 功能中间件化,底层的功能采用中间件的思路,做到可以组装功能。比如,可以组装标尺、缩放和滚动条等功能。

以上就是iDraw.js的重构后特性功能,这些功能我将集成到 @idraw/studio 中,也就是iDraw.js的能力展示场所。

@idraw/studio 向Figma学习

上述内容,提到一个点,就是iDraw.js从2021年第一次提交代码到2023年上半年,定位非常不清楚,全靠一腔热血“无脑”写代码。

这里就存在一个问题,缺少一个发展方向,容易会让兴趣和热情做无用功。因此,在开发@idraw/studio来集成演示iDraw.js功能时候,我就把Figma作为学习对象,尽可能地让@idraw/studio来复刻实现Figma的部分功能。

现在@idraw/studio已经能初步实现类似Figma的网页设计功能,虽然@idraw/studio目前处于很原始阶段,但在接下来一年会推动iDraw.js持续迭代优化,朝着Figma的特性来作为iDraw.js深度开发的方向。

踩过的坑

在开发和开源iDraw.js这两年多的过程并不是一帆风顺的。其中遇到各种磕磕碰碰,好在到了2023年底,轻舟已过万重山,现在也能总结一下踩坑的心得。

时间在精不在多

想清楚要实现的逻辑,每周末或节假日抽出2小时实现。每次写代码,就把精神集中控制在两小时内,休闲时间写多代码会产生疲劳感。

要时刻保持“兴趣驱动”来做技术开源,不要“为了做而做”,要为了“我高兴”和“有成就感”来做。

功能先跑起来,优化以后再说

“技术兴趣”驱动的开源项目,那么天然会自带很多“技术思维”,比如要尽量追求极致的技术性能。但如果过于注重性能优化,将会陷入牛角尖中浪费宝贵的时间和精力。

性能优化是个持续的工程,不是一蹴而就,持续的优化失败会带来连续的挫败感,会大大削弱技术动力。

不要怀疑,兴趣第一

在开源维护iDraw.js这两年里,被人吐槽是玩具,导致自我怀疑花费业余时间维护有意义吗?

发现有更完善的绘图框架,导致怀疑自己重新造轮子有意义吗?

在投入大量业余时间维护时候,怀疑是一个很大的阻力,消除阻力的方式就是强调“兴趣”的重要性,因为是兴趣驱动,自己开心就好,别管哪些和兴趣无关的因素。

未来计划

目前iDraw.js还处于v0.4的beta阶段,还有很多待优化的地方,打算在2024年花一年的业余时间,打磨出稳定的 v0.4 版本。

其它

ubuntu下apache2+php+mysql环境配置

ubuntu下apache2+php+mysql环境配置

安装apache2

# 安装

# 执行文件地址
/etc/init.d/apache2

# 配置文件目录
/etc/apache2

# 启动apache2
sudo apache2ctl -k start

# 停止apache2
sudo apache2ctl -k stop

# 重新启动apache2
sudo apache2ctl -k restart

安装php

# 目前会自动安装到php7版本

sudo apt install php

sudo apt-get install libapache2-mod-php

安装mysql

# 目前会安装到mysql5.7版本

sudo apt-get update

sudo apt-get upgrade

sudo apt-get install mysql-server mysql-client

用TypeScript写了个低配版H5美图工具

前言

最近两月在学习canvas时候,发现很多有意思的技术能力,特别是在图像处理这一领域。让我想起大学课堂教学的《数字图像处理》(冈萨雷斯 版本)。但是很遗憾的是,大学上完课应付考试后全部还给老师了,毕业后一直做WEB相关开发,再也没怎么去接触图像处理这一领域技术。

利用每天下班回家后的零星时间,用TypeScript基于canvas的能力,写了一个H5图像处理小工具,勉强算是低配版的“美图秀秀”。这个图像处理的小工具我命名为 Pictool

pictool-ui-adjust

具体源码地址

https://github.com/chenshenhai/pictool

具体文档地址

https://chenshenhai.github.io/pictool-doc/

在线例子
https://chenshenhai.github.io/pictool/example/index.html

pictool-logo

CDN 快速使用

<script src="https://unpkg.com/pictool/dist/index.min.js"></script>
(function(Pictool) {
  const util = Pictool.browser.util;
  const PictoolUI = Pictool.UI;

  // 获取测试图片,实际使用请输入实际的图片URL
  // 注意如果图片是跨域的,请保证图片源站允许跨域
  util.getImageDataBySrc('./xxx.jpg').then(function(imgData) {
    const pictoolUI = new PictoolUI(imgData, {
      uiConfig: {
        language: 'zh-cn',
      },
    });
    pictoolUI.show();
  }).catch(function(err) {
    alert(JSON.stringify(err));
  });
})(window.Pictool);

example-ui

具体动态效果

pictool-ui-adjust

pictool-ui-effect

pictool-ui-process

NPM使用

快速安装

npm i --save pictool

快速使用

import Pictool from 'pictool';

(async function() {
  const imgData = await Pictool.browser.util.getImageDataBySrc('./xxx.jpg');
  const tool = new Pictool.UI(imgData, {
    uiConfig: {
      language: 'zh-cn',
    },
  });
  tool.show();
})()

把编译后的代码放在HTML页面上,就可以实现上述CDN的使用效果

Pictool 功能

Pictool 图像处理小工具目前支持了常用的图像处理能力,分别都可以独立抽出使用。

图像处理能力

  • Brightness(Lightness) 亮度
  • Hue 色相
  • Saturation 饱和度
  • Alpha 透明度
  • Invert 反色
  • Grayscale 灰度
  • Sobel Sobel边缘计算
  • Sepia 褐色化(怀旧)
  • Posterize 色阶
  • Gamma 伽马处理

图像滤镜效果

可以通过图像处理的基础能力,组合成滤镜效果。
例如 Sobel边缘计算 + 反色 组合就可以产生 素描画 的效果

example-digit-browser-sanbox

var sandbox = new Pictool.browser.Sandbox('./xxx.jpg');
sandbox.queueProcess([
  { process: 'sobel', options: {}, },
  { process: 'invert', options: {}, }
]).then(function(base64) {
  document.querySelector('body').innerHTML = `<img src="${base64}" />`;
}).catch(function(err) {
  console.log(err);
});

浏览器能力

  • 图片数据转换
    • 图片 URL转图片HTMLImage
    • 图片 URL转图片ImageData
    • 图片 ImageData转图片base64
  • 图片压缩: 将图片压缩在 400百万像素内
  • 其他能力,详见文档

Pictool 文档

在写了这个 Pictool 图像处理小工具后,顺便把所有的功能点的使用方式都整理成文档,方便使用时候查阅。

https://chenshenhai.github.io/pictool-doc/

pictool-doc

pictool-doc-quick

TypeScript 使用感想

这次开发这个小工具,其实也是为了深入熟悉 TypeScript 在项目开发使用,主要有一下感想的总结。

    1. 如果是开始接触 TypeScript,建议使用时候,开启strict: true最严格模式。
    1. 所有模块、函数、变量等都要严格声明类型。
    1. 开启 eslintTypeScript 最严格校验和修复
    1. webpackrollup两种编译体系下建议都尝试一遍。
    1. 多折腾多写代码,学习新东西没有捷径可言

后记

经过两个月的开发 Pictool 的沉淀,后续已经开始整理下一本关于canvas图像处理的学习笔记。目前已经沉淀了部分笔记,后续会持续整理更新上去,同时也会在公众号分享其中比较有意思的技术能力。

canvas-note

  • 本博客内容首发会在公众号显示,如果想第一时间知道博客内容更新,可以 watch 本项目 或者 关注我公众号 大海码 DeepSeaCode

qrcode_for_gh_959d1c4d729a_258

Node.js 多进程自动守护管理

前言:放假在家阅读了cfork模块的源码,发现其中的进程重启管理很有意思,值得学习,模仿该功能自己写了简单的进程fork和refork的核心代码片段

主要功能

  • 启动多进程
  • 子进程退出自动fork新进程

多进程启动和自动守护代码

./fork.js

const cluster = require('cluster');
const os = require('os');
const cupCount = os.cpus().length; 

module.exports = fork;

/**
 * @name {Function} frok
 * @param {Object} options 
 * - {String} exec [必填]进程文件 
 * - {Array} args [必填]进程命令参数
 * - {Boolean} silent 是否要发送输 默认false
 * - {Number} count, 进程数量 默认为CPU核数
 * - {Boolean} refork, 当work进程退出或者断开,是否需要重启, 默认是true
 */
function fork (options = {}) {
  if (cluster.isWorker) {
    return;
  }

  if( !options.exec ) {
    return;
  }

  const totalWorkerCount = options.count > 0 ? options.count : cupCount;

  let opts = {
    exec: options.exec
  }; 
  let newWorker;
  let workerCount = 0;

  // 启动主进程
  cluster.setupMaster(opts);

  for( let i=0; i<totalWorkerCount; i++ ) { 
    // 根据配置启动相关的子进程
    newWorker = forkWorker();
  }

  /**
   * @name forkWorker worker进程启动方法
   * @return {Object} 
   */
  function forkWorker() { 
    // 判断进程是否超过CPU数量
    if( workerCount >= cupCount ) {
      return;
    }
    // 如果不超过CPU数量的就fork worker进程
    workerCount ++;
    return cluster.fork();
  }

  /**
   * @name reforkWorker worker进程重启方法
   * @return {Object} 
   */
  function reforkWorker() {
    return forkWorker()
  }

  // 监听进程是否退出
  cluster.on('exit', (worker, code, signal) => { 
    console.log( `the worker pid  ${worker.process.pid} has exited` )
    // 重新fork进程
    reforkWorker();
  });

  // 监听进程是否断开连接
  cluster.on('disconnect', (worker) => { 
    console.log( `the worker pid  ${worker.process.pid} has disconnected` )
    let isDead = worker.isDead && worker.isDead();
    if ( isDead ) {
      console.log( `the worker pid  ${worker.process.pid} is dead` )
      return;
    } 
    workerCount --;
    reforkWorker();
  });

  return cluster;
}

多进程守护使用

index.js

const fork = require('./fork');

fork({
  exec: './worker'
})

worker.js

const http = require('http');
const process = require('process');

const PORT = 3001;

const server = http.createServer((req, res) => {
  res.write('hello fork');
  res.end();
});

server.listen(PORT, () => {
  console.log(`the server is start at port ${PORT}, PID=${process.pid}`)
})

测试效果

启动多进程守护

2018-02-20 3 47 53

手动删除其中的一个子进程

2018-02-20 3 48 27

可以看出当监听到子进程退出后,就会自动重启一个新的进程,保证指定数量多进程的执行

参考代码

https://github.com/node-modules/cfork/blob/master/index.js

JS简单的加密解密方法

前言

前段时间看了阮一峰老师的 《XOR 加密简介》, 突发奇想写了一个基于XOR(异或) 加密解密的JavaScript程序

具体步骤

加密

          +-------------------------------------+
          |         待 加 密 字 符 串             |
          +-------------+----+------------------+
                        |    |
+-----------------------v----v----------------------------+
|                                                         |
|          将 字 符 串 每 个 字 符 转 成 ascll 码             |
|                                                         |
+-----------------------+----+----------------------------+
                        |    |
+-----------------------v----v----------------------------+
|                                                         |
|          将 ascll 码 进 行 xor ( 异 或 ) 加 密             |
|                                                         |
+-----------------------+----+----------------------------+
                        |    |
+-----------------------v----v----------------------------+
|                                                         |
|      将 加 密 的 ascll 码 转 成 自 定 义 进 制 的 字 符      |
|                                                         |
+-----------------------+----+----------------------------+
                        |    |
          +-------------v----v------------------+
          |         加 密 后 的 字 符 串          |
          +-------------------------------------+

解密

          +-------------------------------------+
          |         待 解 密 字 符 串             |
          +-------------+----+------------------+
                        |    |
+-----------------------v----v----------------------------+
|                                                         |
|          将 字 符 串 每 个 字 符 转 成 ascll 码             |
|                                                         |
+-----------------------+----+----------------------------+
                        |    |
+-----------------------v----v----------------------------+
|                                                         |
|            将 ascll 码 进 行 xor ( 异 或 )                |
|                                                         |
+-----------------------+----+----------------------------+
                        |    |
+-----------------------v----v----------------------------+
|                                                         |
|        将 解 密 的 ascll 码 转 成 对 应 进 制 的 字 符       |
|                                                         |
+-----------------------+----+----------------------------+
                        |    |
          +-------------v----v------------------+
          |        解 密 后 的 字 符 串           |
          +-------------------------------------+

 Close 

字符串加密

/**
 * encrypto 加密程序
 * @param {Strng} str 待加密字符串
 * @param {Number} xor 异或值
 * @param {Number} hex 加密后的进制数
 * @return {Strng} 加密后的字符串
 */
function encrypto( str, xor, hex ) {
  if ( typeof str !== 'string' || typeof xor !== 'number' || typeof hex !== 'number') {
    return;
  }

  let resultList = []; 
  hex = hex <= 25 ? hex : hex % 25;

  for ( let i=0; i<str.length; i++ ) {
    // 提取字符串每个字符的ascll码
    let charCode = str.charCodeAt(i);
    // 进行异或加密
    charCode = (charCode * 1) ^ xor;
    // 异或加密后的字符转成 hex 位数的字符串
    charCode = charCode.toString(hex);
    resultList.push(charCode);
  }

  let splitStr = String.fromCharCode(hex + 97);
  let resultStr = resultList.join( splitStr );
  return resultStr;
}

字符串解密

/**
 * decrypto 解密程序
 * @param {Strng} str 待加密字符串
 * @param {Number} xor 异或值
 * @param {Number} hex 加密后的进制数
 * @return {Strng} 加密后的字符串
 */
function decrypto( str, xor, hex ) { 
  if ( typeof str !== 'string' || typeof xor !== 'number' || typeof hex !== 'number') {
    return;
  }
  let strCharList = [];
  let resultList = []; 
  hex = hex <= 25 ? hex : hex % 25;
  // 解析出分割字符
  let splitStr = String.fromCharCode(hex + 97);
  // 分割出加密字符串的加密后的每个字符
  strCharList = str.split(splitStr);

  for ( let i=0; i<strCharList.length; i++ ) {
    // 将加密后的每个字符转成加密后的ascll码
    let charCode = parseInt(strCharList[i], hex);
    // 异或解密出原字符的ascll码
    charCode = (charCode * 1) ^ xor;
    let strChar = String.fromCharCode(charCode);
    resultList.push(strChar);
  }
  let resultStr = resultList.join('');
  return resultStr;
}

测试

let s1 = 'hello world';

// 加密
s1 = encrypto(s1, 123, 25);
console.log('s1=', s1);
// s1= jz15znznzkz3gzczkz9znz16

// 解密
let s2 = decrypto(s1, 123, 25);
console.log('s2=', s2);
// s2= hello world

枸杞水背后的2019年业余技术总结

人到了中年,经常忙里偷闲多喝了两口枸杞水就容易上头,伤春悲秋地胡思乱想。想着2019年已经结束了,不能免俗地总结一下2019年的不羁的业余学习总结。

为啥是只做业余时间的总结?是因为工作是生活所迫,业余学习才是兴趣驱动。在很多时候,业余时间才能静下来看书写字,丝毫没有工作需求那种滚滚烟火的纷扰。用句鸡汤点的表达,业余学习才是超越别人成长的秘诀。

重新变“废”为宝

2019这一年,花了最多心思是在思考怎样把以前学到过的但是没用过知识做个落地使用,例如大学教过一堆图像处理的知识,但是受限于工作的场景,很少有用武之地。因为对于大学学到的知识基本都在吃灰感到可惜,所以在花了三个多月的构思和编程,将TypeScript和“荒废”已久的图像处理知识结合,写了一个H5美图工具 pictool[1]。

pictool-ui-adjust

pictool-ui-effect

pictool-ui-process

写了这个H5美图工具,也顺便熟悉了 TypeScript。同时感慨,工作了三四年,才第一次用TypeScript写了一个完整的项目。

很多时候都在想大学或者平时学到的东西有什么用?其实换个思路来看待这个问题,应该是我目前想实现的事物,在我学到的知识哪些可以用来给达到目的铺路。

重新理解“一万小时定律”

“人们眼中的天才之所以卓越非凡,并非天资超人一等,而是付出了持续不断的努力。1万小时的锤炼是任何人从平凡变成世界级大师的必要条件。” ——出自 马尔科姆·格拉德韦尔《异类》[2]

美国两位畅销书作家,丹尼尔·科伊尔的《一万小时天才理论》与马尔科姆·格拉德韦尔的一本类似“成功学”的书《异类》,其核心都是“一万小时定律”,就是不管你做什么事情,只要坚持一万小时,基本上都可以成为该领域的专家。[2]

“一万小时定律” 这个词汇我是在刚毕业不久合租的室友跟我提到的一个定律,意思是任何人把一件事件做一万小时,就能成为这个领域的大师。当时“年轻气盛”的我是对于这种“成功学鸡汤”是嗤之以鼻的,后来在个人的技术成长遇到瓶颈的时候,又“真香”地喝起这碗鸡汤。

在前端快餐式的技术潮流中,很容易给人一种错觉,就是不管任何技术,只要找到官方文档和多写demo踩多几次坑就能掌握,然后就不知道下一步该怎么学习技术陷入迷茫。

这个问题在我工作了两三年后就变得更加明显。一开始是在技术快餐的错觉中写了《Koa2进阶学习笔记》、《Koa.js设计模式学习笔记》等开源书籍后续就陷入技术快餐式学习带来的迷茫期。

在2019年为了跳出这种快餐式的迷茫状态,刚好遇上 Deno这个技术的起步,就选择在无文档啃源码去学习。换个方式去学习一门技术,从2019年初的时候,就开始写《Deno进阶开发笔记》,跟着Deno的一年多的官方推翻重写等迭代,去维护这本开源书。

一开始的每天一小时读源码维护《Deno进阶开发笔记》,到后续工作太忙了变成每周至少4小时的学习去迭代,在这2019年里一共提交了370多个commit迭代。重新捧起“一万小时定律” 这碗鸡汤,放下技术快餐,静下心去沉淀。

具体业余产出

  • 《Deno进阶开发笔记》

  • 《Canvas开发笔记》

  • Pictool H5美图库

其中最开心的是边学习Deno时候边入门TypeScript,然后基于TypeScript写了Pictool这个H5美图库,也顺便写了 《Canvas开发笔记》 这本开源书。今年的业余学习技术点都是相辅相成,一气呵成。

感触最深的是,写文档文章比写代码还累,还要费神。经常遇到提笔忘词的窘境,怎样组织语言表达出技术的能力的确有待提高。

技术焦虑的反思

人总会走弯路

  • 人总会被眼前看到的,做出当时认为正确的选择

但是遇到弯路最好要当断则断

  • 中途验证遇到阻力,就会开始怀疑当初的选择
  • 要么一条道走到黑,要么及时止损

技术外的故事

程序员的思维陷阱

  • 习惯了机器输入输出的确定性
  • 容易忽略了生活的不确定性

学会跳出“代码民工陷阱”[3]

  • 表达和沟通更重要
    • 把想到的东西表达出来
    • 把能表达出来的东西用文字简练记录下来

认识了更多有趣的人

认识了一个10年的Java+Android开发的小伙伴,离职去卖保险。在一杯咖啡的交谈中,也发现了代码外的其他世界,虽然答应了这位小伙伴会写一篇交谈后的心得,苦于工作繁忙,放了好几次鸽子。

认识了一个工作4年多的iOS开发程序员,离职去“体验世界”,去记录互联网浪潮下的背后故事。他的朋友圈记录着驾车从杭州一路南下,走访互联网浪潮下的各行各业。有国企电视台的工作人员、有电子厂里的厂哥厂妹,也有传统电子厂的硬件工程师,看他记录着各个一二三线城市在互联网影响下的人生百态。

后记

当你看到这段文字时,十分感谢你读完这篇罗里吧嗦的总结,也难为你能看完这篇毫无条理的文章。在2020年,我也懒得去展望给自己立flag,在最后祝各位读者工作顺利,天天开心!

参考资料

git push代码到远程仓库后,想回退到指定commit

git push代码到远程仓库后,想回退到指定commit

/* 1. 先查看commit提交记录 */
git log

/* 2. 找到要的commit版本的加密编码,然后进行revert  */
git  revert  xxxxx //xxxxx 为commit 生成的编码

/* 3. 利用差异 验证回退是否成功 */
git  diff  xxxx    //xxxxx 为刚才回退到的commit编码

/* 如果没有差异,就证明回退成功 */
/* 4. push 远程分支/主干 */
git push origin master

基于JSON Schema的HTML解析器

前言

写这篇文章主要是近年来前端服务端渲染框架层出不穷,开发的选择眼花缭乱。最近有个想法,把HTML的DOM树抽象成用JSON schema 语言描述,可以用于HTML渲染描述中间层抹平框架的差异。

注意:只是作为中间层抹平差异,不是代替框架 。更好地抽象处理HTML的DOM节点在前端渲染,或者服务端渲染。

具体的渲染设想如下:

  • 输入 JSON Schema
{
  tag: 'div',
  attribute: {
    style: 'background:#f0f0f0;'
  },
  content: [
    {
      tag: 'ul',
      content: [
        {
          tag: 'li',
          content: ['item 001']
        }, {
          tag: 'li',
          content: ['item 002']
        }
      ]
    }
  ],
}
  • 输出HTML
<div style="background:#f0f0f0;">
    <ul>
        <li>item 001</li>
        <li>item 002</li>
        <li>item 003</li>
    </ul>
</div>

HTML的基本要素

如果要实现以上解析功能,就先抽象出前端、后端的HTML渲染的元素。

  • tag类型 tag
  • tag属性 attribute
  • tag内容(文本 和 子节点)content

Schema解析实现

设定tag

  • 合法的tag
const legalTags = [
  'html', 'head', 'title', 'base', 'link', 'meta', 'style', 'script',
  'noscript', 'body', 'section', 'nav', 'article', 'aside', 'h1', 'h2',
  'h3', 'h4', 'h5', 'h6', 'hgroup', 'header', 'footer', 'address', 'main',
  'p', 'hr', 'pre', 'blockquote', 'ol', 'ul', 'li', 'dl', 'dt', 'dd',
  'figure', 'figcaption', 'div', 'a', 'em', 'strong', 'small', 's', 'cite',
  'q', 'dfn', 'abbr', 'data', 'time', 'code', 'var', 'samp', 'kbd', 'sub',
  'sup', 'i', 'b', 'u', 'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br',
  'wbr', 'ins', 'del', 'img', 'iframe', 'embed', 'object', 'param', 'video',
  'audio', 'source', 'track', 'canvas', 'map', 'area', 'svg', 'math',
  'table', 'caption', 'colgroup', 'col', 'tbody', 'thead', 'tfoot', 'tr',
  'td', 'th', 'form', 'fieldset', 'legend', 'label', 'input', 'button',
  'select', 'datalist', 'optgroup', 'option', 'textarea', 'keygen',
  'output', 'progress', 'meter', 'details', 'summary', 'command', 'menu'
]
  • 不闭合的tag
const notClosingTags = {
  'area': true,
  'base': true,
  'br': true,
  'col': true,
  'hr': true,
  'img': true,
  'input': true,
  'link': true,
  'meta': true,
  'param': true,
  'embed': true
}

解析tag的schema

  • 解析schema入口
function parseSchema (schema) {
    let html = ''
    
    if (isJSON(schema)) {
      // 如果是JSON类型,就直接用tag类型解析
      html = parseTag(schema)
    } else if (isArray(schema)) {
      // 如果是JSON类型,就直接用tag上下文类型解析
      html = parseContent(schema)
    }
    return html
}
  • 解析tag类型
function parseTag (schema) {
  let html = ''
  if (isJSON(schema) !== true) {
    return html
  }
  let tag = schema.tag || 'div'
  const content = schema.content
  // 检查是否为合法的tag, 如果不是,默认为div
  if (tags.legalTags.indexOf(tag) < 0) {
    tag = 'div'
  }
  const attrStr = parseAttribute(schema.attribute)
  // 判断是否为闭合tag
  if (tags.notClosingTags[tag] === true) {
    // 如果是闭合的tag
    html = `<${tag} ${attrStr} />`
  } else {
    // 如果是非闭合tag
    html = `<${tag} ${attrStr} >${parseContent(content)}</${tag}>`
  }
  return html
}
  • 解析tag内容
// 内容的类型为 Array
// Array的内容为字符串,或者tag类型的JSON
function parseContent (content) {
  let html = ''
  if (isArray(content) !== true) {
    return html
  }
  for (let i = 0; i < content.length; i++) {
    const item = content[i]
    if (isJSON(item) === true) {
      html += parseTag(item)
    } else if (isString(item)) {
      html += item
    }
  }
  return html
}

参考

https://github.com/caolan/pithy

终端控制台的画笔: ANSI转义序列

前言

经常用系统终端进行技术开发的过程会看到一些终端的动画效果,例如这个
002-progress

最近刚好好奇的了解了一下,才知道实现这个能力是利用了终端支持的 ANSI转义序列,任何语言只要能调用终端的标准输入/输出(stdin/stdout),都可以直接使用ANSI转义序列的规范制作对应的终端动画。

什么是ANSI

ANSI是一种字符代码,为使计算机支持更多语言,通常使用 0x00~0x7f 范围的1个字节来表示 1个英文字符。超出此范围的使用0x80~0xFFFF来编码,即扩展的ASCII编码 [1]

什么是ANSI转义序列

ANSI转义序列是一种带内信号的转义序列标准, 相关视频终端控制光标屏幕位置、文本显示、显示样式等操作。[2]

历史 [2]

规范历史

  • 每个计算机显示终端制造商都有自己的转义序列规范
  • 为了解决转义序列的不统一带来的问题,1976年通过的第一版ANSI标准——ECMA-48
  • “ANSI转义序列”这个名词可以追溯到 1979年ANSI采用ANSI X3.64
  • ANSI X3L2委员会与ECMA委员会TC1制定的标准合并为ISO 6429的国际标准
  • 经历过多年改版,从1991年开始的第5版被ISO和IEC用作标准规范
  • 1994年,ANSI取消了其标准,后续均以ISO标准为主

系统支持历史

  • 微软的DOS 1.x不支持ANSI或任何其他转义序列
  • DOS 2.0引入了添加设备驱动程序来支持ANSI转义序列的功能(基于ANSI.SYS标准),由于性能问题,使用率还是很低
  • Win32控制台不支持ANSI转义序列,只能借助其他软件辅助使用ANSI能力
  • Window9x/Window XP/Window7/Window8 均不直接支持ANSI转义序列
  • 2016年,Windows 10 开始在控制台应用程序中支持ANSI转义序列

基本使用

注意,需要在Linux/MacOS 的环境下学习和使用

输出带前景色的文本

000_red_front_color

echo -e "\x1b[31m helloworld\x1b[0m" 
  • \x1b[31m 后面的文本字体色为红色,其中31m就是代表ANSI中的红色

  • \x1b[0m 关闭所有属性

  • 前景色范围在 40 - 49

ANSI 后景色
40 黑色
41 红色
42 绿色
43 黄色
44 蓝色
45 紫色
46 深绿色
47 白色

输出带后景色的文本

000_red_back_color

echo -e "\x1b[41m helloworld\x1b[0m" 
  • \x1b[41m 后面的后景色为红色,其中41m就是代表ANSI中的红色

  • \x1b[0m 关闭所有属性

  • 文本颜色范围在 40 - 49

ANSI 后景色
30 黑色
31 红色
32 绿色
33 黄色
34 蓝色
35 紫色
36 深绿色
37 白色

输出闪烁的文本

000_flash

echo -e "\x1b[5m helloworld\x1b[0m"
  • \x1b[5m 后面的文本闪烁
  • \x1b[0m 关闭所有属性

广泛支持的 ANSI 转义序列

ANSI 功能 备注
\x1b[0m 重置 关闭所有属性
\x1b[1m 粗体或强度 -
\x1b[4m 下划线 -
\x1b[5m 闪烁 低于每分钟150次
\x1b[7m 反显 前景色与背景色交换
\x1b[8m 隐藏 -
\x1b[10m 默认字体 -
\x1b[11m 替代字体 范围(11-19)
\x1b[22m 正常颜色或强度 -
\x1b[23m 非斜体、非尖角体 -
\x1b[24m 关闭下划线 去掉单双下划线
\x1b[25m 关闭闪烁 -
\x1b[27m 关闭反显 -
\x1b[28m 关闭隐藏 -
\x1b[29m 关闭划除 -
\x1b[30m 前景色(黑色) 颜色范围(30-37: 黑-红-绿-黄-蓝-紫-深绿-白)
\x1b[38m RGB前景色 参数为5;n或2;r;g;b
\x1b[39m 默认前景色
\x1b[40m 后景色(黑色) 颜色范围(40-47: 黑-红-绿-黄-蓝-紫-深绿-白)
\x1b[48m RGB后景色 参数为5;n或2;r;g;b
\x1b[49m 默认后景色
\x1b[53m 上划线
\x1b[1A 光标上移1行 上移动n行,就是\x1b[nA
\x1b[1B 光标下移1行 下移动n行,就是\x1b[nB
\x1b[1C 光标右移1行 右移动n行,就是\x1b[nC
\x1b[1D 光标左移1行 左移动n行,就是\x1b[nD
\x1b[2J 清屏 -
\x1b[K 清除光标至行尾内容 -
\x1b[?25l 隐藏光标 -
\x1b[?25h 显示光标 -
\x1b[s 保存光标位置 -
\x1b[u 恢复光标位置 -

Node.js例子

例子一: 实现一个简单的控制台进度条

实现效果

001_progress_simple

实现源码

const process = require("process");

const frame = "▊";

/**
 * 等待操作
 * @param {number} time 
 */
async function sleep(time = 10) {
  return new Promise((resolve) => {
    setTimeout(() => { resolve(); }, time)
  })
}

/**
 * 换行操作
 */
function printNewLine() {
  process.stdout.write(`\x1b[0C \x1b[K\r\n`);
}


class Progress {

  async run(time = 1000, percent = 100, modulo = 2) {
    const count = Math.floor(percent / modulo);
    for (let i = 0; i < count; i ++) {
      await sleep(time / count);
      this._print();
    }
    printNewLine();
  }

  _print() {
    // 控制打印进度条每帧的内容
    // 重复使用的时候打印开始会从上次结束的位置开始
    process.stdout.write(`\x1b[K${frame}`);
  }

}

const progress = new Progress();
progress.run(1000, 100);

例子二: 实现一个带背景和数字变化的进度条

实现效果

002-progress

实现源码

const process = require("process");

/**
 * 等待操作
 * @param {number} time 
 */
async function sleep(time = 10) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time)
  })
}

/**
 * 换行操作
 */
function printNewLine() {
  process.stdout.write(`\x1b[0C \x1b[K\r\n`);
}

/**
 * 清行操作
 * @param {number} len 原有文本长度
 */
function clearLine(len)  {
  process.stdout.write(`\x1b[${len}D`);
}

const frame = "▓";
const backgroundFrame = "░";

class Progress {

  async run(time = 1000, percent = 100, modulo = 2) {
    const count = Math.floor(percent / modulo);

    for (let i = 0; i < count; i ++) {
      await sleep(time / count);
      const progressLength = this._printProcess(i, count, modulo);
      if (i < count - 1) {
        clearLine(progressLength);
      }
    }
    printNewLine();
  }

  /**
   * 打印进度条
   * @param {number} index 进度条当前帧索引位置
   * @param {number} count 进度条帧数
   * @param {number} modulo 进度条帧率变化模数
   */
  _printProcess(index, count, modulo) {
    let progressLength = 0;
    for (let i = 0; i < count; i ++) {
      if (i <= index) {
        progressLength += this._print(frame);
      } else {
        progressLength += this._print(backgroundFrame);
      }
    }

    let percentNum = (index + 1) * modulo;
    percentNum = Math.min(100, percentNum);
    percentNum = Math.max(0, percentNum);
    progressLength += this._print(` ${percentNum}%`);
    return progressLength;
  }

  /**
   * 打印终端文本
   * @param {string} text 文本
   * @param {number} leftMoveCols 光标左移动位数
   */
  _print(text, leftMoveCols) {
    let code = `\x1b[K${text}`;
    if (leftMoveCols >= 0) {
      code = `\x1b[${leftMoveCols}D\x1b[K${text}`;
    }
    process.stdout.write(code);
    return code.length;
  }

}

const progress = new Progress();
progress.run(1000, 100);

例子三: 实现一个等待Loading效果

实现效果

003-loading

实现源码

const process = require("process");


/**
 * 等待操作
 * @param {number} time 
 */
async function sleep(time = 10) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time)
  })
}

/**
 * 换行操作
 */
function printNewLine() {
  process.stdout.write(`\x1b[0C \x1b[K\r\n`);
}


class Loading {

  constructor() {
    this._intervalId = -1;
    this._beforeLength = 0;
    this._isDuringLoading = false;
    this._loadingIndex = 0;
  }

  /**
   * Loading动画开始操作
   * @param {number} speed Loading每帧速度 
   */
  start(speed = 100) {
    if (this._isDuringLoading === true) {
      return;
    }
    this._intervalId = setInterval(() => {
      this._printLoadingText();
    }, speed);
    this._isDuringLoading = true;
  }

  /**
   * Loading 停止操作
   */
  stop() {
    clearInterval(this._intervalId);
    printNewLine();
    this._isDuringLoading = false;
    this._loadingIndex = 0;
  }

  /**
   * 打印 Loading 操作
   */
  _printLoadingText() {
    const max = 10;
    if (this._loadingIndex >= max) {
      this._loadingIndex = 0;
    }
    const charList = [];
    for (let i = 0; i < max; i++) {
      if (i === this._loadingIndex) {
        charList.push('==>');
      } else {
        charList.push(' ');
      }
    }
    
    const loadingText = [...['['], ...charList, ...[']']].join('');
    this._print(loadingText);
    this._loadingIndex += 1;
  }

  _print(text) {
    const code = `\x1b[${this._beforeLength}D \x1b[K ${text}`
    process.stdout.write(code);
    this._beforeLength = code.length;
  }
}

async function main() {
  const loading = new Loading();
  loading.start();
  await sleep(2000);
  loading.stop();
}

main();

参考资料

[1] 百度百科: ANSI
[2] 维基百科: ANSI转义序列

docker 初始化Ubuntu容器下的nodejs环境

Dockerfile文件

# init image
FROM ubuntu:16.10

# init nodejs env
RUN apt-get update

RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get upgrade -y && \
    DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade -y && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y \
    software-properties-common \
    python-software-properties \
    build-essential \
    curl \
    git \
    unzip \
    mcrypt \
    wget \
    openssl \
    autoconf \
    g++ \
    make \
    --no-install-recommends && rm -r /var/lib/apt/lists/* \
    && apt-get --purge autoremove -y

# OpenSSL
RUN mkdir -p /usr/local/openssl/include/openssl/ && \
    ln -s /usr/include/openssl/evp.h /usr/local/openssl/include/openssl/evp.h && \
    mkdir -p /usr/local/openssl/lib/ && \
    ln -s /usr/lib/x86_64-linux-gnu/libssl.a /usr/local/openssl/lib/libssl.a && \
    ln -s /usr/lib/x86_64-linux-gnu/libssl.so /usr/local/openssl/lib/


RUN groupadd --gid 1000 node \
  && useradd --uid 1000 --gid node --shell /bin/bash --create-home node

# gpg keys listed at https://github.com/nodejs/node
RUN set -ex \
  && for key in \
    9554F04D7259F04124DE6B476D5A82AC7E37093B \
    94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
    0034A06D9D9B0064CE8ADF6BF1747F4AD2306D93 \
    FD3A5288F042B6850C66B31F09FE44734EB7990E \
    71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
    DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
    B9AE9905FFD7803F25714661B63B535A4C206CA9 \
    C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
  ; do \
    gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
  done

ENV NPM_CONFIG_LOGLEVEL info
ENV NODE_VERSION 7.4.0

RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
  && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
  && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
  && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
  && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
  && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
  && ln -s /usr/local/bin/node /usr/local/bin/nodejs

RUN node -v

CMD ["node"]

VSCode调试Rust代码

Rust开发

安装环境

开始调试

初始化调试项目

mkdir debug_rs

cd debug_rs

cargo init

编辑调试代码

./debug_rs/src/main.rs 文件上写入待调试代码

fn add(x: i32, y: i32) -> i32 {
    return x + y;
}

fn main() {
    let x = 123;
    let y = 456;
    let result = add(x, y);

    let result = add(result, result);
    println!("result = {}", result);
}

调试配置

  • 选择 调试 -> 添加配置

debug_rs_001

  • 会初始化配置文件

debug_rs_002

  • 打断点

debug_rs_003

  • 调试结果

debug_rs_debug

ES6+thunkify封装和使用 node.js的mysql模块

ES6+thunkify封装和使用 node.js的mysql模块

利用ES6 class 语法重新封装mysql模块

databaseUtil.js

const config = require("./../../config") 
const mysql = require("mysql")

//配置数据库参数
const pool = mysql.createPool({
    host     :  config["DB_HOST"],
    user     : config["DB_USER"],
    password : config["DB_PASSWORD"],
    database : config["DB_DATABASE"]
});    

//数据库基本操作方法
const query = function ( sql, values, callback ) {
    pool.getConnection(function(err, conn) {
        if (err) {
            callback(err, false)
        } else {
            var querys = conn.query(sql, values, function(err, rows) {

                if (err) {
                    callback(err, false)
                } else {
                    callback(null,rows)
                }
            }); 
        }
    });
}

//数据库对外操作类
class DatabaseUtil {

    //建表方法
    createTable ( sql, callback ) {
        query( sql, [], callback )
    }

    //根据id查找数据
    findDataById ( table,  id, callback) {
        let  _sql =  "select * from ?? where id = ? "
        query( _sql, [ table, id, start, end ], callback )
    }

    //分页查找数据
    findDataByPage ( table, start, end , callback) {
        let  _sql =  "select * from ??  limit ? , ?"
        query( _sql, [ table,  start, end ], callback )
    }

    //插入数据
    insertData ( table, values, callback ) {
        let _sql = "insert into ?? set ?"
        query( _sql, [ table, values ], callback )
    }

    //更新数据
    updateData ( table, values, id,  callback ) {
        let _sql = "update ?? set ? where id = ?"
        query( _sql, [ table, values, id ], callback )
    }

    //删除数据
    deleteDataById ( table, id, callback ) {
        let _sql = "delete from ?? where id = ?"
        query( _sql, [ table, id ], callback )
    }
}


module.exports = DatabaseUtil

利用thunkify封装方法并使用

const co = require("co")
const thunkify = require("thunkify")
const DatabaseUtil = require("./databaseUtil")
const createTable = thunkify( DatabaseUtil .createTable )



co(function *(){
    let sql = 'create .... '
    let result = yield createTable( sql ); 

}).catch(function onerror(err) {
  console.error(err.stack);
});

浏览器在同js文件下执行后台线程Worker

前言

对于js的Worker的使用,看了很多网上资料,都是基于worker.js新开一个文件来执行后台线程。

但是如果不想太麻烦新开一个work.js文件,想在同一个js文件里执行后台线程的资料就很少,最后翻遍了MDN 文档,找到了一种在同js文件下执行后台线程Worker的方法,具体实现如下

实现

实现源码

/**
 * 执行函数的后台线程
 * @param func 待后台线程执行的函数
 * @param params 待后台线程执行的函数参数
 * @param feedback 执行后台线程函数后反馈回调函数
 */
 const asyncWorker = function (func, params, feedback) {
   // 设置后台执行函数的UUID
  const uuid = Math.random().toString(26).substr(2);

  // 封装成自执行函数字符串
  const scriptCode = `(${func.toString()})(event.data.params)`;
  const feedbackMap = new Map();
  // 后台线程代码字符串
  const workerCode = `
    onmessage = function (event) {
      let result = null;
      let err = null;
      result = eval(event.data.code);
      postMessage({
        id: event.data.id,
        result: result,
        error: err,
      });
    }
  `;
  const workerCodeStr = encodeURIComponent(workerCode);
  // 初始化后台线程
  const worker = new Worker('data:text/javascript;charset=US-ASCII,' + workerCodeStr);

  // 监听正常后台线程通信
  worker.onmessage = function (event) {
    const callback = feedbackMap.get(event.data.id);
    if (typeof callback === 'function') {
      callback(event.data.result, event.data.error);
    }
    feedbackMap.delete(event.data.id);
  };

  // 捕获后台线程错误
  worker.onerror = function (err) {
    const callback = feedbackMap.get(uuid);
    if (typeof callback === 'function') {
      callback(null, err.message);
    }
    feedbackMap.delete(uuid);
  };

  // 将函数存入map中
  feedbackMap.set(uuid, feedback);

  // 发起通信,执行ID为UUID的函数
  worker.postMessage({
    id: uuid,
    params: params,
    code: scriptCode
  });
};

执行正常后台线程

// 待执行在后台线程函数
// 斐波那契数列函数
const fibonacciFunc = function(params = {}) {
  const { count = 1 } = params;
  let result = 1;
  for (let i = 0; i < count; i ++) {
    result += result;
  }
  return result;
}
// 斐波那契数列的参数为 50
const params = { count: 50 };
// 开始执行后台线程 计算数列
asyncWorker(fibonacciFunc, params, function (result, err) {
  console.log('result = ', result);
  console.log('error = ', err);
});
执行结果
# 斐波那契数列 执行结果
result =  1125899906842624
error =  null

执行异常后台线程

// 待执行在后台线程 异常函数
const errorFunc = function(params = {}) {
  throw new Error('i am an error')
}
// 开始执行后台线程 异常函数
asyncWorker(errorFunc, {}, function (result, err) {
  console.log('result = ', result);
  console.log('error = ', err);
});
执行结果
result =  null
error =  Uncaught Error: i am an error

参考

MDN:Web/API/Web_Workers_API/Using_web_workers

迟到的2018年总结

2019年已经过了两个多月了,本来很早就想写一篇关于2018年的总结,但是由于工作的繁忙,加之碰上春节假期,所以这篇个人技术总结搁置了两个多月。所以在今天,趁着春节假期的余温,把2018年的技术学习历程总结一下。

1. 春风得意马蹄疾,一朝看尽长安花

—— 立flag又打脸

从2017年初把以前koa.js的学习笔记整理成开源书《Koa2进阶学习笔记》后,得到了很多社区朋友的支持和反馈。个人也感觉有点膨胀了,去年2月份,也就是2018年2月份左右,立了个flag,要把平时对Egg.js的学习笔记整理成《Egg.js深入浅出学习笔记》,膨胀后立flag的后果就是响亮的打脸,没有评估时间,也没有完整的学习规划,直至2019年初,该开源书才实现了30% 左右,最后在社区网友的催促下,很惭愧的宣布暂停更新了。

2. 有心栽花花不开,无心插柳柳成荫

—— 意外的产出

在2018年初立flag的后续,为了配套完成《Egg.js深入浅出学习笔记》,研究了Koa.js的发展历程和源码变更历史,同时也硬生生地啃下一堆主流中间件的源码。同时,为了配套Koa使用TypeScript的项目开发,也选择了研究rollup.js的编译。但是最后由于工作太忙了加上笔记深度不够,肚子里干货太少,导致该立flag的开源书难产。虽然立的flag没实现,但是意外的整理了两本学习笔记《Koa.js设计模式学习笔记》《Rollup实战学习笔记》

最后安慰了自己,有时候朝着定下目标努力前进,最后发现画饼太大了,达不到目的地,但起码自己付出过了,即使最后吃不到饼,至少也能收获点芝麻。

3. 读书患不多,思义患不明

—— 探索新大陆

2018年诞生了很多新技术,每一种都很感兴趣,都很想去学。由于懒惰的借口和没有落地应用场景,很多新技术学习的程度都是浅尝即止。同时,加上一整年立flag的打脸,深刻理解贪多嚼不烂的道理,最后就选择了个人比较感兴趣的新领域deno去学习。

deno从2018年6月份诞生开始,我是抱着观望的态度去看待的。直至后续几个月里,deno从golang转移到rust,同时deno_std(deno官方标准模块)的诞生。在我的理解中,deno官方标准模块deno_std有点类似Java的官方maven,统一管理官方审核的包。至此,我就开始觉得deno有点意思了,总得来说令我感兴趣的有以下几点:

  • 1.权限控制网络权限,文件、子进程和环境都被限制使用,除非用户赋予权限。
  • 2.浏览器化的模块机制可以像浏览器引用js链接一样的使用模块。
  • 3.浏览器的API的使用像fetch类似的浏览器API可以直接使用。

4. 乘舟侧畔千帆过,病树前头万木春

—— 再立一年flag

2018年底就开始啃deno的相关资料和开始尝试使用,至今已经有三四个月,期间被deno和deno_std的几次官方变更坑到了,导致开源书的框架和内容几次推倒重来,踩了不少坑。因此,深刻理解官方的免责声明里提到的:

“ Disclaimer
A word of caution: Deno is very much under development. We encourage brave early adopters, but expect bugs large and small. The API is subject to change without notice. ”

其中deno官方免责声明明确提到“API如有变更,恕不另行通知”。不过,经过春节假期在家里的重新构思,学习笔记框架已经初步雏形,目前只完成20%左右,想到deno这几个月来的变更坑到,不想把开源笔记过早开源“误人子弟”,等到写到50%左右再开源出来和感兴趣的小伙伴一起学习共建。

deno_note_screen

筹备中的《deno进阶开发笔记》GitHub私有仓库截图




以上就是我这一枚野生程序员的2018年迟到的总结,没有华丽的辞藻,就只有罗里吧嗦的流水账记录。新的2019年,如果有小伙伴对deno感兴趣,可以评论或者私信,欢迎一起学习进步O(∩_∩)O!



如果有小伙伴想了解我筹备中《deno进阶开发笔记》的进展,可以持续关注本公众号的消息,我会第一时间在公众号里通知大家。

qrcode_for_gh_959d1c4d729a_258


最后的最后,透露点小秘密,本公众号回复关键字 koa、rollup、deno就有相关个人学习笔记资料的回复呦 !

回炉重学HTML/DOM/Element/Node之间的关系

前言

上个月写了一篇《从寻呼机到jQuery,一枚jQuery钉子户的独白》后,引起和技术小伙伴们的对HTML操作的讨论。

A君: jQuery直接操作HTML,让项目代码很难维护。

B君: React/Vue来管理 DOM和抹平DOM的操作,让开发者可以专注前端功能的实现。

C君: 用jQuery不能让页面的节点Node变化方便可控。

D君: 元素Element操作还是交给有模板能力的框架来操作。

讨论过后我回想对话,感觉有哪些不对,HTML,DOM, NodeElement在交谈中各种混用,到底讨论是否是同一个问题呢,抱着这个心态我查了MDN文档,算是初步理清楚了以上几个名词的含义。

什么是HTML

说起这个,应该很多人都很熟悉,就是HyperText Markup Language的缩写,翻译过来就是超文本标记语言

HTML是用来描述网页的结构,如果把网页比喻成一个摩天大楼,那么HTML就是构成摩天大楼的钢筋混泥土结构

同时一个HTML网页,可以描述成一个文档Document

图片来源于网络

图片来源于网络

什么是DOM

DOM,是Document Object Model的缩写,也就是文档对象模型,是对HTML构成网页文档的一种对象描述。换句话说,DOM是用于脚本程序(例如JavaScript)操作HTML网页的对象模型。

DOM 已经实现了对 HTML的节点操作、属性操作、事件操作和内容操作等接口和方法。可以这么说,所有对HTML进行动态脚本(例如JavaScript)的操作,都是对DOM的操作

如果把网页比喻成一个摩天大楼

  • HTML就构成摩天大楼的钢筋混泥土结构
  • DOM就是构建摩天大楼的包工头,做着管理操作HTML的事情。

DOM最常见的接口例如 document.getElementsByName('body'),查找整个DOM tree元素里的body元素

什么是Element

Element,通常称为“元素”,是对接口Node实现,是所有文档对象(DOM)的基类。

  • 实现了节点接口Node的接口操作,例如节点的增删改查。
  • 扩展了对节点的属性操作,例如classNameattribute操作。

如果把网页比喻成一个摩天大楼

  • HTML就构成摩天大楼的钢筋混泥土结构
  • DOM就是构建摩天大楼的包工头
  • 那么Element是摩天大楼的装修工人,主要实现DOM中样式和内容的操作

例如操作DOM的样式

// 获取DOM中的div元素
var divElems = document.getElementsByTagName('div');
// 元素操作
// 给第一个div元素加上 bg-red 的样式名称
divElems[0].classList.add('bg-red')

什么是Node

Node是一个接口interface,同时也是继承父接口EventTargetNode主要描述了节点操作的方法和属性,例如描述了操作父节点parentNode、子节点childNode和兄弟节点previousSibling/nextSibling的操作。

如果把网页比喻成一个摩天大楼

  • HTML就构成摩天大楼的钢筋混泥土结构
  • DOM就是构建摩天大楼的包工头
  • Element是摩天大楼的装修工人
  • 那么Node就是构建摩天大楼的建筑工人,主要实现结构的操作
// 获取DOM中的div元素
var divElems = document.getElementsByTagName('div');

// 节点操作
// 给第一个div元素里追加一个 span的子节点
var span = document.createElement('span');
divElems[0].appendChild(span);

什么是 EventTarget

EventTarget 是一个事件接口,用于注册和触发事件描述的接口,也是最基本的事件监听器的接口。

如果把网页比喻成一个摩天大楼

  • HTML就构成摩天大楼的钢筋混泥土结构
  • DOM就是构建摩天大楼的包工头
  • Element是摩天大楼的装修工人
  • Node就是构建摩天大楼的建筑工人
  • 那么 EventTarget 就是构建摩天大楼的电力工人,主要是事件的注册和触发。
// 获取DOM中的div元素
var divElems = document.getElementsByTagName('div');

// 事件注册
var event = new Event('myclick');
divElems[0].addEventListener('myclick', function() { 
  alert('hello myclick event')
});

// 广播触发事件
divElems[0].dispatchEvent(event)

总结一下

我们回到前言中的语境里,讨论所谓的jQuery操作HTML,其实本质就是JavaScriptDOM的操作。

其中DOMElement的扩展实现,ElementNode接口的一种实现,而最基本的Node接口是继承于底层的EventTarget 事件接口。

  • DOM里相关事件事件监听和操作是,继承于EventTarget实现的。
  • DOM里相关属性和内容操作是,继承于Element实现的。
  • DOM里相节点操作是,继承于Node实现的。

参考资料

git的commit提交规范

看了很多大神、大厂的github仓库提交记录和规范,总结一下几点commit规范

  • feat: 添加新功能
  • docs: 修改注释、文档
  • fix: 修复bug
  • style: 修改代码格式,例如代码lint处理等代码风格处理,不能影响原功能
  • refactor: 重构代码,不能影响原功能
  • perf: 提升性能
  • test: 测试用例增删改
  • chore: 工具操作,例如初始化脚本,启动脚本和代码校验脚本等等
  • deps: 依赖修改,例如升级、降级或锁死版本

Git使用tag发布release

发布步骤如下

打tag版本

git tag -a 0.1.0 -m  "[email protected]"
  • -a 后面加的是 版本号的标签
  • -m 后面加的是 标签的描述/注释

提交标签

git push origin --tags
  • 提交之前标签 tag 到远程 Git 仓库,并且以标签版本号作为 release 版本号

React.js开发与webpack调试

React.js开发与webpack调试

1. 开发和调试所需依赖 /package.json

{
  "name": "react-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "babel-core": "^6.7.7",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.6.0",
    "babel-preset-react": "^6.5.0",
    "jsx-loader": "^0.13.2",
    "react-hot-loader": "^1.3.0",
    "webpack": "^1.13.0",
    "webpack-dev-server": "^1.14.1"
  },
  "dependencies": {
    "react": "^15.0.1",
    "react-dom": "^15.0.1"
  }
}

2. 配置 /webpack.config.js

var path = require("path");
var webpack = require('webpack');

module.exports = {
    entry: {
        "app" : "./src/render.js"
    },
    output: {
        path: __dirname + "/build/",
        publicPath: "/build/",
        filename: "[name].js"
    },
    resolve: {
        extensions: ['', '.js', '.jsx'] 
    },
    module: { 
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                query: {
                    presets: ['es2015', 'react']
                }
            }
        ]
    }
    ,
    plugins: [

    ]
};

3. demo源码

3.1 /index.html

<html>
<head>
    <title>react</title>
</head>
<body>
    <div id="app"></div>
    <script type="text/javascript" src="build/app.js"></script>
</body>
</html>

3.2 /src/render.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App'; 

ReactDOM.render( <App />, 
    document.getElementById("app"));

3.3 /src/App.jsx

import React from 'react'; 
import Botton from './Botton';
import TextBox from './TextBox';

class App extends React.Component {
    render () {
        return (
            <div>
                <h1>react-webpack</h1>
                <TextBox />
                <Botton />
            </div>
        )
    }
}

export default App;

3.4 /src/TextBox.jsx

import React from 'react';

class Botton extends React.Component {

    handleClick () {
        alert( "this is a button" );
    }

    render () {
        return (
            <button onClick={ this.handleClick }>react click</button>
        )
    }
}

export default Botton;

3.5 /src/Button.jsx

import React from 'react';

class TextBox extends React.Component {
    render () {
        return (
            <p>this is a react demo</p>
        )
    }
}

export default TextBox;

4 调试

4.1 直接生成前端文件

  • 直接在webpack.config.js目录下执行
webpack

4.2 实时修改调试

  • 实施调试webpack配置文件,/webpack.config.server.js
var path = require("path");
var webpack = require('webpack');

module.exports = { 

    entry: [
        'webpack-dev-server/client?http://127.0.0.1:3000', // WebpackDevServer host and port
        'webpack/hot/only-dev-server',
        "./src/render.js"
    ],
    output: {
        path: __dirname + "/build/",
        publicPath: "/build/",
        filename: "app.js"
    },

    resolve: {
        extensions: ['', '.js', '.jsx'] 
    },
    module: { 
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loader: 'react-hot!jsx-loader?harmony'
            },
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                query: {
                    presets: ['es2015', 'react']
                }
            }
        ]
    }
    ,
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin()
    ]
};
  • 本地服务器文件 /server.js
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');


new WebpackDevServer(webpack(config), {
    publicPath: config.output.publicPath,
    hot: true,
    noInfo: false,
    historyApiFallback: true
}).listen(3000, '127.0.0.1', function (err, result) {
    if (err) {
    console.log(err);
    }
    console.log('Listening at localhost:3000');
});
  • 启动服务进行调试
node server
  • 这里的实时调试是利用 webpack-dev-server插件,底层是基于socket实时更新浏览器的js文件

git提交代码eslint校验配置

1. 安装相关模块

# 安装钩子
npm install --save-dev husky

# 安装eslint
npm install --save-dev eslint babel-eslint

2. 配置脚本

在项目的package.json中添加提交校验脚本

  • precommit 是作为git commit 前校验
  • prepush 是作为git push 前校验
  • eslint_fix 自定义脚本,是用来执行自动eslint修复用的自定义脚本
{
  "scripts": {
    "precommit": "./node_modules/.bin/eslint --ext .jsx,.js ./",
    "prepush": "./node_modules/.bin/eslint --ext .jsx,.js ./",
    "eslint_fix": "./node_modules/.bin/eslint --fix  --ext .jsx,.js ./"
  },
}

3. 其他

配置eslint规则

  • 新建规则文件.eslintrc或者.eslintrc.js这两种文件都可以支持
  • 具体规则配置可以查阅规则文档 http://eslint.cn/docs/rules/

配置eslint忽略文件

在项目根目录下新建 .eslintignore 文件,具体忽略写法与.gitignore的写法一致

go语言简单web服务

文件目录

.
├── server.go
├── static
│   ├── css
│   │   └── style.css
│   └── js
│       └── index.js
└── template
    └── index.html

server.go 服务

package main

import (
	"net/http"
	"html/template"
	"log"
)

// 渲染模板
func render( writer http.ResponseWriter,  tplFile string, ctx map[string] string) {
	tpl, err := template.ParseFiles( tplFile )
	if err != nil {
		http.Error( writer, err.Error(), http.StatusInternalServerError )
		return
	}
	tpl.Execute( writer, ctx )
}

// 主页操作
func indexHandler( writer http.ResponseWriter, req *http.Request ) {
	context := make(map[string]string)
	context["title"] = "index page"
	context["info"] = "this is a go web"
	render( writer, "template/index.html", context )
	return
}


// main函数
func main() {
	// 主页
	http.HandleFunc("/", indexHandler)

	// 加载静态资源
	http.Handle("/static/js/", http.FileServer(http.Dir("./")))
	http.Handle("/static/css/", http.FileServer(http.Dir("./")))
    
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal("ListenAndServe:", err.Error())
	}
}

go语言模板

<html>
    <head>
        <title>{{.title}}</title> 
        <link type="text/css" rel="stylesheet" href="/static/css/style.css" />
    </head>
    <body>
        <div class="box">
            <p>{{.info}}</p>
        </div>
        <script src="/static/js/index.js"></script>
    </body>
</html>

执行go语言web服务

go run server.go

访问 http://localhost:8080

go-simple-web-server

CentOS安装Let's Encrypt证书

安装客户端

sudo yum -y install yum-utils

sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

sudo yum-config-manager --enable rhui-REGION-rhel-server-extras rhui-REGION-rhel-server-optional

sudo yum install certbot

ubuntu使用nvm管理多版本node.js

ubuntu使用nvm管理多版本node.js

安装git

sudo apt-get install git

安装vim

sudo apt-get install vim-gtk

安装配置nvm

下载安装nvm

https://github.com/creationix/nvm

配置环境变量

# 回到用户根目录
cd 

# 打开配置文件
vim .bashrc

# 在配置文件最后加上
source ~/.nvm/nvm.sh  

#保存配置文件
source .bashrc

# 检查是否安装成功
nvm --h

利用nvm安装管理各版本node.js

# 查看远程所有node.js版本
nvm ls-remote

# 安装4.4.7和6.5.0版本
nvm install 4.4.7
nvm install 6.5.0

# 切换版本
nvm use 4.4.7

# 查看当前版本和已下载版本
nvm ls 

# 设置默认版本
nvm alias default v6.5.0

Node 9下import/export的丝般顺滑使用

前言

Node 9最激动人心的是提供了在flag模式下使用ECMAScript Modules,虽然现在还是Stability: 1 - Experimental阶段,但是可以让Noder抛掉babel等工具的束缚,直接在Node环境下愉快地去玩耍import/export

如果觉得文字太多,看不下去,可以直接去玩玩demo,地址是https://github.com/chenshenhai/node-modules-demo

Node 9下import/export使用简单须知

  • Node 环境必须在 9.0以上
  • 不加loader时候,使用import/export的文件后缀名必须为*.mjs(下面会讲利用Loader Hooks兼容*.js后缀文件)
  • 启动必须加上flag --experimental-modules
  • 文件的importexport必须严格按照ECMAScript Modules语法
  • ECMAScript Modulesrequire()的cache机制不一样

快速使用import/export

  • 新建mod-1.mjsmod-2.mjs文件
/* ./mod-1.mjs */ 
export default {
    num: 0,
    increase() {
        this.num++;
    },
    decrease() {
        this.num--;
    }
}
/*  ./mod-2.mjs */ 
import Mod1 from './mod-1';

export default { 
    increase() { 
        Mod1.increase();
    },
    decrease() { 
        Mod1.decrease();
    }
}
  • 建立启动文件 index.mjs
import Mod1 from './mod-1';
import Mod2 from './mod-2';

console.log(`Mod1.num = ${Mod1.num}`)
Mod1.increase();
console.log(`Mod1.num = ${Mod1.num}`)
Mod2.increase();
console.log(`Mod1.num = ${Mod1.num}`) 
  • 执行代码
node --experimental-modules ./index.mjs

控制台会显示

使用简述

执行了上述demo后,快速体验了Node的原生import/export能力,那我们来讲讲目前的支持状况,Node 9.x官方文档 https://nodejs.org/dist/latest-v9.x/docs/api/esm.html

与require()区别

能力 描述 require() import
NODE_PATH 从NODE_PATH加载依赖模块 Y N
cache 缓存机制 可以通过require的API操作缓存 自己独立的缓存机制,目前不可访问
path 引用路径 文件路径 URL格式文件路径,例如import A from './a?v=2017'
extensions 扩展名机制 require.extensions Loader Hooks
natives 原生模块引用 直接支持 直接支持
npm npm模块引用 直接支持 需要Loader Hooks
file 文件(引用) *.js,*.json等直接支持 默认只能是*.mjs,通过Loader Hooks可以自定义配置规则支持*.js,*.json等Node原有支持文件

Loader Hooks模式使用

由于历史原因,在ES6的Modules还没确定之前,JavaScript的模块化处理方案都是八仙过海,各显神通,例如前端的AMD、CMD模块方案,Node的CommonJS方案也在这个“乱世”诞生。
当到了ES6规范确定后,Node的CommonJS方案已经是JavaScript中比较成熟的模块化方案,但ES6怎么说都是正统的规范,“法理”上是需要兼容的,所以*.mjs这个针对ECMAScript Modules规范的Node文件方案在一片讨论声中应运而生。

当然如果import/export只能对*.mjs文件起作用,意味着Node原生模块和npm所有第三方模块都不能。所以这时候Node 9就提供了 Loader Hooks,开发者可自定义配置Resolve Hook规则去利用import/export加载使用Node原生模块,*.js文件,npm模块,C/C++的Node编译模块等Node生态圈的模块。

Loader Hooks 使用步骤

  • 自定义loader规则
  • 启动的flag要加载loader规则文件
    • 例如:node --experimental-modules --loader ./custom-loader.mjs ./index.js

如果觉得以下文字太长,可以先去玩玩对应的demo3 https://github.com/chenshenhai/node-modules-demo/tree/master/demo3

自定义规则快速上手

  • 文件目录
├── demo3
│   ├── es
│   │   ├── custom-loader.mjs
│   │   ├── index.js 
│   │   ├── mod-1.js
│   │   └── mod-2.js
│   └── package.json
  • 加载自定义loader,执行import/export*.js文件
node --experimental-modules  --loader ./es/custom-loader.mjs ./es/index.js

自定义loader规则解析

以下是Node 9.2官方文档提供的一个自定义loader文件

import url from 'url';
import path from 'path';
import process from 'process';

// 获取所有Node原生模块名称 
const builtins = new Set(
  Object.keys(process.binding('natives')).filter((str) =>
    /^(?!(?:internal|node|v8)\/)/.test(str))
);

// 配置import/export兼容的文件后缀名
const JS_EXTENSIONS = new Set(['.js', '.mjs']);

// flag执行的resolve规则
export function resolve(specifier, parentModuleURL /*, defaultResolve */) {

  // 判断是否为Node原生模块
  if (builtins.has(specifier)) {
    return {
      url: specifier,
      format: 'builtin'
    };
  }

  // 判断是否为*.js, *.mjs文件
  // 如果不是则,抛出错误
  if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) {
    // For node_modules support:
    // return defaultResolve(specifier, parentModuleURL);
    throw new Error(
      `imports must begin with '/', './', or '../'; '${specifier}' does not`);
  }
  const resolved = new url.URL(specifier, parentModuleURL);
  const ext = path.extname(resolved.pathname);
  if (!JS_EXTENSIONS.has(ext)) {
    throw new Error(
      `Cannot load file with non-JavaScript file extension ${ext}.`);
  }

  // 如果是*.js, *.mjs文件,封装成ES6 Modules格式
  return {
    url: resolved.href,
    format: 'esm'
  };
}

规则总结

在自定义loader中,export的resolve规则最核心的代码是

return {
  url: '',
  format: ''
}
  • url 是模块名称或者文件URL格式路径
  • format 是模块格式有esm, cjs, json, builtin, addon这四种模块/文件格式.

Koa2 直接使用import/export

看看demo4,https://github.com/chenshenhai/node-modules-demo/tree/master/demo4

  • 文件目录
├── demo4
   ├── README.md
   ├── custom-loader.mjs
   ├── index.js
   ├── lib
      ├── data.json
      ├── path.js
      └── render.js
   ├── package-lock.json
   ├── package.json
   └── view
       ├── index.html
       └── todo.html

代码片段太多,不一一贴出来,只显示主文件

import Koa from 'koa';
import { render } from './lib/render.js';
import data from './lib/data.json';

let app = new Koa();
app.use((ctx, next) => {
    let view = ctx.url.substr(1);
    let content;
    if ( view === 'data' ) {
        content = data;
    } else {
        content = render(view);
    }
    ctx.body = content;
})
app.listen(3000, ()=>{
    console.log('the modules test server is starting');
});
  • 执行代码
node --experimental-modules  --loader ./custom-loader.mjs ./index.js

自定义loader规则优化

从上面官方提供的自定义loader例子看出,只是对*.js文件做import/export做loader兼容,然而我们在实际开发中需要对npm模块,*.json文件也使用import/export

loader规则优化解析

import url from 'url';
import path from 'path';
import process from 'process';
import fs from 'fs';

// 从package.json中
// 的dependencies、devDependencies获取项目所需npm模块信息
const ROOT_PATH = process.cwd();
const PKG_JSON_PATH = path.join( ROOT_PATH, 'package.json' );
const PKG_JSON_STR = fs.readFileSync(PKG_JSON_PATH, 'binary');
const PKG_JSON = JSON.parse(PKG_JSON_STR);

// 项目所需npm模块信息
const allDependencies = {
  ...PKG_JSON.dependencies || {},
  ...PKG_JSON.devDependencies || {}
}

//Node原生模信息
const builtins = new Set(
  Object.keys(process.binding('natives')).filter((str) =>
    /^(?!(?:internal|node|v8)\/)/.test(str))
);

// 文件引用兼容后缀名
const JS_EXTENSIONS = new Set(['.js', '.mjs']);
const JSON_EXTENSIONS = new Set(['.json']);

export function resolve(specifier, parentModuleURL, defaultResolve) {
  // 判断是否为Node原生模块
  if (builtins.has(specifier)) {
    return {
      url: specifier,
      format: 'builtin'
    };
  }

  // 判断是否为npm模块
  if ( allDependencies && typeof allDependencies[specifier] === 'string' ) {
    return defaultResolve(specifier, parentModuleURL);
  }

  // 如果是文件引用,判断是否路径格式正确
  if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) { 
    throw new Error(
      `imports must begin with '/', './', or '../'; '${specifier}' does not`);
  }

  // 判断是否为*.js、*.mjs、*.json文件
  const resolved = new url.URL(specifier, parentModuleURL);
  const ext = path.extname(resolved.pathname);
  if (!JS_EXTENSIONS.has(ext) && !JSON_EXTENSIONS.has(ext)) {
    throw new Error(
      `Cannot load file with non-JavaScript file extension ${ext}.`);
  }

  // 如果是*.js、*.mjs文件
  if (JS_EXTENSIONS.has(ext)) {
    return {
      url: resolved.href,
      format: 'esm'
    };
  }
  
  // 如果是*.json文件
  if (JSON_EXTENSIONS.has(ext)) {
    return {
      url: resolved.href,
      format: 'json'
    };
  }

}

后记

目前Node对import/export的支持现在还是Stability: 1 - Experimental阶段,后续的发展还有很多不确定因素,自己练手玩玩还可以,但是在还没去flag使用之前,尽量不要在生产环境中使用。

Linux下安装nginx

系统

CentOS

系统工具

yum -y install make gcc-c++ libtool   openssl openssl-devel  zlib zlib-devel 

安装pcre

  • 下载 pcre 包
  • 安装pcre
    • 解压 tar vzxf pcre-8.44.tar.gz
    • 进入解压后目录cd vzxf pcre-8.44
    • 执行配置./configure
    • 安装 make && make install
    • 检查安装结果 pcre-config --version
    • 复制安装结果到 制定目录 sudo cp -a pcre-8.44/ /usr/local/src/

安装nginx

  • 下载nginx
    • 下载地址 http://nginx.org/download/
    • 这里选择当前最新版本 1.17.9
    • wget http://nginx.org/download/nginx-1.17.9.tar.gz
  • 安装nginx
    • tar vzxf nginx-1.17.9.tar.gz
    • 进入解压目录 cd nginx-1.17.9
    • 配置安装 ./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-pcre=/usr/local/src/pcre-8.44
    • 注意:这里的pcre的版本目录根据自己系统安装实际情况修改
    • 安装 make && make install
    • 检查安装结果 /usr/local/nginx/sbin/nginx -v

配置nginx 全局变量

  • 进入主目录 cd ~
  • 编辑配置文件 vim .bashrc
# nginx env
export NGINX_HOME=/usr/local/nginx/sbin/
export PATH=$NGINX_HOME:$PATH
  • 保存配置 source .bashrc
  • 验证配置结果 nginx -v

使用nginx

# 启动 nginx 服务
nginx 

# 关闭 nginx 服务
nginx -s stop

其他nginx配置

多域名共用 80 端口

server {
    listen  80;
    server_name     001.example.com;
    location / {
        proxy_pass      http://127.0.01:3001;
    }
}

server {
    listen  80;
    server_name     002.example.com;
    location / {
        proxy_pass      http://127.0.01:3002;
    }
}

javascript按顺序动态加载js文件

javascript按顺序动态加载js文件

js文件的动态加载一般是通过在HTML里面追加<script> 标签进行处理,但是有时候动态追加的几个js文件有先后的依赖顺序,例如a.js、b.js、c.js依次依赖,如果直接用追加<script>标签方法可能会导致先加载完c.js,但是a.js和b.js没加载完导致报错。

解决的按顺序动态加载js文件的方案是按顺序等待文件加载,依次等待上一个js文件加载完后再加载下一个文件

/**
 * Created by ChenShenhai on 2015/12/6.
 */

(function(){

    /**
     * js列表加载索引
     * @type {number}
     * @private
     */
    var _index = 0;

    /**
     * 加载js文件
     * @name loadJs
     * @param {String} url
     * @param {Function} callback   文件加载后回调时间
     */
    function loadJs(url, callback) {
        var _script = document.createElement('script');
        _script.src = url;
        success = success || function(){};

        if(navigator.userAgent.indexOf("MSIE")>0){
            _script.onreadystatechange = function(){
                if('loaded' === this.readyState || 'complete' === this.readyState){
                    callback();
                    this.onload = this.onreadystatechange = null;
                    this.parentNode.removeChild(this);
                }
            }
        }else{
            _script.onload = function(){
                callback();
                this.onload = this.onreadystatechange = null;
                this.parentNode.removeChild(this);
            }
        }
        document.getElementsByTagName('head')[0].appendChild(_script);
    }

    /**
     * 加载js文件列表
     * @name loadJsList
     * @param {Array} arr
     */
    function loadJsList( arr ) {

        if( _index > arr.length ) {
            loadJs(arr[_index], function(){
                _index ++;
                loadJs(arr[_index]);
            })
        }
    }

   window.loadJsList = loadJsList; 
})();

vue.js组件化开发(1)单页面应用路由

vue.js组件化开发(1)单页面应用路由

更多vue-router的官方API使用 https://github.com/vuejs/vue-router/tree/dev/docs/zh-cn

本博客demo源码 https://github.com/ChenShenhai/vue-dev-demo/tree/master/demo-router

1. 搭建好webpack的开发环境

具体可参考
github.com/ChenShenhai/blog/issues/2

2. 加载路由开发依赖

npm install --save-dev vue-router

3. 单页面组件开发

3.1 配置主文件 src/main.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import { configRouter } from './router'

//加载路由中间件 
Vue.use(VueRouter)

//定义路由
const router = new VueRouter();

//配置路由参数 
configRouter(router)

//加载路由应用 
const App = Vue.extend(require('./app.vue'))

//路由挂载到dom上
router.start(App, '#app');

3.2 配置路由 src/router.js

//路由配置
export function configRouter (router) {

  //普通页面
  router.map({ 

    '/about': { 
      component: require('./page/about.vue')
    },

    //子页面路由设置subRoutes 
    '/user/:userId': {
      component: require('./page/user/index.vue'),
      subRoutes: { 
        'profile/:something': {
          component: require('./page/user/profile.vue')
        }, 
      }
    },

    //404页面
    '*': {
      component: require('./page/404.vue')
    },
  })

  //路由重定向
  router.redirect({
    '/info': '/about',
    '/my/:userId': '/user/:userId'
  })
}

3.3 单页面主应用文件 src/app.vue

<style>
.view {
  transition: all .5s ease;
}
.view-content-enter, .view-content-leave {
  opacity: 0;
  transform: translate3d(10px, 0, 0);
}
.v-link-active {
  color: red;
}
[v-cloak] {
  display: none;
}
</style>

<template>
  <div>
    <a v-link="{ path: '/about' }">/about</a><br/>
    <a v-link="{ path: '/user/1234' }">/user/1234</a> <br/>
    <a v-link="{ path: '/user/1234/profile/my_params' }">/user/1234/profile/my_params</a> <br/>
    <p>重定向</p>
    <a v-link="{ path: '/info' }">/info</a><br/>
    <a v-link="{ path: '/my/1234' }">/my/1234</a> <br/>
    <router-view class="view" transition="view-content" transition-mode="out-in" keep-alive></router-view>
  </div>
</template>

<script>

</script>

3.4 单页面应用的各个页面组件

src/page/about.vue

<template>
  <div>
    <h2>ABOUT PAGE...</h2>
    <p>hello world! this is the page about me</p>
  </div>
</template>

src/page/404.vue

<template>
  <p>404 NOT FOUNT o(╯□╰)o</p>
</template>

src/page/user/index.vue

<template>
  <div>
    <h2>USER PAGE</h2>
    <p>the userId is {{$route.params.userId}}</p>
    <router-view></router-view>
  </div>
</template>

src/page/user/profile

<template>
  <div>
    <h3>USER PAGE - Profile View</h3>
    <p>{{$route.params.userId}} {{$route.params.something}}</p>
  </div>
</template>

demo源码 https://github.com/ChenShenhai/vue-dev-demo/tree/master/demo-router

vue.js图片加载思路

vue.js图片加载思路

1 普通模板

<img src="{{ imageSrc }}" />

第一种写法会出现开始的src加载成{{imageSrc}}报错,然后vue.js进行渲染后图片就正常显示

2 v-bind:src指令写法

<img v-bind:src="imageSrc" />

第二种写法会正常渲染,不会报错,当是当图片URL无效时候,会继续报错

3 v-bind:src结合new Image() 用默认图片处理无效图片

<template>
    <!-- 加载渲染过程中用默认图片 -->
    <img v-bind:src="defaultImageSrc" />
</template>

<script>
export default {
    data () {
        return {
            //目标图片URL
            imageSrc : 'http://xxx/a/b/c.jpg',

            //无图片或图片加载失败后显示的默认图片
            defaultImageSrc : 'http://xxx/default.jpg'
        }
    },

    //vue.js渲染结束
    compiled () {
        let that = this

        //测试图片对象
        let testImg = new Image()
        testImg.src = this.imageSrc

        //如果目标图片加载成功,把默认图片替换成目标图片
        testImg.onload = function() {
            that.defaultImageSrc = imageSrc
        }
    }
}
</script>

node_modules 常用模块

node_modules 常用模块

web框架

  • express
  • koa
    • koa-route
    • koa-static

前端模板引擎

  • ejs
  • jade
  • artTemplate
  • doT
  • nunjucks

数据库

  • mysql
  • mysql2
  • sequelize

线程守护

  • forever
  • pm2

HTML相关

  • cheerio
    • 类jQuery语法,服务端操作DOM
  • js-beautify
    • 格式化 HTML/JS/CSS 字符串

文件/文件夹

控制台

  • chalk
    • 控制台文本颜色

几种js的URL参数操作

URL信息

获取 http://www.example.com/page/index.html?name=hello&key=world#view-show-name

// 获取URL信息
location

/*
{
  "href": "http://www.example.com/page/index.html?name=hello&key=world#view-show-name",
  "ancestorOrigins": {},
  "origin": "http://www.example.com",
  "protocol": "http:",
  "host": "www.example.com",
  "hostname": "www.example.com",
  "port": "",
  "pathname": "/page/index.html",
  "search": "?name=hello&key=world",
  "hash": "#view-show-name"
}
*/

获取URL信息

获取URL参数

一般方法

兼容大部分浏览器

function getURLParam ( name ) {
  if (typeof name === 'undefined') {
    return null;
  }

  let paramReg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
  let value = window.location.search.substr(1).match(paramReg);
  if (value != null) { 
    return unescape(value[2]); 
  }
  return false;
}

getURLParam('name')
// 返回 'hello'

getURLParam('key')
// 返回 'world'

高阶方法

只兼容新版浏览器
js-urlsearchparam-caniuse"

let params = new URLSearchParams(location.search);

params.has('name');
// 返回 true

params.get('name');
// 返回 'hello'

params.getAll("name");
// 返回 ['hello']

koa中间件开发和使用

koa 中间件开发和使用

  • koa v1和v2中使用到的中间件的开发和使用
  • generator 中间件开发在koa v1和v2中使用
  • async await 中间件开发和只能在koa v2中使用

generator中间件开发

generator中间件开发

generator中间件返回的应该是function * () 函数

/* ./middleware/logger-generator.js */
function log( ctx ) {
	console.log( ctx.method, ctx.header.host + ctx.url )
}

module.exports = function () {
    return function * ( next ) {

        // 执行中间件的操作
        log( this )

        if ( next ) {
            yield next
        }
    }
}

generator中间件在koa@1中的使用

generator 中间件在koa v1中可以直接use使用

const koa = require('koa')  // koa v1
const loggerGenerator  = require('./middleware/logger-generator')
const app = koa()

app.use(loggerGenerator())

app.use(function *( ) {
    this.body = 'hello world!'
})

app.listen(3000)
console.log('the server is starting at port 3000')

generator中间件在koa@2中的使用

generator 中间件在koa v2中需要用koa-convert封装一下才能使用

const Koa = require('koa') // koa v2
const convert = require('koa-convert')
const loggerGenerator  = require('./middleware/logger-generator')
const app = new Koa()

app.use(convert(loggerGenerator()))

app.use(( ctx ) => {
    ctx.body = 'hello world!'
})

app.listen(3000)
console.log('the server is starting at port 3000')

async中间件开发

async 中间件开发

/* ./middleware/logger-async.js */

function log( ctx ) {
	console.log( ctx.method, ctx.header.host + ctx.url )
}

module.exports = function () {
  return async function ( ctx, next ) {
     log(ctx)
     await next()
  }
}

async 中间件在koa@2中使用

async 中间件只能在 koa v2中使用

const Koa = require('koa') // koa v2
const loggerAsync  = require('./middleware/logger-async')
const app = new Koa()

app.use(loggerAsync())

app.use(( ctx ) => {
    ctx.body = 'hello world!'
})

app.listen(3000)
console.log('the server is starting at port 3000')

ES6封装请求GET/POST/form请求方法

ES6封装请求GET/POST/form请求方法

使用API

import MyRequest from './my-require.js'

// get 请求 回调使用
MyRequest.get({
  url: '',
  data: {},
  success: ( result ) => {
  },
  error: ( error ) => {
  }
})
// get 请求 ES7使用
async function() {
  let result = await MyRequest.get({ url: '', data: {} })
}

// post 请求 普通使用
MyRequest.post({
  url: '',
  data: {},
  success: ( result ) => {
  },
  error: ( error ) => {
  }
})
// post 请求 ES7使用
async function() {
  let result = await MyRequest.post({ url: '', data: {} })
}

// form 表单请求
MyRequest.form({
  url: '',
  data: {}
})

源码文件

// my-request.js
import 'whatwg-fetch';

function fetchEvent( options ) {
  if ( !options ) {
    return;
  }
  let _url = options.url || '';
  let _type = options.type || 'GET';
  let _data = options.data || {};
  let _success;
  let _error;
  let fetchParams = {
    credentials: 'include'
  };
  if ( _type === 'GET' ) {
    let urlParams = [];
    for ( let key in _data ) {
      let _paramStr = '';
      if ( typeof _data[key] === 'object' ) {
        _paramStr = `${key}=${JSON.stringify(_data[key])}`;
      } else {
        _paramStr = `${key}=${_data[key]}`;
      }
      urlParams.push(_paramStr)
    }

    if ( _url.indexOf('?') >= 0 ) {
      _url = `${_url}&${urlParams.join('&')}`
    } else {
      _url = `${_url}?${urlParams.join('&')}`
    }
    fetchParams = {
      ...fetchParams,
      ...{
        headers: new Headers()
      }
    }
  } else {
    fetchParams
    fetchParams = {
      method: _type,
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify(_data)
    }
    fetchParams = {
      ...fetchParams,
      ...{
        method: _type,
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(_data)
      }
    }
  }

  if ( typeof options.success === 'function' && typeof options.error === 'function' ) {
    _success = options.success;
    _error = options.error;
    window.fetch(_url, fetchParams)
    .then((response) => {
      return response.json();
    }).then( ( result ) => {
      _success( result )
    }).catch( ( err ) => {
      _error( err )
    })
  } else {
    // return window.fetch(_url, fetchParams)
    // .then((response) => {
    //   return response.json();
    // })

    return new Promise(( resolve, reject ) => {
      window.fetch(_url, fetchParams)
      .then((response) => {
        return response.json();
      }).then( ( result ) => {
        resolve( result )
      }).catch( ( err ) => {
        reject( err )
      })
    }).catch((err)=>{
      console.log(err)
    })
  }
}

const request = {
  get( options ) {
    if ( typeof options !== 'object') {
      return;
    }
    options.type = 'GET';
    return fetchEvent( options );
  },


  post( options ) {
    if ( typeof options !== 'object') {
      return;
    }
    options.type = 'POST';
    return fetchEvent( options );
  },


  form( options ) {
    if ( typeof options !== 'object') {
      return;
    }
    let _url = options.url || '';
    let _data = options.data || {};
    let _form = document.createElement('form');
    _form.method = 'POST';
    _form.action = _url;
    for ( let key in _data ) {
      let _input = document.createElement('input');
      _input.type = 'hidden';
      _input.name = key;
      let _value = _data[key];
      if ( typeof _value === 'object') {
        _value = window.JSON.stringify(_value);
      }
      _input.value = _value;
      _form.appendChild( _input );
    }
    _form.submit();

  }

};

export default request;

Mac下加密压缩文件/文件夹

压缩文件

# 执行压缩文件命令
zip -e file.zip fileName

# 命令执行后会提醒输入密码和确认密码
# Enter password: 
# Verify password: 

压缩文件夹

# 执行压缩文件夹命令
zip -e -r dir.zip ./dirName

# 命令执行后会提醒输入密码和确认密码
# Enter password: 
# Verify password: 

浅尝 WebAssembly 在Node.js和浏览器的性能对比

前言

前段时间开发图像处理工具Pictool后,遇到高频的计算瓶颈。在寻找高频计算的前端能力解决方案过程中,入门学习了一下 WebAssembly 在前端中的应用。入门的过程中踩了不少坑,例如使用AssemblyScript 开发wasm时候,发现 npmassemblyscript 已经不维护了,需要自己人工添加成从Github 仓库引用assemblyscriptnpm模块。

同时网上很多教程已经有点不同步,很多按照教程步骤后实现的代码跑不起来。最后参考原有网上的教程,一步步踩坑,实现了demo,同时也写下这篇文章作为笔记!

WebAssembly

什么是 WebAssembly

  • 计算机是不能直接识别运行高级语言(C/C++, Java, JavaScript等)。
  • 计算机能读懂是0和1的电子元件信号,对应到运行的机器码。
  • 在前端浏览器领域里,JS是解释执行,也就是运行到哪就解释成机器码让计算机读懂并执行,在高频计算性能上有一定的瓶颈。
  • WebAssembly 字节码是接近计算机能识别的机器码,只要运行环境有对应的虚拟机,能快速加载运行。

WebAssembly 优势

在前端主要的优势有

  • 体积小
  • 加载快
  • 兼容强

WebAssembly 前端能力现状

  • Node.js 目前已经支持了 WebAssembly
  • 大部分主流浏览器厂商也支持了 WebAssembly

什么是 AssemblyScript

  • AssemblyScriptTypeScript 的一个子集
  • 可以用 TypeScript 语法编写功能编译成 wasm,对前端来说比较友好。

快速开始

demo源码地址

如果想更快速尝试,可以直接去该 demo 仓库获取源码使用。

https://github.com/chenshenhai/assemblyscript-demo

安装 AssemblyScript

由于 AssemblyScriptnpm 官方模块已经停止维护,所以AssemblyScript的模块需要从Github 来源安装。

wasm-003

package.json的依赖加入 AssemblyScript 模块的 Github 来源

./package.json

{
  // ...
  "devDependencies": {
    "assemblyscript": "github:assemblyscript/assemblyscript"
    // ...
  }
}

再执行 npm installGithub 下载该模块到本地 node_module

npm install

编写功能代码

编写一个 斐波那契数列 函数

在 demo 的目录 ./src/index.ts

export function fib(num: i32): i32 {
  if (num === 1 || num === 2) {
    return 1;
  } else {
    return fib(num - 1) + fib(num - 2)
  }
}

编译

package.json 编写编译脚本

./package.json

{
  // ...
  "scripts": {
    "build": "npm run build:untouched && npm run build:optimized",
    "build:untouched": "./node_modules/assemblyscript/bin/asc src/index.ts -t dist/module.untouched.wat -b dist/module.untouched.wasm --validate --sourceMap --measure",
    "build:optimized": "./node_modules/assemblyscript/bin/asc src/index.ts -t dist/module.optimized.wat -b dist/module.optimized.wasm --validate --sourceMap --measure --optimize"
    
    // ...
  },
}

在项目根目录开始执行编译

npm run build

后面会在 ./dist/ 目录下产生编译后的几种 wasm 文件格式

├── dist
│   ├── module.optimized.wasm
│   ├── module.optimized.wasm.map
│   ├── module.optimized.wat
│   ├── module.untouched.wasm
│   ├── module.untouched.wasm.map
│   └── module.untouched.wat

Node.js 使用

./example/node/module.js 文件中,封装wasmCommonJS使用模块

const fs = require('fs');
const path = require('path');

const wasmFile = fs.readFileSync(path.join(__dirname, '..', '..', './dist/module.optimized.wasm'))

const wasm = new WebAssembly.Module(wasmFile, {});

module.exports = new WebAssembly.Instance(wasm, {
  env: {
    memoryBase: 0,
    tableBase: 0,
    memory: new WebAssembly.Memory({
      initial: 256,
      maximum: 512,
    }),
    table: new WebAssembly.Table({
      initial: 0,
      maximum: 0,
      element: 'anyfunc',
    }),
    abort: console.log,
  },
}).exports;

Node.js 使用

const mod = require('./module');

const result = mod.fib(40);
console.log(result);

执行 Node.js 的 wasm 引用

输出结果会是

102334155

浏览器使用

./example/browser/ 目录下部署浏览器访问的服务

├── dist
   ├── module.optimized.wasm
   └── module.untouched.wasm
├── example
   ├── browser
      ├── demo.js
      ├── index.html
      └── server.js

临时浏览器可访问的服务,这里用 koa 来搭建服务

具体实现在 ./example/browser/server.js 文件中

const Koa = require('koa')
const path = require('path')
const static = require('koa-static')

const app = new Koa()

const staticPath = './../../'

app.use(static(
  path.join( __dirname,  staticPath)
))

app.listen(3000, () => {
  console.log('[INFO]: server starting at port 3000');
  console.log('open: http://127.0.0.1:3000/example/browser/index.html')
})

浏览器使用 wasm 模块

具体实现在 ./example/browser/demo.js 文件中实现

const $body = document.querySelector('body');

fetch('/dist/module.optimized.wasm')
  .then(res => res.arrayBuffer())
  .then((wasm) => {
    return new WebAssembly.instantiate(wasm, {
      env: {
        memoryBase: 0,
        tableBase: 0,
        memory: new WebAssembly.Memory({
          initial: 256,
          maximum: 512,
        }),
        table: new WebAssembly.Table({
          initial: 0,
          maximum: 0,
          element: 'anyfunc',
        }),
        abort: console.log,
      },
    })
  }).then(mod => {
    const result = mod.instance.exports.fib(40);
    console.log(result)
  });

访问页面在 ./example/browser/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>demo</title>
</head>
<body>
</body>
<script src="demo.js"></script>
</html>

启动服务

node ./example/browser/server.js

浏览器访问页面

http://127.0.0.1:3000/example/browser/index.html

浏览器会出现结果

102334155

性能测试

Node.js 对比测试

const mod = require('./module');

const start = Date.now();
mod.fib(40)
// 打印 Node.js 环境下 wasm 计算 斐波那契数列 参数为40 的耗时结果
console.log(`nodejs-wasm time consume: ${Date.now() - start} ms`)

// 原生Node.js实现的 斐波那契数列 函数
function pureFib(num) {
  if (num === 1 || num === 2) {
    return 1;
  } else {
    return pureFib(num - 1) + pureFib(num - 2)
  }
}


const startPure = Date.now()
pureFib(40);
// 打印 Nodejs环境下 原生js 计算 斐波那契数列 参数为40 的耗时结果
console.log(`nodejs-js time consume: ${Date.now() - startPure} ms`)

测试结果

wasm-001

  • Node.js环境下,原生js 执行耗时 833 ms
  • Node.js环境下,wasm 执行耗时 597 ms
  • 对比下来,wasm 计算斐波那契数列 比 js 执行快了接近 30%

浏览器对比测试

const $body = document.querySelector('body');

fetch('/dist/module.optimized.wasm')
  .then(res => res.arrayBuffer())
  .then((wasm) => {
    return new WebAssembly.instantiate(wasm, {
      env: {
        memoryBase: 0,
        tableBase: 0,
        memory: new WebAssembly.Memory({
          initial: 256,
          maximum: 512,
        }),
        table: new WebAssembly.Table({
          initial: 0,
          maximum: 0,
          element: 'anyfunc',
        }),
        abort: console.log,
      },
    })
  }).then(mod => {

    const start = Date.now();
    mod.instance.exports.fib(40);
    const logWasm = `browser-wasm time consume: ${Date.now() - start} ms`;
    $body.innerHTML =  $body.innerHTML + `<p>${logWasm}</p>`
    // 打印 浏览器环境下 wasm 计算 斐波那契数列 参数为40 的耗时结果
    console.log(logWasm)
  });


  // 打印 浏览器环境下 原生js 计算 斐波那契数列 参数为40 的耗时结果
  function pureFib(num) {
    if (num === 1 || num === 2) {
      return 1;
    } else {
      return pureFib(num - 1) + pureFib(num - 2)
    }
  }
  const startPure = Date.now()
  pureFib(40);
  const logPure = `browser-js time consume: ${Date.now() - startPure} ms`;
  $body.innerHTML =  $body.innerHTML + `<p>${logPure}</p>`
  console.log(logPure);

测试结果

wasm-002

  • Chrome浏览器环境下,原生js 执行耗时 884 ms
  • Chrome浏览器环境下,wasm 执行耗时 612 ms
  • 对比下来,wasm 计算斐波那契数列 比 js 执行快了也是接近 30%

从上述 Node.js 和 Chrome 环境下运行 wasm 和 原生js 的对比中,wasm的在高频计算的场景下,耗时的确是比原生js低,同时都是接近 30% 的计算性能提升。

参考资料

从寻呼机到jQuery,一枚jQuery钉子户的独白

前言

今年难得的4天五一假期,放假当天就去下馆子吃饭,发现餐馆的叫餐系统很奇特,在前台点餐后,每桌领取块塑料牌子。餐馆前台在饭菜做好的时候,就做操作让食客的塑料牌子响动和发亮,引导食客交回塑料牌子然后去取餐。

当时点餐的我觉得这套系统很高大上,最后回家上网一查,发现这种系统叫“无线取号寻呼系统”,换句通俗的话来讲,就是局域网的“寻呼机”或者“BB机”。这就涨知识了,我的惯性常识认为二十多年前主流通信工具的“BB机”,应该早就退出历史的舞台,尘封在日新月异智能手机的浪潮底下,没想到此类技术还能在餐饮行业的信息化发展中继续发挥着余热。

当我抱着好奇心在网上继续搜索时候,发现当年的“BB机”无线寻呼技术除了应用在餐饮行业,还应用在医院的局域通信中。说到这里,也许读者很奇怪我会这么大的反应,因为我是信息工程专业出身的,虽然毕业后从事了互联网前端开发工作,但是大学4年耳濡墨染的通信技术还是有一定的技术敏感度的。

这次假期吃饭发现的“BB机”技术的新时代应用,在处于互联网前端技术的频繁更替的大环境下,给了我很大的思考,因为我的开发工作时间中,基本每周有那么一两次和同事对所谓前端“新旧技术”的友好(si)交流(bi)。因为我负责的是基础功能的前端应用,接受的锅很多都是近十年的陈年代码,基础应用99%都是基于jQuery的技术去实现的。日常重构和升级,最大的交流是对jQuery是否已经落伍淘汰的讨论和分析。

寻呼机的前世今生

作为一个电子信息相关专业出身的开发者,先从卖弄一下自己了解到的“寻呼机”技术历史

mp-002

注: 图片来自于网络

  • 大概在1970年代,寻呼机在香港和**开始流行,香港称之为“Call机”,也有称之为“传呼机”、“BB机”或者“BP机”。
  • 到了1980年代,寻呼机开始在内地普及。在1990年代,内地的寻呼机业务达到了空前的繁荣。
  • 到了1990年代,此时的香港由于手提电话的开始普及,寻呼机开始没落了。但是直到2017年,香港仍然有一间无线电寻呼运营商继续运营“寻呼机”的服务。
  • 1998年,**的寻呼机数量达到六千多万,已经是世界第一的数量。
  • 2000年后,功能手机开始普及,寻呼机在内地开始式微。
  • 直到2007年,内地大部分身份的运营商开始申请停止寻呼机的无线传呼服务,这也标志着“寻呼机”在内地正式推出通信界的历史舞台。
  • “寻呼机”虽然在推出了主流民用通信方式,但在一些特殊场合,如餐厅和医院,利用其在局域网内无线寻呼通信能力,继续发光发热实现着自己的技术价值。

mp-001

注: 图片来自于网络

jQuery真的过时了么?

先聊聊jQuery的历史

mp-003

注: 图片来自于网络

上述说了一大通“寻呼机”的前世今生,以及在特定场景仍旧发挥着余热,那我们来说说jQuery的前世今生以及是否已经过时了。以下我们来先聊聊jQuery的主要发展历程。

  • 2006年,jQuery发布了第一个正式版本,1.0
  • 2009年,jQuery 1.3.2 引用了Sizzle选择器引擎
  • 2011年,jQuery 1.5.2 重写了Ajax模块
  • 2012年,jQuery 1.8.3 重写Sizzle引擎
  • 2013年,jQuery 2.0.3 不再支持 IE 6-8,降低体积大小,提高性能
  • 2016年,jQuery 3.0 发布,Ajax支持Promise
  • 2018年,jQuery 3.3.1 发布,使用了更多ES和HTML5新特性

从上述的jQuery的发展历程看,jQuery的从诞生开始,发展没有停止过。jQuery在1.x阶段是处理PC端操作的兼容,在2.x阶段是为了摈弃低版本IE的兼容和提高性能,在3.x阶段则是与时俱进,吸收了很多ES和HTML5新特性。

再聊聊主流前端框架的实际应用对比

近几年来,前端的框架和工具库层出不穷,大浪淘沙后,目前呼声较高的框架有 React.js、Vue.js和Angular。同时,在2018年GitHub宣布放弃使用jQuery的时候,前端社区对摈弃jQuery的声音层出不穷。因为我工作中可以说50%的开发是跟jQuery有关的,在这种背景下,我就在想jQuery真的有那么落后么?

我换了一种思考角度,我为啥还使用jQuery?是在什么情况下我选择使用了jQuery?

我开发过程中,遇到的场景是服务端是Java,主要提供服务端渲染,前端用jQuery绑定对应的操作事件和DOM变化。
我的业务需求,遇到的是SEO强需求,需要在服务端渲染模板上动态渲染好数据,等待爬虫来抓取数据。
我的维护中。同时还要最低要兼容到IE8,保证在IE8上基本能正常使用。

  • 现在我们假设选则了React

那么第一个就面临着IE8的兼容性问题,这个是无解的,即使引用一堆 polyfill 库也不能彻底解决问题。
再次就是服务端模板渲染的问题,如果是以服务端Java的模板为主呢,那么React本身的JSX模板就需要重写一套,前端页面渲染时候,覆盖在服务端渲染的DOM上面。如果是以React模板为主呢,就得需要人力和机器投入去整一套SSR系统专门做服务端渲染。

  • 现在我们假设选择Vue或者Angular

其实会发现遇到的困难和 React类似的。

从上述很浅显的应用选择层面就可以知道,其实jQuery还是有一定的应用场景的,React/Vue/Angular虽然流行且提高开发效率但是也有一定的场景短板的。就像刚开始的“BB机”的历史和如今的应用场景一样,总结一句话,没有落后的技术,只有不适应场景的技术。

我们使用jQuery要注意什么?

其实如果只是考虑Chrome和IE11+的浏览器,普通的DOM操作和事件直接用原生浏览器WebAPI就可以满足要求,但是如果考虑到兼容性和API统一性,用jQuery其实也是个不错的选择。React/Vue/Angular的方便性就是抹平了操作DOM的方式,让开发者可以更关注于组件的组合和业务的实现,而jQuery核心就是操作DOM,所以很多DOM操作的注意事项要关注的。

jQuery的使用注意点其实就只有一点,就是操作DOM事件和数据生命周期控制,保证DOM在销毁时候,对应有事件和数据也一并清除,避免事件注册太多和数据残留导致的内存溢出。

  • 事件的生命周期
    • jQuery给DOM注册了$('div').on('click')事件,如果DOM有销毁的步骤,那么在销毁前就要一并把事件给删除掉$('div').off('click')
  • DOM绑定数据声明周期
    • jQuery给DOM绑定了数据$('div').data({a:123}),如果DOM有销毁的步骤,那么在销毁前就要一并把数据给清除掉

技术选型的STAR法则

我们从“寻呼机的前世今生”讲到“jQuery的是否过时”,其实核心的一点就是在讨论一个技术是否有过时落伍一说。

一门技术的诞生肯定是有其应用场景,所谓的“过时”,是应该相关的应用场景有更加高效低成本的技术代替了。

一门技术其实就是一种工具,工具的诞生是为了解决实际问题,脱离了实际问题的本身,技术的价值很难说清楚。所以这里面对技术选型,我的建议是参考STAR法则。

  • Situation 场景
  • Task 任务
  • Action 行动
  • Result 结果

套用上述的内容我们可以这么说

  • S: 餐馆点菜和送菜需要提高效率
  • T: 顾客点菜、厨师做好菜、通知顾客菜好了,让顾客取餐,来降低运营成本
  • A: 要定制一个系统,来让顾客点菜,厨师做好菜后,通知顾客去领取,所以需要一个局域网的通信低成本系统,对比很多寻呼技术,发现“BB机”的成本最低,就去落地开发。
  • R:最后结果是,无线餐馆寻呼系统实现了,顾客不用去找服务员问进度等待被寻呼,服务员也不用频繁被顾客询问,厨师也不用频繁被服务员催。

问题圆满解决

我为啥选择jQuery技术呢

  • S: 在现有Java系统上,低成本实现SEO友好的动态数据展示型页面
  • T: Java服务端动态数据渲染,前端展示型页面,轻量的事件交互,要低成本。
  • A: Java服务端直接根据不同数据动态渲染HTML,前端用jQuery直接操作DOM的事件和效果
  • R: 快速让页面上线,无需新增SSR开发量。

前端技术的信息熵

熵,通常作为一个物理名词,用来描述一个系统的混乱程度,失序程度的指标。在我大学信息工程专业学习的信息导论中,信息熵是接受的每条消息里包含信息的数量和不确定性的综合度量。也就是消息里包含的信息数量、不确定性和随机性越大,就代表着信源的熵就越大。

mp-005

注: 图片来自于网络

近几年互联网的技术发展飞快,各种技术框架爆炸性增长,带来的是眼花缭乱的技术选型困难症。对比原来比较中规中矩的前端技术体系,在2012年前,jQuery + jQuery衍生框架是主流前端技术体系,主要的理念是如何更加兼容地操作DOM和渲染页面。

但是大概2012年以后,各种前端技术理念开始普及,例如MVVM, Virtual-Dom等,对原有有序的前端技术体系带来冲击,极大提高了前端技术体系的信息熵,这带来利的一面是技术选择面广了,弊的一面是需要更多时间去衡量和选择技术。

到现在,一提到前端技术体系,充斥着眼花缭乱的技术框架和衍生框架。信息熵的增大,有序变无序并不可怕,最重要的是,要怎么学会在庞杂的前端信息中学会选择技术,学会应用到实际的场景中。

后记

对于技术优劣的讨论,一切脱离了实际问题场景讨论技术优劣的都是耍流氓。没有的绝对的银弹,只有合适场景的解决方案。

  • 本博客内容首发会在公众号显示,如果想第一时间知道博客内容更新,可以 watch 本项目 或者 关注我公众号 大海码 DeepSeaCode

qrcode_for_gh_959d1c4d729a_258

Nginx配置HTTPS证书

注意: 本文章是生成 Let's Encrypt 免费HTTPS证书,有效期3个月,需要有域名的所有权,有一台线上服务器。

生成HTTPS证书

创建帐号

在服务器中建一个目录

mkdir my_ssl

cd my_ssl
openssl genrsa 4096 > account.key

创建 CSR 文件

openssl genrsa 4096 > domain.key
openssl req -new -sha256 -key domain.key -out domain.csr

后续过程要输入 域名信息

配置 Nginx 验证服务

server {
    server_name  example.com;

    location ^~ /.well-known/acme-challenge/ {
        alias /home/xxx/www/my-ssl/;
        try_files $uri =404;
    }

    location / {
        rewrite ^/(.*)$ https://yoursite.com/$1 permanent;
    }
}

获取网站证书

wget https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py
python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir ~/www/challenges/ > ./signed.crt

结合中间证书和网站证书

wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem --no-check-certificate
cat signed.crt intermediate.pem > chained.pem

结合根证书和中间证书

wget -O - https://letsencrypt.org/certs/isrgrootx1.pem > root.pem --no-check-certificate
cat intermediate.pem root.pem > full_chained.pem

配置Nginx

server {
        listen       443 ssl;
        server_name  example.com;

        ssl_certificate     ~/www/my-ssl/chained.pem;
        ssl_certificate_key ~/www/my-ssl/domain.key;

        location / {
            proxy_pass      http:/example.com;
        }
    }

vue.js组件化开发webpack环境搭建

vue.js开发与webpack调试

1 webpack开发环境搭建

https://github.com/vuejs-templates/webpack

1.1 搭建步骤

  • 把github.com/vuejs-templates/webpack整个应用clone下来。
//npm安装全局vue模块 
$ npm install -g vue-cli

//创建应用环境
$ vue init webpack my-project

//进入应用环境
$ cd my-project

//加载应用环境依赖
$ npm install

//进入开发模式
$ npm run dev

组件化开发应用

http://cn.vuejs.org/guide/application.html

收录前端相关技术组织社区

javascript 判断PC和移动端浏览器类型和版本

javascript 判断PC和移动端浏览器类型和版本

让代码说明原理 (~ ̄▽ ̄)~(~ ̄▽ ̄)~

/**
 * Created by chenshenhai on 2015/9/22.
 */

;(function(){

    /*
     * 判断浏览器是否为移动端
     * @name    isMobile
     * @param   {boolean}   true为移动端
     */
    isMobile = function() {
        var userAgentInfo = navigator.userAgent;

        if( !!userAgentInfo.match(/AppleWebKit.*Mobile.*/) || !!userAgentInfo.match(/AppleWebKit/) ) {
            var temp = userAgentInfo.toLowerCase();
            if( temp.indexOf('android') > -1 || temp.indexOf('iphone') > -1
                || temp.indexOf('ipad') > -1 ||  temp.indexOf('windows phone') > -1
                || temp.indexOf('blackberry') > -1 ||  temp.indexOf('hp-tablet') > -1
                || temp.indexOf('symbian') > -1 ||  temp.indexOf('phone') > -1
            ) {
                return true;
            }
        }

        return false;
    };


    /*
     * 获取PC端浏览器信息
     * @name    getPCBrowserInfo
     * @param   {Object}    浏览器信息
     */
    getPCBrowserInfo = function() {
        var ua = navigator.userAgent;

        var name = 'unknown';
        var version = 'unknown';
        var engine = 'unknown';
        var engineVer = 'unknown';
        var machineSys = 'unknown';
        // var machineSys = ua.substring( ua.indexOf('(') + 1, ua.indexOf(')') ).split(';')[0];

        var tempUa = ua.toLowerCase();
        if( tempUa.indexOf('windows') > -1 ) {
            machineSys = 'windows';
        } else if( tempUa.indexOf( 'linux' ) > -1 ) {
            machineSys = 'linux';
        } else if (tempUa.indexOf('mac') > -1 ) {
            machineSys = 'mac';
        }

        if (window.opera){
            engineVer = version = window.opera.version();
            engine = 'opera';
        } else if (/AppleWebKit\/(\S+)/.test(ua)){
            engineVer = RegExp['$1'];
            engine = 'webkit';
            if (/Chrome\/(\S+)/.test(ua)){
                version = RegExp['$1'];
                name = 'chrome';
            } else if (/Version\/(\S+)/.test(ua)){
                version = RegExp['$1'];
                name = 'safari';
            } else {
                //approximate version
                var safariVersion = 1;
                var wekitVersion = parseFloat(engineVer);

                if (wekitVersion  <  100){
                    safariVersion = 1;
                } else if (wekitVersion  <  312){
                    safariVersion = 1.2;
                } else if (wekitVersion  <  412){
                    safariVersion = 1.3;
                } else {
                    safariVersion = 2;
                }

                version = safariVersion;
                name = 'safari';
            }
        } else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
            engineVer = version = RegExp['$1'];
            engine = 'khtml';
            name = 'konq';
        } else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){     engineVer = RegExp['$1'];
            engine = 'gecko';
            //determine if it’s Firefox
            if (/Firefox\/(\S+)/.test(ua)){
                version = RegExp['$1'];
                name = 'firefox';
            }
        } else if (/MSIE ([^;]+)/.test(ua)){
            engineVer = version = RegExp['$1'];
            engine = 'ie';
            name = 'ie'
        }

        return info = {
            'machine' : 'PC',
            'name' : name,
            'version' : version,
            'engineVer' : engineVer,
            'engine' : engine,
            'machineSys' : machineSys,
            'totalInfo' : ua
        };

    };


    /*
     * 获取mobile端浏览器信息
     * @name    getMobileBrowserInfo
     * @param   {Object}    浏览器信息
     */
    getMobileBrowserInfo = function ( info ) {
        var ua = navigator.userAgent;

        var name = 'unknown';
        var version = 'unknown';
        var engine = 'unknown';
        var engineVer = 'unknown';
        // var machineSys = 'unknown';
        var machineSys = ua.substring( ua.indexOf('(') + 1, ua.indexOf(')') ).split(';')[0];

        // var tempUa = ua.toLowerCase();

        if( !!ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) ) {
            machineSys = 'iphone';
        } else if( ua.indexOf('Android') > -1 || ua.indexOf('Linux') > -1 ) {
            machineSys = 'android';
        } else if( ua.indexOf('Windows Phone') > -1 ) {
            machineSys = 'windows phone';
        }

        if (window.opera){
            engineVer = version = window.opera.version();
            engine = 'opera';
        } else if (/AppleWebKit\/(\S+)/.test(ua)){
            engineVer = RegExp['$1'];
            engine = 'webkit';
            if (/Chrome\/(\S+)/.test(ua)){
                version = RegExp['$1'];
                name = 'chrome';
            } else if (/Version\/(\S+)/.test(ua)){
                version = RegExp['$1'];
                name = 'safari';
            } else {
                //approximate version
                var safariVersion = 1;
                var wekitVersion = parseFloat(engineVer);

                if (wekitVersion  <  100){
                    safariVersion = 1;
                } else if (wekitVersion  <  312){
                    safariVersion = 1.2;
                } else if (wekitVersion  <  412){
                    safariVersion = 1.3;
                } else {
                    safariVersion = 2;
                }

                version = safariVersion;
                name = 'safari';
            }
        } else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
            engineVer = version = RegExp['$1'];
            engine = 'khtml';
            name = 'konq';
        } else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){     engineVer = RegExp['$1'];
            engine = 'gecko';
            //determine if it’s Firefox
            if (/Firefox\/(\S+)/.test(ua)){
                version = RegExp['$1'];
                name = 'firefox';
            }
        } else if (/MSIE ([^;]+)/.test(ua)){
            engineVer = version = RegExp['$1'];
            engine = 'ie';
            name = 'ie'
        }


        return info = {
            'machine' : 'MOBILE',
            'name' : name,
            'version' : version,
            'engineVer' : engineVer,
            'engine' : engine,
            'machineSys' : machineSys,
            'totalInfo' : ua
        };
    };

    userBrowser = function() {

        var browser = {};
        var userAgentInfo = navigator.userAgent;
        var isMobileBrowser = isMobile();
        if( isMobileBrowser ) {
            browser = getMobileBrowserInfo();
        } else {
            browser = getPCBrowserInfo();
        }

        return browser;
    };





})();

ubuntu下apache2+php+mysql环境配置

ubuntu下apache2+php+mysql环境配置

安装apache2

# 安装
sudo apt-get install apache2

# 执行文件地址
/etc/init.d/apache2

# 配置文件目录
/etc/apache2

# 启动apache2
sudo apache2ctl -k start

# 停止apache2
sudo apache2ctl -k stop

# 重新启动apache2
sudo apache2ctl -k restart

# 如果重启遇到一下问题
# AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message
#需要在apache2.conf 文件中加上 ServerName 127.0.0.1

安装php

# 目前会自动安装到php7版本

sudo apt install php

sudo apt-get install libapache2-mod-php

安装mysql

# 目前会安装到mysql5.7版本

sudo apt-get update

sudo apt-get upgrade

sudo apt-get install mysql-server mysql-client

彻底卸载以上的安装

# 彻底卸载apache2
sudo apt-get --purge remove apache-common
sudo apt-get --purge remove apache
sudo find /etc -name "*apache*" |xargs  rm -rf 
sudo rm -rf /var/www
sudo rm -rf /etc/libapache2-mod-jk
sudo rm -rf /etc/init.d/apache2
sudo rm -rf /etc/apache2
dpkg -l |grep apache2|awk '{print $2}'|xargs dpkg -P


# 彻底卸载php
sudo apt-get –purge remove libapache2-mod-php php php-gd php-mysql
sudo apt-get autoremove php
sudo find /etc -name "*php*" |xargs  rm -rf 
dpkg -l |grep ^rc|awk '{print $2}' |sudo xargs dpkg -P


# 彻底卸载mysql
sudo apt-get autoremove --purge mysql-server
sudo apt-get remove mysql-server
sudo apt-get autoremove mysql-server
sudo apt-get remove mysql-common
dpkg -l |grep ^rc|awk '{print $2}' |sudo xargs dpkg -P
sudo find /etc -name "*mysql*" |xargs  rm -rf 


# 检查是否完整删除
dpkg -l | grep apache
dpkg -l | grep apache2
dpkg -l | grep php 
dpkg -l | grep mysql

基于ES6 + node.js的静态服务器

基于ES6 + node.js的静态资源服务器

前言:

  • 平时开发简单页面时候,需要起apache或者nginx来作为静态资源的服务器,各种配置太麻烦了。-
  • 前端的开发还还是前端的方式去解决,用node.js写一个静态资源服务器,方便页面开发和配置,顺便尝试用一下ES6开发node.js服务应用。

内容

  • 主要是用于访问js,.css ,.html和图片等常见的静态资源文件。
  • 如果访问的url是文件目录,则会显示出文件目录。
  • 尝试了用ES6的import和export来封装node.js模块。
  • 但是node.js目前不支持import/export特性,需要通过babel进行编译才能运行。

源码仓库

https://github.com/ChenShenhai/node-server

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.