Coder Social home page Coder Social logo

blog's People

Contributors

alan110 avatar

Watchers

 avatar  avatar

blog's Issues

小程序开发方案-wepy + minui

使用minui 组件

需要安装minui的cli工具,同min isntall pkg,安装完毕会自动编译到dist目录

wepy引入组件是引入编译后的组件,路径为编译后的js相对路径

引入的文件不能包含.js后缀

babel 编译的那些事儿

babel

查看所有的插件信息

babel-preset

babel-preset 只支持特殊语法的编译,比如说箭头函数,解构等。但是具体的函数方法是不会编译的。需要引入额外的垫片

es7 包含不同的stage,包含不同的特性目前的实现进度,依次向下包含

Stage 0 - Strawman: just an idea, possible Babel plugin.
Stage 1 - Proposal: this is worth working on.
Stage 2 - Draft: initial spec.
Stage 3 - Candidate: complete spec and initial browser implementations.
Stage 4 - Finished: will be added to the next yearly release.

就目前的进度来看,一般用stage2 就完全足够了,包含了async,... 等语法

es7-stage进度查询
tc39进度

babel-preset-env

官方推荐的preset,包含所有stage4的plugin(注意不支持前面的stageX, 使用的话需要额外引入)

  • 它可以根据配置的环境,动态引入preset,和按需打包垫片代码, 相当于集合了 自动引入功能plugin + babel-polyfill + 环境编译
  • 使用browserlist 库的query来指定浏览器版本
{
"presets": ["env"]
}
上面这个配置的作用和babel-prset-latest的作用一致也就是下面这个配置
{
"presets": ["latest"]
}
{
  "presets": [
    ["env", {
      "targets": {
        "chrome" : 52,
        "browsers": ["last 2 versions", "safari >= 7"]
        // node : 6.10.11
         "modules": false,
         "loose": true
      },

      "useBuiltIns" : true   // 使用 babel-polyfill 打包垫片
    }]
  ]
}

设置useBuiltIns后,会根据配置的环境,生成需要被引入的垫片代码,比如:

import "babel-polyfill";

在chrome56上编译后:

import "core-js/modules/es7.string.pad-start";
import "core-js/modules/es7.string.pad-end";
import "core-js/modules/web.timers";
import "core-js/modules/web.immediate";
import "core-js/modules/web.dom.iterable";

babel-plugin

babel-preset 是 babel-plugin的集合

babel-polyfill 和 babel-runtime

babel-preset 只支持特殊语法的编译,比如说箭头函数,解构等。但是具体的函数方法是不会编译的。需要引入额外的垫片

babel-polyfill

它会以入侵的,污染native代码的方式,完全模拟一个ES2015的环境,包含core-js , 和 regenerator runtime它应该用在一个最终的应用上,而不是一个公共库,或者工具。当使用babel-node时,这个垫片会自动被加载。

当引入它,整个垫片库都会被引入(比较大),但是一劳永逸

babel-runtime

你可以没有全局污染的通过按需引入,使用内置的语法比如Promise, Set, Symbol等,非常适合在开发一个公共库的使用场景,因为你不清楚你的代码运行的环境是什么

具体做了如下的事情:

  • 自动引入 babel-runtime/regenerator 当你使用了 generators/async 函数
  • 自动引入 babel-runtime/core-js 中的你具体使用的es6特性
  • 移除内联的 Babel helpers 使用 babel-runtime/helpers 替代

var promise = new Promise ; 相当于
var _promise = require("babel-runtime/core-js/promise");

babel-cli

可以用来测试babel的输出
添加npm script "babel": "./node_modules/.bin/babel index.js"

它依然会去使用当前目录下的.babelrc 配置,可以通过命令行参数去掉

docker入门

docker 基本介绍

docker相当于轻量级的vm:容器本身的开销低,启动速度快,但隔离/控制弱,无法虚拟其它OS
特点是在一个物理机的OS里能同时 虚拟 巨多个本OS的运行环境(要求应用本身占的资源(主要是内存)不多)

所以,它最适合有大量小型(资源要求低、负荷也低)但需要互相隔离的应用 的单位比 每个应用都采用独立物理机省,比 每个应用都采用独立虚拟机也省

容器不是虚拟机,虚拟机是实实在在的隔离物理资源,而容器可以理解为从软件上实现的隔离。
每一个容器可以看成是一个软件应用,这个应用可以实现虚拟机的作用。有点像安装系统时的系统镜像,只不过把他当成了一个软件来运行。
所以它启动快,耗费的资源少。

docker的盛行,让devops的概念大火,即运维部署也和开发一样,敏捷化,自动化。它和自动化测试一样,赋予了开发者更多的责任,拓展了开发者的疆域。

1.To开发者——得益于Docker,让他们有可能在一条或者几条命令内搭建完环境,并且保证开发,和部署时的环境一致。同时大大减小了要更换环境时的成本。
2.To运维者——Docker把整个开发环境打包成一个Dockerimage交给运维团队直接运行

docker 安装

Mac 官方下载
image
其它系统安装请参照文档左侧的列表

官方docker-toolbox包含可视化终端,不过个人尝试后感觉不好用,还是使用命令行比较方便

基本使用

我们以一个简单例子来看看如何使用docker。

使用docker pull ngnix 命令会默认从官方镜像仓库下载一个镜像到本地,下载完成后执行

docker run --name webserver -d -p 80:80 -p 3000:3000 nginx

访问http://localhost:80, 我们的ngnix容器就部署好了。上面这段命令是什么意思,后面会详细描述。
image

