Code Monkey home page Code Monkey logo

blog's Introduction

All in Web3

Remote

Blog Gmail

blog's People

Contributors

riskers 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  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

blog's Issues

egret引擎初探

egret 是国内的一款优秀的开发游戏开发引擎,去年的时候曾经了解过,做过一个简单的demo,然后就不了了之了,因为实际工作是用不到的。

但是,近来工作中拿到别人拿给我看到的H5案例,很酷很炫的那种,一看源码,全是egret之类的游戏引擎来做的。其实不难理解,这类引擎都是通过canvas绘制界面,性能必然比DOM要好。试想一下这样的页面如果用DOM来做,恐怕做完之后也是卡顿的不要不要的:

本文只介绍最简单的egret,不是教程,具体的教程、API等参见手册

工具流

我觉得egret和其他游戏引擎相比最强大的就是它的工具流,有设计师用的,有开发用的,一应俱全。可以在官网找到。

egret-tools

  • Egret Engine: egret游戏引擎,包括 Game、Tween、EUI、粒子系统等扩展库
  • Egret Wing: egret的IDE,可以自动构建、发布
  • Res Depot: egret的资源管理工具,如果不用它,你一定会很头大
  • Texture Merger: 可以合并纹理集,后面会说到
  • Egret Inspector: Chrome 调试工具

一般来说,使用上面的工具就可以开始开发我们的小游戏了。

其他几个工具可用可不用。

语言

egret并不是用的JS,而是TypeScript。TS是JS的超集,整体感觉很像Java,据说对C#程序员很友好。

class P2 extends egret.Sprite{
	private man: Man;
	static initX = -830;
	public constructor(){
		super();
	}
}

TS国内资料比较少,这是我找到的一份中文手册:

不需要先学习TS才能使用egret,完全可以边用egret边学习TS,毕竟TS里的很多东西是和JS一样的。

项目构建、发布

介绍完背景,就可以开始使用 egret 了。

  1. 使用Egret Wing新建一个项目 Hi,点击下一步

  2. 选择舞台宽度和高度以及适配方案,点击完成:

  3. 可以看到Hi项目的目录

    • src: 源代码目录,Main.ts 是入口文件
    • libs: 项目中使用的库
    • resource: 项目中使用的资源(图片、音乐等)
    • egretProperties.json: 项目配置文件
    • index.html: 项目入口
  4. 构建项目:项目-构建并运行,可以看到最简单的egret项目。

  5. 发布:项目-发布,正式包就在 bin-release

显示对象与显示容器

  • 显示对象:能够参与渲染的对象,都继承自 DisplayObject,Shape是最简单的显示对象
  • 显示容器:显示对象的容器,继承自 DisplayObjectContainer,Sprite是最简单的显示容器

关系如下:

DisplayObject
	|--- DisplayObjectContainer
		|--- Sprite
		|--- Stage 舞台
	|--- Shape 矢量绘图
	|--- Bitmap 位图
	|--- TextField 文字

显示对象都必须addChild在显示容器中才能够被显示。这里是最简单的例子:

class Main extends egret.DisplayObjectContainer {

    public constructor() {
        super();
        
        var obj = new egret.Shape;
        
        obj.graphics.beginFill(0x000000);
        obj.graphics.drawRect(0,0,100,100);
        obj.graphics.endFill();
        obj.x = 100;
        obj.y = 100;
        
        this.addChild(obj);
    }
}

碰撞检测

游戏中经常需要知道两个物体是否接触,egret对于碰撞检测是很方便的。

var isHit = obj.hitTestPoint(x,y);

事件

egret的事件机制与DOM类似,分为捕获阶段、目标阶段、冒泡阶段,也有自定义事件以及触发。

这个其实很好理解,不再多说。

动画

egret的动画系统也很方便:

egret.Tween.get(shp).to({
  x: 40
},100)

表示100ms内把x移动到40

资源管理

RES 是egret为开发者准备的一套功能完善的资源加载机制。开发者编写游戏时,无须关心资源加载的细节,只需要指定资源,并且在对应的位置中添加相应地执行代码即可。

在resource中,default.res.json是资源配置文件:

  • resources: 资源
    • name
    • type
    • url
  • groups: 资源组
    • name
    • keys

不用手写,使用 ResDepot 工具即可轻松管理:

ResDepot

使用 b1_png 这张图的时候只要这样其他什么都不用管:

var b1 = new egret.Bitmap(RES.getRes('b1_png'))

位图

var img = new egret.Bitmap;
img.texture = RES.getRes('pic_name');
this.addChild(img);

getRes 返回的数据并不是位图,而是内容数据,也就是位图纹理,对应 egret.Texture。需要设置位图的texture属性为 getRes 返回的数据,图片才能显示。

纹理集

纹理集,其实就是CSS里的雪碧图。我们可以通过 Texture Merger 轻松制作管理。

比如我们可以用这样的图片:

可以『拼』成这样的动画:

实际上只要这样加载资源即可:

音乐

音乐的播放更加简单

var sound = RES.getRes('music_mp3');
var channel = sound.play()

遗漏

egret的内容远远比上面的介绍多得多,还有粒子系统、龙骨骨骼引擎、3D系统、网络请求等等,可以制作出非常复杂、庞大的游戏。可以看 HTML游戏引擎深度测评 里对egret的介绍就知道现在egret能够做出什么样的游戏。

egret为游戏开发提供了完整的工具流,尤其是资源加载,我在开发的时候很舒服,只用关心游戏逻辑就好了。作为前端来说,掌握egret也算是一个课外实践了。总之,好处多多,赶紧使用吧。

献上我使用egret花了3天时间边学边做的项目:

http://huodong.mobilem.360.cn/0422/index.html


向我捐助 | 关于我 | 工作机会


最近三年

离职了,也该为这三年的职业生涯做一次总结了。

2022 以来的这小半年,算是我的 「Gap Year」,学了大量的东西: Web3、区块链、交易等等。

三十六个月

看看三年前,发的朋友圈:

我现在都记得刚搬到清上园那天,我站在罗森门口,看着街对面办公楼的样子。唰地一下,三年过去了,快到我现在能回想起这三年三十六个月里,几乎每个月做了什么,遇到了什么人,发生什么事我都记得!

我觉得这很可怕,应该是我变老了。因为前几年从来不会感慨什么时间不够的问题,这个玩意今天没搞明白,就明天搞喽,没什么大不了了。这三年不是了,我会开始很功利地学习一些东西,或者好听点,叫有目标地去学习。

先说说当时为什么来这家公司,因为这里接受我转型后端开发。那为什么要转后端呢?现在想来,也是我上一次创业失败后的 PTSD,当时急于需要证明自己以及拓宽自己的技能树和知识面,为下次创业做准备。

转型的结果如何?我觉得还不错,是在自己当时的预期之内。我记得刚来的时候,自己列了一个单子,是我自己根据我当时对后端的理解去写的,也算是我对这一段职业经历的目标。现在想想,那其实也是一份对自己的 OKR,只不过,不知道需要多长时间达到这个程度。

这三年,有几点是我明显进步的:

  1. 后端能力直线上升,这个自然不必说。
  2. 综合能力拉满了,沟通能力,知识图谱建立好了,往后的日子估计是按照这个走。
  3. 有信心了,我要的不仅仅是后端能力的提升,因为任何知识都可能过时或者遗忘,但学习的方法论本身是不会过时的。我想,我已经掌握了学习一个新领域的基本方法,之后可能就不是三年了。
  4. 认识到自己的不足,比如英语能力,之后的日子里会努力提升的。

这次,我耐住了寂寞,第一次在一份工作上待了三年,是职业生涯最长的一段。这期间我一直在思考自己到底想要什么,想要做什么。感觉这三年在公司学到的东西倒没什么,反而自己瞎折腾,自己写代码,掌握了不少东西

毕业

我把每一家公司都当作是去上学,离职的时候就是毕业的时候。毕业的时候,就是我列的 OKR 完成的时候。这也是我几乎没有涨薪,还在这待了三年的理由

为什么离职?因为入职的时候列的、已经扩充了很多倍的 TODO 在去年底达到了,再待着也不会有提升了,就想去外面的世界看看。当时入职的时候就说这家公司应该是我在北京的最后一家公司了,没想到成真了。

遗憾

三年里错过了三次机会,一次是创业之前去美团的机会;一次是字节前端的机会;还有一次是字节后端的机会。过去了就是过去了,没什么好说的。如果按照这条路往下走,除了薪水更高,钱包更鼓了之外,好像也没什么大不了的。

为什么不更博客了?这三年开始记笔记,不想写博客了。因为博客比笔记要花好几倍的时间。这是我三年来反反复复修改的知识图谱,这里顺便谢谢 Notion 这么好的软件,让我的大脑省去了不少的负担。

新路

离职去哪里了?

这次出去找工作,主要在看 remote 的机会,有创业的,唯独没有看所谓大厂机会的,对我而言没有什么吸引力了。最让我惊讶的是,remote 工作里,有比较高薪水的,着实让我有些心动,说明世界往一个好的方向前进。

最后选了一个能 remote,薪水还非常有吸引力(一下把这几年没涨的工资补上了)的公司,行业还是我看好的 Web3。最近公司又融了一笔钱,我希望这是好的开始!

唉,年底又是世界杯了,四年一个槛啊,上一次世界杯的时候在创业,再上一次世界杯的时候刚毕业。

蹲下去,是为了跳得更高!

再见了,可惜这个月一直在居家办公,不让去科技园,最后都没法在园区里照几张相片。


向我捐助 | 关于我 | 工作机会


Redux、MobX 数据流的总结


使用 redux-thunkredux-sagaredux-observableMobX制作同样的一个小项目:

data flow demo

这个项目调用github API查找 github 用户,点击头像之后还可以知道这个用户的 follower 和 following。简单来说,就是调用了三次 API(即三次异步请求)。


目录

本文不是教程,并不会手把手地教你如何使用他们,只是一个指引,一份示例,让你能够了解他们各自的特点,以便选择最适合自己的。


redux-thunk

code

redux-thunk我们在 Redux 中操作异步最简单的方法。配合 async/await,你可以像写同步代码一样进行异步操作。

示例

// action creators
const getUsers = username => {
  async dispatch => {
    dispatch({
      type: 'LOADING'
    })

    try{
      const response = await fetch(`https://example.com/`)
      let data = await response.json()

      dispatch({
        type: 'SUCCESS',
        payload: data
      })
    }catch(error) {
      dispatch({
        type: 'FAILURE',
        error: error
      })
    }
  }
}

redux-thunk flow

      dispatch                                       dispatch           update
VIEW ----------> function ----------> ACTION object ---------> REDUCER --------> STORE --------> STATE
|          (return async dispatch)                                ^
|                                                                 |
|                                                                 |
|                                                                 |
|       dispatch                                                  |
|---------------------> ACTION object -----------------------------

VIEW 会 dispatch 一个 ACTION object 或一个高阶函数(返回 async function):

  • 当 dispatch 一个 ACTION object 时,reducer 会更新数据
  • 当 dispatch 一个函数时,我们就可以在里面做各种异步操作(利用 await),然后 dispatch 一个 ACTION object,之后 reducer 更新数据

redux-saga

code

redux-saga 简化了 action creator,redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理。Sagas 可以被看作是在后台运行的进程,监听发起的 action,然后决定基于这个 action 来做什么:是发起一个异步调用(比如一个 Ajax 请求),还是发起其他的 action 到 Store,甚至是调用其他的 Sagas。一旦出发 sagas 监听的 action,sagas 会通过一系列的effects dispatch (比如 put)一个 action。

示例

// action.js
export const searchUsers = (username, page) => {
  return {
    type: CONST.FETCH_GITHUB_SEARCH_USER,
    payload: {
      username,
      page
    }
  }
}
// App/sagas.js
function* fetchUsers(action) {
  let {
    username,
    page
  } = action.payload

  yield put({
    type: CONST.FETCH_GITHUB_SEARCH_USER_LOADING
  })

  yield call(delay, 2000)

  try {
    let response = yield call(fetch, `https://api.github.com/search/users?q=${username}&page=${page}`)
    let data = yield response.json()

    yield put({
      type: CONST.FETCH_GITHUB_SEARCH_USER_SUCCESS,
      payload: data
    })
  }catch(e) {
    yield put({
      type: CONST.FETCH_GITHUB_SEARCH_USER_FAILURE,
      error: "No This User"
    })
  }
}

export default function* () {
  yield takeLatest(CONST.FETCH_GITHUB_SEARCH_USER, fetchUsers)
}
// sagas/index.js
import AppSaga from 'containers/App/sagas'

export default function* rootSaga() {
  yield AppSaga()
}

redux-saga flow

      dispatch                  not match            update          update
VIEW ----------> ACTION object -----------> REDUCER --------> STORE --------> STATE
                       |                       ^
                       |                       |
                       |  match                |
                       |                       |
                       |  (data)               |
                       |                       |
                      \|     dispatch          |
                     SAGAS ------------>  ACTION object

VIEW dispatch 一个 ACTION object:

  • 这个 ACTION object 没有被 sagas 监听,则 reducer 会更新数据
  • 这个 ACTION object 被 sagas 监听,则走入 sagas 的流程,在 sagas 中 dispatch ACTION object,reducer 更新数据

redux-saga vs redux-thunk

  • 原理不同:Sagas 不同于 Thunks,Thunks 是在 action 被创建时调用,而 Sagas 会在应用启动时调用(但初始启动的 Sagas 可能会动态调用其他 Sagas),是常驻后台的(因为 generator)。

  • redux-saga 有cancel、takeLeast这些操作,这是 async 做不到的(这也是 generator 相对 async 的优势)

  • redux-saga 易测试,比如它可以无阻塞地调用一个 generator(fork)、中断一个 generator (cancel)。这些特性在业务逻辑复杂的场景下非常适用。测试代码详见这里

  • redux-saga 保持了 action 的原义,保持 action 的简洁,把所有有副作用的地方独立开来(这样action里会很干净)。这种特性让 redux-saga 在业务逻辑简单的场景下,也能保持代码清晰简洁。


redux-observable

code

你要先了解 rxjs ,然后再往下读。因为 redux-observable 就是让你能够在 Redux 中使用 rxjs。

为了方便理解 rxjs,我做了一个纯 js 版的项目 jsbin,之后能够我们再把它改成 react 版的。

redux-observable flow

      dispatch                  not match            update          update
VIEW ----------> ACTION object -----------> REDUCER --------> STORE --------> STATE
                       |                       ^
                       |                       |
                       |  match                |
                       |                       |
                       |  (data)               |
                       |                       |
                      \|     dispatch          |
                     EPICS ------------>  ACTION object

Epic 是 redux-observable 的核心,它是一个函数,接收 actions 流作为参数并且返回 actions 流: Actions 入, actions 出。返回的 actions 会通过 store.dispatch() 立刻被分发,所以 redux-observable 实际上会做 epic(action$, store).subscribe(store.dispatch)

redux-observable vs redux-saga

可以看到,redux-observable 和 redux-saga 的数据流是相似的。关于他们的异同,可以查看这两篇文章,不再赘述:


mobx

code

先看一个 demo,它包含了 @action @observable @observer @computed @inject 等重要概念。

MobX flow

                            modify                    update                    trigger
Events ----->  Actions  ------------->     State     --------> Computed values ---------->  Reactions
              (@action)                (@observable)             (@computed)               (@observer)
                 ^                                                                              |
                 |                                                                              |
                 |                                 (mobx-react)                                 |
                 |------------------------------------------------------------------------------|

MobX vs Redux

MobX vs Redux: Comparing the Opposing Paradigms 演讲已经介绍,不能看视频的看 MobX vs Redux: Comparing the Opposing Paradigms - React Conf 2017 纪要 也是可以的。



向我捐助 | 关于我 | 工作机会


使用 travis + gitbook + github pages 优雅地发布自己的书

作者: 一波不是一波

