Just do IT

静心、坚持、沉淀

JavaScript

构建一个无缝融合的动态组件加载器

背景 现在市面上有许多的低代码解决方案,但这些方案往往是偏向于构建一整个站点并且单一组件库的,没有从太多偏向于面向已有项目的多组件库的解决方案。 面向已有站点的问题有的人会说将页面或者模块用 iframe 嵌入,但想想如果用这种方案,那假如页面需要登录状态,那控制登录的上下文就必须通过一些比较 hack 的手段在页面间传递。 多组件库则需要通过不同的组件 adapter 模式实现 好巧不巧最近搭建这样一套低代码解决方案,设计了一套 DSL(Domain Specific Language),手写了一个渲染引擎,正准备将这个模块做成一个动态加载模块供给各项目使用的时候,想了想还差一个重要的部分 — 组件库。 当然我们可以将一整个组件库交给使用方全数导入给到渲染引擎用于 Map 出组件,但这是一个比较暴力的做法,也不利于数据源的一致性。 作为在一家非常极客的公司上班的员工,导入整个组件库对我而言绝对只能是一个临时方案,主要基于以下几点考虑: 项目的性能(我们应该尽量控制客户端包的体积大小) 渲染模块的易用性(使用方不需要过多的再对渲染模块进行特殊加载配置) 编辑的页面/模块以及生成运行时的一致性 既然已经有了上述的背景以及想法,渲染引擎以及 DSL 的存储都是一件非常简单的事情,组件注册中心需要如何提供一个可以基于 DSL 使用的组件自动下发组件模块的能力就成为了这个问题的核心要点了。 下图为理想状态下的实现架构: 技术要点 那么要做这个页面渲染模块(Page

如何一口气优化所有应用的首屏加载
技术

如何一口气优化所有应用的首屏加载

优化前后的差异别的先不说,先上效果 优化前: 优化后: 什么,看不出差距?没问题,我们用专业的 Lighthouse 工具来看看,还是先上图: 优化前: 优化后: 现在我们可以直观的看出,在优化前后首屏的加载速度成倍的提升。 Loading 究竟在 load 什么?首先,大家可能会有一个疑问,这首屏白屏到 loading 转圈圈过程中,网站都在干什么呢? 我简单的罗列一下,大致分为以下步骤: 下载资源解压加载资源运行程序 根据判断条件显示 loading 动画加载用户登录的 SDK获取授权状态隐藏 loading 动画显示页面内容那么这其中前两点在缓存情况下基本可以忽略不计,我们可以着手优化的部分就只存在于第三点之中了。 具体分析通过 Chrome 提供的 Performance 工具我们对优化前版本的加载时间线进行分析,从中我们可以清楚的看到,在 loading 转圈圈的过程中线性执行着: 加载 SDK 资源登录校验等待 SDK 中的其他相关请求其中资源加载部分占用了一定的时间(但正如上文所述,

简化Redux-saga
redux

简化Redux-saga

想一下,如果你需要写一个基于Redux 的项目,你需要重复的写非常多的Action Constants,非常多的Action Creator以做相当大一部分差不多相同的事情。 于是出现了为了帮你减少书写重复Constants及Action Creator 的库redux-act。 但只有Redux并不能完全满足我们的业务需求,毕竟SPA项目中总是需要从服务端获取数据的,于是这时候我们整合进来Redux-saga。 Redux-saga能非常好的帮助我们处理异步事件,但是同样的,Redux-saga需要书写许多的Action Creator并指定其Take类型再合并到Redux-Saga的入口处,而且这些Action Creator及effect非但需要一一让参数对应,还不方便做统一的事件处理。 于是Saga-action-creator诞生了。 Saga-action-creator的特性 减少重复繁琐的书写action creator 直观的参数传递 支持插件 保留了Redux-saga的所有特性 优秀的Typescript支持 更方便的测试 如何使用 使用Saga-action-creator的方法非常简单,只需3步 一、定义Saga effects并导出 import createSagaActions from 'saga-action-creator'; import { takeLatest, call } from 'redux-saga/effects'; import { getUserInfo, updateUser } from '.

使用Nginx解决跨域非简单请求问题
技术

使用Nginx解决跨域非简单请求问题

写了几年的前端,发现经常在关于跨域请求带上cookie的问题。 既然这样,那就整理一番吧 阮一峰大佬写过一篇介绍跨域请求非常详细的跨域资源共享 CORS 详解 其中介绍到 简单请求(simple request) 和 非简单请求(not-so-simple request) 两种大类。具体的请转到阮老师的博客细读,就不在这里展开。 简单请求 如果你的站点仅仅只需要做简单跨域GET、POST请求,就是连cookie都不要的那种。那么恭喜你,你只需要在你的服务中为返回添加2条header就可完事 Access-Control-Allow-Origin: *允许其他任何域对该域的请求(当然*号也就表示你敞开家里的大门,谁都可以进了) Access-Control-Allow-Headers: X-Requested-With, accept, origin, content-type'允许携带这些头部对你的服务请求 诶,加上这两条之后鸭,就可以完成简单的跨域请求啦。 非简单请求 那如果是非简单请求该怎么办呢? 阮老师的博客中也介绍到了需要处理OPTION、加一堆头部以及客户端XHR请求中增加withCredentials携带凭证选项 但往往我们需要处理的OPTION请求都非常简单,就是只要是某(几)个主域下的请求都来者不拒,统统接受,但是如果把这一对处理放到服务上去做又会变得非常冗余。 这其中也包括复杂请求不允许Access-Control-Allow-Origin设置通配符*,必须指定当前请求的协议、域名,

You don't know Typescript
typescript

You don't know Typescript

前言 相信找到这篇文章的大家,对于Typescript(后称Ts)已经是有一定的了解了。 这篇文章就不在赘述Ts的基本语法了。这里主要给大家介绍一些Ts不常用到的类型定义及推到方式 注意:以下描述的写法,皆于Ts 2.8以上版本适用 自动获取函数返回值类型 如果你用Ts书写React及Redux你可能会遇到这样的一个问题 @connect((state: IState) => { list: state.list, }) class Container extends Component { render() { const { list } = this.props; // ... } } 类似这样的一段代码,你应该会收到一个报错 Error:(6, 13) TS2339: Property 'list' does not exist on type 'Readonly<PropsWithChildren>'. 要解决这个问题我们可以给Component的props泛型传递类型引用进行解决 type

基于Three.js对Krpano基本功能的还原(上)
图形

基于Three.js对Krpano基本功能的还原(上)

前言 前面我们对全景图进行了简单的展示原理介绍以及现有的一些可直接使用的框架选型,但是这还不够,我发现一些其他公司对全景图效果进行了大量的开发,我也希望可以DIY实现更多的效果,于是对各他们的全景展示效果进行了简单的调研,发现基本都基于Three.js进行开发。 那么接下来我们也尝试着先将原有的Krpano的功能使用Three.js进行一些还原。 开发需求分析 基于现有的一些全景图的一些框架我们简单罗列一些基础功能 1.场景的构建,也就是前面的展示原理文章所讲到的集中形式展示的图片对空间的生成 2.各个空间之间的热区位置坐标的摆放 3.点击各个热区对各空间的切换动效 4.其他附加的功能,如:VR模式,陀螺仪等 为了快速对其功能的还原,我们先简化这个开发需求为: 1.场景暂时只使用6面体进行还原 2.还原热区坐标点,并实现空间之间的切换 设计 这里我将整个项目划分为几个大模块 Core: 整个全景图的核心,继承于Scene,也就是整一个场景 Control:整个全景图拖拽,缩放的控制器,用于控制相机的rotation及fov Container:用于存放多个场景,作为后续房间切换时空间平移动画用 Room:用于构建一个个空间的实体 HotSpots:用于构建生成每个空间的热区 基础的架构设计完后,我们的全景图将基于数据驱动进而生成场景

全景框架分析选型
技术

全景框架分析选型

功能所需 前面的文章《全景图的展示原理》中描述了全景图的相关展示原理,其中包含了: 必要构成部分 多种图形图片展示的支持 必要的参数调整 VR的支持 场景间切换的热点 基于上述的基础构成内容,我们可以进一步进行预推断出其他的可能所需的扩展内容 自动旋转播放 自定义热点形式 自定义热点图形图标 场景切换特效 场景内插入视频 模型立体面探测 框架 / 类库对比 基于上述的关键功能点找来现有的一些全景框架进行对比 框架名 图片类型支持 VR支持 热点支持 参数调节 其他功能 Google VR 只支持柱形图 支持 支持 支持偏航角 未知 / 需开发 Marzipano 支持多种 支持 支持 支持 支持多种自定义功能 / 效果 A-Frame 支持(需开发) 支持 支持(需开发) 支持 支持多种自定义功能

全景图的展示原理
技术

全景图的展示原理

什么是全景图 全景图通过广角的表现手段以及绘画、相片、视频、三维模型等形式,尽可能多表现出周围的环境。360全景,即通过对专业相机捕捉整个场景的图像信息或者使用建模软件渲染过后的图片,使用软件进行图片拼合,并用专门的播放器进行播放,即将平面照片或者计算机建模图片变为360 度全观,用于虚拟现实浏览,把二维的平面图模拟成真实的三维空间,呈现给观赏者。 -- 摘自百度知道 全景效果的构成 通常来说全景图主要由视频或图片构成预览场景,使用图片通常有如下几种形式构成: 一张360°环绕图片SPHERE (或弧形环绕成球形的球体图) 环绕图或称柱形图,顶面地面无连接,这种全景生成完会在顶面地面留下两道黑色的空间,就类似用iPhone拍摄的全景模式拍出来的照片,即使预览时视角也无法向天空或者地面查看 弧形环绕(球体图):这种则包含了顶面地面,则可直接360无死角查看 六面图(或称为cube box立方体图) 将空间渲染成一个立方体,并对应切割出如下图中的6块(通常会拼接成图片url中的对应面的简称字母u/d/l/r/b/f) 当然在六面体的基础上又衍生出**tile瓦块图 ** 瓦块图的诞生主要是为了解决全景图在某些高质量 拍摄/生成 手段下,整体场景构成的图片(

node.js

在Node.js下直接使用Typescript

最近因为日常工作中基本使用TS进行开发 习惯后在想自己写一些Node工具的时候能否也直接使用TS进行开发,于是小小的研究了一下。 发现node.js 的Module 模块中有一个不推荐使用的API require.extensions 虽然他不被推荐,但小打小闹还是可以利用这个API进行折腾的 写一个简单的基于Ts的compile 不说什么,先上代码 const fs = require('fs'); const path = require('path'); const ts = require('typescript'); const compilerOptions = ts.readConfigFile(path.join(__dirname, '../tsconfig.json'), p => { return fs.readFileSync(p, 'utf-8'); }).config; require.extensions['.ts'] = function (m,

消灭问题

在日常生活工作中常常出现许多问题,但往往很多时候总是在解决问题而不是消灭问题。 这是在我读完王垠的博文“解决问题”与“消灭问题” 后对自己为何那么喜欢梳理代码、思考这堆需求应该怎么去做更为合理以及过往的一些技术选型的思考。 消灭问题 前些天有名同事请教我一个问题 首先上来他先问我: “我现在有几个组件,我底下的组件需要依赖上面的组件的抽屉开关每次开/关的时候获取他的高度从而达到重新计算底下的组件的高度,但上面的组件的抽屉效果有延迟,导致底下的组件获取高度的时候会有一种等他收起结束再向上突然弹回的效果,体验不是很好,有什么好的结局办法么?” 我大致画出他想实现的一个效果的效果图如下: 当点击收起按钮时,组件1高度变为100个像素,组件2,3自动向上移动并且组件3自动撑满高度,并在内容超出其高度时出现滚动条。 当下实际脑海中已经浮现出方案,但我反应过来的方案并不会单单期望帮他解决掉由于样式的渐变效果与回调事件之间的延迟带来的体验差问题。 他期望得到这个问题的解决方案在我看来是一个伪命题,实质从布局的层面上已经有许多处理的有问题的地方,例如上述的列表是一个整体右侧绝对定位的模型,但在盒子内部组件3又被做成绝对定位,实际完全不需要,再加上项目特殊性,对浏览器的兼容要求低,可以使用许多新特性,简单的使用flex布局就可以实现。他需要解决的是如何自动化这个弹性布局。 在面对问题的时候更多需要想想如何能够从另一个层面上彻底消灭问题,当然需要在合理的范畴内。 某些时候,实际会为自己无法去消灭问题而感到懊恼,前阵子看到前同事朋友圈的一段话实在是说到点上: 最开始只是打算换个灯泡,搬梯子时发现踏板的螺丝松了,打开放螺丝刀的抽屉时又看到轴承缺少润滑油,准备开车去买润滑油时发现车子有毛病,打开引擎盖时发现旁边站着一脸茫然的老板,老板:我只是让你换个灯泡,结果你在修车? 比起“

Docker Build Image npm install Permission Denied

由于国内访问类似node-sass、chromedriver等包的下载十分之慢 有小伙伴抱怨CI Build过程卡死、过慢等问题 于是决定对CI的runner进行升级、以及一些包的全局预装 上手操作 由于CI的runner官方提供了一个基类Image,基于这个Image我这边做了一些处理: 把apt-get 安装的node.js替换为使用 n 安装并切换至lts版本 更新系统包,安装公共系统依赖wget curl gcc make build-essential unzip zip 全局安装mirror-config-china node-sass chromedriver 等 更新runner时区至北京时间 根据一些业务场景等等的相关配置 在包的全局安装的时候一直出现Permission Denied的错误,起初的一些解决思路: 对应报没权限的目录进行chmod、chown设置 给命令加sudo 回去看官方image,他对User做了设置于是在这边将User改回root 但全都无效,细细观察会发现,实际上报无权限的都是postinstall中执行的node子进程创建目录的时候 最终经过多方寻找,在github的node-gyp issue 中找到类似的问题 可以通过在npm执行中加入unsafe-perm = true及sudo执行,以获得执行权限。

JavaScript

超长数值字符串相加以及数值交换

超长数值字符串相加 今天面试碰到了面试官询问了几道算法题。 正如标题。知道用递归运算,也知道要用分治进行解答。 但。。面试过程就是写的一塌糊涂,静下心来就都的挺好的。 答题思路 补位,将两个数值相累加必定碰上位数不同问题,将字符串前不同的以0补上 拆分为数组并将每个元素转化为数值型,方便后面计算 开始进行计算,两个一位数相加结果或许会大于10,如大于10则将10位数值进位累加如下一个计算分片 以一个数组存储计算后的每一位结果 当所有单位计算过程完成后最顶级位数可能大于10,则向前补一位,即将多出来的10位数插入数组 最后将数组翻转(因为递归从内到外,也可以每一次都进行unshift操作而则可省去翻转),后用空字符连接 上源码 function count(a, b, i, d, arr) { if (i >= 0) { // 当前是否已经到达末位 var c = a[i] + b[i] + d; // 累加 if (c > 9)

前后端分离引入RPC之从入门到放弃
grpc

前后端分离引入RPC之从入门到放弃

前面是废话,如果你着急请跳至下方正文部分 嗯。。怎么开始好呢,最近在开发一个项目 但又不想只是简单的做做这个项目 于是在开发到一半多的时候开始进行升(zuo)级(si) 想对项目进行前后端分离 A:什么是前后端分离啊 我:给你看张图吧 A:你想怎么实现这个分离啊,难道你还要去写JAVA? 我:简单点吧,把现在做的一个node项目拆成两个做 A:。。。。 像我这么一个喜欢探(zhuang)究(13)的人嘛,当然是说搞就搞啦 于是开始到处扒资料,google、github最近的访问量一定因为我提升了不少 然后发现嗯大致人家都是这么搞的 通过用node搭建一个浏览器访问的平面层,然后调用各种服务 好,说做就做,啪啦啪啦写了起来 写到一半的时候发现,那我两个系统间要怎么进行数据通信 询问了一下周围的人,很多人说简单点服务端提供RESTful接口给客户端调用然后把你要的数据拼接起来就好啦 但是我记得在前面的学习过程中看到很多http调用的性能瓶颈等的问题,于是开始扒其他的方案 突然发现了一个叫RPC的概念, 嘿嘿。我就知道有人也想弄点不一样的。 说到RPC,就不得不讲解一下什么是RPC了: RPC(Remote

C++ 学记(2)未完待续...

存储类 auto存储类: 是所有局部变量默认的存储类 register 存储类: 用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。 static 存储类: 指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。 extern 存储类: 有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。

C++ 学记(1)数据类型

C与C++ C++ 读作”C加加“,是”C Plus Plus“的简称。顾名思义,C++是在C语言的基础上增加新特性,玩出了新花样,所以叫”C Plus Plus“。 在C语言中会把重复使用或具有某项功能的代码封装成一个函数,将具有相似功能的函数放在一个源文件;调用函数时,引入对应的头文件就可以。 而在C++中,多了一层封装,就是类(Class)。类由一组相关联的函数、变量组成。你可以将一个类或多个类放在一个源文件,使用时引入对应的类就可以。 C语言源文件后缀为.c,C++源文件后缀为.cpp。C/C++ 使用相同的编译器,Windows 下一般是微软的 cl.exe,Linux 下一般是 GCC。 程序结构 预处理器指令 函数 变量 语句与表达式

同构

浅谈同构

什么是同构 在维基百科中描述: 在抽象代数中,同构(英语:isomorphism)指的是一个保持结构的双射。在更一般的范畴论语言中,同构指的是一个态射,且存在另一个态射,使得两者的复合是一个恒等态射。 首次接触同构 在之前学习React.js中偶然发现【同构】一词,很不解这个概念,究竟何为同构?他的意义何在? 在后续的学习中发现,由于React.js将所有的DOM节点都使用JS去进行创建,及维护,带来了非常多便于后续代码调试及维护的便利但也带来了首屏加载速度慢、SEO等问题的出现。 由于Node.js也可直接编译JS文件,在日常开发React.js过程中我们通常也会将使用了ES6、JSX编写的JS代码使用Webpack、bowerify等工具将其编译为ES5浏览器可识别的代码进行使用。于是便也萌生出直接将React.js当做服务端的模板对其进行同步编译的想法,在起初使用React.js 0.12.*的版本的时候还未出现ReactDOM模块,但当时便已经出现了几套同构渲染的方案,原理则是与现今的官方提供的ReactDOMServer.renderToString大致相同。 大致流程如下: 服务器读取路由 将React.js的生命周期componentWillMount前的操作及内容在服务器同步渲染并生成为字符串输出至HTML页面中 将当前状态输出至页面中 浏览器读取服务器生成的当前状态并在页面中React.js读取状态且开始执行【挂载生命周期】后的事件

JavaScript

JavaScript基于观察者模式(订阅者/发布者)

之前在做很多功能模块时会出现一些零碎的问题 例如做一个倒计时的功能插件,这时应该怎么设计让其更加灵活更加多变?如何去解耦整个模块与其他模块间的依赖? 在之前学习Nodejs的时候发现了一个模块,EventEmitter(事件广播)。 后面在查看部分框架源码等等的时候发现很多地方会用到这个模块,甚至某些前端的浏览器应用框架使用了打包工具引用了该模块打包进去使用。 后面思考了一阵子,能否模仿着个模式开发一个小型的功能模块在模块中注册自己的事件给到使用该模块时可以直接模仿着jQuery对各个元素的监听时使用的on方法。 起初的做法是把这样的一串代码直接写在模块中,这样就初步完成了基于观察者模式的开发。但是当模块一多的时候就发现如果每个模块都去书写这样一个小插件的话会导致后续维护等等方面的问题。 于是想起了JS继承。即前段时间发布的上一篇文章。关于JS原型链继承 一文中简单描述了JS如何基于原型链进行继承的实现。 说了这么多,先上代码 "use strict"; var Events = function(){ this._events = {}; } Events.prototype.emitter = function( evenName , args , that ){ if(!this._events || !this._events[evenName]) return; that = that || this; var i = 0 ,_arr

关于JS原型链继承

关于JS原型链继承

JS在ES6前的语法上属于无类语言。但在某些场景下又需要使用到继承,于是衍生出多种”继承“方式。 在众多继承方式中较常使用的一种继承方式,以new 关键字new出父级对象并赋予子类对象的原型链上。 但在继承过程中会出现一个问题,因为将父类对象中的所有对象及方法赋予到子类原型上,而原型上的所有数据都是在各个方法中共享。 因此原本在父类中私有的属性变量在此时都变成子类对象中的公有属性对象(存在于prototype原型链上),于是须在子类对象使用call或apply调用一次父类(类似super操作),初始化父类方法以及覆盖当前原型上的方法,直接上例子。 var Person = function(name, age){ this.data = { km : 0, name : name, age : age }; } Person.prototype.run = function( km ){ this.data.km+= km } var Teacher = function(name, age, pro){ Person.apply(this,arguments);//如果不加入这段代码,