docker 常用命令

基本

docker images // 查看当前有的镜像
docker rmi ID // 删除一个镜像
docker ps // 查看当前正在运行的容器
docker ps -a // 查看所有的容器,包括已经停止的
docker rm ID // 删除一个容器
docker stop ID // 停止一个容器
docker start ID // 运行一个已有的容器

运行

docker run --name webserver -d -p 80:80 -p 3000:3000 nginx // 从指定镜像中运行容器

--name: 指定一个别名
-d: 静默运行,允许在后台,容器不会关闭
-p: 指定端口映射
-P: 自动映射端口

docker exec -it webserver [CMD] 进入sheel // -I 交互式 -t 终端

-it 交互式终端
CMD 为要执行的命令一般为bash或者zsh

tips

  1. 其中运行命令非常重要,它决定了如何去运行容器,容器有哪些能力。如果要修改容器的启动参数,一般需要重新运行容器,不能动态改变。
  2. 只要容器不删除,不重启,那么运行产生的数据都是存在的。这点需要注意, 避免数据丢失
  3. 默认容器内的端口服务,在端口外面是无法访问的,需要在docker run -p 30000:22 命令中指定端口映射到宿主机, 或者在dockerfile中指定端口映射,然后docker run -P 自动映射端口。

dockerFile 编写

image

dockerfile是用来描述镜像制作的。他的基本命令

FROM IMAGE // 基于一个基础镜像,可以使用官方的简洁镜像,默认是指向官方的镜像参数
COPY 拷贝目录下的文件到镜像之内
RUN 在镜像构建时执行的命令,每一条RUN命令都会创建一个docker layer
EXPOSE 暴露端口映射,主要用于运行容器时添加-P参数,自动映射端口
CMD 容器启动时执行的命令,不论有多少条CMD,只有最后一条生效

构建镜像

进入Dockerfile文件的目录执行 docker build -t ngnix:v3 . , 指定编译的镜像名称以及tag

推送到远端仓库

需要先打上tag和远端仓库联系起来, 再push
docker tag local/dev:v1 hub.xiaojukeji.com/lijialong/dev:v1
docker push hub.xiaojukeji.com/lijialong/dev:v1

tips

  1. 每一条RUN命令都会创建一个layer,层,docker是基于layer叠加复用的,层级太多,会使得镜像庞大冗余,层级太少不便于镜像的制作,所以关键部分的制作建议单独一个RUN命令,不然每次构建都会重头再来太过痛苦
  2. CMD命令是在容器启动时执行的命令,需要有一个前台执行的进程卡住,否则docker会将没有进程执行的容器杀掉,可用比如top,sshd等命令来卡住容器

express 源码解析

重点就在于路由和中间件

本文中的部分代码和图片,均有借鉴,本人偷懒就不想再画一遍了,重点是说明问题。

中间件原理

原理其实很简单,就是一个数组,每个单元就是一个中间件。记录当前index,通过一个包裹对象的next方法,循环数组执行。

express在此基础上添加了路由匹配,和子中间件。

image

/**
 * 仿照express实现中间件的功能
 *
 * Created  on 2017/3/6.
 */

var http = require('http');

/**
 * 仿express实现中间件机制
 *
 * @return {app}
 */
function express() {

    var funcs = []; // 待执行的函数数组

    var app = function (req, res) {
        var i = 0;

        function next() {
            var task = funcs[i++];  // 取出函数数组里的下一个函数
            if (!task) {    // 如果函数不存在,return
                return;
            }
            task(req, res, next);   // 否则,执行下一个函数
        }

        next();
    }

    /**
     * use方法就是把函数添加到函数数组中
     * @param task
     */
    app.use = function (task) {
        funcs.push(task);
    }

    return app;    // 返回实例
}

// 下面是测试case

var app = express();
http.createServer(app).listen('3000', function () {
    console.log('listening 3000....');
});

function middlewareA(req, res, next) {
    console.log('middlewareA before next()');
    next();
    console.log('middlewareA after next()');
}

function middlewareB(req, res, next) {
    console.log('middlewareB before next()');
    next();
    console.log('middlewareB after next()');
}

function middlewareC(req, res, next) {
    console.log('middlewareC before next()');
    next();
    console.log('middlewareC after next()');
}

app.use(middlewareA);
app.use(middlewareB);
app.use(middlewareC);

源码的目录结构

源码地址

image

主要关注application.js , router.js , route.js 这几个文件
我们知道express挂载路由的方式,有app.use , app[method] 2种,我们一步一步来看它是如何实现的

app.use

app.use(function(req, res, next){
});

app.use('/path', function(req, res, next){
})

app.post('/path', function(req, res, next){
})

application.js 是express的原型,看看里面的use方法

... 前面省略

  // restore .app property on req and res
    router.use(path, function mounted_app(req, res, next) {
      var orig = req.app;
      fn.handle(req, res, function (err) {
        setPrototypeOf(req, orig.request)
        setPrototypeOf(res, orig.response)
        next(err);
      });
    });

app.use 底层调用了router.use. 我们看下router.js 里面的use方法

proto.use = function use(fn) {
  var offset = 0;
  var path = '/';

... 此处省略

 var callbacks = flatten(slice.call(arguments, offset));

  if (callbacks.length === 0) {
    throw new TypeError('Router.use() requires a middleware function')
  }

  for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i];

    if (typeof fn !== 'function') {
      throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
    }

    // add the middleware
    debug('use %o %s', path, fn.name || '<anonymous>')

    var layer = new Layer(path, {
      sensitive: this.caseSensitive,
      strict: false,
      end: false
    }, fn);

    layer.route = undefined;

    this.stack.push(layer);
  }


}

