alan110 / blog Goto Github PK
View Code? Open in Web Editor NEWmy blog
my blog
需要安装minui的cli工具,同min isntall pkg,安装完毕会自动编译到dist目录
wepy引入组件是引入编译后的组件,路径为编译后的js相对路径
引入的文件不能包含.js后缀
需要loader等插件也在内存中
https://github.com/christianalfoni/webpack-bin 一个在线编译的编辑器
https://github.com/christianalfoni/webpack-bin/blob/master/server/index.js#L31
https://github.com/christianalfoni/webpack-bin/blob/master/server/preLoadPackages.js
You see how I preload them into memory :-)
官方支持内存fs system
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,... 等语法
官方推荐的preset,包含所有stage4的plugin(注意不支持前面的stageX, 使用的话需要额外引入)
自动引入功能plugin + babel-polyfill + 环境编译
{
"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-preset 是 babel-plugin的集合
babel-preset 只支持特殊语法的编译,比如说箭头函数,解构等。但是具体的函数方法是不会编译的。需要引入额外的垫片
它会以入侵的,污染native代码的方式,完全模拟一个ES2015的环境,包含core-js , 和 regenerator runtime 。 它应该用在一个最终的应用上,而不是一个公共库,或者工具。当使用babel-node时,这个垫片会自动被加载。
当引入它,整个垫片库都会被引入(比较大),但是一劳永逸
你可以没有全局污染的通过按需引入,使用内置的语法比如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的输出
添加npm script "babel": "./node_modules/.bin/babel index.js"
它依然会去使用当前目录下的.babelrc 配置,可以通过命令行参数去掉
docker相当于轻量级的vm:容器本身的开销低,启动速度快,但隔离/控制弱,无法虚拟其它OS
特点是在一个物理机的OS里能同时 虚拟 巨多个本OS的运行环境(要求应用本身占的资源(主要是内存)不多)
所以,它最适合有大量小型(资源要求低、负荷也低)但需要互相隔离的应用 的单位比 每个应用都采用独立物理机省,比 每个应用都采用独立虚拟机也省
容器不是虚拟机,虚拟机是实实在在的隔离物理资源,而容器可以理解为从软件上实现的隔离。
每一个容器可以看成是一个软件应用,这个应用可以实现虚拟机的作用。有点像安装系统时的系统镜像,只不过把他当成了一个软件来运行。
所以它启动快,耗费的资源少。
docker的盛行,让devops的概念大火,即运维部署也和开发一样,敏捷化,自动化。它和自动化测试一样,赋予了开发者更多的责任,拓展了开发者的疆域。
1.To开发者——得益于Docker,让他们有可能在一条或者几条命令内搭建完环境,并且保证开发,和部署时的环境一致。同时大大减小了要更换环境时的成本。
2.To运维者——Docker把整个开发环境打包成一个Dockerimage交给运维团队直接运行
Mac 官方下载
其它系统安装请参照文档左侧的列表
官方docker-toolbox包含可视化终端,不过个人尝试后感觉不好用,还是使用命令行比较方便
我们以一个简单例子来看看如何使用docker。
使用docker pull ngnix 命令会默认从官方镜像仓库下载一个镜像到本地,下载完成后执行
docker run --name webserver -d -p 80:80 -p 3000:3000 nginx
访问http://localhost:80, 我们的ngnix容器就部署好了。上面这段命令是什么意思,后面会详细描述。
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
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
重点就在于路由和中间件
本文中的部分代码和图片,均有借鉴,本人偷懒就不想再画一遍了,重点是说明问题。
原理其实很简单,就是一个数组,每个单元就是一个中间件。记录当前index,通过一个包裹对象的next方法,循环数组执行。
express在此基础上添加了路由匹配,和子中间件。
/**
* 仿照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);
主要关注application.js , router.js , route.js 这几个文件
我们知道express挂载路由的方式,有app.use , app[method] 2种,我们一步一步来看它是如何实现的
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里面。 我们可以猜测,最后的执行方式和我们上面讨论的原理应该是类似的。
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方法
所以整体的数据结构是2层stack,非路由中间件放入1层,路由中间件放入二层。他们都是通过layer对象包裹,区分方式就是看layer.route 是否有值, 将二者关联起来
对于中间件结构,我们需要找到其起始方法。
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 官方称为路由级中间件,拥有完整的路由,中间件系统。也被称为mini app。
var flatten = require('array-flatten');
....
var handles = flatten(slice.call(arguments));
这个库是用于将嵌套的数组拉平成1维的。
在中间件处理上很有用,提供了非常大的灵活性
app.get('/path', [mid ,mid ,[mid,mid],mid], function(req, res, next){
})
将构建部署集成到gitlab
大概就是当有一个提交,需要经过一系列的操作任务处理,直到所有都通过。
pipeline 就是流水线,每个流水线有多个stage , 每个stage 有多个 job
执行job的程序境叫 runner
配置具体的runner,在gitlab的setting/pipline 里面有url,token信息
一个页面 = 页面树结构 + data + 动态逻辑
所以可视化,就是来解决这3块的问题
编辑器即服务
场景生命周期
场景基础架构,场景如何渲染
场景动态加载,组件动态加载
场景数据化,
这样只需要1个入口,根据数据动态加载和切换
一个组件需要和编辑器本身交互,需要和用户交互,需要配置它的熟悉面板,包含了它自身的数据,以及如何渲染
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我们放到后面再来解析,先看看整个代码的结构
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
我们看看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方法的第二个参数是预设调用参数,当实际调用时预设的参数会被放在前面。 这里用于每次 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]
Koa中间件返回是一个递归的promise链条
为了兼容middleware里面可能有await,所以函数可能是async,所以返回的可能是promise。 所以不管是不是异步,都给它最后再包上一层 renturn promise.resolve (),保证执行的顺序。
所以可以解答,如果没有等待,await next(), 那么promise 会直接返回,但是没有后续middleware生效
express如果不调用next,也不res.send, 则会一直等待
网上很多文章都说一个是线性的,一个是洋葱模型。其实这种说法不对, 执行的时候都是洋葱形。
express基于栈来实现递归调用, koa是compose(真递归), 只不过koa底层利用了async,不用写callback了
"extends": "eslint:recommended", // eslint 官方推荐
"extend:" eslint-plugin-vue // vue官方规则,会校验模板的语法
"extends: 'airbnb-base' // 星号最多,最严格
目前大多数风格是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"
],
https://juejin.im/post/5c3eba70e51d4542253fd489 2018 技术回顾
https://mp.weixin.qq.com/s/gyNJvnobRaLko4fTz38rkA 2018 最受欢迎项目
https://blog.csdn.net/zwjweb/article/details/89029010
浏览器兼容性
前端能做的事情
前端的发展方向
技术世界都在web化,能用web技术解决的,都在慢慢的向web技术靠拢。 chrome + nodejs
回顾 2018年,JavaScript 社区最大的事件或许是十一月的 Event-stream 漏洞风波。
围绕这个事件,有大量关于开源项目安全问题和维护责任的讨论。
说到开源,六月份 微软收购 Github 算是一个爆炸新闻。
在过去的几个月里,微软为了成为 JavaScript 界主要参与者之一而做了大量工作,正如 TypeScript 语言和 VS Code 编辑器的成功所表明的。
微软、滴滴,阿里等等都在积极促进开源项目。
https://uniapp.dcloud.io/
mpx
flutter
RN / weex
https://didi.github.io/chameleon/
包括ts,deno, WebAssembly,SharedArrayBuffers ,OffscreenCanvas , node10的NAPI
electric, 浏览器worker
有异常并不可怕,可怕的是找不到异常,以及不可掌控的异常。这极大的影响着软件系统的稳定性。
异常处理连接着软件系统的边界,系统监控,稳定性。
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);
}
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)
}
}
数据驱动编程,是一种编程范式,这个说法软件领域已经很久了,但是对于web前端来说,不过是最近几年才开始流行的。比如vue,react,anglar等等框架的大火。让前端开发方式发生了巨变。
我想提取是这种编程的思路,以及了解为什么前端的开发方式会变成现在这样。
这种**小到一个函数的实现,大到一个框架的基本理念都有用处。不光要会用框架,也要去明白框架背后**,就算不用框架,你也能知道该怎么样去做。
在数据驱动编程中数据不仅仅是对象的抽象,更重要的是它还可以去定义程序的控制流。
面向对象考虑的是封装性,而数据驱动编程考虑的尽可能的少编写固定代码。
数据驱动编程的核心出发点是相对于程序逻辑,人类更擅长于处理数据。
数据比程序逻辑更容易驾驭,所以我们应该尽可能的将设计的复杂度从程序代码转移至数据。
书中的值得思考的话:
数据压倒一切。如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明。编程的核心是数据结构,而不是算法。——Rob Pike
只有跳脱代码,直起腰,仔细思考数据才是最好的行动。表达式编程的精髓。——Fred Brooks
数据比程序逻辑更易驾驭。尽可能把设计的复杂度从代码转移至数据是个好实践。——《unix编程艺术》作者。
1、控制复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。
2、代码流程从命令式变成了 声明式
2、隔离变化。像上面的例子,每个消息处理的逻辑是不变的,但是消息可能是变化的,那就把容易变化的消息和不容易变化的逻辑分离。
3、机制和策略的分离。和第二点很像,本书中很多地方提到了机制和策略。上例中,我的理解,机制就是消息的处理逻辑,策略就是不同的消息处理(后面想专门写一篇文章介绍下机制和策略)。
前端其实一种GUI软件,前端编程其实是在做GUI编程。GUI软件已经发展了超过10年,前端的很多**与框架都是借鉴pc软件时代或者端时代的开发**。
数据驱动**在GUI软件的开发模式的演变中占有重要地位
慢慢的发展,我们发现我们更加的关注数据,从命令式编程范式,转向了声明式编程范式。人类并不擅长流程处理,命令调用,更擅长处理数据,和理解声明式的,有语义的调用。
GUI 包含 页面渲染, 业务逻辑, 抽象数据模型, 在早期的GUI编程,报考前端中,这3类代码通常混在在一起。 因为程序简单,逻辑不复杂
以前端为例子,那个时候是 dom 驱动,事件驱动
{
"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 的概念最早出现在二十世纪八十年代的 施乐帕克 实验室中(对,就是那个发明图形用户界面和鼠标的实验室),当时施乐帕克为 Smalltalk 发明了这种软件设计模式。
随着GUI的发展, 前牛人提出了MVC模式,将这些代码的职责划分为了3类,页面渲染(view), 业务逻辑(controlor), 抽象数据模型(model),数据,以及数据处理被单独抽象了出来,我们开始关心GUI之下隐藏的数据逻辑,大大简化了GUI软件的编程与维护
前端对应是 Backbone.js
但他们之间的职责仍然混淆不清,不光要关系数据的处理,还要关心大量的维护view 的代码穿插在业务逻辑中。view 能够直接与model交互,所以view里面也还夹杂着一些业务逻辑,无法通用,随着前端开发的复杂性越来越高,工程越来越庞大,代码量越来越多,这种模式已经不能满足开发需求了。
之后ibm工程师,提出了MVP,消除了 view 和 model的依赖。 controlor 成为了 view 和model的桥梁。view只和controlor交互,变成了passvacv model, 消极的,无状态的,可复用的。不依赖业务逻辑。
但它仍然没有解决大量的维护view 的代码穿插在业务逻辑中, 需要手动维护, controlor变得非常臃肿
是由微软提出来了的。
MVVM 最早于 2005 年被微软的 WPF 和 Silverlight 的架构师 John Gossman 提出
将同步view和model自动化了,数据响应式。 让我们更加集中的关注数据,和业务逻辑。
一大批新一代前端编程框架的出现,vue,react, 改变了前端开发的思维方式,从dom驱动,事件驱动变成了数据驱动。
vue的响应式原理
单向数据流
第一点数据结构的处理,因为数据决定了整个页面的展示,数据结构开始的设计非常关键,数据结构的可扩展性决定了页面的可扩展性,如果开始数据模式不好,后期维护也会非常难受。
第二点是处理好模块和数据中对应的关系。
可以看到数据驱动的难点和关键点就是数据结构的设计。而这个也是很考验开发者能力的。数据结构的好坏直接决定了后期业务开发的质量。
dpub h5早期的数据结构改动频繁,为了兼容老数据,还专门做了一层数据兼容层来做这个事情
所以在问卷调查的关联数据结构设计上,没有采用1维度数组,而采用了hash对象的方式 。 虽然当下没有实现这么复杂的功能,但是为以后的扩展打下了基础。
移动端尺寸不一,所以需要一套自适应的方案。
需要考虑我们的场景,有些时候,我们只需要宽度按设备尺寸伸缩,纵向上可以是固定的值
有时候,我们需要横向,纵向上都能按设备尺寸伸缩,例如铺满屏幕的应用。
为什么移动端会有尺寸适配的问题
物理像素(Device Pixels)
真实物理设备上的像素点, 单位是px,但也是相对的,因为我的点可能比你的点小
逻辑像素(设备独立像素)
逻辑像素是一个相对的抽象概念,并不是实际的具体长度。比如css像素,它只是一个单位,具体渲染出来有多大得看设备。
dpr (Device pixel ratio)
dpi/ppi (Dots Per Inch / Pixel Per Inch)
viewport
正是因为移动端的分辨率高,物理尺寸小
当设置 width=device-width 时,会将物理像素viewpoint强行浓缩到手机的物理尺寸之中。此时就会有一个dpr的值 = 物理像素/物理尺寸 , 即一个css像素是2个物理像素渲染的。
解决方案就是用 x2, x3 的图片,写的时候写1倍数值
所以需要单独对1px问题做适配
0.5 px
scale
背景图片
渐变
可以通过媒体查询,按情况判断
通过rem等比缩放页面
页面所有尺寸单位都基于html标签上的字体,结合媒体查询就能实现等比缩放,不过过渡不平滑
根据设置宽度,动态设置rem,可以实现平滑过渡
满屏模式的应用,不能等比缩放,因为不同设置尺寸不同,缩放后的页面不一定能适配1屏幕。
不能用高度百分比,因为设备高度不同
这个不用说了,就一个if , else 的问题
百分比布局只能用在横向上,比如宽度, 并且它是参照父元素的宽度。
高度百分比,需要html,body 等一层一层的往下写高度,才能有参照,参照的是父元素的高度
但这个特性可以用来解决已知图片比例的自适应问题。
因为padding是按父元素的宽度来计算百分比的,在我们知道图片比例的情况下,可以通过padding来根据宽度自适应计算出高度。
.wrap{
height : 0;
padding-top : 75%;
}
.img{
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
}
不需要层层继承父元素的宽,高,只需要参照最外层的定位元素就可以了。
缺点就是所有元素都脱离了的文档,采用平面定位的方式来布局。
既然要伸缩,就需要一个统一的参照标准,这是最好的。
在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 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属性。
这是淘宝沉淀的自适应方案,就是采用动态计算meta标签的scale和rem,来做的。
有个关键点是,需要结合设计稿,css写尺寸就是设计稿量出来的尺寸,然后根据dpr,动态设置viewport的scale标签,来进行缩放,此方案可以解决1px问题。
rem 和viewpoint,scale 是2个维度的东西, viewport , scale 是为了解决width=device-width 造成的1px问题。
rem只是一个辅助缩放参考单位
原理 :
注意:
如果是瀑布流式的页面,使用此方案没有什么问题,字体可以按dpr倍数缩放,小大机型上看到的文字更多。如果是绝对定位的页面,那就不能使用dpr方案缩放文字,只能用rem,原因是dpr倍数缩放会保证文字的大小在所有机型上一致,但是文字和绝对定位的top,rem 将不能匹配。如果是大段文字,拆分成几个绝对定位元素拼接,那么位置对齐就会有问题。
如果是瀑布流式页面,使用此方案没问题,如果是全屏覆盖的翻页场景,此方案图片缩放会有问题,高度上的缩放可能会超出1屏幕, 流式页面可以滚动,所以没问题。
弹性布局对移动端比较友好,功能比inline-block强大,在横向上的布局,建议用flexbox
移动端兼容,建议使用autoprefix
.f-box{
display: flex;
flex-wrap: nowrap;
}
.f-box-cld{
flex : 1;
width: 40%;
}
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共享。
我们可以利用script标签的src属性发送跨域请求。这种方式优点在于我们可以向任意多的域发送请求。并且共享cookie。
例如用户登录了A.com, 获得了服务端写的cookie,其中包含登录的token。这时前端发送跨域请求,并带上获得的token给其他域,其他域再写cookie,这样就共享了一个登录token。用户访问b.com的时候就不要输入登录密码了。
也还有别的方案,比如iframe等。我们的目的是传递cookie值就行了。不过iframe的体验不太好,比较trike,这里不多说。
有时候我们需要读取别的域下的cookie,也可以利用script的跨域能力。
在访问a域时,向b域发起跨域请求,b域服务端获取到所有的cookie并以jsonp的形式返回。前端就能拿到cookie信息了。
在统计中很常见,统计用户行为轨迹。这个实现一般是在cookie中记录用户的唯一ID。所有的请求都会带上这个唯一ID,在日志分析的时候,按照这个ID merge日志就能得出这个用户的行为了。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.