转载请注明出处并保留原文链接( #48 )和作者信息。

这篇文章教你怎么用 gitbook + travis 在 github pages 上优雅地发布书籍。

模板: https://github.com/riskers/gitbook-template/tree/master

效果: https://riskers.github.io/gitbook-template/

项目结构

git clone https://github.com/riskers/gitbook-template

cd gitbbok-template && rm -rf .git # 去掉模板中的历史记录

修改模板:

  • .travis.yml:
    • recipients: 修改成你的邮件
    • REF: 修改你项目的 github 地址
  • book.json: 修改 gitbook 相应配置,不是这里的重点,不多介绍。配置结果见 https://riskers.github.io/gitbook-template/ ,可根据喜好自己修改
  • chap01chap02 对应 SUMMARY.md 中的地址,就是这本书的内容了。

然后就是在 github 上新建一个项目,并且 push 上去,然后你能看见这样的项目结构:

github pages

如果没有注册过 github pages 服务,还要先注册(注册过程略)。

新建 gh-pages 分支:

git checkout -b gh-pages
git push origin gh-pages

在项目『Settings』-> 『GitHub Pages』开启 github pages 服务:

Travis

给这个项目开启 Travis 服务: https://github.com/marketplace/travis-ci

最后你应该能看到这个界面:

个人设置里申请 token 好让 Travis 有权限改这个项目:

然后选择 repo,点击生成按钮:

复制生成的 token,填写在 Travis 的设置-全局变量中,并且取名为 TOKEN:

试试效果

添加一行字:

然后 push 上去,可以看到 Travis CI 在跑了:

稍作等待,跑完之后,可以在 gh-pages 下看到 Travis CI 给我们推过来了 gitbook build 之后的内容:

可以在 github pages https://riskers.github.io/gitbook-template/chap01/1-1.html 上看到效果:

完成!

后续

这一阵在用 gitbook 写一本电子书,发现多于两本就要收费了,而老版本是不收费的,所以我最近一直在用老版本。但可能是维护少的原因,老版本的同步总是不及时,于是我放弃了 gitbook 服务。

本来想在本地 gitbook build,然后 push 到 pages 服务,但是这样太不优雅了,就琢磨了一下怎么用 CI 来做这件事,刚好之前在公司折腾过一阵 gitlab ci,很快就解决了。

至此,我做到了在 master 上写 md,然后 push 到 master, Travis 自动执行 gitbook build,并且把生成好的文档 push 到 gh-pages,好让 pages 服务生效。全自动的,是不是很优雅?哈哈。

顺便做个广告,这两本书都是按照这种方式写的:


向我捐助 | 关于我 | 工作机会


GithubX 复盘

五一过后,就提了离职,加上北京一直让居家办公,我磨洋工般地把 GithubX 迭代了两个版本,0.0.41.0.0

ps. 其他时间在学 rust,感觉之后工作会用到,这次终于入门稍微深入了一点,可能是已经提了离职,心情很放松地去学的缘故。

除了上一篇写的那两点,这次又学到了或者说稍微费了点时间做的:

  1. 为了同时支持 IndexedDB 和 MongoDB API,我抽象了一层 Service,前端调用接口就变成了: action -> Service,Service 会根据用户的选择自己决定是调用 IDB 层还是 API 层。然后用 interface 抽象了 Model 层,这里使用了策略模式,这一部分我觉得我写得算是整个项目里最的。
  2. Top Level Await 的使用,还是在 Service 里,需要在实例化 Serivce 之前得到用户的选项。但是 Chrome 的 API chrome.storage.local 是异步的,如果常规思路,就是把实例化这一步放在 async 里,但是这样就无法 export 了。最后用 Top Level Await 解决,其实也就一行,加个 await 就行了!
  3. CSS Grid 布局,之前没尝试过,这次整个项目就是用它来做的。
  4. 之前总是考虑在 content_page 监听一个 DOM 去搞定用户在点击 star 按钮的时候弹窗,但是这样一旦 Github 的 DOM 改变,我也要变!后来直接换思路,监听 star 时的 API,一旦请求这个 API,就直接弹窗,不用管 DOM 结构了!而且因为是弹窗形式,在整个 Github 网站里都通用!这个想法我自己都觉得挺惊艳的

还衍生出 Github-API 项目:

  1. 没有使用 MySQL 而是 MongoDB 来作为数据库,不得不说,Java 配 MongoDB 真的是难受,难怪大家一直说 Nodejs + MongoDB 才是最佳组合。不过因为是自用,慢就慢了。其中还学会了 Mongo 的 Aggregation,和 MySQL 的 join 类似,在之前还总是好奇 Mongo 是怎么联表的。
  2. 打通了 Github Action 到 Docker Hub : docker.publish.yaml

截至目前,GithubX 一共发布了 4 个版本,有 377 个用户。

总结一下

前两年学的概念太多,有点应接不暇,导致「学而不思则罔」,只学了概念,没有进行反思,没有深入地实践,没有 coding,就会流于表面,没有沉淀。仔细看看,这个项目是三年前我刚入职的时候就建立的,一直拖拖拉拉,到现在离职了才完成。中间经历过很多,但都不足以成为我放弃的理由,尽管中间还是放弃了,这个之后单开一篇来说。


向我捐助 | 关于我 | 工作机会


别了 2019

2019 一个字:变,其中包括**的转变(创业失败后的总结),工作的转变(前端做到后端),公司的转变(换了个工作)。

书单

可以看到,今年主要在学 Java,不过今年的非技术书看得太少,2020 争取多看几本。

成长

  • 技术

    今年开始做后端开发,也在部门接手了几个项目,开始学习 Java 技术栈。另外,因为对并发感兴趣,想要研究几个不同的并发模式,所以又学了 Erlang。现在能分清 Java 的多线程并发、Golang 的 CSP 和 Erlang 的 actor 模型。

    我入职的第一周就开始接手 Java 项目,坦白说,学习一门类 C 的编程语言对于已经有几年编程基础的我来说其实不难,我从 TS 切到 Java 就用了2、3 天。难就难在相关技术栈不是几周就能玩明白的,什么 maven、Spring、RPC、消息队列等等,这些东西不是和语言相关,而是一种**,这要花很多时间。这一点在 学习编程语言的一些经验 已经说明白了。

  • 工作

    今年,从创业公司离开来到小米。10 个月的创业经历当是交了一次学费,2018 年过得还是挺丧的。

    另外,现在明显感觉到了害怕,因为团队里已经有 97 年出生的同学,明显感觉到自己年纪比较大了,自己还是要多多努力啊。

    迷思 中有总结。

  • **

    去年我可以说我理解到了**『我首先是工程师,其次是前端工程师』**。今年,我体会到了什么是技术,什么是业务,技术和业务的关系是什么。这点在 谈技术选型:根据业务做技术 已经有总结。

展望 2020

不多说了,前几天在知乎回答过了。


向我捐助 | 关于我 | 工作机会


OpenResty 初试

可以理解 OpenResty = Nginx + Lua,可以用 Lua 扩展 Nginx

其中 OpenResty 官方提供了 Lua 组件,我这次使用了2个第三方组件:

背景

之前有个项目没有用前后端分离,整个项目都是 Java 的,前端代码会打包在 jar 包里。每次前端要发布,需要后端来发,这不合理!

所以,改造之。改造很顺利,使用的是 Nginx 方案:

在 CI 阶段,前端打包 -> 整个编译后的文件(HTML/CSS/JS) 全都放在 Docker 容器里 -> 容器就装一个 Nginx -> 搞定

到这里就结束了,但是,这个项目在 JSP 模板里,给前端注入了变量,如:

<html>
...
<script>
	var userInfo = <%USER_INFO%>
</script>
</html>

这个 <%USER_INFO%> 会被渲染成:

var userInfo = {
	"name": "riskers",
	"role": "admin"
}

本来没问题,但现在我们使用了 Nginx 来放置这个模板文件,就会出问题: 因为 Nginx 不会注入变量,用户访问这个页面就会出错: 因为 var userInfo = <%USER_INFO%> 这句会抛错。

这时候,就可以使用 OpenResty 解决问题

使用 OpenResty 解决问题

先安装 OpenResty, Mac 上直接使用 brew 安装:

brew install openresty

然后安装 lua 模块:

# 让 Nginx 拥有渲染模板能力的组件
luarocks install resty-template

# 让 Nginx 可以发送 HTTP 请求的组件
luarocks install lua-resty-http

这样改造流程变成了:

前端打包 -> 整个编译后的文件(HTML/CSS/JS) 全都放在 Docker 容器里 -> 容器就装一个 OpenResty -> 进入 index.html 前先调用 view.lua -> 搞定

至于 view.lua 干了两件事:

  1. 先调用 getUserInfo 接口,得到用户信息之后
  2. 再将用户信息注入在模板里

这样还没完,还要将其 Docker 化

Docker 化

Docker 化基本就是本机怎么做的,再在 Dockerfile 里再写一次

在调试 Nginx 配置的时候,一开始每改一部分,要 build 一次镜像,然后在前端编译过程中花很长时间,才能看到调试结果。后面我也有经验了: 在 Dockerfile 里写好要安装的软件后,就可以直接 exec 进去,直接改 Nginx 配置,然后重启,立刻看到效果。这可能也是我自己在 Docker 化时最大的省时间技巧了。

还有一个地方花了我一点时间,如何在两个 FROM 的基础镜像基础上交互,即把上一个镜像(Node)build 出来的 dist 文件夹 COPY 到这个镜像 (openresty) 里。ASCOPY --FROM 搭配起来就解决了:

FROM node:lts-alpine as builder

# ...

COPY --from=builder /app/dist /fe

总结

看起来可能很简单,可整个过程花了我2天时间,解决了:

  • OpenResty、Lua 以及 luarocks 安装
  • Lua 语言的适应
  • Nginx 配置的学习
  • resty-template 组件和 lua-resty-http 组件的学习
  • 开发 OpenResty 脚本时的调试
  • Docker 化的逐步调试

这也是我第一次如此较深入地去了解 OpenResty 并投入使用,完整代码在 https://github.com/riskers/openresty-demo


向我捐助 | 关于我 | 工作机会


React16的新特性(内附实例)

注: 本文说的 React16 是指当前最新版本 16.5.2 ,而不是指 16.0.0。很多特性都是在 16.0.0 之后陆陆续续加的,而不是在一次性在 16.0 加入的。特别是 getDerivedStateFromProps 在 16.4.0 之后还改过一次。

createRef

实例: https://stackblitz.com/edit/react16-ref

之前已经有 string ref(将被废弃) 和 callback ref,React16 新加入 createRef:

// init
this.btnRef = React.createRef();

// access
this.btnRef.current

new Context API

实例: https://stackblitz.com/edit/react16-new-context

// init
const Context = React.createContext();

// use
<Context.Provider value={/*...*/}>

<Context.Consumer>
	{value => {
		//...
	}}
</Context.Consumer>

life cycle

新加入 componentDidCatch / getDerivedStateFromProps / getSnapshotBeforeUpdate 三种生命周期,并且将 componentWillMount / componentWillReceiveProps / componentWillUpdate 置为 UNSAFE

componentDidCatch

实例: https://stackblitz.com/edit/react-componentdidcatch

static getDerivedStateFromProps

实例: https://stackblitz.com/edit/react-getderivedstatefromprops

组件实例化后接受新 props 后被调用,需要 return 一个对象来更新状态返回null表示新的props不需要任何state更新

getSnapshotBeforeUpdate

在react render()后的输出被渲染到DOM之前被调用。

React.Fragment

实例: https://stackblitz.com/edit/react16-fragments

一直以来,React render 只能 return 组件,不能是 string、 array、 boolean等值,这其实限制了开发者的能力。React 16 给我们带来了这些新功能:

// string
render() {
	return 'this is string'
}

// number
render() {
	return 123
}

// boolean
render() {
	return true && <div>abc</div>
}

另外,render return 要求一定要有一个根组件,而开发者就不得不在外层套一个 <div>,现在 React16 也给了我们方法:

// 方法1: 返回 array,不过每一项必须有 key
render() {
	return [
		<h1 key="a">a</h1>,
		<h1 key="b">b</h1>,
	]
}

// 方法2: React.Fragment
render() {
	return (
		<React.Fragment>
			<h1>a</h1>
			<h1>b</h1>
		</React.Fragment>
	)
}

// 方法3: 其实还是 React.Fragment,不过是简写
render() {
	return (
		<>
			<h1>a</h1>
			<h1>b</h1>
		</>
	)
}

portal

实例: https://stackblitz.com/edit/react-portal-tips

React.createPortal 可以让开发者在组件中写逻辑,而在页面的别的位置渲染出来:

render() {
	return ReactDOM.createPortal(
	  <div>
	    this is a dialog
	  </div>,
	  document.body
	);
}

向我捐助 | 关于我 | 工作机会


[译] 剖析responsive image

之所以会翻译这篇文章是因为我昨天看到@勾三股四的这篇微博,里面推荐的文章就是下面我要翻译的。因为自己一直对响应式图片这个技术很关注,但是一直没有一个很好的总结机会,今天趁着翻译这篇文章总结了,这是本人翻译的第一篇文章,有错误的地方请指出。

原文地址

剖析responsive image

最近对responsive image有一些感悟然后赶紧记下来免得忘了。下面就是我的感悟:

尺寸固定,不同屏幕密度

如果你是用像素来固定图片尺寸,又想在不同屏幕密度屏幕上实现响应式图片,可以这样:

<img width="320" height="213"
	src="cat.jpg"
	srcset="cat-2x.jpg 2x,cat-3x.jpg 3x">

它可以正常运行在所有现代浏览器上,而且在不支持srcset的浏览器也可以降级到src

有一些规则是上面图片所没有提到的:

  • srcset里的每一项都是<url> <density>x结构 ,就像cat-2x.jpg 2x
  • srcset里项目的顺序并不重要
  • 如果你没有声明width/height,浏览器会按照屏幕密度展示它本身原始的width/height。 比如2x资源被匹配到了,它会被渲染成本身的 50%width和 50%height
  • 提示一下,如果在3x设备像素比的设备上浏览器渲染的是1x的图片,可能是因为糟糕的网络环境

案例

这个案例使用的是3张一样的图片,只是尺寸不一样,这样我们很难看出区别。所以译者在这里换了这几张图,然后在Chrome中模拟手机调试,更换分辨率,应该就很明显了。

译者案例

这种方法因为要人为匹配设备像素比,所以1x2x3x4x等等,这样HTML就会太臃肿,所以有了下面的新方法。

尺寸和屏幕密度都不同

不同宽度的图片在响应式站点里是很常见的。在这篇博客里,图片内容都是占据了文章100%的宽度,但是文章的宽度并不总是窗口宽度的100%。

为了让浏览器匹配到正确的图片,我们需要知道:

  • 不同尺寸图片的地址
  • 每张图片的宽度
  •   `<img>`的宽度
    

最后一点是特别困难的,因为图片开始下载是在CSS解析之前的,所以<img>的宽度不能从页面布局那得到。

<img src="panda-689.jpg"
	srcset = "panda-689.jpg 689w,
				panda-1378.jpg 1378w
				panda-500.jpg 500w
				panda-1000.jpg 1000w"
	sizes = " (min-width:1066px) 689px,
				(min-width:800px) calc(75vw-137px) ,
				(min-width:530px) calc(100vw-96px) ,
				100vw" >

通过srcset属性,浏览器知道了哪些图片可用以及这些图片的宽度。
通过sizes属性,浏览器知道了<img>相对于一个已知宽度窗口的宽度。
这样,浏览器就可以匹配最佳资源加载了。

你不再需要说明屏幕密度,浏览器自己会辨别。如果浏览器窗口宽度是1066px甚至更大,<img>会被定为689px。在1x设备浏览器上会下载panda-689.jpg,但是在2x设备浏览器上将会下载panda-1378.jpg

这里感觉作者并没有解释清楚 sizessrcset的工作原理(参照下面的译者案例来看)。

首先,是关于sizes的理解:
比如当前窗口800px,那么sizes会匹配到(min-width:800px) calc(75vw - 137px) ,则这个<img>对应的宽度就是800px*0.75-137px=463px。这个宽度的设定相当于

<img src="..."  width="463" />

知道了<img>width,然后再看srcsetw:
dpr1的时候,463px对应463w,查找srcset,找到500w适合它,就显示500的这张图。
dpr2的时候,463px对应926w,查找srcset,找到1000w适合,就显示1000的这张图。

一些规则是上面没有提到的:

  • srcset里的每一项是<url> <width-descriptor>w,比如panda-689.jpg 689w
  • srcset里每一项的顺序没有影响
  • 如果srcset包含了一个宽度描述符(w),则src会被那些支持srcset的浏览器忽略掉
  • sizes里的每一项是<媒体查询> <图片宽度>形式,除了最后一个仅仅是<图片宽度>
  • sizes里的宽度单位都是px
  • 浏览器使用sizes里的第一个匹配到的媒体查询,所以sizes里的顺序是很重要的
  • 和上面一样,浏览器下载一个低分辨率图片可能是因为糟糕的网络

如果你没有指明<img>的宽度,浏览器也会正常解析。对sizes精确设置,但是一个不是很确切的宽度也很好。比如

sizes="(min-width:1066px) 689px ,
	(min-width:800px) 75vw,100vw"

挑选哪些图片资源放在srcset里是很困难的,我也没有完全掌握技巧。在上面的例子里,我设置了一个最小尺寸(注:原文中是最大)(689px),然后给2x设备设置刚才的两倍尺寸(1378px)。另外两个设置是在这两个值中间任意取的。我没有设置更小的宽度比如320px,因为在这一情况下的屏幕密度是2x或者更高。

srcset + sizes 在 Chrome、Firefox和Opera中都兼容。至于其他浏览器,也会很安全地回退到src属性。不用等待很久,WebKit nightly 和 下一个稳定版本的Edge就会很好地支持它。

WebKit nightly是WebKit的mac port,对于Safari就像Chromium对于Chrome

开发者需要了解的WebKit


案例

原文中<img>有一个内联样式width:100%,一开始没有注意到的话还以为没有变化呢。上面的案例已经修改过了 -。-

还是因为作者使用了内容相同,尺寸的图片,所以我换了图片重新做了一个例子:

译者案例

这种新方法的srcset用来指向提供的图片资源,没有上面方法的1x2x,这个都交给浏览器。例子中就指向了3个尺寸图片。

sizes用来表示尺寸临界点,用媒体查询定下图片的实际宽度。

不同宽度、分辨率和艺术指导

和之前的例子类似除了框架的不同宽度的变化。它允许你集中精力对付更小的宽度。

  • 只要你想,你可以有很多<source>
  • 你必须包含一个<img>
  • 媒体查询<source>元素上总是被服从的
  • 媒体查询是基于窗口的宽度,不是<img>
  • 第一个匹配到的<source>会被使用,所以顺序很重要
  • 如果没有匹配到<source>,则<img>被调用
  • <img>必须出现在<source>后面
  • <source>不支持src,但是支持srcset

一旦<source>或者<img>被选中,srcsetsizes属性就像之前的例子一样解析。

艺术指导:剪裁图片内容来适应特定环境,任何时候我们裁剪或是修改图片来适应一个断点(而非简单缩放),都是一种艺术指导。

可以看出来,艺术指导比之前的srcset+sizes又多了一层维度:source的媒体查询。

<picture>元素在Chrome、Firefox和Opera中兼容良好,在其他浏览器可以回退到<img>。而下一代的Edge也可能会支持

--

案例

不同类型

这个方法可以让你有更优化的方式去提供给支持它们的浏览器。

<picture>
	<source type="image/webp"
		srcset="snow.webp">
	<img src="snow.jpg">
</picture>

  • type属性是mime类型
  • 你可以有很多种资源和混合typemediasrcset甚至是sizes取创建一些惊奇的东西

它在Chrome、Firefox和Opera上兼容良好,其他浏览器还可以回退到<img>


案例

我的总结

  1. 对于响应式图片我们一开始使用x方式,但是这种方法有缺点:
    • PC端和移动端无差别对待:它们都有1x2x3x屏幕,这样 iPhone 可能加载一张2x的图片,甚至以后还会加载出3x4x的图片!
  2. 使用w方式就没有上面的问题了,一切都交给浏览器自动判断自动加载
  3. 艺术指导就比w方式多了一层维度,可以更加精准地加载图片

扩展阅读

希望上面的文章能够对各种用例起到参考作用,你可以继续看看下面的文章:

译者推荐文章

与本文结构类似的文章:

补充文章:


向我捐助 | 关于我 | 工作机会


模拟 background-size 样式开发整屏页面

我们在做移动端开发的时候经常遇到这样的需求:界面背景要刚刚好在整个屏幕中,不能超出屏幕,而且肯定还有一些元素要固定在界面中某个位置。

比如这样的设计图(720 x 1280 的尺寸),我们不仅仅要这个背景不超出屏幕,而且城堡里的图标要不偏不倚地在城堡中的那个位置。

这样分析一下我们就知道了我们要解决的问题:

  1. 背景图片不能变形
  2. 元素位置要固定

一般的方法

现在一般的做法:整张图片当做背景,background-size100% 100%,页面元素百分比绝对定位。

狠狠戳demo

从demo可以看出元素定位的问题用 absolute + 百分比 解决了 ,但是背景变形了(把.wrapbackground-size值改成contain试试)。为了不让这种变形更加夸张,给.wrap添加了 min-widthmax-width

这个方法还可以优化一下:

  1. 背景图,因为现在背景图都 188kb 了。可以把背景图切成几分铺在底层,demo
  2. logo等绝对不能变形的图片就要单独用 img 引入,然后 absolute 定位

如果允许背景图片一点点地变形以及限定尺寸范围,那么使用上面的方法简单、迅速,很多情况下也都是这么做的。

今天介绍的是一种新方法,背景不会变形且定位准确。

JS 模拟 background-size 开发整屏页面

最近在做一个新页面的时候,我不满足于上面的方法,就想看看有没有别的解决办法。要图片在容器中不变形的最好办法就是 background-size:contain 了。看看我们现在的进展,这个页面完美适应窗口,且不会变形。那怎么让元素定位在不确定 contain 以后宽高未知的背景图上呢?

谁说宽高未知的?在我之前的文章CSS Contain&Cover的数学公式里介绍过,之前一直以为没什么用的结论可以在这里用上。

参考上面的文章,我们有了这样的思路:viewport 在这里就是 windowimage 在这里是 .container。然后加上这个结论(contain部分)我们不难得算出背景图在 contain 以后的宽、高。

在之前的基础上,添加计算背景图宽、高的逻辑:

var $appContainer = $('.app-list'),
	 $window = $(window),
    winWidth = $window.width(),
    winHeight = $window.height();
	
var rWindow = winWidth / winHeight,
	 rContainer = 720 / 1280,
	 scale = 1;
	
if(rWindow<=rContainer){
	var height = winWidth / rContainer;
	$appContainer
		.width(winWidth)
		.height(height)
}else{
	var width = winHeight * rContainer;
	$appContainer
		.height(winHeight)
		.width(width)
}

还要在样式上保证 .app-list 是居中的:

.app-list{
	position: absolute;
	left:50%;
	top:0;
	-webkit-transform:translate(-50%,0);
	transform:translate(-50%,0);
}

这样就可以看到,无论窗口怎么变化 .app-list 始终和 .container 的背景图贴合在一起:

狠狠戳demo

还剩一个定位问题了,很简单,这里就只做了两个 icon 的定位。查看完整demo

至此,你可以随意改变窗口的大小,而页面一直都会保持在整屏中,而且不会变形,其中的元素也可以定位准确,基本上满足了整屏页面的所有问题。

其实,整屏页面就只有两个关键点:背景图片不变形 、页面中元素位置固定,你只要能做到这两点就可以了。但是,你也能看到我们用上面的办法做出的效果两侧会有突兀的背景露出来

我们白忙活一场么?看下图:

背景 contain 以后不就是这两种情况么?
图1是我们现在的情况,图2是背景的宽高比小于容器的宽高比的情况。可以想象,无论什么样的背景,我们在使用了这种方法后要么露出左右的背景,要么露出下面的背景。
虽然我们解决了 背景图片不变形页面中元素位置固定问题,但是这个问题又在困扰着我们,所谓『鱼和熊掌不可兼得』。

这里介绍的方法其实不是PM们想要的,他们可能想要第一种方法的效果。但其实是设计图的影响,本文demo的设计图其实不适合这种方法。现在看一个适用于这种方法的demo,这种设计恐怕是第一种方法所不能做到的。因为这个设计图的主体是一个手机,所以绝对不能变形,而且就算有背景在两侧或上下露出来,也不影响整体效果。

总结

这里只是给大家多一种思路,具体问题还需要具体对待,不能一概而论。

  • 设计图是大背景允许变形的情况下,还是用传统方法好
  • 设计图是一个整体,且背景露出来也不影响效果的情况下,用 background-size 的方法
  • 如果设计图是不能变形的,而且高度不定,还要兼容所有手机。你把本文的图片甩给他,问他要怎么样,那这篇文章也没有白写了。

2015-12-17 更新

今天发现一个库pageResponse就是用这种思路来做 covercontain 布局的。

和上文介绍的思路一样,你只要一次定位,终生位置不变。


向我捐助 | 关于我 | 工作机会


写了一本 Chrome Extension 小书

最近和 icepy 合作写了一本书,关于 Chrome Extension 开发的,已经发布在 gitbook 上了。

喜欢的请在 github 上给我们一个 star 鼓励我们,也可以直接和我们在 gitter 上联系。

这也是我第一次和别人合作写一本书,体验很不一样,从8月初开始,到8月底差不多结束,里面包含了我们两个人在 Chrome Extension 开发这块的心得和体会。过程比较辛苦,不过想想这个月过得比较充实,每天除了工作,下班后,还要写书。不过也因为是我们两个人自发地写,内容比较松散和随意。这是我们第一次合作,总是值得纪念的。


向我捐助 | 关于我 | 工作机会


移动端适配方案(上)

要搞懂移动端的适配问题,就要先搞明白像素和视口。

像素

在移动端给一个元素设置 width:200px 时发生了什么?这里的px到底是多长呢?像素是网页布局的基础,但是我们一直在用直觉使用它。

其实存在两种像素:

1. 设备像素

屏幕的物理像素,任何设备屏幕的物理像素的数量都是固定不变的,单位是pt

2. CSS像素

在CSS、JS中使用的一个抽象的概念,单位是 px

顺便说下,CSS像素也可以称为设备独立像素(device-independent pixels),简称为dips,单位是dp

那么,我们现在再来说说一个元素 width:200px 以后会怎么样。这个元素跨越了200个CSS元素,200个CSS元素相当于多少个设备像素取决于两个条件:

  • 页面是否缩放
  • 屏幕是否为高密度

这两方面后面再解释,先梳理一下手机硬件之间的关系,注意这里使用的都是物理像素

以 iPhone5 为例,我们已知的是:

  1. 分辨率1136pt x 640pt
    指屏幕上垂直有 1136 个物理像素,水平有 640 个物理像素
  2. 屏幕尺寸4英寸
    注意英寸是长度单位,不是面积单位。4英寸指的是屏幕对角线的长度。
  3. 屏幕像素密度326dpi
    屏幕像素密度(Pibel Per Inch)简称 ppi ,单位是 dpi(dot per inch)。这里指屏幕水平或垂直每英寸有326个物理像素。原则上来说,ppi越高越好,因为图像会更加细腻清晰。

ppi 是可以通过 分辨率屏幕尺寸 计算得到的:

ppi

这个网站列出了很多设备的分辨率屏幕尺寸,并且计算了ppi

视口

桌面浏览器中,浏览器窗口就是约束你的CSS布局视口(又称初始包含块)。它是所有CSS百分比宽度推算的根源,它的作用是CSS布局限制了一个最大宽度,视口的宽度和浏览器窗口宽度一致。

但是在移动端,情况就很复杂了。

布局视口

一个没有为移动端做优化的网页,会尽可能缩小网页让用户看到所有东西。这是我的手机查看博客园的样子,你也可以在Chrome中以移动开发模式看到。

浏览器厂商为了让用户在小屏幕下网页也能够显示地很好,所以把视口宽度设置地很大,一般在 768px ~ 1024px 之间,最常见的宽度是 980px。

所以,在手机上,视口与移动端浏览器屏幕宽度不再相关联,是完全独立的,这个浏览器厂商定的视口被称为布局视口

布局视口我们是看不见的,只知道网页的最大宽度是 980px ,并且被缩放在了屏幕内。

可以这样设置布局视口的宽度:

<meta name="viewport" content="width=640">

媒体查询与布局视口

700px 指的是布局视口的宽度

@media (min-width: 700px){
    ...
}

document.documentElement.clientWidth/Height返回布局视口的尺寸

视觉视口

视觉视口是用户正在看到的网页的区域,大小是屏幕中CSS像素的数量。


window.innerWidth/Height返回视觉视口的尺寸

理想视口

布局视口明显对用户是不友好的,完全忽略了手机本身的尺寸。所以苹果引入了理想视口的概念,它是对设备来说最理想的布局视口尺寸。理想视口中的网页用户最理想的宽度,用户进入页面的时候不需要缩放。

现在讨论所谓的『最理想的宽度』到底是多少?其实,如果我们把布局视口的宽度改成屏幕的宽度不就不用缩放了么。可以这样设置告诉浏览器使用它的理想视口:

<meta name="viewport" content="width=device-width">

定义理想视口是浏览器的事情,并不能简单地认为是开发者定义的,开发者只能使用。


screen.width/height返回理想视口的尺寸,有严重的兼容性问题---可能返回两种值:

  1. 理想视口的尺寸(下载浏览器)
  2. 屏幕的设备像素尺寸(内置浏览器)

Screen size testsUnderstanding viewport可以测试你的设备的screen.width值,同一设备的不同浏览器返回的值可能是不一样的。这一情况主要发生在默认浏览器和下载浏览器(如UC、Chrome)之间。

默认浏览器是安卓系统内置的浏览器,长下面那个样子。而且它使用的是Webkit而不是Blink。只有在更新安卓系统的时候才能更新它。直到安卓4.3,Google不再更新。

安卓webkit

而下载浏览器都返回的是理想视口尺寸。

缩放

缩放与设备像素、CSS像素的关系

缩放是在放大或缩小CSS像素,比如一个宽度为 200px 的元素无论放大,还是200个CSS像素。但是因为这些像素被放大了,所以CSS像素也就跨越了更多的设备像素。缩小则相反。

缩放与视口

缩放会影响视觉视口的尺寸

页面被用户放大,视觉视口内CSS像素数量减少;被用户缩小,视觉视口内CSS像素数量增多就行了。这个道理应该是不难想的。

用户缩放不会影响布局视口

注意,这是『用户缩放』,后面会说开发者设置缩放的情况

缩放比例

我们在开发者工具中可以在这里查看缩放比例:

查看缩放比例

这里的 0.3 是相对于理想视口的。

在下载浏览器中,可以这么算(理想视口与视觉视口的比):

zoom level = screen.width / window.innerWidth

禁止缩放

<meta name="viewport" content="user-scalable=no">

设置缩放

<meta name="viewport" content="initial-scale=2">

使用initial-scale有一个副作用:同时也会将布局视口的尺寸设置为缩放后的尺寸。所以initial-scale=1width=device-width的效果是一样的。

完美视口

解决各种浏览器兼容问题的理想视口设置

<meta name="viewport" content="width=device-width,initial-scale=1">

设备像素比

在谈到像素的时候,讲到除了缩放,屏幕是否为高密度也会影响设备像素和CSS像素的关系。

缩放程度为100%(这个条件很重要,因为缩放也会影响他们)时,他们的比例叫做设备像素比(device pixel ratio):

dpr = 设备像素 / CSS像素

可以通过JS得到: window.devicePixelRatio

设备像素比也和视口有关:

dpr = 屏幕横向设备像素 / 理想视口的宽

总结

这一篇介绍了移动端适配需要掌握的知识,先说明了移动端存在的两种像素,然后介绍了三种视口,由缩放对视口的影响引入理想视口,最后说明设备像素比。下一篇介绍现在市面上的适配方案。

参考文章

下面这些文章可能也会对你有帮助:


向我捐助 | 关于我 | 工作机会


使用adb调试应用

安卓开发中,adb是很重要的调试手段。其实,对于前端来说,adb工具也是学习一下的,因为它确实很方便。

比如,我在手机助手中,调试一个安装APP的页面,这个过程中就要反复下载应用安装,然后退出页面卸载,很麻烦,使用adb就只要一条命令就可以卸载应用了。再比如,APP中一般会缓存数据,这样给我们调试也带来很多麻烦,如果不使用adb清除缓存的话,就必须去设置里清除,太麻烦了。

所以来学学adb吧

安装

  1. 安装 JDK

  2. 下载 SDK

  3. 解压 SDK 压缩包,然后进入 android-sdk-macoxs/tools,双击 android 启动 Android SDK Manager,安装 build tools

  4. 在自己的目录(home directory)中创建 .bash_profile

    cd ~
    touch .bash_profile
  5. .bash_profile 中写下:

    export PATH=${PATH}:/Users/xxx/android-sdk-macosx/tools
    export PATH=${PATH}:/Users/xxx/android-sdk-macosx/platform-tools

    路径是不固定的,指向 android-sdk-macosx 目录即可

  6. 执行

    source .bash_profile
  7. 输入 adb 回车,就可以看到adb界面了

命令

只说我最常用的两条命令

卸载应用

adb shell pm uninstall com.example.MyApp

com.example.MyApp 是应用的包名


清除缓存

adb shell pm clear com.example.MyApp

更多的命令请看这里

今天的文章很水,就这样吧

参考文章


向我捐助 | 关于我 | 工作机会


Electron入门体验(内附react脚手架)

最近两周在做一个给内部使用的桌面软件,使用的是 Electron,现在就这次使用 Electron 的经验,记录一下。

介绍

什么是 Electron ?简介说的很清楚:使用 JavaScript, HTML 和 CSS 构建跨平台的桌面应用,就是用前端技术来开发桌面软件。

可以在这里看到用 Electron 开发的应用,其中最知名的就是 VSCode 了。所以,已经有那么多公司在使用这项技术了,我们也可以放心使用。

quick start

如果想直接体验用前端技术开发桌面软件,可以按照官方的示例来学习。

如果要在正式项目中使用,可以安装 electron-forge,这是一个成熟的脚手架,集成了打包、发布功能,还可以选择模板(如React、Vue、Angular 等)作为渲染层框架。

建议在熟悉 Electron 技术后自己基于使用的框架调整为自己最舒服的架构。我在实际使用中,发现 React 模板没有安装 redux,以及不支持一些ES6语法,所以自己又基于 electron-forge 做了一套适合自己的模板: https://github.com/riskers/electron-react-boilerplate。

Electron 入门

目前,我对 Electron 的理解是:

electron = chromium + Nodejs + Native api

其中:

  • chromium 内核渲染界面,这一部分可以理解为浏览器,是前端最熟悉的 HTML、CSS、JS
  • Nodejs 被封装起来,代表可以直接使用原生 Node api 以及海量的 npm 包
  • Native api 则是 Electron 给我们把 windows、macos 甚至是 Linux 的api(比如本机联网状态、通知、菜单等)差异抹平,让我们更方便调用。

只要你是前端,并且看懂Electron 应用结构,就已经入门了。

Electron 分为主进程和渲染进程,这点很像 Chrome 插件的开发,渲染进程只是一个界面,但是有一个主进程常驻在程序中。

electron文档汉化做得很好,其实已经不用多说什么了。作为前端,chromium 渲染层和 Nodejs 是我们最熟悉的,剩下的就是根据需要去查怎么调 Native API 了。


2018.09.21 updated

Electron 通信

最近对 Electron 有了更新的认识,Electron 在通信上有两种方案:

  • ipcMain / ipcRender
  • webContents / remote

Electron 存在主进程和渲染进程,是前端们之前从来没有处理过的,需要好好研究。


向我捐助 | 关于我 | 工作机会


折腾 Python 版本管理

最近开始写 Python,对于一直写 JS 的我来说,十分不习惯 Python 的版本管理,一个项目的依赖装在了全局。强迫症的我就开始找 Python 的版本管理方案。

pyenv

install

  1. install

    curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash
    
  2. .zshrc 添加:

    export PATH="$HOME/.pyenv/bin:$PATH"
    eval "$(pyenv init -)"
    eval "$(pyenv virtualenv-init -)"
    
  3. 安装不同版本 Python

    CFLAGS="-I$(brew --prefix openssl)/include -I$(xcrun --show-sdk-path)/usr/include" \
    LDFLAGS="-L$(brew --prefix openssl)/lib" \
    pyenv install -v 3.5.0
    

usage

  1. 查看版本

    pyenv versions # 查看系统当前安装的python列表
    pyenv version # 查看系统当前使用的python版本
    
  2. 设置版本

    pyenv global # 设置全局 Python 版本,将版本号写入~/.pyenv/version文件
    pyenv local # 设置当前项目 Python 版本,将版本号写入当前目录下的.python-version文件

pipenv

install

  1. 安装:

    python3 -m pip install --upgrade --force-reinstall pip
    pip3 install pipenv
    
  2. .zshrc 添加:

    export PIPENV_VENV_IN_PROJECT=1 # 在每个项目的根目录下保存虚拟环境目录.venv
    

usage

pipenv --two/three # 创建一个虚拟环境

会生成 PipfilePipfile.lock,类似 package.jsonyarn.lock。还支持 pipenv --python 3.7.0 这种指定版本的用法,如果本地没有这个版本,就会安装这个版本。

其他用法:

  • pipenv install [package]: 安装模块
  • pipenv graph: 列出项目所有依赖
  • pipenv shell: 进入 shell

最佳实践

pyenv 安装多版本 Python 后,用他切 local 版本,其他包版本的事交给 pipenv。

  1. pyenv install -v 3.5.0
  2. cd envtest
  3. pyenv local 3.5.0: python -V: 3.5.0 (设置成功)
  4. pipenv --python 3.5.0
  5. pipenv shell -> python -V: 3.5.0 (设置成功)


向我捐助 | 关于我 | 工作机会


打造最舒适的webview调试环境

你在做移动web开发的时候是不是只是在Chrome下开启移动模式,然后就啪啪啪闷头敲代码了?如果你平时只是做做宣传页,Chrome的移动模式可能就能满足你。但是现在越来越多的应用采用Hybrid的开发方式,这样的话就可能在web页面上调用webview注入的函数,那么,这个页面在Chrome上只会报错,因为我们不在webview里,根本没有注入的那些函数。

以我现在做的项目为例,要在页面里判断在客户端有没有登录,可以这样写:

var isLogin = AndroidWebview.hasLogin() ;

结果可想而知,AndroidWebview是客户端在webview里注入的方法,这里当然会报错了。

真机测试

这种情况怎么开发呢?回顾一下以前的两种办法 :

  • 真机 + Chrome inspect :Chrome 版本必须高于 32,其次你的测试机 Android 系统高于 4.4

    1. 先用数据线将 Android 测试机连接到电脑上。需要打开测试机上面“开发者选项”中的 “USB 调试”功能。
    2. 在PC的Chrome上打开Chrome://inspect即可找到你的设备
    3. 手机进入一个webview页面,即可在Chrome上看到调试台了

    可以看到,第一个记录是手机里的浏览器的;第二个是记录是手机助手里的webview。

  • 真机 + weinre : 在你本地创建一个监听服务器,并提供一个JS脚本,需要在需要测试的页面中加载这段 JS,就可以被 Weinre 监听到,在 Inspect 面板中调试你这个页面。

    1. 安装 weinre npm install -g weinre
    2. 开启 weinre weinre --httpPort 8888 --boundHost -all-
    3. 浏览器打开 localhost:8888 :
    4. 将 "2" 这段脚本加载到调试的页面最后,手机进入页面,然后进入 "1" ,就可以看到控制台了

这两种办法都需要真机测试,你可以想像一下你在开发、调试时的流程:

  1. 写代码
  2. 拿起手机,进入页面
  3. 有BUG,重复1、2
  4. 开发新功能,重复1、2、3

然后你的手不停地在键盘和手机之间切换,多么痛苦。后来,我遇到了Genymotion

Genymotion

这是一款安卓模拟器,有了它我们可以在电脑上开启一个安卓机。具体使用我就不细说了,很简单请自行搜索。

这是我在模拟器上安装的手机助手:

而且使用 Chrome inspect 是直接可以调试模拟器中的webview的:

这样,我们就可以不用手忙脚乱地写代码、看手机了,一切都在PC上调试。但是我们在模拟器上看到的是线上代码,我们加一个新功能还要发布代码才能看到效果?

Charles / Fiddler

幸好有Charles这样的工具(Windows下请使用Fiddler),Charles会在本地开启一个代理服务,默认接口8888。通过这个代理,模拟器上的请求会被转移到电脑上,我们可以任意地去替换请求文件让我们更加方便地调试页面。

设置监听端口

Proxy Settings - HTTP Proxy - Proxies - HTTP Proxy 中设置

监听Chrome

因为 Charles 只会监听全局和Firefox,为了能监听Chrome,使用Proxy SwitchyOmega插件,增加一个情景模式:

switchyOmega

在这个情景模式下,我们就可以抓到在Chrome里的数据了:

sina

注意:Charles默认是不支持https的,我们选择 设置 - Proxy Settings - SSL ,选中 Enable SSL Proxying 。然后在 Locations 里填写要抓包的域名和端口,点击 AddHost填写域名,如 www.baidu.comport443 。具体参考最后的文章。

监听Genymotion

别忘了,使用Charles的初衷是让我们可以用本地的文件替换线上文件,不用每次修改都要发布。

  1. 在Genymotion中,Settins - Network (port选9999是因为我之前在Charles中设置的是9999) :

    settings

  2. 在开启的模拟器中,设置 - WLAN - 长按2秒 - 修改网络代理设置改为手动,主机名为10.0.3.2,端口为9999,和上面一致。

  3. 然后在模拟器中打开webview页面就可以看到所有请求了

  4. 右键保存源文件到本地,然后添加一行alert代码 。

  5. 在请求上右键,选择 Map Local

  6. 选择刚才修改过的文件

  7. 重新载入页面 :

这样,我们利用模拟器+Chrome+Charles就可以完美开始、调试webview页面了,模拟器当做手机,Chrome insepct 调样式、接口、查看数据,利用Charles映射本地文件直接查看效果。

至于iOS上的webview调试,模拟器+Safari+Charles应该也是一样的。我没有试过,试过的人请告知。

参考


向我捐助 | 关于我 | 工作机会


平淡的 2021

平淡的 2021

今年用一个字表达,就是「淡」,就像一杯白水一样。

旅途

今年算是工作这么多年来,出去玩的最多的一年了,毕竟再不用随心飞要过期了。

今年去了南京、昆明、大理、重庆、武汉、上海、三亚,还游了三峡:

迪士尼乐园

三亚

看书

因为出去玩得多了,所以看书的时间多了,大多是在睡前和飞机上看的。

看了看读书记录,今年前半段开始密集地看人物传记类书籍:

一贯地爱读历史类和政治类书籍:

没用的技术书:

其他:

还有基本关于量化交易和区块链的书,要么是草草翻过,要么是写得不好,就不列出来了。

每次心态有起伏的时候,就会抓来几本书开始读,这本读腻了,就换一本,时间就消磨过去了。

投资

今年算是艰难的,艰难守住去年的盈利,不谈也罢。

抬头看天

今年不再着眼于工作中的技术,放眼于区块链和量化交易两个点,他们都是计算机技术和其他学科的交叉点,我理解。

区块链的学习应该是夏天开始的,之前总结过,不再赘述。

至于量化交易,刚刚开始学,还没有入门,里面的统计学、金融知识太多,怎么入门,明年再说吧。

回顾 2021

2021 计划,目前基本都做到了, 果然计划还是定的细一点比较好实现。

学习上,今年开始全面使用 Notion,不得不说,现在已经离不开它了,实在太方便了。它是我的知识库,是网状的,让我的知识可以铺展开。

心态

今年我的心态有了很大的变化,对生活,对工作。

这一年我无数次问自己,三年前做的决定是不是正确的 —— 因为创业失败的打击让我决心去做后端,三年过去了,学到了不少东西,但是不知道是不是变得更好了。

今年在工作上我比较消极,在一个不那么在乎技术的位置上,其实待得也不怎么舒服。所以,年后我一定会离职了,毕竟到 3 月就待了三年了。我从来没有在一家公司超过三年,这次也不能例外。

今年互联网行业风向一转,仿佛要把前些年赚的钱全部吐出来。整个世界都很浮躁,不过我越来越平静,逐渐地认识到自己能做到什么,做不到什么,也知道自己想要的是什么生活。

「天高任鸟飞,海阔凭鱼跃」吧

展望 2022

今年列 TODO 的比较少,只有这些

祝自己新的一年顺利吧!


向我捐助 | 关于我 | 工作机会


科学上网终极方案

因为 GFW 的存在让我们一直无法畅快地浏览网站、使用软件、开发程序,现在就这几个方面一一展开说明。

事先准备

SwitchyOmega - 浏览器科学上网

下载地址: https://chrome.google.com/webstore/detail/padekgcemlokbadohgkifijomclgjgif

可以在浏览器中方便地针对某些网站使用 SS 。

Proxifier - GUI 软件科学上网

下载地址: https://www.proxifier.com

它可以让你自己添加软件到 rules 中,这样你的这些软件就可以使用 SS 来科学上网了。比如 TelegramSignalTerllo 等就可以畅快地使用了。

proxychains4 - 终端科学上网

比较麻烦,还要关闭 Mac 的 SIP:

  1. 重启Mac,按住Option键进入启动盘选择模式,再按⌘ + R进入Recovery模式
  2. 实用工具(Utilities)-> 终端(Terminal
  3. csrutil disable
  4. 重启
  5. 查看是否成功 csrutil status

自己编译安装:

  1. git clone [email protected]:rofl0r/proxychains-ng.git
  2. cd proxychains-ng
  3. ./configure
  4. make && make install
  5. cd .. && rm -rf proxychains-ng

修改配置,配置文件在 /etc/proxychains.conf:

# proxychains.conf  VER 4.x

strict_chain

chain_len = 1

proxy_dns
remote_dns_subnet 224

tcp_read_time_out 15000
tcp_connect_time_out 8000

localnet 127.0.0.0/255.0.0.0

[ProxyList]

socks5 127.0.0.1 1086

然后就大功告成了,试试:


2018.11.14 updated

最近在玩 Go,安装 golang.org/x 下的安装包的时候发现 proxychains4 go get 竟然不能正确地安装,原因是 proxychains 是通过修改动态加载库来实现代理,但go是静态编译的,因此,proxychains无法代理go。

幸好 ShadowsocksX-NG 提供了 http 代理,在.zshrc 添加:

alias p-on='export http_proxy=127.0.0.1:1087;export https_proxy=$http_proxy'
alias p-off='unset http_proxy;unset https_proxy'

在终端下,p-on 可以打开 http 代理,p-off 可以关闭 http 代理

2022.7.27 updated

现在使用 clashX 就够了


向我捐助 | 关于我 | 工作机会


向我捐助

向我捐助

写作不易,如果觉得我写的东西对你有帮助,请我喝杯咖啡吧。

微信

支付宝

使用 redux 管理 flutter 应用数据


本文发布在使用 redux 管理 flutter 应用数据,后续一直会更新,感兴趣的可以关注一下。

最近在学 flutter,边学边记录了一本 写给前端看的 flutter 笔记,感兴趣的小伙伴可以一起来完善他。


使用 redux 管理 flutter 应用数据

redux 是什么?简单来说,就是为了解决 UI 层状态管理的方案,如果不熟悉,请先看文档学习一下,今天的重点不是学习 redux,而是直接用 redux 管理 flutter 的状态。

和 react 的比较

首先,flutter 和 react 真的太像了,连状态管理都有 redux 方案:

flutter react
redux.dart redux
flutter_redux react-redux
redux_thunk redux-thunk

另外,甚至还有 redux-epics 能够搭配 rxdart 对标 redux-observable。对于 rxjs 熟悉的人应该也能够很快地开始 rxdart 了,再次证明知识是相似的,技能也是能够越学越快的。

为了不混淆大家,接下来我把 react 中的 redux 称为 react/redux, flutter 中的 redux 称为 flutter/redux

正题

现在我们用 flutter 写一个简单的 redux 例子:

redux

完整代码在 https://github.com/riskers/flutter_notebook_redux ,有过 redux 经验的应该能看明白。使用到了:

  • redux
  • flutter_redux
  • redux_thunk

项目架构完全是之前按照我之前在 React 中的经验来做的

有几点需要注意:

  1. 初始化 Store

为了方便,可以在 state 中以一个静态方法初始化:

static AppState initialState() {
  return AppState(
    count: 0,
    clickCount: 0,
  );
}

然后在入口文件中调用:

final store = Store<AppState>(
  reducers,
  middleware: [thunkMiddleware],
  initialState: AppState.initialState(),  // 调用
);
  1. reducer 复制对象的方法

在 react/redux 中我们习惯这样写 reducer:

const reducer = (state={
  loading: false,
  data: [],
}, action) => {
  switch(action.type) {
    case CONST.FETCH_GITHUB_SEARCH_USER_LOADING:
      return {
        ...state,
        loading: true
      }
    case CONST.FETCH_GITHUB_SEARCH_USER_SUCCESS:
      return {
        loading: false,
        data: action.payload.items
      }
    default:
      return state
  }
}

重点是 ...state 这样的解构写法,实际是浅复制了 state 对象,而在 dart 中没有这么方便的方法,比如 reducers.dart 中的 copyWith:

AppState counterReducer(AppState state, dynamic action) {
  switch (action) {
    case INCREMENT:
      return state.copyWith(count: state.count + 1);
    case DECREMENT:
      return state.copyWith(count: state.count - 1);
  }

  return state;
}

copyWith 是我们自己写的扩展方法:

AppState copyWith({count, clickCount}) {
  return AppState(
    count: count ?? this.count,
    clickCount: clickCount ?? this.clickCount,
  );
}

??条件表达式count: count ?? this.count 表示 copyWith 只要没传进来 count 就把 AppState 实例的 count 值赋给 count,然后再实例化一个 AppState 对象。

参见其他的复制对象的方案

  1. actions

比较有特点的就是 redux_thunk 的异步 action 写法:

ThunkAction asyncIncrement() {
  return (Store store) async {
    await Future.delayed(Duration(seconds: 3)); // 延迟 3 秒

    store.dispatch(INCREMENT);
  };
}

async / await 是不是很熟悉?

  1. StoreProvider / StoreConnector

StoreProvider 很简单,就是在根组件中挂载 store:

@override
Widget build(BuildContext context) {
  return StoreProvider<AppState>(
    store: store,
    child: MaterialApp(
      theme: ThemeData.dark(),
      home: Home(),
    ),
  );
}

而 StoreConnector 就比较麻烦了,需要先定义一个 ViewModel:

class AppStateViewModel {
  final AppState state;
  final void Function() onClick;

  AppStateViewModel({
    this.state,
    this.onClick,
  });
}

ViewModel 中规定你要在这个组件中使用的 Store 中的数据

class AddButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreConnector<AppState, AppStateViewModel>(
      converter: (store) {
        return AppStateViewModel(onClick: () {
          store.dispatch(INCREMENT);
          store.dispatch(CLICK);
        });
      },
      builder: (context, vm) {
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: FloatingActionButton(
            onPressed: vm.onClick,  // trigger!
            tooltip: 'Increment',
            child: Icon(Icons.exposure_plus_1),
          ),
        );
      },
    );
  }
}