使用flatten方法拉平中间件数组,对每一个中间件创建了一个new Layer 包裹
layer.route = undefined , 这个的意义我们后面再说
最后push到了router 的stack里面。 我们可以猜测,最后的执行方式和我们上面讨论的原理应该是类似的。

app[method]

application.js 中,定义了app的各种http方法,

methods.forEach(function(method){
  app[method] = function(path){
    if (method === 'get' && arguments.length === 1) {
      // app.get(setting)
      return this.set(path);
    }

    this.lazyrouter();

    var route = this._router.route(path);
    route[method].apply(route, slice.call(arguments, 1));
    return this;
  };
});

this.lazyrouter() ; 初始化全局的router,只有1个
我们发现底层是调用的router.route方法返回了route对象,并调用route对象的相对应的method方法
我们看看router.route的源码

proto.route = function route(path) {
  var route = new Route(path);

  var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
  }, route.dispatch.bind(route));

  layer.route = route;

  this.stack.push(layer);
  return route;
};

创建了一个route对象
创建了Layer对象
Layer绑定了route的dispatch方法,这个就是触发中间件的"start"方法
将layer对象压如stack中
layer.route = route 我们还记得app.use方法里面会有layer.route=undefined , 这里有了明显区别赋值为了route对象
我们再来看看route对象是什么

function Route(path) {
  this.path = path;
  this.stack = [];

  debug('new %o', path)

  // route handlers for various http methods
  this.methods = {};
}

.... 省略500行

Route.prototype.dispatch = function dispatch(req, res, done) {
  var idx = 0;
  var stack = this.stack;
  if (stack.length === 0) {
    return done();
  }

  var method = req.method.toLowerCase();
  if (method === 'head' && !this.methods['head']) {
    method = 'get';
  }

  req.route = this;

  next();

  function next(err) {
    // signal to exit route
    if (err && err === 'route') {
      return done();
    }

    // signal to exit router
    if (err && err === 'router') {
      return done(err)
    }

    var layer = stack[idx++];
    if (!layer) {
      return done(err);
    }

    if (layer.method && layer.method !== method) {
      return next(err);
    }

    if (err) {
      layer.handle_error(err, req, res, next);
    } else {
      layer.handle_request(req, res, next);
    }
  }
};

是不是很熟悉,我们发现route对象也是一个中间件结构,有stack,有dispatch 。
app.post('/path' , mid , mid, mid , function(req, res ,next ){}) 当我们写这样的代码的时候,中间件会被Layer对象包裹,最后其实是被压入了route对象的stack里面, router对象做了一层转发。这么做的目的是为了区分非路由中间件,和路由中间件。 所以app.use 是创建非路由中间件,app.method是创建路由中间件。底层都是调用的router方法

数据结构

image

所以整体的数据结构是2层stack,非路由中间件放入1层,路由中间件放入二层。他们都是通过layer对象包裹,区分方式就是看layer.route 是否有值, 将二者关联起来

整体流程

image

中间件是如何触发的呢

对于中间件结构,我们需要找到其起始方法。
proto.handle router对象的handle方法,是触发中间件的方法,route对象是dispatch方法

// route.use
var layer = new Layer(path, {
      sensitive: this.caseSensitive,
      strict: false,
      end: false
    }, fn);

// router.route
var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
  }, route.dispatch.bind(route));


new express()  -> express.handle  -> application.handle -> router.handle

// router.handle
proto.handle = function handle(req, res, out) {
  next();
  function next (){   
        var layer = match(path)
        layer.handle_request()
  }
})

// layer.handle_request
Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;

  if (fn.length > 3) {
    // not a standard request handler
    return next();
  }

  try {
    fn(req, res, next);
  } catch (err) {
    next(err);
  }
};

为什么 express.router 可以作为app.use的一个中间件?

express.router 官方称为路由级中间件,拥有完整的路由,中间件系统。也被称为mini app。

array-flatten

var flatten = require('array-flatten');
....
var handles = flatten(slice.call(arguments));

这个库是用于将嵌套的数组拉平成1维的。
在中间件处理上很有用,提供了非常大的灵活性

app.get('/path', [mid ,mid ,[mid,mid],mid], function(req, res, next){

})

debug 模块

gitlab-CI

CI / CD 持续集成,持续交付

将构建部署集成到gitlab

大概就是当有一个提交,需要经过一系列的操作任务处理,直到所有都通过。
pipeline 就是流水线,每个流水线有多个stage , 每个stage 有多个 job

执行job的程序境叫 runner

安装注册runner

配置具体的runner,在gitlab的setting/pipline 里面有url,token信息

runner 操作

页面制作可视化深度思考

页面制作可视化要描述的东西,解决的问题

一个页面 = 页面树结构 + data + 动态逻辑
所以可视化,就是来解决这3块的问题

场景化

编辑器即服务
场景生命周期
场景基础架构,场景如何渲染

异步化

场景动态加载,组件动态加载
场景数据化,
这样只需要1个入口,根据数据动态加载和切换

组件系统

一个组件需要和编辑器本身交互,需要和用户交互,需要配置它的熟悉面板,包含了它自身的数据,以及如何渲染

组合组件机制

koa 源码解析

