072020.4

纯库

随着时间的流逝,越来越对很多看似花里胡哨的库感到失望,其中以 react 和基于 react/vue 所做起来的库为最。对 vue 报以热烈的喜爱,在于,它的开箱即用性和易用性。虽然是一个框架,但你完全可以把它当作一个库来使用。这就是我认为的纯库。所谓纯库,就是 cdn 加载进来,立即可用的库,不需要复杂的构建环境,不需要上下游的 babel 插件,只需要一个 <script src> 就可以使用其功能的库,而这样的库,以 jquery 为最,以 bootstrap 为最,这才是 web 开发的本质,简洁的声明式编程。react 给 web 开发开了一个坏头,但这和 react 自己没有太多关系,而是类 react 的 web 框架,以及趴在 react 上的 web 组件。react 不是最好的 react 库。但要知道,react 的设计理念,是要抽象视图层为 virtual dom,从而可以在跨平台上发挥作用,因此,它有自己的一套事件系统,如果不是为了跨平台,没必要自己做事件系统,而那些只管用 react 做 web 开发的开发者,实际上,和 flutter 做 web 应用一样憋足。而 vue 一个 cdn 挂载进来,把各类插件用 Vue.use 加载进来,马上就可以用。这就像 jquery 一样,插件加载进来就可以用,真正做到的 web 开发的便捷性。失去便捷性的 web 开发,应该交给另外一门语言去做,比如 ts,甚至 go,总之,便捷性是我认为 web 开发的真谛。为了工程的稳定和可持续发展,引入庞大的构建体系,这没有错,但是如果随便一个东西,都一定要走这么庞大的构建体系,那么真的是背道而驰。

19:49:17 已有0条回复
052020.4

有生之年,很难想象,那个曾经在社区仰望的技术大神,就这么草草的离开了人世。回顾司徒正美一生,最主要的成就或许在于亮点:1.推出 avalon 框架,并且基于该框架思想后续发展出来的 anu 等框架或 ui 库;2. 在 DOM 编程上所展现的黑魔法技巧,以及传道授业所写下的大量技术文章。

avalon 是在 virtual dom 出现之前,能够兼容到 ie6 的 mvvm 响应式 js 框架。当然,要兼容一些特定浏览器(包含低版本 css),还需要加载一些额外的 js 文件,但是,它自己会自动加载。它在用法上很像 angularjs,但是采用了 Object.defineProperty 来实现响应式。在 react, vue 出现之前,avalon 是一个令人感到神奇的框架。但是随着 virtual dom 的统治,他也在 avalon2 中采用这项技术。虽然 avalon 早期版本令人惊叹,但后续紧跟潮流,也不免让人有些唏嘘,脱离了 DOM,他的想象力也被当红的技术所框定。

36 岁去世,未婚。假如,假如时间允许,他或许还会出一本新书,或者在框架上突发奇想提供一个新的思路。但是,总归是没有假如。这世界来过,也留下了些东西,等待年月渐渐将这些痕迹抹去。再过几年,便不会再有人记得这个名字,随之而去的,还有 avalon 框架。

在感叹年轻的开发者们,要注意自己的身体,要用于对 996 说不的同时,作为档案专业毕业的学生,我一直对“档案是人类社会的记忆”这一点耿耿于怀。为什么我们不能把这个人给我们带来的都留住?再过一段时间,他的博客域名过期了,他的开源项目停更了,那他就像未曾来过这个世界一样,一切烟消云散。有没有一种方法,让人类的思想瑰宝可以永世长存?

21:51:23 已有0条回复
232020.3

(data.key = value) !== value

JavaScript 这门语言,让开发者感受编程乐趣的方式多种多样。今天掌握了一个新技能。我们来看下:

data.key = value
var a = data.key
value === a

这段代码平淡无奇对吗?但是,如果我告诉你,最后返回的结果是 false,是不是很好玩。

很显然,在这段代码前面,我还干了一些其他事情,导致最后的比较返回 false。我们来看看我都做了什么:

Object.defineProperty(data, 'key', {
  get() {
    return this._originalData.key
  },
  set(v) {
    this._originalData.key = { ...v }
  },
})

当前,在这之前,我还处理 _originalData 等,但这一段代码就可以了。也就是说,我规定了 key 属性在被赋值的时候,都要做怎么样的处理。这是已经到元编程的层面了,所以说 js 好玩。