StoreConnector 有两个方法:

  • converter: 使用给定的转换器函数将存储转换为 ViewModel,并将 ViewModel 传递给 builder 函数
  • builder: 承接 converter 返回的数据来使用

以 AddButton 组件为例,converter 返回 onClick 函数来触发 dispatch, builder 承接到这个函数,这样就能在发起 dispatch,走 redux 的流程了。


本文只介绍 redux 在 flutter 中的应用,如果你对 React 很熟,你应该能明白 redux 的意义,除了有个全局 store 外,深层组件之间通信也不是问题了,对于 flutter 这种框架也是很有意义的。

当然,直接使用 redux 可能会比较重,这和 react 是类似的,小应用的话可以用全局总线来处理,甚至你可以一个界面就是一个 statefulwidget,直接都用 setState 来改变数据渲染界面,这样每个界面都是独立的数据了。

总体上来说,在 flutter 上使用 redux 还是很麻烦的,好在官方在 2019 roadmap 中提到了要提供最佳实践,让我们期待吧!


向我捐助 | 关于我 | 工作机会


浅谈JSONP

这是我在13年初写的文章,当时懵懵懂懂写下了自己对JSONP的理解。


提到JSONP,我当时在网上找了无数帖子也没有看懂它。那些文章大同小异,都是讲到JSONP原理以后就戛然而止,把我们这些初学者搞得云里雾里。所以,写下这篇文章,希望对大家有帮助!