koa的源代码非常简洁,不过几百行。实现中间件的逻辑甚至只有几十行
相比express,koa将路由给拆了出来,相当精简
中间件的执行逻辑放到了koa-compose 插件里面

源码目录结构

主逻辑

我们看application.js里面的这2个方法

  listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }

  callback() {
    const fn = compose(this.middleware);

    if (!this.listenerCount('error')) this.on('error', this.onerror);

    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }

这是new koa().listen 的主逻辑。逻辑非常清晰的,通过compose插件处理middleware返回一个启动函数。后续就是构造上下文,以及处理中间件。compose我们放到后面再来解析,先看看整个代码的结构

构造ctx上下文,处理中间件

  createContext(req, res) {
    const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    request.ctx = response.ctx = context;
    request.response = response;
    response.request = request;
    context.originalUrl = request.originalUrl = req.url;
    context.cookies = new Cookies(req, res, {
      keys: this.keys,
      secure: request.secure
    });
    request.ip = request.ips[0] || req.socket.remoteAddress || '';
    context.accept = request.accept = accepts(req);
    context.state = {};
    return context;
  }

  handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }

createContext 是封装了req,res,到ctx.request , ctx.response, 提供了一些便捷访问属性
handleRequest 是接收一个请求后,的处理主逻辑。通过调用fnMiddleware递归遍历中间件,之后处理handleResponse

response处理

我们看看handleResponse 的底层方法,respond做了什么

function respond(ctx) {

 ... 省略100行

  let body = ctx.body;
  const code = ctx.status;


  // status body
  if (null == body) {
    body = ctx.message || String(code);
    if (!res.headersSent) {
      ctx.type = 'text';
      ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
  }

  // responses
  if (Buffer.isBuffer(body)) return res.end(body);
  if ('string' == typeof body) return res.end(body);
  if (body instanceof Stream) return body.pipe(res);

  // body: json
  body = JSON.stringify(body);
  if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
  }
  res.end(body);
}

handleResponse是根据ctx.body的类型,返回给前端,buffer,string,或者stream

中间件递归调用

前面我们已经梳理了整个结构,下面我们来看看精华的部分,koa-compose的实现
compose 其实是函数式编程的一个重要概念,一个函数的输出是下一个函数的输入。koa-compress 当然不是标准的compose,他的初始接收参数是fn数组,中间每个函数的参数都是context

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

是用前置错误检测的方式,返回一个函数,可以使得检测只执行一次。

return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); 记录了一个index代表当前的执行的fn序号,每一层的调用,fn(conext, next ) ,dispatch方法其实就是next,next方法返回的是下一个fn的promise。整个是一个递归调用,也就是这样:

dispatch(0)
    return fn(context, dispatch(1))
        return fn(context, dispatch(2))
             return fn(context, dispatch(3))

bind方法的第二个参数

bind方法的第二个参数是预设调用参数,当实际调用时预设的参数会被放在前面。 这里用于每次 i+1

function list(){
    // 让类数组arguments拥有数组的方法slice,这个函数实现了简单把类数组转换成数组
    return Array.prototype.slice.call(arguments);
}

list(1,2,3);//[1,2,3]

//给list绑定一个预设参数4 
var list1 = list.bind(undefined,4);

list1();//[4]

list1(1,2,3);//[4,1,2,3]

promise

Koa中间件返回是一个递归的promise链条
为了兼容middleware里面可能有await,所以函数可能是async,所以返回的可能是promise。 所以不管是不是异步,都给它最后再包上一层 renturn promise.resolve (),保证执行的顺序。

所以可以解答,如果没有等待,await next(), 那么promise 会直接返回,但是没有后续middleware生效
express如果不调用next,也不res.send, 则会一直等待

koa与express中间件的实现区别

网上很多文章都说一个是线性的,一个是洋葱模型。其实这种说法不对, 执行的时候都是洋葱形。
express基于栈来实现递归调用, koa是compose(真递归), 只不过koa底层利用了async,不用写callback了

vue 源码解析

响应式整体设计

image

几个关键部分 observer,compiler,watcher

watcher是observer和compiler的桥梁

compiler

compiler解析模板,根据exp构造watcher

observer

observer监听数据变化,以及依赖收集

watcher

watcher初始化会计算一次value,通过调用this.get() , 触发exp中涉及属性的getter

编码规范

eslint

推荐风格

"extends": "eslint:recommended", // eslint 官方推荐
"extend:" eslint-plugin-vue // vue官方规则,会校验模板的语法
"extends: 'airbnb-base' // 星号最多,最严格

2空格还是4空格

目前大多数风格是2空格

要不要写分号

最佳实践

跨编辑器配置

.editorConfig + 插件
在根目录下新建.editorConfig文件

#代表这是项目的根目录
root = true
#表示所有文件都使用下面的饿规则
[*]
charset = utf-8
indent_style = space
#设置缩进为2
indent_size = 2  
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

可以配置不同文件的缩进配置,同时跨编辑器覆盖设置
webstorm默认支持,vscode ,subline需要安装插件 editorConfig for vscode

解决自动修复

eslint --fix src 不能修复一些问题

package.json里面加入 "fix":"node_modules/.bin/eslint --fix src" 使用项目内部的eslint,是用全局cli 会找不到配置

使用vscode来自动修复,在你的vscode设置文件里添加:

1、在vscode添加 eslint 插件
2、在vscode添加 vetur 插件
3、修改你的setting.json

// 添加进你的vscode的 setting.json

"eslint.autoFixOnSave": true,
"eslint.validate": [
    "javascript",{
        "language": "vue",
        "autoFix": true
    },"html",
    "vue"
],