这不单单是好玩的问题,而是在实际编程中,我们可能遇到的问题。你可能会问,谁会在自己的项目中搞这种蛋疼的事?不过很抱歉,很多处理数据的库,都会这么干,用来解决一些特别的逻辑。我想有一天你肯定也会这么干。我们来看一个可能出现 bug 的实用场景。

const files = []
const file = input()

files.push(file)
if (files.find(item => item === file) {
  // do something
}

类似的代码我们经常用吧。把一个元素加入到一个数组中,需要的时候,又在数组中,去找这个元素。这么干,说实话,经常出事。所以,我建议你最好 files.find(item => item.id === file.id),用一个字符串或数字作为唯一标志。不然翻车别怪我没提醒过。

为什么会这样呢?

你怎么知道 push 没有被修改过?

files.push = function(v) {
  return Array.prototype.push.call(this, { ...v })
}

所以,你敢随便自认为吗?js 编程就是这么任性。甚至有些人直接在原型链顶端搞事情,你扛得住吗?不要用一个没看过源码的不可靠第三方库!!!这是老人言。现在有些人,非蠢即坏,你防不住的。另外,善用 Reflect 或许也是忠告。

虽然你可以

const pushed = files.push(file)

这样可以得到 push 之后的真实数据,然而,然而,你怎么知道在类似 angular 之类的框架中,这些数据有没有被篡改过。总之,少年,我劝你善良。

14:19:27 已有0条回复
122020.3

.nojekyll 原来是告诉 git 仓库文档系统不要忽略 _ 开头的文件。之前在做 docsify 的时候,一直没法加载 _sidebar.md 最后改成 index.md 之后搞定了,但是为啥 _sidebar.md 就不行呢?原来如此。

10:51:43 已有0条回复
112020.3

wordpress 模板层次结构和优先级(地图)

wordpress 模板层次结构和优先级

102020.3

因为想要体验yarn的优点,于是想安装yarn来使用,也可以顺带装逼。然而,当我使用官方的安装教程,一一试了一遍,全告失败。最后,使用brew安装它之后,不但没安装上,还把npm给干掉了,node好像也出了问题(看提示是要升级成一个新版本的node的样子),我mlgb,我就想试一试你,没想你还要接管我的node,劳资就只要这个版本的node啊!npm命令直接被搞死了,报错。

安装yarn导致npm坏掉

最后,直接放弃yarn,什么玩意儿。还得重新去node官网下载对应的版本来安装,艹!另外,百度网盘离线下载加超级会员,下载真香。

20:41:57 已有0条回复
042020.3

个人感觉,像 Flutter 这种框架,虽然理论上非常好,关键在于,会有谁花那么多时间 focus 在上面。它自己之所以自成一个独立体系 ,独立派别,根源是在 dart 语言上。除非在嵌入式开发方面,它称为某个不可替代的工具,否则,真的是要凉凉。你说用它做原生应用,有专门的 swift 和 android 工程师,用它写 web 简直是在开玩笑,只有在嵌入式方面目前还没有特别大的竞争对手。但是,基于 js 的 quickjs 也可以做嵌入式开发啊。如果你是开发者,虽然觉得 flutter 好,但是你手上有一把 js 的高级定制锤子,还会用其他工具么?如果当初 flutter 用 js,或者 dart 是基于完全的 js 语法,作为 js 的超级存在,那么我觉得后续发展会更好。可惜了可惜了。

13:36:01 已有2条回复
  1. 大佬 移动端 哪个会好一点 weex 还是?
    #912 zaqxsw 2020-03-08 19:49 回复
  2. 当然是 android 和 ios 原生开发最好,flutter 和 weex 应该差不多,但是 flutter 用 dart,rn 最惨
    #913 回复给#912 否子戈 2020-03-08 19:52 回复
262020.2

自己写一个 livereoload 中间件

在项目中,我们经常需要一个自动刷新的功能。无论你使用 webpack-dev-server 还是其他工具,不够,市面上的工具都稍微有点复杂,会另外起一个端口,用于服务端和客户端相互通信。这在某些条件下反而不符合要求,有些情况下,我们只有一个端口可以使用,只能在当前服务基础上进行处理。我专门写了一个 express 的中间件来实现。

const chokidar = require('chokidar'); // 除了其他依赖,还需要依赖这个,用于监听文件变动

// 开启保存自动刷新功能
if (livereload) {
	app.use(createLiveReload({
		matchUrls: [/^\/some/, '/index.html'],
		renderFile: path.join(__dirname, 'www/index.html'),
		watchFiles: path.join(__dirname, 'www/**/*'),
                isReady: () => true,
	}))
}

/**
 * 创建一个 livereload 中间件
 * @param {*} options
 */
function createLiveReload(options) {
	const { matchUrls, renderFile, watchFiles, intervalTime = 1000, isReady } = options

	let latest = Date.now()
	let modified = latest

	chokidar.watch(watchFiles, {
		ignored: /(.*\.(map)$)|(\/vendor\/)/,
	}).on('all', () => {
		if (typeof isReady === 'function' && !isReady()) {
			return
		}
		modified = Date.now()
	})

	return function(req, res, next) {
		const { method, originalUrl } = req

		if (method.toLowerCase() !== 'get') {
			next()
			return
		}

		const scriptUrl = '/_live-reload.js'
		const url = URL.parse(originalUrl)
		const { pathname } = url

		if (pathname === scriptUrl) {
			if (modified > latest) {
				const content = `window.location.reload(true)`
				latest = modified
				res.end(content)
			}
			else {
				const content = `
					var currentScript = document.currentScript;
					setTimeout(function() {
						// 移除老的脚本
						currentScript.parentNode.removeChild(currentScript)
						// 插入新的脚本
						const script = document.createElement('script')
						script.src = '/_live-reload.js?v=' + Date.now()
						document.body.appendChild(script)
					}, ${intervalTime})
				`
				res.setHeader('content-type', 'application/json; charset=utf-8')
				res.end(content)
			}
		}
		else if (matchUrls.some(item => item instanceof RegExp ? item.test(pathname) : item === pathname)) {
			fs.readFile(renderFile, (err, data) => {
				if (err) {
					res.sendStatus(404);
				}
				else {
					const html = data.toString()
					const body = html.indexOf('</body>')
					const script = `<script src="${scriptUrl}"></script>`
					if (body > -1) {
						const content = html.replace('</body>', script + '</body>')
						res.end(content)
					}
					else {
						const content = html + script
						res.end(content)
					}
				}
			})
		}
		else {
			next()
		}
	}
}

实现原理,就是在前端界面不断的移除,新增脚本,脚本的内容由服务端输出,在服务端文件变化时,脚本内容为 window.location.reload(true) 从而刷新页面。

需要注意两个点:

  • 由于它会直接读取文件后立即渲染,所以,在 express 的路由列表中,要注意其顺序,放在 app.use(express.static(...)) 后面,这样可以保证静态文件都可以被访问到,放在其他所有动态路由的前面,这样能保证通过 matchUrls 匹配到的 url 能够使用 renderFile 进行渲染
  • 你可能需要根据不同的 url 渲染不同的文件,此时,你要多次调用 createLiveReload 来实现多个中间件实例,当然,这个时候你要保证 matchUrls 的顺序是正确的。

这就是简单的自刷新中间件了。

09:29:34 已有0条回复
242020.2

今天在处理 express 的 req.headers 时遇到坑,特地记录下来。在 req.query, req.cookies, req.headers 这三个对象,按理应该按照客户端传过来的数据进行处理,但是唯独 req.headers 是全小写的,无论你客户端传过来的是大写还是小写,这里接收到的都是小写。这是为什么呢?原来,这和 http 协议还有关系,http/1.0 时是大小写敏感的,也就是说 Token 和 token 是两个不同的 key,到了 http/1.1 大小写又不敏感了,这两个 key 是同一个 key,再到 http/2.0 协议要求 HEADER 的 key 必须全部小写,RFC7504,我也是醉了。因为 http/1.1 和 http/2.0 是当下最广泛的 http 协议,所以 express 索性全部用小写,以兼容后面的版本。

那么怎么解决这个问题呢?使用 req.get() 即可,req.get 是大小写不敏感,遵循 http/1.1 的规则,这样就可以避免大小写带来的问题。至于 cookies 等其他参数,则是大小写敏感的。

21:00:36 已有0条回复