为什么要有JSONP

回答这个问题之前,大家先想想什么是AJAX,JSONP就是一种能够解决AJAX办不到的事情而存在的一种取数据的技术。什么事情是AJAX办不到的呢?就是跨域!

跨域:顾名思义,就是当前网页的地址和我们要取的数据地址不在一个域下。这是因为浏览器都有一个“同源策略”— 两个页面的域名必须在同域的情况下,才能允许通信。

怎么才算一个域呢?

相同域名,相同端口,相同协议(因为不是这里的重点,大家可以请教Google)

“同源策略”的意义:“同源策略”有效地阻止了一些危险行为,比如你进入www.aaa.com,同时浏览器又开了一个www.bbb.com,如果这个www.bbb.com是一个木马网站,在没有“同源策略”的情况下,它就可能嵌入一些代码,来取得你在www.aaa.com的信息(因为这时两个页面是可以通信的) 。而正是因为有了“同源策略”,刚才可以通信的情况才不会发生。

“同源策略”带来的麻烦:上面的例子是我们在不知情的情况下,保护我们的网络安全的,但如果我们就是要让www.aaa.com取得www.bbb.com上的数据,行不行呢?答:不行!还是因为”同源策略”!我们想从自己信任的网页上取得数据都不行,这可怎么办呢?

JSONP出现

在需要跨域通信的岁月里,一些卓越的前端工程师们想到了这个”作弊”的办法来逃避”同源策略”。”同源策略”虽然很厉害,阻止了一个页面到另一个页面的通信,可是src指向的路径它放过了,提到src,大家是不是想起了<script>?对,JSONP就是利用”同源策略”的这一”漏洞”来进行”作弊”的。(其实有src属性的不止有<script>,还有<img><iframe>,而<iframe>也是能够运用JSONP的)。

下面看看JSONP的原理:

JSONP:JSON with PaddingJSON大家这都知道,是一种数据通信格式,而”Padding”是什么意思,别急,往下看就知道了。

我们先举一个简单的例子:

www.aaa.com中:

<script type="text/javascript" src="http://www.bbb.com/abc.js"></script>
<script type="text/javascript">
    function abc(json){
        alert(json['name']); 
    }
</script>

www.bbb.com/abc.js中:

abc({'name':'risker','age':24});

页面会弹出risker,有感觉了么?

JSONP是这样工作的:像前面所说的那样,我们可以取到www.bbb.com/abc.js,里面是一个abc函数,这个函数也会被加载到www.aaa.com。加载完成后,就应该执行abc了,然后我们在www.aaa.com定义abc函数,这个函数里写一些处理数据的语句。这样其实就简单地实现了跨域访问数据了,这也就是JSONP的原理了。而JSON with Padding的意思,就是abc(json)中的json
abc({'name':'risker','age':24})

这个JSON对象被包在abc这个函数中当作参数来被处理,而JSON with Padding这个词很形象地形容了这个过程。

JSONP的简单实例

在网上能找到的JSON基本只是介绍到这里就完了,但是这让初学者看不到一个实实在在的例子。所以下面才是这篇文章和其他网上介绍JSON文章不一样的地方,我带给大家一个例子!
大家一定对百度的自动搜索框有印象,它就是一个JSONP的实例:

先查看demo

分析一下:

  1. 分析数据地址回顾上面的例子,我们首先要知道数据的来源地址,就是上面的www.bbb.com/abc.js里的数据。在Chrome中查看Network。然后随便在搜索框里输入点什么,比如s,观察Network里是不是多了东西,点开它,就是我们输入“s”后传回的数据了:

    这个地址是 http://suggestion.baidu.com/su?wd=S&p=3&cb=window.bdsug.sug&from=superpage&t=1365392859833 , 我们分析一下,wd后面是s,那就可以断定百度定义wd是搜索的关键字,cb是一个回调函数,其他的对我们就不重要了。回调函数是我们取到数据要后执行的函数,相当于我们上面的abc函数。它是可以自己取名的。像http://suggestion.baidu.com/su?wd=S&p=3&cb=succ&from=superpage表示取到数据后执行succ函数:

    这样,我们的数据就包在了succ函数里做一个参数,再次证明了JSON with Padding 的原理。

  2. <script>标签,其src指向数据地址:这是要动态生成的,不能把地址写死,要不然取到的都是一样的数据了。所以我们要动态生成<script>,动态指定src属性:

    var oScript = document.createElement('script');
    oScript.src = 'http://suggestion.baidu.com/su?wd='+oTxt.value+'&amp;p=3&amp;cb=succ&amp;from=superpage';
    document.body.appendChild(oScript);
    
  3. 不要以为这样问题就解决了,F12一下,就看到生成了好多<script>!这是因为我们每输入一个字符就动态生成一个<script>,造成了代码冗余!解决一下:

    if(oScript){
        document.body.removeChild(oScript);
    }
    

    好,这样,我们的搜索框效果就做好了,因为主要讲JSONP部分的工作原理,就不做成百度下拉框那样了,大家可以自己去布局。当然,真正的百度搜索框还要在此基础上涉及事件的冒泡取消等等,就不是这里的重点了,不做阐述。

JSONP总结

  1. JSONP是为了传数据而存在的技术。网页之间的通信原本有AJAX就够了,而AJAX因为浏览器“同源策略”面对跨域情况就束手无策了。JSONP就这样被发明了,利用<script>src属性不受“同源策略”的控制,“作弊”般地巧妙地逃过了浏览器的这一限制。
  2. JSONP方法本质是创建<script>标签,其src指向我们的数据地址。地址后面附带一个回调函数(名字一般是callback或者是别的什么,就看后台给我们的是什么了,函数名是我们起的)。然后,声明这个回调函数。这样,只要一引入上面的<script>标签,就相当于执行了那个回调函数。
  3. 虽然jQuery把JSONP内置在了AJAX里,但是我们一定要清楚,AJAX和JSONP是完全不一样的,一定不要混淆!以后我会更新一篇介绍AJAX的文章的。
  4. 这里是前端和后台的交汇之处,想要真正融会贯通,还要学学后台的知识。我也是在学了PHP之后才把JSONP搞懂的。

向我捐助 | 关于我 | 工作机会


<2017></2018>

<2017></2018>

2017年最大的变化就是换了一家公司,从 360 来到阿里巴巴。来到阿里,证明了自己这两年自己的努力没有白费。来到阿里半年后,逐渐地有了从业务上选型技术的观点:无论多牛逼的技术,只要不符合自己业务的需求,只能放弃。 现在保持之前5年的习惯,写一篇关于自己这一年的总结。

职业

来到阿里做一个前端,算是我的一个心结。因为当年毕业找工作的时候,就是想去阿里,可惜当时人家不要我。来这一年,对 React 技术栈更加深入,MobX、rxjs也有过调研。而在团队中,我是负责工程化的,这一年来,做了几个工程化的包,现在在做UI自动化测试的东西。流水帐记一下,主要就是:

  • ESLint 的推进
  • 升级到 webpack2
  • webpack 构建优化(webpack dll plugin、happypack)
  • 推进 CSS Modules
  • 基于 puppeteer 的自动化UI测试系统
  • chrome-sunglasses

技能

2017年年初的时候,给自己定的计划是:

  1. React 技术栈的深入
  2. iOS 开发入门
  3. Python 的入门

现在来看,只有第一点做到了,不过还有一些其他的额外收获,比如:

今年我学过的语言有 C / OC / Swift / Haskell,不过都没有很深的开发经验,说白了就是没有代码量的累计,所以都忘了。

这样看来,今年有些混,没怎么学习新知识。明年再接再厉吧。

博客

现在博客 star 数是 832,watch 数是 246,比去年多了好多,谢谢大家关注。

今年只写了几篇博客:

数量不多,不过都是干货,也都是自己实践、学习后写出来的。而且大量的思考,我都没有写在博客里,而是写在了简书和gist里。

分享

今年只给团队分享了 CSS Modules: https://riskers.github.io/share/share/css_modules.htm

书单

今年读数量减少很多,只有16本:

旅行

今年一共去了4次杭州:

  1. 入职为期两周的培训
  2. 部门年会
  3. 部门 outing
  4. 阿里十八周年年会

夏天的那次绕着西湖走了一圈,很累,很美。

恩,就这样吧。


向我捐助 | 关于我 | 工作机会


团队规范第一步 - EditorConfig & ESLint & git hook

本文介绍现在最常用的前端工程化工具:

  • EditorConfig
  • ESLint
  • git hooks

EditorConfig

EditorConfig 是一套用于统一代码格式的解决方案,官网介绍很简单。简单来说,EditorConfig 可以让代码像是在同一个编辑器里打开的。

在项目跟目录下新建 .editorconfig 配置文件,配置项目有:

  • indent_style
  • indent_size
  • tab_width
  • end_of_line
  • charset
  • trim_trailing_whitespace
  • insert_final_newline

配置文件的格式如下:

root = true

[*.js]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

这样,只要团队内都使用这样的一份 .editorconfig ,再根据我们用的编辑器去安装相应的 EditorConfig 插件就行了。主流编辑器都支持,比如:

ESLint

前端检验工具有:

  • JSLint: 是道格拉斯(《JavaScript语言精粹》的作者)开发的,他强制使用JS语言中的精粹(就是书中介绍的那部分)。缺点是不能配置。
  • JSHint: 其实是一个可配置的 JSLint ,你可以配置规则。但是不支持自定义规则。
  • ESlint: 易扩展,可自定义规则,还支持 JSX。

所以,很明显 ESLint 是现在大家首选的检验工具。

使用很简单:

  1. 安装 eslint : npm install --save-dev eslint
  2. 添加 npm scripts:
{
  "scripts": {
    "lint": "eslint src --ext .js"
  }
}

执行 npm run lint 就会检查 src 目录下的 .js 文件。

  1. 在根目录下新建 .eslintrc:
{
  "extends": "airbnb"
  "rules": {
    "semi": ["error", "always"],
    "quotes": [2, "double"]
  }
}
  1. 执行 npm run lint 即可

eslint-config-airbnb 是根据 airbnb 规范所制作的 ESLint 配置。

git hook

git hook是你每次进行git相关操作的时候,都会触发的一些与该操作对应的事件,并运行相应的脚本代码,实现例如格式化提交信息,发送提交邮件,运行单元测试等功能。

工作原理

git hook分为client side和server side,其中,client side的git hook是在创建git项目的时候,就会自动产生,并且保存在名为 .git/hooks 目录下:

可以看到有很多种 hook ,有 .sample 后缀表示还没有开启。我们可以做个试验,修改 .git/hooks/pre-commit.sample.git/hooks/pre-commit ,并且修改内容为:

之后,我们执行 git commit 的命令时:

前端应用

我们可以利用 git hook 做很多事情,比如 ESLint 代码,如果没有通过,就不允许 commit 。但是 git hook 是 需要 shell 编程脚本。

我们前端是不熟悉的,还好有 pre-commithusky 帮助我们。下面以 pre-commit 为例,介绍怎么配置 hook:

{
  "scripts": {
    "lint": "eslint src --ext .js,.jsx",
    "precommit-msg": "echo 'Pre-commit checks...' && exit 0"
  },
  "pre-commit": [
    "precommit-msg",
    "lint"
  ]
}

这样,我们就在每次 commit 之前依次执行 precommit-msglint,如果 lint 出错,则不会成功 commit 。

这些都是约束团队编码的工具,能够帮助团队更好地协作,让整个团队的代码看起来像是一个人写的

20170718 更新

lint-staged 可以取到修改的文件,这样就可以只 lint 你本次修改的文件而不是所有文件:

// commit 之前得到修改的文件,然后进行 eslint 命令

  "lint-staged": {
    "src/**/*.{js,jsx}": "eslint"
  },
  "scripts": {
    "precommit": "lint-staged", 
  }

再配合 VSCode 的 ESLint 插件,不要太舒服

20190506 更新

上文已经过时,自从 prettier 出现后,已经可以代替 editorconfig 了,而且 prettier 还能集成进 eslint 。
最让我高兴的是,我最近发现 ESLint 通过 typescript-eslint-parser 可以检查 TypeScript 了,也就是说可以不用 tslint 了,这玩意当时让我很抓狂。

详细的不聊了,本来想更新一篇,但是看到 https://github.com/AlloyTeam/eslint-config-alloy 已经说得这么清楚了,就没有必要再制造水文了。


向我捐助 | 关于我 | 工作机会


移动端适配方案(下)

注: 这篇文章部分内容已经过时,vw 方案(见文末)已经足以适应各种场合,且兼容性很好。


上一篇介绍了像素和视口这些基本概念,现在接着移动端的适配方案。

推荐一篇文章:MobileWeb适配总结,里面说到的三种布局方法已经说的很详细,还分别做了demo,我就不做了,这里说说三种方案的原理以及我使用中的感受,希望各为互补,大家理解是最重要的。

之前做过PC页面的人聊的最多的就是『兼容』,这是因为浏览器之间的差异引起的,不再多说。而移动端是基本没有『兼容』的问题的,全是CSS3,简直不要太开心。可是『适配』问题随之而来。

什么是『适配』?做PC页面的时候,我们按照设计图的尺寸来就好,这个侧边栏200px,那个按钮50px的。可是,当我们开始做移动端页面的时候,设计师给了一份宽度为640px的设计图。那么,我们把这份设计图实现在各个手机上的过程就是『适配』。

那么,我们怎么开始呢?目前有三种方法:

  • 固定高度,宽度自适应
  • 固定宽度,viewport缩放
  • rem做宽度,viewport缩放

这三种方法的核心都是视口的确定,现在以实现这个设计图为例说明。

固定高度,宽度自适应

demo

这也是目前使用最多的方法,垂直方向用定值,水平方向用百分比、定值、flex都行。腾讯京东百度天猫亚马逊的首页都是使用的这种方法。

随着屏幕宽度变化,页面也会跟着变化,效果就和PC页面的流体布局差不多,在哪个宽度需要调整的时候使用响应式布局调调就行(比如网易新闻),这样就实现了『适配』。


原理

这种方法使用了完美视口:

<meta name="viewport" content="width=device-width,initial-scale=1">

这样设置之后,我们就可以不用管手机屏幕的尺寸进行开发了。

固定宽度,viewport缩放

demo

设计图、页面宽度、viewport width使用一个宽度,浏览器帮我们完成缩放。单位使用px即可。

目前已知荔枝FM网易新闻在使用这种方法。有兴趣的同学可以看看是怎么做的。


原理

这种方法需要根据屏幕宽度来动态生成viewport,生成的 viewport 基本是这样:

<meta name="viewport" content="width=640,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no">

640 是我们根据设计图定下的,0.5 是根据屏幕宽度动态生成的。

生成的viewport告诉浏览器网页的布局视口使用 640px,然后把页面缩放成50%,这是绝对的等比例缩放。图片、文字等等所有元素都被缩放在手机屏幕中。

这个gif图说明了一切:

rem做宽度,viewport缩放

demo

这也是淘宝使用的方案,根据屏幕宽度设定 rem 值,需要适配的元素都使用 rem 为单位,不需要适配的元素还是使用 px 为单位。

具体使用方法见使用Flexible实现手淘H5页面的终端适配

上文提供了sass和postcss的px2rem转换方法,我这里给出less的px2rem。因为less不支持函数,所以需要安装插件 less-plugin-functions ,然后就简单了:

.function{
    .px2rem(@px,@base:72px){
        return: @px / @base * 1rem;
    }    
}

这样使用:

div{
    width: px2rem(100px);
}

使用这个方法的库:


原理

实际上做了这几件事情:

  1. 动态生成 viewport
  2. 屏幕宽度设置 rem的大小,即给<html>设置font-size
  3. 根据设备像素比(window.devicePixelRatio)给<html>设置data-dpr

rem

这么设置的好处是可以让不同设备的rempx都显示一样的长度。

设置rem

设置rem的意义在于得到一个与屏幕宽度相关的单位,本来vw是最合适的,但是因为兼容性的问题,只能使用rem来做。这样,让不同设备的rem显示一样的长度

vw是CSS3引入的单位,1vw = 1%窗口宽度

上面的布局我们可以这样:

html{
	font-size: 屏幕宽度 / 10; 
}
.btn{
	width:8.75rem;
	height:1.25rem;
}

这样,无论屏幕宽度是多少,.btn都是相对于屏幕的这么宽,做到了适配。

设置 viewport 缩放 和 data-dpr

这两个设置其实是干的一件事,就是适配高密度屏幕手机的px单位。

.a{
  font-size:12px;
}
[data-dpr="2"] .a{
  font-size: 24px;
}
[data-dpr="3"] .a{
  font-size: 36px;
}

而缩放是动态的,这样,不同设备下的px显示一样的长度

之前说过CSS像素和物理像素与缩放、dpr都有关系,这里说明:

在普通手机上,.a字体设置为12px;

在dpr是2的手机上,[data-dpr="2"] .a字体为24px,又因为页面缩放50%,字体为还是12px

总结

坦白说,我不觉得第一种方案能就称为『适配』方案,因为太笨了,只能做一些列表等简单排列的样式。

但是一些复杂的活动页的布局,用它可能就力不从心了:

PK活动

这是我曾经做过的一个页面,『PK』要和左右两张图平行,而且下面的『不怒自威』、『义薄云天』和下面的战斗力位置都要固定,不能有差。如果用第一种方案,可能各个元素就要绝对定位,然后各种百分比来定位了。且不说计算麻烦,而且辛苦一番最后的结果尺寸是和设计图有出入的。

但是,第二种和第三种方案就绝对不会有这种情况,不会和设计图有差。再说第二种和第三种方案的区别,目前我唯一知道的区别就是第三种方案更加灵活,有两种单位可以使用,想让元素适配的时候就用rem,想让文字不缩放的时候就用px

如果你没有明白我以上讲的,可能是我太啰嗦了,你可以看看下面的那些文章。当然,最好的理解方式就是用这些方案做几个页面,到时候就明白了。

其他文章

这两篇文章看着简单,尤其是上一篇讲视口,花费了将近一周的时间去翻阅资料。本来以为很简单的东西,才发现有那么多名堂。好了,后天年会,祝我中大奖吧。

PPT

20170821 update

vw方案: https://www.w3cplus.com/css/vw-for-layout.html

demo: http://huodong.m.taobao.com/act/layouttestvw.html

只要明白 CSS 的 vw 单位是什么意思就明白这个方案了,可以看作是第三种方案的完美版,先定死 viewport,再通过 vw 取代 rem。


向我捐助 | 关于我 | 工作机会


CSS Contain&Cover的数学公式

background-sizecontaincover是怎么用的,大家应该都明白。但是里面也有一些有趣的数学关系。

基本概念

上面就是我们对于 rimage (图片宽高比)、rviewport (容器宽高比) 的定义。

将图片放进容器