2018 前端技术总结

https://juejin.im/post/5c3eba70e51d4542253fd489 2018 技术回顾
https://mp.weixin.qq.com/s/gyNJvnobRaLko4fTz38rkA 2018 最受欢迎项目
https://blog.csdn.net/zwjweb/article/details/89029010

技术生态

  1. react 持续火爆, 以及周边生态
  2. typescript 趋势明显,构建更健壮的应用程序
  3. webpack4、babel7 性能大大优化
  4. graphQl
  5. serverless

学习资源

image

前端环境

  1. 浏览器兼容性

  2. 前端能做的事情

  3. 前端的发展方向

技术世界都在web化,能用web技术解决的,都在慢慢的向web技术靠拢。 chrome + nodejs

总结

回顾 2018年,JavaScript 社区最大的事件或许是十一月的 Event-stream 漏洞风波。

围绕这个事件,有大量关于开源项目安全问题和维护责任的讨论。

说到开源,六月份 微软收购 Github 算是一个爆炸新闻。

在过去的几个月里,微软为了成为 JavaScript 界主要参与者之一而做了大量工作,正如 TypeScript 语言和 VS Code 编辑器的成功所表明的。

开源的参与者越来越多

微软、滴滴,阿里等等都在积极促进开源项目。

react, vue, anglaer 继续在各自的生态圈中大力发展

跨端框架火热

https://uniapp.dcloud.io/
mpx
flutter
RN / weex
https://didi.github.io/chameleon/

js、浏览器的边界在拓展

包括ts,deno, WebAssembly,SharedArrayBuffers ,OffscreenCanvas , node10的NAPI
electric, 浏览器worker

如何做好 nodejs 异常处理

有异常并不可怕,可怕的是找不到异常,以及不可掌控的异常。这极大的影响着软件系统的稳定性。
异常处理连接着软件系统的边界,系统监控,稳定性。

nodejs异常处理的难点在哪儿,有哪些问题

  1. 异步编程的异常处理
  2. 在一个系统中如何去做好错误和异常处理

异步编程的异常处理

普通try catch

try catch 无法捕获异步异常, 这个是所有问题的根源,
其实包括promise,也无法捕获。
async是基于promise的,也无法捕获。

process.on('uncaughtException', function(err) {
    console.error('Error caught in uncaughtException event:', err);
});
 
try {
    process.nextTick(function() { // 或者是  setTimeout
        fs.readFile('non_existent.js', function(err, str) {
            if(err) throw err;
            else console.log(str);
        });
    });
} catch(e) {
    console.error('Error caught by catch block:', e);
}

promise

then 方法的第二个参数,是一个错误回调,可以捕获上一个promise的异常。这会让整个链式流继续进行下去。

** 如果不是刻意为之,不想中断流程,那么最好不要写这个参数 **

promiseStart
.then(function(response) {
    console.log('resolved');
    return new Promise(function(resolve, reject){
        resolve('promise is resolved');
    });
},function (error){
    console.log('rejected:', error);
    // 如果这里不抛出error,这个error将被吞掉,catch无法捕获异常
    // 但是如果抛出error,这个error会被下一个then的reject回调处理,这不是我们想要的
    throw(error); 
})
.then(function (response){
    console.log('resolved:', response);
},function (error){
    console.log('rejected:', error);
    throw(error);
})
.catch(function(error) {
    console.log('catched:', error);
})

/* 
 输出:
 rejected: promise is rejected
 rejected: promise is rejected
 catched: promise is rejected
 */

使用catch方法,统一捕获之前任意promise的异常, 或者reject状态

promiseStart
.then(function(response) {
    console.log('resolved');
    return new Promise(function(resolve, reject){
        resolve('promise is resolved');
    });
})
.then(function (response){
    console.log('resolved:', response);
})
.catch(function(error) {
    console.log('catched:', error);
})

/* 
 输出:
 catched: promise is rejected
 */

我们来试试promise 能否捕获异步异常

Promise.resolve()
.then(function(response) {
    console.log('resolved');
    setTimeout(()=>{
        throw new Error('332')
    })
})
.then(function (response){
    console.log('resolved:', response);
})
.catch(function(error) {
    console.log('catched:', error);
})

/* 
 输出:
 catched: promise is rejected
 */

是无法捕获的,我试试把异常抛出的位置放到then方法里面

Promise.resolve()
.then(function(response) {
    console.log('resolved');
})
.then(function (response){
   setTimeout(()=>{
        throw new Error('332')
    })
})
.catch(function(error) {
    console.log('catched:', error);
})

/* 
 输出:
 catched: promise is rejected
 */

async 方法是基于promise的,效果完全一致

const sleepException = function (time) {
    return new Promise(function (resolve, reject) {
        setTimeout(async function () {
          throw new Error('这是一个错误')
        }, time);
    })
};

async function main2(params) {
    try {
        sleepException(1000)
    } catch (err) {
        console.log('111111', err)
    }
}

结论

  1. 普通回调中的异常无法捕获
  2. promise,async 的回调中也无法捕获
  3. 只有在promise的then中可以捕获异常,miscrtask可以捕获 , mascrtask不能捕获

常用库的异常处理

express

koa

superagent

在一个系统中如何去做好错误和异常处理

数据驱动编程

前言

数据驱动编程,是一种编程范式,这个说法软件领域已经很久了,但是对于web前端来说,不过是最近几年才开始流行的。比如vue,react,anglar等等框架的大火。让前端开发方式发生了巨变。