三种方法

  • stretch : 把图片的宽高强行设置为容器的宽高

    注: h'image、w'image、r'image分别为图片改变后的高、宽、宽高比。之后文章这些名词也是这个意思,不再解释。

    stretch的方式可想而知后果:

    那么保持怎样的数学关系才能保证图片放进容器之后不会变形呢?
    答案也是明显的:

    r'image = rimage

    接下来介绍的两种方法就是不会变形的,也就是说能够上面的公式对于它们来说是已知条件。

  • contain : 让图片适应容器,我们把它“装”进容器,同时也会留下空白。就像我们看电影时的"黑边"。

    对于contain方法来说,也只有图片放进容器后的高度( h'image )是未知的,我们来算一下:

    如果不知道contain为什么是这样的建议先看看background-size

  • cover : 也可以让图片“遮”住容器。

    contain对应,cover方法要来算一下 w'image

宽高比的影响

不知道大家注意到没有,刚才我们推导contain的 h'imagecover的 w'image 时使用的图片的宽高比总是大于容器的宽高比。

这导致了什么?导致了我们推导时使用的 条件3 是不一定正确的。
额,这么说我也有点晕,看图:

可以看到,我们只考虑了 rimage > rviewport的情况。

结论

我们考虑rimage < rviewport后加完整了,图片放进容器之后的宽、高如下:

这样我们就求到了图片在应用background-size属性之后在容器中实际的宽、高。

比例 hidden

现在讨论图片放进容器后的图片与容器的比例关系hidden,这样我们就可以以此关系让图片随着容器的变化而变化。
注意,hidden是一个小于1的比例,至于为什么要这样设定后面有解释。

contain布局为例,rimage > rviewport :

而以cover布局为例,rimage > rviewport :

以此类推,得到所有情况的 hidden

这样可以看到四种可能性,但是别忘了我们在上面可是推导过 w'image 、h'image

所以hidden最终的结果是:

可以看出来,hidden就只有两种结果,rimage / rviewportr viewport / rimage,而且这个数是小于1的(这是上面就确定的)。

所以,hidden的计算可以简化为:

后记

你可能想,搞了半天,这到底能干吗?直接用background-size不就好了,为什么还要得到具体的宽、高,得到了伸缩比又能怎么样。
我也想了想,如果只是图片,似乎上面都是废话。但如果是DOM呢?这是不是就是一种布局方式?

我也不知道,知识有时候就是这样。当你需要用到的时候,你才觉得有用。

参考文章

CSS – Contain & Cover


向我捐助 | 关于我 | 工作机会


css动画的steps

animation默认以ease方式过渡,会以在每个关键帧之间插入补间动画,所以动画效果是连贯性的。easelinear等之类的过渡函数都会为其插入补间。但有些效果不需要补间,只需要关键帧之间的跳跃,这时应该使用steps过渡方式。本文后面有案例。

steps用法

简单地说,就是原本一个状态向另一个状态的过渡是平滑的,steps可以实现分布过渡。steps用法 :

steps(n,[start | end])

n是一个自然数,steps函数把动画分成n等份。

  • step-start
    等同于 steps(1,start) ,动画分成 1 步,动画执行时以左侧端点为开始 ;
  • step-end
    等同于 steps(1,end) ,动画分成 1 步,动画执行时以结尾端点为开始 。

没懂对不对?我也没懂,上面是官方的说法。接着往下看吧

辨析steps

可以在demo中查看step的区别:狠狠地戳下去

steps(4,start)steps(1,start) 的最大区别就是每次动画"跳跃"的时候,即从 0% -> 25% 的时候,steps(1) 直接一步到位,瞬间从0%的状态跳到25%的状态,而steps(4)会逐渐走4步,即从 0% -> 25% 要跳 4 步 !

慢慢体会一下,应该就知道steps的作用了吧

案例 - 控制台效果

先戳 demo

这里就是steps(1,start) ,动画又是只有 0%(100%) 、 50% 两个状态,所以直接一步跳跃,直接走完。

你可以改成steps(4),就更能知道steps的作用了

案例 - 人人网首页效果

先看看人人网首页的效果:

再戳demo

一步一步分析:

  • 首先,我们不加动画,就像s1

  • 然后,加animation但是没有steps,像s2那样。这样很奇怪是不是,就是因为没有steps,动画是连贯的,所以我们看到了跑马灯似的效果:

  • 最后,当然是我们的终极效果s3,因为设计师把我们看到的类似 Flash 的动画逐帧导出成一张大图,再加上合适的steps和动画时间,就看到了人人网首页的那般顺滑的动画效果!
    注意,为了保持最后停止的状态,还要加一个 forwards ,这里不是本文重点,就不细说了。

再考考你有没有搞清楚steps:为什么图片是20帧,可是设置成steps(12)呢?

因为steps是设置的每一步动画的跳跃步数,而不是整个动画的跳跃步数。举个例子:

@-webkit-keyframes ani{
   0%{...}
   50%{...}
   100%{...}
}
.xxx:hover{
   -webkit-animation:ani 2s steps(10) ;
}

上面的代码指的是0%50%之间有10个跳跃,50%100%之间有10个跳跃。而不是0%~100%之间有10个跳跃。

呼呼,终于理清了steps的作用。感觉这个过程就是渐进增强的体验,一开始用户只能体验s1,后来有了css3,可以体验美妙的动画了,就像s3。但愿以后我们能够做到让每一个用户都满意。

参考


向我捐助 | 关于我 | 工作机会


利用hash监听Android上的物理返回键

我们都知道JS是没有办法监听到Android机上的物理返回键的,所以我们要"变通"地实现这个功能。

webview

在webview中实现这个功能比较简单,因为客户端是可以监听到返回键的,所以客户端只要给webview留一个接口就可以了。这是手机助手的监听物理返回键的接口:

/**
 * 截获物理返回键
 * setOnBackKeyDownListenner(int type);    void
 * type : 1 截获 0 释放
 *
 * 按下物理返回键触发事件
 * onBackKeyDown
 *  
 */
 
// 弹窗时
AndroidWebview.setOnBackKeyDownListenner(1);
showPopup();
 
// 按物理返回键时客户端回调页面函数
function onBackKeyDown(){
    // 释放物理返回键
    AndroidWebview.setOnBackKeyDownListenner(0);
    hidePopup();
}

开始监听

openPop事件执行的时候弹窗出现,然后开始监听物理返回键

window.openPop = function() {
    $('.mask').css('display','-webkit-box').addClass('js-close');
    AndroidWebview.setOnBackKeyDownListenner(1);
}

取消监听

onBackKeyDown是按下物理返回键触发的事件,可以看到按下返回键后执行closePop并且不再监听返回键

window.onBackKeyDown = function(){
    // 关闭弹窗
    window.closePop();
    // 释放物理返回键
    AndroidWebview.setOnBackKeyDownListenner(0);
}

可以看出来,webview下的监听物理返回键还算是"比较"简单的

浏览器中

看下面的案例,这个页面有4个子页面,这里用hash记录所处的页面,有这么几个好处:

  1. 利用hashchange的事件触发,可以记录页面状态。比如页面在#page2,这时候用户刷新了页面,页面还是会在page2
  2. 这是记录在history里的,也就是说按下浏览器的后退键或者手机的物理返回键是可以回退到上一个状态的,这样在回退的时候我们利用hashchange不是也就做到了"变相"地监听了返回键么?

向我捐助 | 关于我 | 工作机会


flutter入门-搭建环境

Flutter 近来几个月炒得大热,对于移动开发者来说是挑战可能也是机遇。至此,跨平台开发 领域算了一下,有:

我列出这个表的时候,有些头皮发麻,不知道对于我这个想通过跨平台开发进入移动原生开发的人来说该选择哪个。之前只了解过一点 react native,感觉开发者体验并不好,想要 inspect 元素还要再开一个 devtool。这次,索性就直接试试 flutter。

现在记录一下我作为前端开发者玩 flutter 的体会。

安装

按照文档 的顺序来做,没什么好说的,我搭建环境还算顺利,如果你遇到问题,可能需要学会科学上网。概述一下我的安装过程(Mac),一切以官网为准:

  1. 下载 Flutter 源码(注意,是 beta 分支):

    cd ~
    git clone -b beta https://github.com/flutter/flutter.git

    然后在 .bashrc(如果你用 zshrc 的话就是 .zshrc) 中修改环境变量,将 flutter 项目根目录下 bin 路径加入环境变量 PATH:

    export PATH=$PATH:/Users/riskers/flutter/bin

    flutter --version 验证是否安装成功

  2. 检查你的机器上欠缺的环境

    因为是跨平台开发,所以你需要安装 iOS 和 Android 开发环境:

    flutter doctor

    就会看到下图,x 号是你需要安装的环境,这里因为都已经安装好了,所以都是对号。

    doctor

  3. 给 VSCode 安装插件: Dart 和 Flutter,这样就能够用 VSCode 开发 Flutter 了。

体验 Flutter

  1. 创建新应用:

    create new app

  2. 运行新应用

    按下 F5 进行调试,选择你的设备(需要你提前准备好模拟器或者真机)

    Android

    ios

    如果你运行 Android 项目遇到问题,查看这个回答

  3. 然后是 hot reload:

    hot load

    hot reload 已经是标配了,如果 hot reload 都没有,也太差劲了。

Debug

目前 layout 的 inspect 只支持在 Android Studio或IntelliJ IDEA的 Flutter插件

更多

更多的实践会在未来慢慢总结出来,敬请期待。


向我捐助 | 关于我 | 工作机会


Tiny Tiny RSS + fever 搭建自己的 RSS 服务

一直以来,我使用 reeder3 + Inoreader 订阅了很多博客,但是4月的时候发现不能再添加新的订阅了,它收费了。想到每天早上再也不能一来就看新闻,就很烦,于是开始研究替代方案。现在我使用的是 Tiny Tiny RSS + fever + reeder3 的方案,到现在也没发现什么问题,就记录一下,分享出来。

以下分为本机搭建远程搭建两部分,纯属傻瓜配置,可一步一步来。

本机搭建

install ttrss

  1. download docker-compose.yml:
curl -L "https://raw.githubusercontent.com/riskers/tty-fever-rss/master/docker-compose.yml" -o ./docker-compose.yml

我将 docker-compose.yml 文件放在了 github上,其中有注释的部分需要自己修改一下

  1. docker-compose up -d

  2. open localhost:181

  • 帐号 admin
  • 密码 password

集成 fever

在插件中启动 fever:

然后可以看到并且设置密码:

在 rss reader3 中

在阿里云 ECS 上搭建

我自己因为有多台设备,所以本机搭建还是不方便,就在阿里云 ECS 上搭建了。购买 ECS 后,安装 docker 和 docker-compose,过程略。

其他都和本地搭建都一样,只不过需要在 ECS 的安全组中暴露 181:

最后,展示一下效果:


参考文章:


向我捐助 | 关于我 | 工作机会


回望 2020

2020 是多么玄幻的一年,不知道多少年后会不会有人记得这一年世界上出了这么多事情。

现在简单总结一下我的 2020

技术

今年对 Java 技术栈更加了解了,不过前端倒有些生疏了,现在有些朋友问我前端的问题,我都有点没有反应过来了。

这点就不再多说,意义不大,列个单子:

  • Java Web: 系统学习了 Spring / MyBatis 这些框架, 要么没法和业务同学对接
  • K8S: 因为团队的关系,已经在实战
  • 中间件: 对 MQ 、RPC 这些分布式概念有了新的认知,有实践
  • 对 MySQL 有较深认识

投资

今年的投资还是挺成功的:

这还只是美股,港股和A股还有基金也还行。

以至于最近有点飘了,觉得赚钱有点容易,还是要老老实实的啊。

结婚

这是今年最大的事情了,本来 5 月份的婚礼因为疫情延期到 10 月,不过最后也还算顺利。

书单

  • 技术
    • Java设计模式及实践: 这本书。。大量地贴代码图片,然后只讲概念,实在不怎么友好。
    • 设计模式之禅(第2版): 没有看完,只看了前两章,把 23 个设计模式过了一遍,感觉作者把知识娓娓道来,真的是专家!不过不喜欢这种写作风格,有点程序员似的抖机灵,时不时弄个冷笑话什么的其实挺尬的。 还有,书里大部分都用很简单的例子来讲解,其实可以多一些实战的,比如代理模式那可以讲讲 Spring 的 AOP 怎么玩的,Builder 模式那可以讲讲 lombok 的 @buidler,不过这可能也是作者想要说明白道理吧。 总结一下,打个4星。短期内不会再专门碰设计模式了,等下次又觉得代码怎么都写不好的时候再翻翻后面几章。
    • Kubernetes即学即用: 没事翻了翻,一些翻译的问题比如把 Job 直接翻译成作业这样好么? 总体来说,中规中矩,没什么自己的东西,就是文档的再次整理,不过动物书一贯是这样,也没什么好指摘的。
    • Java异步编程实战: 粗粗读了一遍感兴趣的部分,线程、线程池、CompletableFuture 搞清楚了。后面几章以后再看
    • Spring Boot实战: Spring 核心概念是 IOC 和 AOP,关键点是事务。 至于其他的比如 Spring MVC 是 web 必备;Spring JPA 不怎么用,都是 Mybatis 了;至于 Spring Boot,就是起步依赖 + 自动配置的一个 Spring 脚手架;Spring Cloud 这种概念可以了解,公司一般有自己的 RPC 框架;Spring Security 不用看。 有了这个概念,再去学 Spring ,事半功倍。
    • Spring实战(第5版 )
    • 数据结构与算法图解: 很快读完,该有的内容都有了,而且我觉得是我目前读过的最适合算法入门的书。不要被它用 ruby、python 代码劝退,这恰恰说明了算法与语言无关。
    • JavaScript忍者秘籍(第2版): 致敬经典
    • 分布式中间件技术实战(Java版)
    • 深入理解Apache Dubbo与实战: 粗粗看完一遍 感觉好像没说什么
    • 深度剖析Apache Dubbo核心技术内幕: 一晚上翻了一遍,大段代码,还都是图片的,大段截图。谈不上什么『技术内幕』
    • 分布式Java应用: 仔细一看 原来都是 10 年前的书了。 先放一放,过段时间再说。
    • 分布式微服务架构:原理与实战: 睡前很快翻完,怎么说呢。这似乎不能称为一本书。 唯一有借鉴价值的是目录,可以照着目录自己总结一下脑图。
  • 运营
    • 数据化运营速成手册: 老板让做个业务,相关知识补一补。一晚上翻完,前3章讲 Excel 做图表的技巧,后面讲数据分析的思路。也算张个见识吧
  • 历史
    • 论**: 阴谋 阳谋 『**总是被他们中最勇敢的人保护得很好』
    • 不可不知的朝韩史
    • 五万年**简史: 在飞机上看完的。 整个朝鲜历史简单到几句话可以概括,是**历史的翻版。 前一千年是前三国、后三国,后一千年是高丽王建和朝鲜李成桂。民族英雄是李舜臣和世宗大王。
    • 汴京之围: 蝴蝶效应,从攻辽到反被金攻,是实力不济。臃肿的官僚机构加上摇摆的皇帝意志是不配活在那个乱世的。 一个国家从盛世到灭亡,只用了三年,唏嘘。
    • 一读就上瘾的**史: 不错的睡前读物 将近花了 7 个晚上读完 比较喜欢这样纵向对比的写作手法
    • ***传: 将近一个月读完
    • 日本人为何选择了战争: 科学地解释了日本人为什么要战争
  • 金融
  • 小说
    • 奔跑吧,程序员: 创业的时候就在读了,现在才读完。从公司到个人完整分析了创业的方方面面,如果我早2年读,可能就不会创业了。
    • 凤凰项目: 我怎么感觉他们面对的情况只要上了云就迎刃而解了。
    • 长夜难明: 国庆回家的时候候机、在飞机上、出租车上看完的,一口气读完,结尾很真实,难怪电视剧结尾改了
    • 我在未来等你: 看这本书的时候,一直在想,如果我遇到了 18 岁的自己,会告诉他什么呢?

总结

总的一年上来看,工作整体平平,技术上自我感觉有一定长进,更加看中同事之间的交流。现在的角色更像扳手,哪里需要就往哪里拧一下。

新年计划

说到列新年 TODO LIST 这种事情,我已经坚持了将近十年,现在拿出十年前的清单看看,竟然挺励志的: 『过英语四级』、『找到实习』、『学习一门后端语言』、『涨薪50%』等等,还是挺有意思的。

每年这个时候是最爽的,把去年的目标全选复制,然后新建一个文件贴进去,把完成的划掉,然后把今年想做的加上去。现在核对去年的清单,果然和往年一样,有超出预期的,有没有达到的,也有根本没有开始的,不过这样随心而为的感觉也挺好的。


向我捐助 | 关于我 | 工作机会


一个前端的docker笔记(附demo)


Docker 大家应该都听过,不过可能对于我们前端来说用得比较少,后端开发和运维应该是对它很了解了。我从去年开始了解到 Docker 的好处后,对它一直很有兴趣。最近,用了一个比较整块的时间研究了一下,这篇文章记录下来。最近2周的空闲时间,看了一下 Docker ,记录一下,免得以后忘了。本文的代码都在 https://github.com/riskers/docker-demo 可以找到。

  1. 一个 Node 应用
  2. docker化一个应用
  3. 发布一个镜像
  4. docker-compose
  5. 多容器应用

Docker 存在的意义

  • 加速本地开发和构建,开发人员可以构建、运行并分享Docker容器,容器可以在开发环境中构建,然后轻松地提交到测试环境中,并最终进入生产环境
  • 能够让独立服务或应用程序在不同环境中,得到相同的运行结果。
  • 用 Docker 创建隔离环境进行测试
  • Docker 可以让开发者先在本机上构建一个复杂的程序测试,而不是一开始就在生产环境进行测试

对于我们前端来说,这样比较好理解:构建好的镜像只要就是 package.json,里面只要写好需要的包名和版本号,任何人拿到这份文件都可以获取对应的包,并且能够运行这个程序。但是,我们忘了一个很重要的点,Node 和 npm 也是有版本的啊!举个例子,Node 在 7.x 以上才支持 async ,如果我们的服务器上 Node 的版本是 4.x ,那么部署上去肯定是不行的,然后查个半天 BUG ,才发现是 Node 版本的问题。Docker 就是帮我们解决这种问题的,它能够把 Node 版本也能够像 package.json 那样记录在配置文件中。

Docker概念

  • Docker 客户端和服务器: https://docs.docker.com/engine/installation/
  • Docker 镜像: 用户基于镜像来运行自己的容器,可以把镜像当做容器的『源代码』,镜像体积很小,易于分享、存储和更新
  • Registry: Docker 用 Registry 保存用户构建的镜像,Registry 分为公共和私有两种:
    • Docker 公司运营的公共 Registry 叫做 Docker Hub,我们可以在上面注册账号,分享并保存自己的镜像。
    • 也可以在 Docker Hub 保存自己的私有镜像或者架设自己私有的 Registry
  • Docker 容器: 把应用程序或服务打包放进去,容器是基于镜像启动的,容器中可以运行一个或多个进程。
    • 镜像是 Docker 生命周期中的构建或打包阶段
    • 容器则是启动或执行阶段

Docker 常用命令

容器

  • 判断 docker 是否能正常工作

    docker info # 返回所有容器和镜像的数量、基本配置等
  • 运行容器

    docker run image-name

    比如 docker run ubuntu,会先检查本地是否存在 ubuntu 镜像,如果本地没有该镜像的话,那么 Docker 就会查看 Docker Hub 中是否有该镜像,找到的话就会下载该镜像并将其保存到本地。

    随后,Docker 在文件系统内部用这个镜像创建了一个新容器,该容器拥有自己的网络、IP地址,以及一个用来和宿主机进行通信的桥接网络接口。

  • 使用容器

    docker run -t -i ubuntu 会进入容器,然后 exit 离开容器,容器就停止运行了。

    -i: Keep STDIN open even if not attached
    -t: Allocate a pseudo-tty

    但容器还是存在的,docker ps -a 查看所有容器(正在运行的、已经停止的)

    docker ps 只会列出正在运行的容器。

  • 容器命名

    Docker 会为每一个容器自动生成一个随机 id,我们也可以自己为容器指定名称。

    docker run --name your_container_name -i -t ubuntu
    

    容器名称不允许同名,可以使用 docker rm 删除同名容器。

  • 重新启动已经停止的容器

    docker start your_container_name / your_container_id
    docker restart your_container_name / your_container_id
    
  • 获取信息

    docker inspect your_container_name / your_container_id
    
  • 附着到容器(重新启动并运行一个交互式会话shell)

    docker attach your_container_name
    
  • 守护容器

    docker run -d your_container_name #创建守护容器
    docker top your_container_name #查看容器内进程
    docker exec your_container_name touch a.txt #在容器内部运行进程
    docker stop your_container_name #停止容器
  • 删除容器

    docker rm your_container_name/id
    

镜像

  • 列出镜像

    docker images
    
  • 拉取镜像

    docker pull
    

    docker store 上每个镜像都有很多个标签
    docker pull ubuntu:12.04 表示拉取 12.04 这个标签

  • 构建镜像(Dockerfile + docker build)

    一般来说,我们不是真正地创建新镜像,而是基于一个已有的基础镜像,构建新镜像而已。

    FROM ...
    
    RUN ...
    
    # 指定容器内的程序将会使用容器的指定端口
    # 配合 docker run -p
    EXPOSE ...
    • RUN: 指定镜像被构建时要运行的命令
    • CMD: 指定容器被启动时要运行的命令
    • ENTRYPOINT: 同 CMD ,但不会被 docker run -t 覆盖
    • WORKDIR: CMD/ENTRYPOINT 会在这个目录下执行
    • VOLUME
    • ADD
    • COPY
    docker history images [name]
    
  • 从新镜像启动容器

    docker run -d -p 4000:80 --name [name]
    

    可以在 Dokcer 宿主机上指定一个具体的端口映射到容器的80端口上

  • 推送镜像

    docker push [user_name]/[image_name]
    
  • 删除镜像

    docker rmi [user_name]/[image_name]
    

总结

本文大部分是我读《Docker开发指南》时做的笔记,并结合实际自己做了 demo

因为我接触 Docker 的时间很短,关于一些概念可能会有一些错误,欢迎指正。

最后啰嗦一下,其实我作为一个前端在工作中根本用不到 Docker ,只是为了兴趣才来看看,万一以后能用到呢。我的拖延症很严重,本来这是去年就要学的,拖到最近才学。


向我捐助 | 关于我 | 工作机会


关于我

14年毕业,目前在老家 Remote,全栈工程师






大学毕业前的二十多年都在西安度过。

大学时有迷茫,不知道将来要干什么,但觉得学习总不会有错。奈何对本专业(通信)不感兴趣,所以,设计(PS)、视频特效(AE)等也都有了解。

这么度过了两年多,直到大三决定开始进入前端领域。当时没有什么想法,就觉得能实现自己的设计图挺好的。

毕业后顺理成章地进入前端行业。

目前为止(2023),待过一线大公司(Ali)、没落的公司(360)、创业公司、500 强(xiaomi),现在在一家公司 remote 中。

技能树

  • 移动、PC Web 深入了解,最爱 React + TypeScript,有大型项目经验
  • 最近几年在做后端,技术栈是 Java
  • 其他技术上如 Go、Rust、Nodejs 等有一定了解
  • 完全可一个人 hold 住整个项目的前端、后端的开发,以及上线运维等工作

正在学习、关注的点

  • 最近半年在研究 Web3 方向,熟练 Solidity、ethers.js、Chainlink 等技术
  • 兴趣太广泛,还想研究一下量化交易,可惜时间不够
  • 目前也在学英语,目标是能流利地和老外交流

开源项目

联系方式

  • Email: gaoyibobobo#gmail.com

社交

代码

微信公众号

基本日更

其他

  • 博客 就是你看到的这个,原创的学习总结或者技巧
  • 技术随笔 放学习感悟、读书笔记等不值得放在博客里的东西
  • 简书 乱七八糟的东西
  • gist 记录一些配置、代码片段或者不成熟的技术点备忘

爱好

  • 历史:可以从古侃到今,从外聊到内
  • LOL:常年混迹于铂金分段,工作后不怎么打了
  • 电影:喜欢烧脑、悬疑的电影

Zepto事件委托的小坑

问题

今天同事(妹子)遇到一个 Zepto 的事件委托的问题来问我,我当时也懵了,后来解决了。问题还是比较坑的,拿出来分享一下。先看看是什么问题:

页面1

自己解决

为什么?!为什么事件委托在 .a 上可是却也触发了 .b 上的委托。看着妹子求知的眼神,我胸中一阵气短。猜想着是 .a 委托事件最后换了 class,DOM立刻更改了,就在 .a 事件后触发了 .b。所以我立刻让她这样改一下就可以延缓DOM更改:

$doc.on('click','.a',function(){
	alert('a事件')
        var $this = $(this) ;
	setTimeout(function(){
		$this.removeClass('a')
		.addClass('b')	
	},30)
})

然后就正常了 页面2

刨根问底

虽然妹子对我一阵赞许,可是我心里还是隐隐不安,回来通过咨询大牛和看源码知道了这是什么原因。

先看看这个页面 页面3

查看源码我们可以看到,页面3页面1 几乎一模一样,就是在 .a.b 的事件委托顺序不一样:

页面1

页面3

那为什么 页面3 就可以正常呢?就是因为 Zepto 的事件委托和我们想象中的事件委托是不一样的。

Zepto 的事件委托是:

在代码解析的时候,所有document的所有 click 委托事件都依次放入一个队列里,click 的时候先看当前元素是不是.a,符合就执行,然后查看是不是.b,符合就执行。

这样的话,就导致如果 .a 的事件在前面,会先执行 .a 事件,然后 class 更改成 bZepto再查看当前元素是不是 .b,以此类推。

这就是 页面1 出现BUG的原因,而 页面2 之所以也能解决这个问题是因为 class 变化实在延迟之后,click 事件当时没有检测到 .b

看看 Zepto 的事件部分是怎么写的。可以看到是用$this.each 循环绑定在 $this 上的事件。对应在我们的例子,就是 document 上绑定的事件都被塞进一个队列中。

再看看 jQuery 的事件委托:

document上委托了2个 click 事件,click 后判断是否当前符合条件(选择符),然后把事件拿出来执行。

这是符合我们一般的认知的,也是那个妹子那样写代码的原因。你不妨把页面1的 Zepto 换成 jQuery 看看。

这是一个 ZeptojQuery 不同的地方,以后要注意了。


向我捐助 | 关于我 | 工作机会


<2015></2016>

2015年是工作的第一年(去年8月来公司后直到今年的2月才算正式到现在的部门,中间发生了很多事就不多说了)。这一年里,我算是真正入了前端的门,在知识体系上算是真正了解了前端的水有多深。

今年6月的时候,下决心买了 Mac ,使用了几个月以后,也习惯了 『Mac生活节奏』:

  • 一直使用 inoreader 订阅别人的博客,Mac 配合 Reeder 不要太爽,现在每天上班第一件事就是查看订阅的博客
  • 使用 DayOne 写日记,以前在 windows 上一直没有发现好用的日记软件,现在在 Mac 上记日记也很方便
  • 使用 markdown,以前是在 Sublime 上装 markdown 扩展,在 Mac 上我一开始使用的是 Macdown ,现在用的是 MWeb ,已经付费了,很好用

今年学会了怎么做 Chrome 插件,还做了两个玩意:

今年我找回了看书的习惯,这是我的书单,有技术的,有小说,不过看得还是太少了,2016 再加油吧。

今年重新开始写博客,以前的博客用的是 wordpress ,很慢,渐渐的就不想写了。今年工作中有一些东西想写下来,就在 github 里写了。

今年开始喜欢李宗盛的歌,李宗盛的歌词就像白话,语句简单却总是意义深长,今年,渐渐听懂了李宗盛。以前听人说过,每个男人心里都有一个李宗盛,你没有听懂他的歌只是你还没有长大。

今年看了一些电影,印象最深的是『西西里的美丽传说』和『教父』,尤其是『教父』里地那句『男人应该以家庭为重』。

最近在翻译 Chrome 开发者工具 的文档,1月中应该就完成了。

就是这样,希望自己2016变得更强。最后,放上公司的吉祥物。


向我捐助 | 关于我 | 工作机会


我的时间与任务管理办法

在很长的一段时间里,我都在尝试各种各样的管理软件帮助我管理时间:

  • OmniFocus
  • 奇妙清单
  • trello
  • Pocket
  • DayOne

还有一些记笔记的地方:

  • 印象笔记
  • MWeb
  • 简书
  • 博客

因为各种各样的原因,导致我一直没有用起来,有时候还是感觉像无头苍蝇,很多事情让心里压力很重。

最近,改进了一下自己的管理模式,记录在这里。


笔记类

  • Trello: 任务管理和小贴士笔记
  • gist: 偏向笔记管理和代码记录,gist 上不过是很简单的记录,复杂的还是要在 MWeb 上写
  • MWeb + 坚果云: 学习新技能的笔记

而 Trello 和 gist 最终的流向是 MWeb,每一个 Trello 卡片归档后应该放在 MWeb 对应的文件夹里。比如我最近在搞 DevOps,我就在 Trello 里建了一个 DevOps 的看板:

我会分别针对 Docker、k8s、 CI 等等知识分别建立列表,之后,我把需要我掌握的资料、知识点都放在列表里作为卡片存在。边学习一个卡片的知识,就边在这个卡片里记录下来:

学完一个就会归档一个,之后在 MWeb 里把对应的知识点记录下来:

这样子,就做到了: 认识有了终点

尽量清除列表里的卡片,清除一个,就是学会了一个。看板可以留下来,因为以后肯定还有要深入的地方,你可以还这个看板里加列表。

横向地,你可以有多个看板,这样你就可以很清楚地看到自己学过的领域:

代码类

  • github: 实际的练习项目,与 Mweb 不冲突,Mweb 记md,github 记 demo
  • blog: 纯能拿出去分享的知识,一般会花好几个小时完成

日记类

  • 简书: 偏向吐槽,或者随想,十分钟的胡诌完成
  • 有道云笔记: 之前的笔记和年度总结
  • DayOne: 日记流水帐

向我捐助 | 关于我 | 工作机会


对于 docker 的再体会

将近一年前我学习了一段时间的 docker,并且记录了一个流水账一般的笔记。之后一直没有正经在项目中使用过,最近一个项目中需要使用,又再次学习了一遍,再次记录一点。

关于 docker 的想法

  • Dockerfile 的作用: Dockerfile 就是将配置环境固化下来,很像 yarn,不仅安装了软件,还锁定了版本
  • docker-compose 的作用:
    • 可以方便地启动连接多个 docker
    • 固化参数

固化 这个词我都加粗了,因为我没想到更适合的词。

代码不放在 docker 里

我看到不少文章中在 Dockerfile 里都使用 COPY 命令把源代码放置在 docker 镜像里,但是就像上面说的,docker 容器只应该提供一套环境,不应该和代码强关联。所以,代码和环境应该使用 volume 命令来连接起来。

对命令行不熟

推荐使用 kitematic.com,这是官方的一个 GUI 软件,使用方便,对于我这种虽然了解概念,但是命令行不熟的人来说很有用。

docker 对本地开发的帮助

比如你新启动一个项目,需要使用 mysql。你可以自己在本机上安装 mysql,还要想着本机的 mysql 版本和服务器上 mysql 版本是不是一致的问题。或者,你可以使用 docker,一条命令就搞定:

docker run -it -p 33306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 -v ~/mysql:/var/lib/mysql mysql

然后我们在 kitematic 中可以看到:

端口映射和 volume 一目了然,然后我们试试这个 mysql 有没有映射到本机的 33306 端口:

这是可以连接上的:

并且,我们可以看到数据已经保存到本地而不是容器里:


向我捐助 | 关于我 | 工作机会


[译]CSS-理解百分比的background-position

译文地址

通过这篇文章能够深刻理解 background-position 的百分比。

正文开始:

通过这篇文章我要教大家解决一个曾经很困扰我的麻烦问题。我们要使用百分比的background-position值来解决一些问题。

通常使用方法

  1. 摆放图片

    通常在容器里摆放图片是给出具体图片的topleft相对容器的topleft的值。

    在CSS中很容易做到。

    • 在容器里使用<img>标签

      .container{
      	position:relative;
      }
      .container img{
      	position:absolute;
      	top:12px;
      	left:20px;
      }
    • 或者可以使用background-position

      .container{
      	background-position:12px 20px;
      }
  2. 在容器里移动

    现在你想让图片在容器里面移动而且还不能超出容器边界。你肯定是要简单算一算图片topleft的最大值。

    然后得到left值的范围是 0 到 container_width - image_width,同样也可以得到top值的范围。

  3. 图片比容器大

    到目前为止,我们讨论的问题都很简单。现在,我们要看看图片比容器大的情况。容器必须要被图片填满。

    同样我们可以算出left值的范围是 0 ~ container_width - image_width,只不过这次container_width - image_width 是负值。

    你可以搞明白正值和负值的关系,也可以凭直觉搞定。当你看到12px 20px你很容易知道图片是怎么放置的。但是,你看到-12px -20px就比较难想明白了。

  4. 不变量

    好了,现在你已经写好了位置点并且没有任何问题。现在,因为某些原因,我们不用长方形容器了,用正方形容器。那么之前的那些位置值就不那么合适了。

    我们之前计算的值不再有效,因为现在情况变了。你想要改变图片和容器大小也是一样的道理。

    可以从图中看到,如果使用固定的值,那么一旦改变某些条件,那么就可能会让已经写好的布局乱掉。

背景图片的百分比方法

  1. 定义

    我们要换一个确定图片位置的方法了。当图片的左边框和容器的左边框挨着时,left0%。当图片右边框和容器的右边框挨着时,left100%

    这两个例子分别就是 0% 和 100% 的情况:

    我们很容易得到两者之间的值

    left = (container_width - image_width) * percentage
    
  2. 范围检测

    这个方法最方便的就是我们不用再算图片相对容器的范围。它就是 0 ~ 100 。

  3. 不变量

    我们画两个轴,一个对于容器,一个对于图片。如果我们设置值为60%,则两个轴的60%会重合在一个点上。

    就像上面的图片一样,这个新的方法在不同比例大小情况下也工作得很好。

  4. 水平和垂直

    如果你细心的话你会注意到图片和容器一样大的话,两个轴会完全重合。设置 30% 还是 80% 都不重要。

    再看看数学公式

    left = (container_width - image_width) * percentage = 0 * percentage = 0 
    

    你只需要设置两个值lefttop就行了。

总结

一开始,我没有明白百分比值是怎么对background-position作用的。我真的有点迷惑,因为使用百分比让我不能直观地感受到变化。然而,后来我发现使用百分比解决图片定位是极其方便的。


向我捐助 | 关于我 | 工作机会


又是一年

技术

想想今年,从6月开始,微信小程序、C 、密码学、Chrome 插件、Angular、rxjs、Docker、CI、k8s、eggjs,这些都玩了。

还有就是保持初心,我首先是工程师,其次才是前端工程师,在后半年稍闲的时候开始关注更多的后端知识,容器相关的东西,然后就是开始学习 C 和计算机基础,没事刷刷 leetcode ,学学数据结构和算法。

简单说说今年的进步和蜕变。

FE

  • TypeScript: 在创业公司成功使用了 TypeScript,也算是变成现代的前端了
  • rxjs: 去年学习了两遍,没入门。今年又学了一遍,终于搞懂了
  • Angular: 详见 https://www.jianshu.com/p/877d9e1c66e4

前端边界

  • Electron: 在阿里和创业公司都使用它写了简单的项目
  • 微信小程序: 8 月的时候写了一个微信小程序,感谢 Taro 让我方便地使用 React 写微信小程序
  • Chrome Extension: 在阿里,在创业公司都有开发的经验,8 月的时候和别人合作写了一本小书 https://welearnmore.gitbook.io/chrome-extension-book/

后端

今年正式使用 Node 写简单的后端,使用的是 eggjs + TS, 写起来真爽!

DevOps

  • CI / CD
  • Docker / k8s

在创业公司,使用 Docker 和 gitlab-ci 正式搭建起来 CI 环境,明年需要实践 CD 和 k8s

Flutter

简单地入门了,还顺便学习了 dart 语言

其他编程语言

今年除了 TypeScript 外,还学习了 Go 和 C。

学 Go 主要是想做一些系统编程

Go is modern Python
Rust is modern C++

学 C 那就完全是想用它实现数据结构了

书单

  • Learning TypeScript中文版: 一下午大概花了2个小时翻完了,怎么说呢。说好的 typescript,其实只有1 3 4 章讲了。而且这本书的 typescript 版本是 1.5,现在都 2.6 了。 其实 ts 非常简单,如果你已经熟练使用 ES2015,那么只需要了解它的类型声明和类就足够了。 如果有两三年JS基础的要学 typescript ,我觉得看文档就足够了,看书一是跟不上版本,二是磨磨唧唧,看不到重点。就像这本书一样,一会将构建,一会讲自动化测试,最后还用 backbone 实现了一个应用。这些对于一个已经有不错前端基础的人来说是浪费时间。 这里是最新的 ts 中文文档,我觉得不用看这本书浪费时间了 https://github.com/zhongsp/TypeScript
  • 你不知道的JavaScript(下卷): 上市第一天就买了,只是为了对这一系列的支持。想起来,上册还是2016年下半年买的,现在1年多了,我的 JS 功力比那时候强多了。这一系列功不可没。 现在说说这一次的下册,第一部分是概述,我记得原书这部分是全系列的开篇吧。第二部分是讲 ES2015 的,当时对这一系列挺期待的。不过 ES2015 这一年也用的比较熟了,阮一峰的书也帮助了我不少。 今天花了大概4个小时翻了一遍,却想起了这么多的往事。
  • 深入浅出Node.js: 14年买的一本书,现在才看完。因为当年实在看不懂,经过这两年的前端工作,很多东西不看都懂了。比如异步编程,模块,部署node等,只有进程管理和内存管理要仔细看看。
  • 深入浅出RxJS: 读了两遍,第一遍不怎么看得懂。第二遍,跳过很多操作符,直击最基本的概念: Observable / Observer / Subject / Schedule ,然后看和 React 的结合,加上自己用了 Angular,有了更深的体会。
  • 图解密码技术(第3版): 让我这种原本什么都不知道的人,看完对密码世界有了一个宏观的认识。这本书一章一章循循善诱,这一章会引出下一章,大概花了一周看完。 之前一直不懂的对称加密、公钥私钥加密、哈希函数、消息验证码、数字签名、证书、CA、PKI 这些概念有了一个比较清晰的认识。现在脑海理竟然能构建出了 https 的流程图,要知道,这是我之前一直都记不住的。 我的读书笔记:https://www.jianshu.com/p/293f8dafdbb6
  • 刷新: 还行 微软的文化
  • 世界是数字的: 通俗易懂,当初入门计算机的时候怎么没找到这样的书。
  • 理解Unix进程: 很一般吧 多是一些总结性的文字 不是现在我这个阶段想要的。 是用 ruby 来解释进程的。。。
  • Angular权威教程: 陡然发现 Angular 已经7了,感觉找本书来看看学习学习。没有全部看完,因为使用 Angular 纯属玩玩,做了一个实例: https://github.com/riskers/ng-data-flow 第一章是一个 demo 第二章是 TS,不用看 三、四、五、六、七章都是 Ng 中的基础概念 八、九、十 讲的是服务、依赖注入等 十一是一个使用 service 的 demo 十二、十三是讲 redux 以及怎么在 Ng 中使用 redux ,不用看 十四章是高级内容,目前我还没用到,也没看 十五是测试,和官网内容差不多
  • Angular从零到一: 只是大概翻了一下
  • 揭秘Angular(第2版): 只是大概翻了一下

项目

其他记录

博客

写在博客的都是干货,至少我自己认为是。

简书

又看了看,果然简书上写的都是废话,絮絮叨叨像个老太太。

创业

最近大半年在创业公司, 干的却不怎么是前端的活。可能是因为创业公司的属性,而且主要是老板们也没想好要干什么。感觉看似忙忙碌碌的大半年,却好像什么也没有得到。

为什么离开阿里?答案都在这里: https://www.jianshu.com/p/cb27ef093402

房子

等了一年多,房子终于在国庆前下来,现在还在装修。

电影

唯一推荐《血观音》,这是今年看过的最精彩的电影

名言

  • 善作者不必善成,善始这不必善终
  • 为天地立心,为生民立命,为往圣而继绝学,为天下开太平

展望 2019

前端现在有 PWA 、webAssembly、Flutter 这些热点可以玩,不过我明年应该会把重心放在后端上面。

  • PWA: 你的公司可能用不到,因为墙的关系,但是 service worker 和 cache 这两个 API 值得你学习。
  • webAssembly: 今年正是它我才会学 Go,还有个小插曲,本来是学 Rust 的,但是这个语言上手有点难,就转投 Go 的阵营了。
  • Flutter: 世上跨端方案千千万,但是选择 Flutter 的理由很简单: 它的爹是 Google;是传说中的系统 fuchsia 的指定框架;和以往的跨端方案都不同,是一种新的方案。

另外,基础很重要,在今年学习 k8s 的时候,就涉及到 cgroups 这个 Linux 概念了,对于我来说,很难理解。还是要好好学习 CS 基础。

这一部分,可以参考我在知乎的回答


向我捐助 | 关于我 | 工作机会


工作五年记

今天我刚好工作满五年,为什么记得这么清楚,因为8月8号是北京奥运开幕式。那时候 360 让我选择是7月8号还是8月8号,我果断选择了8月8号,在那个炎热的夏天又多玩了一个月。现在想想,要是那时候选择7月入职,可能后面很多人很多事就经历不到了。

刚入职的第一个月是校招培训,可能也就是大家说的『洗脑』,不过整整一个月还是很开心,不仅仅上了两周的课,最后两周还是 hackson ,其中也有很多很多难忘的回忆,这是在 hackson 自己做的一款 App:

当时小组里的同学如今也都各奔东西,祝愿他们都发展顺利,事业有成。

在 360 度过了两年半的时间,记得刚工作那会,什么东西都不知道,不知道怎么和产品经理通过,不知道还原设计稿有那么多东西要考虑,不知道怎么和领导沟通。那时候,只能每天晚上下班以后,在公司看书学习,到了10点去健身房锻炼一会,然后洗个澡回家。焦虑感持续了将近一年,这期间学会在 google 上找自己的答案,学会了怎么和同事交流,学会了怎么看一本书。在往后的一年,也就是 2016 年,感觉一直在重复自己前一年的工作,能力没有得到进步,所以在年底终于鼓足勇气出去面试,得到了我人生中第二份工作。

2017 年的时候来到阿里,不得不说,这里几乎一切比 360 来得要正规,360 是草莽的,一个项目可能就是一个后端、一个前端、一个设计师、一个测试就搞定了,一切的流程都是能省则省,能简则简。而阿里,定下一个项目,要评审需求、定排期、定 PM(我们当时的 PM 是这个项目中某一个开发同学兼任)、定开发人力、定测试人力等等,然后一个项目下来,你可能会发现大量的时间不是在 code ,而是和各种角色的人沟通,以及在各种钉钉群里回复。在阿里的一年半的时间,我技术长进不少,但是我学到更多的是非技术层面的东西。我**上成熟不少,我以前一直认为技术就是一切,但是阿里会告诉你,技术很重要,但是其他的东西更重要。『阿里就是一个江湖』,不知道是谁最开始说的,我深深赞同。在阿里,压力很大,除了正常的业务需求外,还有 KPI 的压力,这些让我有点吃不消,甚至让我深深地焦虑和畏惧。

今年初,我又来到小米,做一个全栈开发者,希望这里可以职业生涯最重要的一站。

2014.8.8 - 2019.8.8,时间就是这么飞快,已经在北京蹉跎五年的光景,从刚毕业的愣头青,到现在猛然发现已近而立。五年,不快不慢,不长不短,有些事情更加坚定,有些事情也逐渐放下。

只望,不负岁月,不负青春,不负自己。


向我捐助 | 关于我 | 工作机会


2016...2017

今天才有心思写2016年的总结,一直构思了很久,不知道怎么写,写些什么。总是觉得时间很快,快到自己什么都没干一年就又过去了。

如果说 2015 年是混混沌沌度过的,那么 2016 年就是有计划、有目标地开始提升自己。我就随意想想,记下流水帐。

看书

今年我开始养成了看书的习惯,一半是技术方面的书,一半是小说什么的。

技术

历史

  • 明朝那些事儿 古今多少事,都付笑谈中。 读过那么多历史,知道了那么多王侯将相的故事,作者在最后最后点题了一句『按照自己的方式,去度过人生』。 感谢明月在两周的时间里带我重读了明史,让我重新认识了徐阶、高拱、张居正,再次为于谦、卢象升、孙承宗的气节折服。
  • 血腥的盛唐 在kindle上前后大概半个月读完 有点啰嗦 中晚唐部分写的略概括。这是 既明朝那些事以后读的第二本朝代通史,让我了解到了之前都不怎么清楚的晚唐。晚唐积重难返 藩镇割据 宦官乱政 牛李党政 不亡都难
  • 《五代十国的枭雄们》系列 有七本书,顺着唐朝灭亡的主线,接着回顾了一下一直不怎么了解的五代十国

其他

东野圭吾

之所以单列出来,是因为我今年无意中读了他的《解忧杂货店》之后一发不可收拾地喜欢上了他的书

  • 解忧杂货店 几个小故事依靠一个杂货店和孤儿院展开,很是精彩。记得很清楚是花了一个通宵读完的。
  • 白夜行 『天底下最让人害怕的不是毒蛇猛兽,不是绝症,是人心』 越想越可怕,让我不寒而栗。 其实在江利子出事的时候我就已经怀疑雪穗了,只不过怎么也想不到原来雪穗和桐原是一起的。这是今年读得最过瘾的小说,深深地迷上了东野圭吾
  • 嫌疑人X的献身 怎么也没想到结局是这样的,3天读完
  • 幻夜 读到一半就已经猜到美冬是假的了,和《白夜行》一样悲剧收场
  • 虚无的十字架 讨论死刑、遗族以及凶手的救赎
  • 秘密 结局有点恐怖,东野的小说总是这样,读完总是阵阵凉意,越想越怕
  • 梦幻花 故事一般般
  • 恶意 用自己的命换别人的声誉 人心最可怕
  • 放学后 感觉杀人动机很牵强,结尾比较过瘾

数了数,将近读了40本书,也算是及格了吧,希望今年继续保持

旅游

今年去了一趟张家界,以后争取每年出去旅游一次,多出去看看,见识见识。

技术思考

2016 年又是一个前端飞速变化的年份:

  1. 模块化:我从 15 年底自己使用 webpack,16年陆陆续续用在很多项目中

    还使用 webpack 开发了一个 es6 的库 https://github.com/riskers/Turntable

  2. MVVM:React、Vue、Angular 三分天下的趋势已经明了,不用纠结于学哪一个,更不要同时学,只要学精一个,其他的不是问题。(我是 React 党,今年有计划陆续写一些文章记录最近半年的学习成果)

  3. Node:我认为现在 Node 有 3 个作用:

    • npm:前端有了自己的模块管理工具
    • 工程化、流程化:webpack、gulp 全是基于 Node
    • web 框架:express、Koa 等,但是,单纯地使用 Node 开发后端很少见,大公司都是用 Node 来做中间层
  4. es6:哦,不对,应该叫 es2015 因为 babel 而开始普及

  5. 因为以上种种工具,前端工程师沦为配置工程师

用 Node 作为中间层,前后端完全分离之后,这个页面是 React 渲染还是 Node 渲染都是前端自己决定的。后端只负责把接口安全、迅速就够了。所以,开发 web 以后会越来越像开发客户端。后端只提供接口,前端负责浏览器内所有的东西。你想让这个页面避免白屏、能够SEO,就使用 Node 获取数据然后渲染页面;你想做 SPA ,就使用 React 开发,然后直接放服务器上就行。当然,还可以用让 React 在服务端渲染,这样这个应用即是 SPA ,有良好的体验,还能够避免白屏、能够 SEO,这部分也是我最近在研究的。

新年目标

博客在 16 年都没怎么更新过,一来是觉得有些东西自己也掌握得不好,就都没有发在博客上,都记录在了笔记上。新年争取多写几篇有质量的博客。

我会在业余时间学学客户端开发,以及服务端开发,因为我觉得不应该给自己设限,我首先是一个开发工程师,然后才是一个前端开发工程师。我不排斥任何技术,因为技术是给业务服务的。

我每年的跨年夜都会写一个明年的 todolist,今年已经5年了,有点可怕。

2017-01-06-14837158083630


向我捐助 | 关于我 | 工作机会


2022 - 全新的开始

又到年底了,2022 这一年真的是忙碌且充实。前两天刚刚阳过,没什么精神工作,就总结一下这一年。

流水帐

先来个全年流水帐总结。

过年期间因为西安疫情严重,所以有生以来第一次没有和父母一起过年,除夕和媳妇吃火锅。

上半年在北京,3月的时候,因为公司有人确诊,所以一整个楼层的人被集中隔离。我在一个酒店房间待了 2 周。5 月的时候,防疫政策愈发严格,整个 5 月,我都没有出小区。

当时的心情 down 到极点,想着要离开北京,但是一直舍不得这里的工资待遇。于是,无意中发现了一些远程工作机会,随着了解,发现远程工作比自己想象得多得多。终于,大概在 5 月底的时候,找到了达到自己预期的工作,可以 remote,工作内容也是自己感兴趣的。最终把入职时间定在了 6 月 6 号,定好高铁票,回老家了!

2022 在北京最后一张照片:

于是经过了半年的试用期,在 12 月的时候,成功转正。这半年,因为没有通勤时间,而且基本没有浪费时间的没有意义的会议,所以我在完成工作之后有大量的时间去做自己感兴趣的事情。心情不好的时候,就打打游戏;心情好的时候,就看看书,练练英语。

世界杯决赛那晚,也是我新冠发烧最严重的一天,我庆幸自己没有睡过去,没有错过这场精彩的比赛。自己这些年早就养成了习惯,每年元旦的时候,写下自己过去这一年的总结,现在已经坚持了十年。每次世界杯结束的时候,写下这四年自己的变化,算是在不同时间维度上总结一下自己。

这次,世界杯又结束了,而上一届的时候还在创业呢。现在比当年,工资涨了一大截,能力我觉得也涨了不少,已经学会不站在执行者的角度看待工作问题。还会享受生活了,不是每天悲摧地去上班,而是满怀期待地去开始每一天。

远程

现在的工作是全栈,前端、后端 55 开。

关于 remote 的机会,还是要学好英语,因为,远程工作多数是美国公司,或者有美国同事,英语绝对是加分项!建议每个人都要学好英语,这样,你就可以和全世界的人竞争,不用 996 也可以拿到一份体面的工资。

还有一点不爽,书房的桌子比较小,能放的东西比较少,可能还需要再买个升降桌。

开源项目

在开源项目上来说,做了 GithubX,之前已经总结过,不多说了。

这个项目在某种程度上来说,也顺利地帮我找到了现在这个工作。

还有就是,这个项目帮我搞到了 JetBrains 一年的 Licence:

IMG_1408
IMG_1409

电影

今年明显电影看得少了,有时候宁愿看些老电影,列一下我觉得今年看过的比较值得看的。

  • 山河月明: 电视剧我比较少看,因为很浪费时间,这部基本是我隔离的时候,就用 iPad 播放着听个声。基本是三星剧,能列出来,主要是我觉得朱标是这部剧的惊喜,是我心中最符合朱太子形象的演绎。
  • 风起陇西:应该是五一的时候看的,那时候被封在小区,剧情都有些忘了,印象中还不错。
  • 天才基本法:二倍速看完了,没看过原著,主要是好奇两个世界的设定才看的
  • 神探大战: 和当年的《神探》差远了
  • 目中无人: 花6块钱看的,出乎意料地好看,打戏也太帅了
  • 利刃出鞘2: 节奏不如第一部
  • 她和她的她:难道有男生会接受女朋友的前男友告诉自己这么私密的事情吗
  • 危笑:号称是今年最恐怖的电影,但我觉得不怎么吓人

  • 刘嘉概率论通识讲义
  • 三国配角演义:感觉每一章都可以改编成电影
  • 三国不演义: 三本一口气花了一周读完,复习了知识,也了解到演义和正史的出入!
  • 长安的荔枝: 马亲王真的厉害,总是能在历史的缝隙中找到故事。 之前也知道「一记红尘妃子笑无人知是荔枝来」,但是没想过会有多少人要为此送命。
  • Living Clojure: 大约 5 个晚上读完,边敲代码边读。 很棒的入门书,薄薄的一本,不到 200 页,去掉多余的最后两章,也就 150 页。 150 页,却也把这么复杂的语言描述了个轮廓出来,我反正是学到了不少。
  • 黑客与画家: 一周的睡前读物,刚工作时读过,因为没什么工作经验,所以一知半解。现在再看一遍,理解不同了,尤其是学过一点 Clojure 之后,能理解书里描述的 lisp 语言优势了。对软件、创业也有一些理解了,读这本书很有意思。邮件过滤那一部分很有意思,数学和编程关系如此之大,也是把我这两年的感受,尤其是有一点点数据分析理解之后。
  • Python语言及其应用(第2版): 两天读完,目标不要求多熟练,只是把 Python 过个大概。 第一部分是入门 Python 的绝佳好书。第二部分有点没必要,属于介绍 Python 应用和生态,有部分是过时的。比如,包管理器现在专业 Python 使用的一般是 conda;lint 现在都是 pylance;别的部分没仔细看了,觉得没必要。
  • 筚路维艰: 在退烧的那几天迷迷糊糊读完的,虽然已经知道那个时代的事情,但真正读起来的时候,还是那么震撼。而且读起来并部枯燥。

游戏

8 月买了 XBOX XSX,到现在已经通关了好几个游戏:

  • 双人成行:和媳妇前后大概拉扯了4个月玩通关了。 里面很多场景设计都很好玩,打蜜蜂那关有点难
  • 廖添丁:绝代凶贼之末日:上 XGP 之后就玩了, 大概 2 个下午通关,对这种画风的横版过关游戏没有抵抗力
  • 行尸走肉 The Walking Dead:第一季行尸走肉的剧情真的很好
  • 刺客信条 起源:花了大约40小时 2周的时间通关 35级就通关了 主线紧凑 支线冗余 后面有些乏味了
  • 深入 Inside:老游戏了,当时是云通关,这次是自己玩的,大概5个小时通关
  • 刺客信条 奥德赛:44小时42级通关,任务随缘做,剧情比起源差点,就是找爸爸找妈妈的故事。 最不解的是,这一部主角就是个杀手,刺客还在,信条没了,杀人只为钱。

XGP 真的无敌,这些基本都是在 XGP 上玩的,已经续费一年了。

做饭

因为长时间在家,饭需要自己解决。因为实践得多,这半年感觉厨艺大涨。

技术

9 月,通过一个项目,把这几年落下的前端知识都补回来了(虽然不愿意承认,也足以说明前端没什么核心科技,233),还顺带学习了 Web3 DApp 开发。

年底的时候,临近圣诞假期,我这边比较闲,思绪又乱了起来,就开始学些乱七八糟的东西,比如口琴、微积分(越发地觉得数学很重要)。

  • Web3,包括且不限于 Solidity 和 区块链的知识
  • Quant,包括且不限于 Data Science 和 ML

今年新学习的编程语言:

  • Clojure,上面说过了,11 月初开始,主要是那段时间比较闲,想要换个思路学点别的东西
  • rust,5 月被封在小区里,而且已经提了离职,实在没事干,就学了下,已经忘完。无意发现了 bevy,Rust 终于在出了数据库引擎、区块链之外,有了一个我喜欢的领域,可以做个游戏来学习 Rust,不过这个目前还没有计划
  • Solidity,在找 Web3 工作前后,学习了一段时间,因为不写合约,也忘了
  • Python,要不是为了那几个库,我是绝不学它的

输出

上面都算是输入,其实今年还有一些输出的,算是近几年输出最多的一年了:

总结

今年的学习状态比之前好太多了,尤其是回家的这半年,作息规律,感觉记性都变好了。在北京的时候,有段时间学啥都记不住,现在基本心态变好,都能学了!

去年列的计划基本都实现了,印象中还是第一次年度计划能实现得这么完整。

2022,Never See Again,恶心的一年赶紧过去吧。

2023 计划

2023 计划先列在这里,有些少,也比较抽象,后面可能再填充。毕竟,年度计划这种东西会随时变化的。


向我捐助 | 关于我 | 工作机会


给前端看的 dart 包管理与发布


本文中的代码已发布在 https://github.com/riskers/dart-pub-a-library


最近开始学习 Flutter,那就不可避免地开始 dart 的学习,除了语法的学习,就是要熟悉它的技术栈和模块化了。
这样也方便以后给 Flutter 生态贡献力量。

文本主要介绍 pub 模块管理工具在 dart 项目中的使用,pub 对于 dart 相当于 npm 对于 Node.js

  1. 在新项目中新建立 pubspec.yaml 文件,相当于 Node 中的 package.json:

    pub 目前不支持自动生成(类似 npm init 的功能),只能自己手写
    pubspec.yaml 格式

    name: pub_study
    version: 0.0.1
    description: pub study by rxdart
    dependencies:
      rxdart: ^0.20.0
    environment:
      sdk: '>=2.0.0 <3.0.0'
    authors:
      - riskers <gaoyibobobo@gmail.com>
    homepage: https://github.com/riskers/dart-pub
  2. 执行 pub get 安装 rxdart 模块,然后会自动创建 .packagespubspec.lock 两个文件:

    pubspec.lock: 相当于 Node 的 yarn.lock ,是用来锁版本的,关于其是否加入版本库,要看你的 package 是 library 还是 application

    # Generated by pub
    # See https://www.dartlang.org/tools/pub/glossary#lockfile
    packages:
      rxdart:
        dependency: "direct main"
        description:
          name: rxdart
          url: "https://pub.dartlang.org"
        source: hosted
        version: "0.20.0"
    sdks:
      dart: ">=2.0.0-dev <3.0.0"
    

    .packages: 加入 .gitignore

    # Generated by pub on 2019-01-07 18:42:25.471988.
    rxdart:file:///Users/gaoyibo/.pub-cache/hosted/pub.dartlang.org/rxdart-0.20.0/lib/
    pub_study:lib/
    
  3. 执行 pub deps 可以看到项目的依赖:

  4. 将代码写好:

    | - lib
        | - src
             | - print_string.dart
             | - print_number.dart
        | - pub_study.dart
    
  5. pub publish --dry-run 检查是否能发布:

    然后就可以发布了 pub publish,按照提示点击链接,就可以看到:

    https://pub.dartlang.org 可以看到:

  6. 测试,新建文件 pubspec.yaml:

    name: test
    dependencies:
    	pub_study: ^0.0.1

    执行 pub get:

    新建 test.dart:

    import 'package:pub_study/pub_study.dart';
    
    void main() {
      print_string();
    }

    执行:


本文中的代码已发布在 https://github.com/riskers/dart-pub-a-library



向我捐助 | 关于我 | 工作机会


我的学习方法论

这个时代太棒了,什么知识都可以在网络上找到,之后就是筛选的烦恼。现在总结一下我的学习方法论。

目的

首先,明确自己学一个技术的目的,这样才能有的放矢。不要含糊地定义目标为「学会」,什么叫学会?是得心应手地处理工作叫「学会」,还是能实际上手做一些小功能叫「学会」,还是了解了几个 API 叫「学会」。

我觉得,针对问题解决问题最重要!而不是看到一个新玩意,就去「学会」!搞不好你以为「学会」了,可没有实践,最后也会一事无成。

前些年,我就陷入这个怪圈。陷入选择怪圈,想要同时 TS 和 Golang,结果贪多又没有实践,今天学的明天就忘,自己也很痛苦。幸好,最近写 GithubX 又找到了写代码和学习的感觉!让我知道了:学习最好的效果就是用学到的去做一个东西出来,也最牢靠!

回顾历程

我上大学之前,准确地说,是大二之前,是不知道编程这回事的。大一暑假,我自己打工赚了不到两千块钱,加上之前攒的生活费,给自己买了一台笔记本,是神舟的,我记得很清楚。

大二,学习 PS、PR,给学生会做做海报和宣传视频。现在想想,要是一直坚持下去,不知道现在自己会不会是一个设计师或者影视后期呢?

大三,学习 CSS,开始切一些页面练手。当时也不知道怎么获取知识,就是把图书馆里相关的书都借回来,对着里面代码敲。结果是可想而知的,没有什么效果。之后发现其实有些人的博客写得特别好,愿意分享出来,尤其是张鑫旭的博客,我当时几乎把他每一篇文章都看了,然后敲下来。

大三暑假,去了北京实习两个月,住在蚂蚁间里。让我明白了多人是怎么协作的,也让我知道了 Node.js 和 Angular.js 两个框架。

大四,继续夯实基础,然后找工作。其实我在这个阶段还处于没开窍的阶段,也根本不会知道未来工作会有什么困难在等着我。

2014/8 - 2017/1,第一份工作移动 Web 前端。这个时候主要是用 jQuery 开发业务,至于 React、Vue 这些框架都还没有,JS 模块化主要靠 RequireJS 或 SeaJS,工程化是 Grunt 或 Gulp 这些工具。可以说,我是花了一年左右的时间,才把这些东西怎么用,怎么组装搞明白的。因为公司里没有技术氛围,我属于 free farm 状态,技术学习途径主要依然是看博客和看书,还处于「量变」积累的过程。

2017/1 - 2018/4,第二份工作 PC Web 前端。这个团队就比较正规了,还是 React 技术栈,这里我算是进入正规军了。构建规范化、流程化,也有技术分享了。不过,后面因为部门变动,我就离开了。这个阶段开始买电子书而不买纸书了,因为搬家太重,而且电子书可以搜索

2018/5 - 2019/3,创业,前端。这依然是一份目前我面对的最困难的一份工作,因为是创业,我什么都要做,而且没有规范,没有指标,没有工作量。唉,很是心类的一个阶段。不过,这个时候的我,已经不会再对自己的发展迷惘了,心中有了大概的学习计划。不管公司如何,我自己总是在进步的,当时想法就是这么简单。离开的时候,我心里是比较从容的,因为我已经知道我要往全栈的方向发展了。

2019/3 - 2022/5,Java 后端,转型了。后端知识之复杂,体系之庞大,是我之前没有遇到过的,是全新的领域,我之前的那点 Nodejs 储备量根本不够用了。所以,我用了几乎一年的时候,把常见的框架、组件都搞明白怎么用了,第二年的时候才有了点后端开发的感觉。我很少买书了,除了经典书籍,我现在学什么基本都通过 Google 和 Youtube 学习了,而且现在微信读书有太多电子书资源了!

可以看到,我几乎每去一家公司,都是去学东西的,也总能学到东西。

学习的过程

对于大部分开发任务,我们没有必要掌握一门编程语言的所有细节。好办法是花尽量少的时间去掌握日常开发所需要的 80%,而不是花 80% 时间去掌握平时比较少见的 20%。

比如我在用 Java 开发的时候,其实 2 天我就把 Java 的语法过了一遍,然后基本就可以开发业务了,但是,直到一年后,我才能完全明白这些是怎么回事。

学习一门技术的时候要快速掌握其最核心的部分,抛弃细枝末节,直接动手实现目标,中途遇到不会的再 Google 即可。这样才是高效快速的学习方式(缺点就是基础可能不牢靠,需要自己整理知识图谱来巩固)。

学习过程从来不是线性的,所以如果你看别人给出一个最佳的学习路径,比如看完某本书,然后再看另外一本更深入的,然后再买个什么课的,就比较坑了。

知识的学习一定是螺旋向上的。学习任何技术都不会是线性的,是螺旋式的,要积累量变到质变的能量。这个能量,可能是累计的代码量,可能是若干个知识点等等。是理论和实践相互交叉在一起向上的。

还要逐渐把自己心中的 Roadmap 勾勒出来,不断完善。大脑里保存一份记忆宫殿,在用到的时候,取走对应的那部分,我努力做到这个样子。

我觉得要学一门新技术,最快的办法就是看完文档之后,自己试着做些小项目,如果还想继续深入就做一些更大的项目,或是参与到开源项目中去。而绝对不是细细地扣着文档来学语法或者功能点,这样不是不行,而是有点笨拙。一定是做的时候学,是最快的!光看语法有什么用,没有项目要自己去找项目,技术不练怎么精进。

没有参与开源项目是我上面说的我在迷茫时期犯的最大的错

通常是技术不难学,但是领域知识比较难学,因为要实践某些领域知识需要相关环境和工作内容。

比如前端,JavaScript 难么,不难,但是学完你就能干前端了么?还差得远呢。什么 npm、DOM、MVVM 框架一上来,你就懵了。
比如后端,Java 不难,但是学完你能干后端么?还需要数据库、框架、分布式知识的加持。
比如 DApp,Solidity 其实也不难,但是需要了解背后成体系的区块链知识。

持续学习的好处

上面说了「怎么」学习,现在说说「为什么」。

有人问过我,说他做了三年 Java,想要转 Go,但是又怕 Java 退步了。我其实觉得挺不可思议的,为什么会退步呢?

要知道学得越多,就会学得越快,当然前提是你之前的真的学会了。

我告诉他,退步说明你 Java 还不牢靠吧,但是如果只为了「守」住什么技术,而不去学别的技术,这恐怕是懒惰的表现。

努力是最大成功因素,绝大多数的人努力的程度,都还用不到去拼天赋的程度。编程其实有点像卖油翁的,最后,可能就是「无他,唯手熟尔」的境界。

学习方法

很多新手还是像在学校一样,看书就从第一章开始看。不得不承认,出了学校,就已经不存在让你可以成体系学习一个东西的时间和机会了。工作中,全部都是业务的学习。

那怎么学呢?不停地写,遇到不会的就查!这是最快的学习方式。但是,这样获得的知识不牢靠,形成不了体系。这时候就需要自己整理知识图谱了,我是用的 Notion 做整理,获取知识途径可以是 Youtube、书、博客等等。不断地优化 Notion 的同时,就是自己不断思考和整理知识碎片的时候!

而且,看书要带着问题去看,看书是为了解决问题,而不是单纯地去学。

古人说,「学而不思则罔,思而不学则殆」,放在这里:只顾着学习新玩意,赶时髦技术,就会让自己忙忙碌碌,但却无所适从,甚至有让身体的勤劳掩盖头脑的懒惰的嫌疑;而空有想法,却从不去实践,只会让自己陷入焦虑,毫无意义。

有过两段迷茫的时期,一段是刚工作 free farm 的时候,一段是刚转型后端时候。虽然两段时间跨得比较久,但也有共同点,都是在我刚进入一个新领域的时候。

之前有一段时间,我就陷在这里。那时候光想着学概念,一点都没想着要找个实际项目来做。导致 TS 一直没学会,还不如我最近 2 个月做 GithubX 学的 TS 多呢。

那一年也不是自己学不进去后端的那些东西。就像前几年,前端的那些不也是学不进去么?比如当时想学重绘重排这些东西,到现在忘了;比如当时学的 Docker,直到真正做了后端才学会。

对于我来说,整体知识了解之后(一般是看书或者文档),直接开始上手写个小项目,遇到不会就查,记录在 Notion 上。项目做完会有正反馈,可以刺激大脑。积累一定量不会的知识之后,就可以进阶了,尝试把他们在 Notion 上归类。让知识成体系,解决上面说的知识不牢靠问题。

当然,即便整理出来了,也不能保证学会,还要时时练习才行,让大脑形成肌肉记忆。

现在我不会因为同时学很多东西,而忙乱得一匹了。做到慢就是快

主要的原因还是我学会了整理知识:Notion 帮了我大忙,网络式的知识图谱,是我的第二大脑。这也是我已经不怎么写博客的原因了。我可以在1分钟内,找到我遗忘的东西。

如何构建知识图谱呢?不要给自己设限。比如最开始做前端的时候,学跨域的解决方案,看到可以 Nginx 转发,就觉得那不是我干的。到后来,Docker、数据库都会用了,人还是不要给自己设限。

总结复盘

我的复盘方式就是写周记,每天的想法都记在 Dayone 上,每周末对着 Dayone 整理一下这一周的学习成果。

我是从初中开始学英语,否没有学会音标。因为老师讲的不好,自己就没有学会。上学时,如果听不下去老师讲的,就只能自己看书,而英语这种东西又不是看书就能看会的。

而最近又在学英语,照着一个**大学老师的课程学音标,收获极大,绝对超过了我之前十几年对音标的理解。

真的感谢网络时代,让我又有了学习的快乐。


向我捐助 | 关于我 | 工作机会


负margin的总结

这篇文章是从原博客转载过来的,是2013年写的,有些不对的地方请指出。


这是一篇我自己关于负margin的理解,今天因为做项目用到了负margin,几经找资料,终于搞懂了,就赶紧记下来,免得忘记了!

  • margin为正时,top、left属性是以content上(左)或垂直上方相连元素margin的下(右)边为参考线垂直向下(右)位移。
  • margin为负时,right、bottom属性是元素本身的border右(下)边为参考线水平向右(下)位移。

总结

  1. 盒子最后的显示大小等于盒子的border+padding+正margin,而负margin不会影响其大小。

  2. margin为负且盒子static时:

    • 若属性为top、left,盒子将被拉进指定的方向;
    • 若属性为bottom、right,将后续的元素拖拉进来,覆盖本来的元素。
    • 若width没有被设置,设定负margin-left/right会将元素拖向对应的方向,并增加宽度,此时的margin的作用就像padding一样

选项卡demo

demo

怎么样实现上面菜单栏的选中状态下没有下边框的效果?
一般的思路是每个菜单栏设置边框,选中的状态没有下边框

其实还可以这样,边框不是上面菜单栏的,而是下面内容块的:
demo2
明白了把,所以只要给菜单栏设置margin-bottom:-1px就可以让下面的内容块上移1px,刚好让菜单栏的背景色盖住那个1px的边框。
如果选中状态没有背景色,就悲剧了:
demo3

请看 demo

现在看这个例子没有明显展示出负margin的能力,再看下面的

负margin加宽元素

再看一个width没有设置,通过负margin加宽的元素的布局例子,这是很常见的例子,如果不用负margin,就会很麻烦呢
demo

圣杯布局

因为BFC有这个特性:

元素在设定width时,添加borderpaddingmargin会导致元素变宽;但是在没有设定width时,元素会自动填满父元素,添加paddingbordermargin会使元素变窄,减少量等于他们三个之和。
demo

负margin实现两列等高布局

demo

参考文章

负值之美

以上是网上资料总结,我的总结就一句话:left、top不论正负自己动,right、bottom不论正负别的元素动!正的向外,负的向内!考虑问题的时候还要考虑到盒子的特性问题!!
PS:遇到问题只要先想想什么是margin,margin的作用是什么,则负margin的工作原理则迎刃而解!


向我捐助 | 关于我 | 工作机会


教你一步步从零构建webpack开发多页面环境


使用 webpack 已经将近一年了,期间用它构建过4、5个项目,踩过一些坑,现在用自己的理解记录下来。

我现在教你如何一步一步搭建 webpack 开发的多页面项目。本文项目地址在 https://github.com/riskers/generate-pages-tutorial

首先需要安装:

git clone https://github.com/riskers/generate-pages-tutorial

这里使用的是 webpack 1.x,webpack 2 见文末

注意每一步的 webpack.config.jspageage.json

一、基本 JavaScript 模块的处理

cd 1_multi_pages
npm install
npm run build

查看 webpack.config.js 可以其实就是配置多个 entry 而已,可以看到 dist 下生成编译好的文件:

|--- dist
        |--- page1
                |--- main.js
        |--- page2
                |--- main.js

这里的目录层级和 entry 中的模块名(page1/mainpage2/main)对应。

打开 page1.htmlpage2.html 就可以看到我们的js模块生效了。现在进入下一步!

二、CSS 模块的处理

通过上一步,我们已经解决了 JavaScript 模块的问题,而页面中还有 CSS 。webpack 默认是只处理 JavaScript 的,所以我们要引入 css-loaderstyle-loader 来处理 CSS。

cd 2_css
npm install
npm run build

CSS

{
    test: /\.css$/,
    loaders: ['style', 'css']
}

loader 是专门处理某些模块的处理器。webpack 只能处理 js ,为了能够处理 CSS ,就需要 css-loader;而为了能够在页面中插入 CSS ,还需要 style-loader

打开 page1.html 就可以看到 css 生效了。

less

{
    test: /\.less$/,
    loaders: ['style', 'css', 'less']
}

如果使用的是 less ,就需要安装 lessless-loader

打开 page2.html 就可以看到 less 生效了。

sass

{
    test: /\.scss$/,
    loaders: ['style', 'css', 'sass']
}

如果使用的是 sass ,就需要安装 node-sasssass-loader

打开 page3.html 就可以看到 less 生效了。

postcss

module: {
    loaders: [
        {
            test: /\.css$/,
            include: ROOT + '/src/page4',
            loaders: ['style', 'css', 'postcss']
        }
    ]
},
postcss: function() {
    return [autoprefixer]
}

如果使用的是 sass ,就需要安装 postcss-loader,这里是以 autoprefixer 为例。

打开 page4.html 就可以看到 less 生效了。

生成 CSS 文件

以上方法都是用 JS 生成 CSS,但是实际上,我们需要的是 CSS 文件,可以使用 extract-text-webpack-plugin 来解决。

打开 page5.html 可以看到效果

三、reload

上面2节我们已经掌握 JS 模块和 CSS 模块的处理,并且能够让 CSS 独立生成文件了,现在我们觉得每次修改代码然后 build 再刷新浏览器这个过程实在太慢了,而且也没必要每修改一行代码,就生成新文件,这是构建速度慢的主要原因。

webpack-dev-server 是 webpack 自带的一个开发服务器,支持热替换、代理等等功能。

cd 3_reload
npm install
npm run dev

打开 0.0.0.0:8888/page1.html ,你就可以看到页面了。而且无论你修改 main.jsstyle.csstpl/page1.html 都会让浏览器自动刷新。

这里使用了:

  • html-webpack-plugin: 在页面中自动注入 js 和 css
  • html-webpack-harddisk-plugin: 每次修改 pages/tpl 内文件时,会自动在 pages/html 内生成对应的文件
  • raw-loader: 可以 require html 文件,做到每修改一次 tpl 文件,浏览器自动刷新一次页面

还有一点值得注意,因为 reload 功能是开发时才需要的,所以我们在 build 的时候要把这部分剔除,cross-envDefinePlugin 的配合可以做到这点。

  • cross-env 能够不分系统地在全局注入变量,下面这条命令就是将 DEV 注入 ENV 环境变量
cross-env ENV=DEV webpack-dev-server --inline --hot --quiet --progress --content-base pages/html  --host 0.0.0.0 --port 8888
  • DefinePlugin 将 process.env.ENV 这个环境变量注入 ENV
new webpack.DefinePlugin({
    'ENV': JSON.stringify(process.env.ENV)
})
  • main.js 中就可以区分是开发环境还是生产环境了:
if(ENV == 'DEV') {
    require('pages/html/page1.html')    
}

四、ES2015 && babel

如果你要在 webpack 中配置 ES2015 的开发环境,需要 babel 的帮助:

  • babel-core
  • babel-loader
  • babel-preset-es2015
  • babel-preset-stage-0
  • babel-plugin-add-module-exports
  • babel-plugin-transform-runtime
cd 4_es2015
npm install
npm run dev

然后在 webpack.config.js 中:

{
    test: /\.js$/,
    loader: "babel",
    exclude: /node_modules/
}

注意 exclude: /node_modules/ 很重要,否则 babel 可能会把 node_modules 中所有模块都用 babel 编译一遍!

并且,在根目录下新建 .babelrc

{
    presets: [
        "es2015",
        "stage-0"
    ],
    plugins: [
        "transform-runtime",
        "add-module-exports"
    ]   
}

然后我们就可以写我们可爱的 ES2015 了:

import './style.css'
import { log } from '../common/index.js'

五、引入库

cd 5_library
npm install
npm run dev

CommonsChunkPlugin

CommonsChunkPlugin 是 webpack 自带的插件,能够把公有模块提取出来:

plugins: [
    new webpack.optimize.CommonsChunkPlugin('common','common.js') 
]

HtmlWebpackPlugin 中加入 common/index.js 模块,我们就可以看到 common/index.js 模块被提取到了 common.js 中。否则的话,page1/mainpage2/main 中都会打包 common/index.js

externals

实际开发中,我们还会在页面使用 <script> 引入一些常用库,比如 jQuery ,那么我们需要

// 当在 js 中 require jQuery 时,实际上是指向 `window.jQuery`
externals: {
    jQuery: 'window.jQuery'
}

然后我们就可以在 page1/main.js 中使用 jQuery 了:

import $ from 'jQuery'
$('body')
    .append('<p>this is jQuery render</p>')
    .css('color', '#FFF')

ProvidePlugin

当然,对于 jQuery 这种每个页面都会使用到的库来说,每次都要 import $ from 'jQuery' 显得很不优雅。可以这样配置:

plugins: [
    new webpack.ProvidePlugin({
        $: 'jquery'
    })
]

这样就可以像 page2/main.js 中那样了:

$('body')
    .append('<p>this is jQuery render</p>')
    .css('color', '#3f3f3f')

六、代理

经过上面几个步骤,我们基本上已经完成了 webpack 的开发环境搭建,但是 pages 里全是静态页面,而我们后端实际上使用的可能是 PHP、Python 甚至是 Node 渲染的动态页面。

现在我们要解决的问题是把现有的 webpack 开发环境和已有的后端环境结合起来,我们这里使用的是 webpack-dev-serverproxy 功能:

devServer: {
    proxy: {
        '*': {
            target: 'http://localhost:8000'
        }
    }
}
cd 6_proxy
npm install
npm run dev

为了模拟一个后端环境,这里使用 PHP 自带的 server 在 8000 端口开启服务:

php -S 127.0.0.1:8000 -t ./pages/html

然后打开 http://0.0.0.0:8888/page1.php 就可以看到页面被 webpack-dev-server 代理过来了。你可以在 pages/html 下得到正式的已经配置好资源路径的页面。

执行 npm run build 后,你会在 pages/html 下得到相应 CDN 地址的资源路径,CDN 地址可以在 npm scripts 下配置:

"scripts": {
    "build": "cross-env CDN=http://cdn.a.com webpack"
}

七、团队协作

到此为止,一个 webpack 搭建的多页面开发环境已经完成了,还有一些与 webpack 无关的话题要注意一下。

ESLint

ESLint 是代码检查工具,这里不多介绍了。如果你使用的是 es2015 ,记得安装 babel-eslint 就好。

pre-commit

pre-commit 是一个很好用的工具,你可以使用它强制性地让团队成员在 commit 代码前执行任何命令(ESLint、测试等等)

"scripts": {
    "lint": "eslint app/src/ app/stylesheets"
},
"precommit": [ "lint" ]

注意使用时要先安装:

node node_modules/pre-commit/install.js

八、一个脚手架模板

我还建立了项目 https://github.com/riskers/generate-pages ,它包括了最最基本的 webpack 开发多页面骨架,感兴趣的也可以看看。由其是 map.js 和 模板文件的映射,这种思路应该可以帮你少写很多代码。

最重要的是,赶紧动手开始使用 webpack 吧!

希望这份教程帮到了你 -_-

20170215 更新

最近,webpack 2 终于正式发布了,之前大概看过他的 beta 版本,但是没有正式发布之前我始终没有去实践,这两天得空把 https://github.com/riskers/generate-pages-tutorial 上的代码更新到了 v2 版本。总体来说,webpack2 默认支持 module、提供 tree shaking 是2个比较让我在意的新功能,其他的以后再细细研究了。


向我捐助 | 关于我 | 工作机会


GithubX: 一个 Github 体验增强插件

最近开发了一款增强 Github 体验的 Chrome 插件 - GithubX

代码也开源了: https://github.com/riskers/github-plus-extension

痛点

解决了我个人的一个痛点:Github 原生对 stars 和 gists 无法分组和打标签,这样让我每次在查一些不常用库的时候,总是会花很长时间查找。

看看效果

可以在后台在 star 分组和打标签了:

然后在 github 页面展示出来:

上面是对已经 star 过的项目,如果你有新的 star,会自动弹窗让你操作:

自我总结

这个项目其实应该 2 年前就做完了,一直拖到现在,也主要是自己的拖验症比较严重。

这次迭代了 3 个版本,差不多 2 周一个版本,主要用业务时间来做这件事情。

学到了什么

复盘

这次技术选型的思考:一开始想得太多,导致进度很慢。

  1. 存储选型

    最开始本地数据是存在 localStorage 里的,不过后面分组的时候,要做到类似 One To Many 的关系查询,会很麻烦,我勉强克服了。直到要打标签的时候,我绝望了,因为这是 Many To Many 关系,用 localStorage 太麻烦了。于是,使用 indexedDB 解决这种问题。

  2. 状态管理选型

    一开始我认为这只是一个小项目,没打算上 redux。所有的状态管理都放在 context 里,结果,因为数据管理太复杂,使用 context 反而让状态越来越复杂,得不偿失,于是用 redux 替换。查看这个 commit会更清楚。

说了这么多,如果你感兴趣,不妨试试吧。下载地址


向我捐助 | 关于我 | 工作机会


毕业8年,暨而立之年

八年了,借着离开北京这个机会,总结一下职业生涯和自己的生活

突破自己

上一篇文章,也总结了我的工作历程,不再赘述。

一个公司的岗位可以分为前端、后端、数据、产品等等,但是人不应该给自己设限。

我一路走来,各个类型的公司都待过,大公司,创业公司,二线公司。前端、后端也都做过。也都是不想自己一直在一个领域一直做下去,我想多看看,多学学。

庆幸自己切了好几次技术栈,从前端到后端,再到 Web3,每次都抱着热情,都是那个时候自己最喜欢干的。

思考行业

大多数程序员都认为自己不用关心业务数据,我也是,一直到去年,我的目标还是把编程技能搞好。

直到最近半年,我才考虑行业的问题,思考自己的职业发展,思考自己的未来。看来,不光是知识是螺旋的,人的认知也是

我把自己的人生当作一家公司来经营,建立了 slack 频道:
slack

工作感悟

工资不会一直涨

除非你为公司创造的价值一直变大。其实,就算你做到了这一点,只要有人不这么认为,也做不到一直涨。

这也是有理论依据的,是之前看一个 HR 分享的观点。

公司不会为了你多余的能力付给你高薪水

一定不要留恋所谓「大厂」或者所谓「高级职位」的光环,在你没有利用价值的时候,一脚就被踢开,没有谁是不可替代的。

而且,大厂就一定是人很多,在饼能不断做大的时候,大家可以其乐融融一起冲。但是,在业务停滞甚至萎缩的时候,吃相就一定很难看了,职场就是江湖。

在一家公司工作的理由,只有两个:钱多或者能学到东西,如果都没有,尽早撤吧。公司付给你薪水,而你的劳动恰好能为公司创造价值,这样就能双赢,就能长久合作下去。

不要有养老心态

养老,有人说找个不忙的公司养老,比如外企。

但是,自己可不能停止学习啊。没办法,这一行就是这样,停止学习就是自己扼杀了自己的职业生涯。

骑驴找马

还是要骑驴找马的,万一找到合适的呢?

其实,我是很建议大家每年都出去看看机会的。多面面,看看外面的行情,也了解了解自己的行情。

不要在一个地方呆得太安逸了,小心「死于安乐」。

工作态度

对于工作的态度,我比较特别,并不是像大多数人那样,希望在这家公司升职加薪。我总是平常心,抱着上学的态度来到一家公司,当我觉得没什么东西可以学的时候,就应该毕业离开了。

和自己和解

有人说,长大成熟就是和自己和解的过程。30 岁了,在北京的八年,已经深深地认识到自己能做成什么事,做不成什么事,自己的能力上限在哪里,哪些事情是努努力能做到,哪些事是只能想想的。

  1. 自己记性不行,或者说不是那种面试型选手,并不能轻易得到 offer,甚至一面都过不了。
  2. 现在自己的兴趣点和技术栈太杂了,会给人一种多而不精的感觉,所以工作只能找全栈的,单个前端或后端可能都拿不下来。那么,注定就是小公司了。
  3. 既然在已有的赛道上跑不赢别人,拿我现在要在 Web3 领域努努力了,换个人少的赛道跑,行吧。

这次,我选择换个赛道看看别的机会。希望这是个好的 30 岁开始。争取下次换工作,不用面试!

离开北京

为什么不买房?当年一直想在北京有个房子,真正有了钱可以买的时候,又犹豫不决了。就是觉得太亏了!在北京一个首付,在老家差不多直接全款大平层了,何必呢?

所以,累了,回家了。

本来想着最多最多在北京待三年,结果三年又三年,三年又三年,八年了。

我和媳妇决定绝不会在北京买房,买了房就失去了很多种可能,背着房贷会让自己裹步不前。当然,这也是因为穷的。

我不会赌,赌的机会成本太大了。万一输了,就万劫不复。

最后让我下定决心一定要离开的北京的事情是,连续两年年底,被租房中介坑了,报警的过程中遇到的事情,让我对北京再无任何信任和留恋。

我其实考虑过润到外面,但是,成本太大,拖家带口的,先不考虑。

啥也不是

我和他们比就啥也不是,苦也算不得什么:

前两天读《出师表》,到「苟全性命于乱世,不求闻达于诸侯」的时候,我眼里有泪花了。是年纪到了么?

一路走来

一路走来,有时候想想,觉得自己特别不容易。在北京这个地方,人生地不熟的,真的很难。

所有人都在看着你飞得高不高,没有人管你飞得累不累

我在上大学之前,是不知道世界上有苹果这家公司的,是室友的一个 iTouch 让我知道这个小小的东西有多了不起。也不知道手机是 3G 的,想想还是挺白痴的。

因为来自小地方,所以我知道「钱」的重要性,也特别能省钱和攒钱。我庆幸自己进入社会八年之后,还有一份少年的心,一份没有被污染的心。可能,这也叫幼稚吧。

现在回头看,我幸运地选择了编程这条路。这对于我这种没人脉、没资源、情商又不高的人来说,简直是最好的选择了。只要一台电脑,我就可以干活了。而且,从我现在的状态来看,我可以一直干下去!

毕业八年,给自己鼓掌!

见自己

30 岁,对自己认识越来越清晰,知道自己想要什么生活。所以,安逸和自由的生活!

也对「财务自由」有了另一份理解,或许,在满足自己生活的条件下,随意地、舒服地做一份工作,这也是一种自由吧。

其实自己兴趣真的挺广的,还有时间的话,还想要学学:

  • 吉他,自己一直想要学的
  • 英语,听、说要达到能和外国人交流的程度,这样能认识世界

远程工作

今年找工作,发现 remote 的工作机会比自己想像的要多得多。

我见有人把 remote 取名叫旅行办公,WOR(working on road)。我之后会一直全职远程,不会再坐班。因为一旦开始,就回不去了。

为什么选择这家公司呢?因为看好 Web3 行业,薪资也还满意,在加上可以 remote,我就直接回家乡了。至少也能学到东西,先进入这个行业再说。

斜杠青(中)年

争取做个斜杠青年或者中年,工作的时候是工程师,下班是自媒体?不知道,试试呗。毕竟在家办公,时间很充裕。

安慰一下自己

我好像工作以来,从来没有为钱烦恼过,明明赚得也不多,可就是不烦恼,可能是心态好吧,呵呵。

我一个普通本科,放在人堆里普通得不能再普通的人,能够完全靠自己在西安买了房子,结了婚,这已经是我的极限了。

凭什么还要求那么多呢?

自己又不是名牌高校毕业,没出国深造过,英语不好,情商一般,资源没有的人,能活成现在这个样子已经不错了。

承认自己的平庸吧,未来加油!毕竟,怎么过不是一辈子呢。


向我捐助 | 关于我 | 工作机会


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.