我想提取是这种编程的思路,以及了解为什么前端的开发方式会变成现在这样。

这种**小到一个函数的实现,大到一个框架的基本理念都有用处。不光要会用框架,也要去明白框架背后**,就算不用框架,你也能知道该怎么样去做。

在数据驱动编程中数据不仅仅是对象的抽象,更重要的是它还可以去定义程序的控制流。
面向对象考虑的是封装性,而数据驱动编程考虑的尽可能的少编写固定代码。

数据驱动编程的核心出发点是相对于程序逻辑,人类更擅长于处理数据。
数据比程序逻辑更容易驾驭,所以我们应该尽可能的将设计的复杂度从程序代码转移至数据。
书中的值得思考的话:
数据压倒一切。如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明。编程的核心是数据结构,而不是算法。——Rob Pike
只有跳脱代码,直起腰,仔细思考数据才是最好的行动。表达式编程的精髓。——Fred Brooks
数据比程序逻辑更易驾驭。尽可能把设计的复杂度从代码转移至数据是个好实践。——《unix编程艺术》作者。

数据驱动实例

在其之下隐藏的设计**

1、控制复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。
2、代码流程从命令式变成了 声明式
2、隔离变化。像上面的例子,每个消息处理的逻辑是不变的,但是消息可能是变化的,那就把容易变化的消息和不容易变化的逻辑分离。
3、机制和策略的分离。和第二点很像,本书中很多地方提到了机制和策略。上例中,我的理解,机制就是消息的处理逻辑,策略就是不同的消息处理(后面想专门写一篇文章介绍下机制和策略)。

数据驱动在GUI软件上的应用

前端其实一种GUI软件,前端编程其实是在做GUI编程。GUI软件已经发展了超过10年,前端的很多**与框架都是借鉴pc软件时代或者端时代的开发**。

数据驱动**在GUI软件的开发模式的演变中占有重要地位


慢慢的发展,我们发现我们更加的关注数据,从命令式编程范式,转向了声明式编程范式。人类并不擅长流程处理,命令调用,更擅长处理数据,和理解声明式的,有语义的调用。

GUI 包含 页面渲染, 业务逻辑, 抽象数据模型, 在早期的GUI编程,报考前端中,这3类代码通常混在在一起。 因为程序简单,逻辑不复杂

以前端为例子,那个时候是 dom 驱动,事件驱动

image

{
    "user":{
        "name":"zhangtaifeng",
        "img":"..."
    },
    "about_msg":{
         "user_detail":[
             {
                   "title":"姓名",
                   "value":"zhangtaifeng"
              },
             {
                   "title":"学号",
                   "value":"123456"
              },
             {
                   "title":"邮箱",
                   "value":"..."
              }
        ],
         "maintenance":{
             "icon":"../images/...",
             "href":"",
              title:"保修"
        },
        "buy":{
              "icon":"../images/...",
              "href":"",
              "title":"购买"
        }
    }  
}

MVC

MVC 的概念最早出现在二十世纪八十年代的 施乐帕克 实验室中(对,就是那个发明图形用户界面和鼠标的实验室),当时施乐帕克为 Smalltalk 发明了这种软件设计模式。

随着GUI的发展, 前牛人提出了MVC模式,将这些代码的职责划分为了3类,页面渲染(view), 业务逻辑(controlor), 抽象数据模型(model),数据,以及数据处理被单独抽象了出来,我们开始关心GUI之下隐藏的数据逻辑,大大简化了GUI软件的编程与维护
前端对应是 Backbone.js

但他们之间的职责仍然混淆不清,不光要关系数据的处理,还要关心大量的维护view 的代码穿插在业务逻辑中。view 能够直接与model交互,所以view里面也还夹杂着一些业务逻辑,无法通用,随着前端开发的复杂性越来越高,工程越来越庞大,代码量越来越多,这种模式已经不能满足开发需求了。

MVP

之后ibm工程师,提出了MVP,消除了 view 和 model的依赖。 controlor 成为了 view 和model的桥梁。view只和controlor交互,变成了passvacv model, 消极的,无状态的,可复用的。不依赖业务逻辑。

但它仍然没有解决大量的维护view 的代码穿插在业务逻辑中, 需要手动维护, controlor变得非常臃肿

MVVM

是由微软提出来了的。
MVVM 最早于 2005 年被微软的 WPF 和 Silverlight 的架构师 John Gossman 提出
将同步view和model自动化了,数据响应式。 让我们更加集中的关注数据,和业务逻辑。

一大批新一代前端编程框架的出现,vue,react, 改变了前端开发的思维方式,从dom驱动,事件驱动变成了数据驱动。

数据响应式原理

  1. 脏检查
  2. 数据劫持

vue的响应式原理

数据的维护

状态管理

image

单向数据流

数据结构设计

第一点数据结构的处理,因为数据决定了整个页面的展示,数据结构开始的设计非常关键,数据结构的可扩展性决定了页面的可扩展性,如果开始数据模式不好,后期维护也会非常难受。

第二点是处理好模块和数据中对应的关系。

可以看到数据驱动的难点和关键点就是数据结构的设计。而这个也是很考验开发者能力的。数据结构的好坏直接决定了后期业务开发的质量。

dpub h5早期的数据结构改动频繁,为了兼容老数据,还专门做了一层数据兼容层来做这个事情

所以在问卷调查的关联数据结构设计上,没有采用1维度数组,而采用了hash对象的方式 。 虽然当下没有实现这么复杂的功能,但是为以后的扩展打下了基础。

结语

移动端自适应


title: 移动端响应式解决方案
date: 2017-03-13 19:13:21
tags:

移动端尺寸不一,所以需要一套自适应的方案。
需要考虑我们的场景,有些时候,我们只需要宽度按设备尺寸伸缩,纵向上可以是固定的值
有时候,我们需要横向,纵向上都能按设备尺寸伸缩,例如铺满屏幕的应用。

设备适配问题,主要场景其实也就2个

  1. 长文档流的
    按尺寸等比缩放就可以了,可以用rem动态计算font-size
  2. fullpage,全屏的应用
    要么百分比,但是可能会有拉伸
    要么切图配合,内容尽可能在中间,再加上背景图片去做适配

为什么移动端会有尺寸适配的问题

概念

物理像素(Device Pixels)

真实物理设备上的像素点, 单位是px,但也是相对的,因为我的点可能比你的点小

逻辑像素(设备独立像素)

逻辑像素是一个相对的抽象概念,并不是实际的具体长度。比如css像素,它只是一个单位,具体渲染出来有多大得看设备。

dpr (Device pixel ratio)

  • 表示每个逻辑像素(css)有几个物理像素
  • dpr = 物理像素 / css像素
  • window.devicePixelRatio 可以直接获取

dpi/ppi (Dots Per Inch / Pixel Per Inch)

  • 像素密度,每英寸的像素点个数。
  • Math.sqrt(750750 + 13341334) / 4.7 = 326ppi // 屏幕对角线的像素尺寸 / 物理尺寸(inch)

viewport

  • 视口,桌面上视口宽度等于浏览器宽度,也等于css像素,但在手机上有所不同 。
  • 它也是一个虚拟的概念,因为手机的分辨率高,在没有缩放的情况下,手机上的视口要比屏幕宽度大得多,但它实际尺寸就是屏幕那么大,所以就只好缩小网页,将其容纳于视口之中。
  • , 所以有了这个经典的meta标签,设置视口width=device-width 。
  • 这也是为什么手机上能双指缩放

为什么要做自适应

正是因为移动端的分辨率高,物理尺寸小
当设置 width=device-width 时,会将物理像素viewpoint强行浓缩到手机的物理尺寸之中。此时就会有一个dpr的值 = 物理像素/物理尺寸 , 即一个css像素是2个物理像素渲染的。

这样理论上看起来不会有什么问题了,但是图片会显得很模糊,原因是图片是按像素点渲染的,而实际的像素点要多一倍,或者2培,所以模糊

解决方案就是用 x2, x3 的图片,写的时候写1倍数值

另外就是1px问题,css写了1px,而实际看起来是2px或者3px,看起来线条会很粗,原因就是按dpr浓缩的问题。

所以需要单独对1px问题做适配

0.5 px
scale
背景图片
渐变

虽然做了这些适配,但是在小屏幕上,或者大屏幕看起来就不协调了,50px在不同尺寸下不一定合理

  • 可以通过媒体查询,按情况判断

  • 通过rem等比缩放页面

页面所有尺寸单位都基于html标签上的字体,结合媒体查询就能实现等比缩放,不过过渡不平滑

动态设置rem

根据设置宽度,动态设置rem,可以实现平滑过渡

满屏模式

满屏模式的应用,不能等比缩放,因为不同设置尺寸不同,缩放后的页面不一定能适配1屏幕。

不能用高度百分比,因为设备高度不同

具体方案比较

媒体查询

这个不用说了,就一个if , else 的问题

百分比

百分比布局只能用在横向上,比如宽度, 并且它是参照父元素的宽度。
高度百分比,需要html,body 等一层一层的往下写高度,才能有参照,参照的是父元素的高度

  • 需要层层继承父元素的宽,高
  • 但是在高度上使用百分比,在不同机型上会有拉伸效果。比如图片,因为图片的伸缩比例和设备的比例是不同的。
  • 其中有个坑就是margin,padding,它的百分比是参照的父元素的宽度。 所以用百分比布局,如果要在纵向上自适应,就相当麻烦了。

但这个特性可以用来解决已知图片比例的自适应问题。
因为padding是按父元素的宽度来计算百分比的,在我们知道图片比例的情况下,可以通过padding来根据宽度自适应计算出高度。

.wrap{
    height : 0;
    padding-top : 75%;
}

.img{
    position : absolute;
    top : 0;
    left : 0;
    width : 100%;
    height : 100%;
}

百分比绝对定位

不需要层层继承父元素的宽,高,只需要参照最外层的定位元素就可以了。
缺点就是所有元素都脱离了的文档,采用平面定位的方式来布局。

动态计算rem

既然要伸缩,就需要一个统一的参照标准,这是最好的。

在rem是参照html元素的字体大小, 可以做到所有元素有统一的参照。

不过rem的布局,在缩小时不一定能完全按比例缩小。

rem 默认是1rem=16px

rem的换算

手机上的分辨率高,一个css像素点往往是几个物理像素点组成, 所以ue给的psd量的尺寸需要经过换算,

iPhone6 375设备宽度 750设计稿宽度 正好是2倍
6s 414设备宽度 1125设计稿宽度 3倍

下面是一个用js动态计算html的rem的例子

(function setResponseRem(basePx, width) {
    var doc = document,
        win = window,
        docEl = doc.documentElement,
        resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
        timer = null,

        recalc = function () {
            clearTimeout(timer);
            timer = setTimeout(function () {
                var viewportWidth = docEl.getBoundingClientRect().width || docEl.clientWidth;
                if (!viewportWidth) return;
                docEl.style.fontSize = basePx * (viewportWidth / width) + 'px';
            }, 10);
        };

    recalc();

    win.addEventListener(resizeEvt, recalc, false);
})(100,750);

rem自适应,css=实际图的px / 100
basePx : 100 ,换算基准, 1rem等于多少px,这个值可以随意定,因为后续px换算rem时会除以这个值,设为100只是为了方便计算
width : 750 代表设计稿宽度
viewportWidth : 代表设备宽度,css像素
viewportWidth / width : 缩放比例, 其实就 1/dpr
fontSize : 实际的html上的字体大小

我们的换算基准是固定的,缩放的任务交给了rem

动态计算meta标签

<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
从前面的viewport的知识,我们知道,viewpoint是可以进行缩放的,一般情况下,我只是把viewpoint设置成了设备的css宽度,并且没有缩放。通过rem动态计算来实现缩放。其实也可以用它来实现响应式缩放。
通过js计算出当前设备的dpr
scale = 1 / dpr
动态设置meta标签的scale缩放程度。

rem我们也用,只不过它只是作为一个写法标准,缩放的任务是交给了meta 标签的scale属性。

lib-flexable

这是淘宝沉淀的自适应方案,就是采用动态计算meta标签的scale和rem,来做的。
有个关键点是,需要结合设计稿,css写尺寸就是设计稿量出来的尺寸,然后根据dpr,动态设置viewport的scale标签,来进行缩放,此方案可以解决1px问题。

rem 和viewpoint,scale 是2个维度的东西, viewport , scale 是为了解决width=device-width 造成的1px问题。

rem只是一个辅助缩放参考单位

原理 :

  1. 默认也是使用viewpoint缩放 + rem 实现, 字体是另外一套缩放方案,根据dpr倍数缩放
  2. 只兼容了ios, 安卓的dpr都是1,看起来没什么问题。

注意:

  1. 如果是瀑布流式的页面,使用此方案没有什么问题,字体可以按dpr倍数缩放,小大机型上看到的文字更多。如果是绝对定位的页面,那就不能使用dpr方案缩放文字,只能用rem,原因是dpr倍数缩放会保证文字的大小在所有机型上一致,但是文字和绝对定位的top,rem 将不能匹配。如果是大段文字,拆分成几个绝对定位元素拼接,那么位置对齐就会有问题。

  2. 如果是瀑布流式页面,使用此方案没问题,如果是全屏覆盖的翻页场景,此方案图片缩放会有问题,高度上的缩放可能会超出1屏幕, 流式页面可以滚动,所以没问题。

flexbox

弹性布局对移动端比较友好,功能比inline-block强大,在横向上的布局,建议用flexbox
移动端兼容,建议使用autoprefix

  .f-box{
    display: flex;
    flex-wrap: nowrap;
  }

  .f-box-cld{
    flex : 1;
    width: 40%;
  }

cookie跨域共享


title: cookie SSO
date: 2016-11-26 14:56:18
tags:
categories: "网络"

基本介绍

cookie能够保存少量的信息在浏览器端。我们知道http协议是无状态的。每次访问,都是一次新的访问,如果我们希望记录一些浏览信息。就需要依靠cookie来存储,比如登陆信息,比如浏览信息。

cookie可以在服务端设置,也可以在客户端设置。其大小和个数有限制。一个cookie大小限制在2k。一个站点最多有20个cookie。

cookie有同源策略,不同域下,是不能读写的。

cookie如果不设置过期时间,则只会保存在内存里面。当浏览器关闭时就会销毁。

如果设置了过期时间,就会以文件的形式保存在硬盘里,到了过期时间就会销毁。

当访问一个域时,这个域下所有的cookie都会被带上在http的请求头里,不管是前端设置的,还是在后端设置的。

基本操作

document.cookie = 'name=alan;age=11;expires=121321';

cookie 是以;分割的键值对字符串。网上有很多教程,这里不多说。基础教程

同主域跨域

默认情况下,cookie只在相同服务端路径下的文件可以访问,设置path='/', 几可以在整个站点共享,跨目录。
设置domain=".baidu.com", 则相同主域下cookie都可以共享。例如a.baidu.com, 和b.baidu.com

跨主域写

在SSO单点登录系统中,会有多个主域名,只要登录了其中一个域,那么访问别的域就不需要登录了。实现这个的关键就是跨域的cookie共享。

Alt Text

我们可以利用script标签的src属性发送跨域请求。这种方式优点在于我们可以向任意多的域发送请求。并且共享cookie。

例如用户登录了A.com, 获得了服务端写的cookie,其中包含登录的token。这时前端发送跨域请求,并带上获得的token给其他域,其他域再写cookie,这样就共享了一个登录token。用户访问b.com的时候就不要输入登录密码了。

也还有别的方案,比如iframe等。我们的目的是传递cookie值就行了。不过iframe的体验不太好,比较trike,这里不多说。

跨主域读

Alt Text

有时候我们需要读取别的域下的cookie,也可以利用script的跨域能力。
在访问a域时,向b域发起跨域请求,b域服务端获取到所有的cookie并以jsonp的形式返回。前端就能拿到cookie信息了。

UV统计

在统计中很常见,统计用户行为轨迹。这个实现一般是在cookie中记录用户的唯一ID。所有的请求都会带上这个唯一ID,在日志分析的时候,按照这个ID merge日志就能得出这个用户的行为了。

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.