根据我的观察 VitePress 目前用的最多的是文档站,比如 Vue 的官方网站、Vite 的官方网站、Rollup 的官方网站等。但是拿它来做博客站的不多,但也完全没有问题,比如 Vue 的官方博客。另外我还找到一个叫 vitepressblog.dev 的站点,它是一个 VitePress 的博客主题。对两者做了简单的比对之后,我觉得 Vue 的官方博客的主题更简单更适合我的需求,所以我就直接把源文件拿来用了。
我之前的博客内容主要分了四部分:
我把这四部分进行了逐步迁移
先把独立页面迁移过来,这个很简单,把每个文件复制到相同的路径就好了
把所有文章都拷贝到新仓库的 blog
目录,但不急于调试文章的展示页面,因为这些文章还暂时没有入口,所以我紧接着先迁移了文章导航
实现文章导航。Vue 的博客站主题有一个明显的局限性,就是它的博客列表不支持分页。我这里运用的是 VitePress 自身的 Dynamic Routes 特性,在工程里同时创建了 page/[page].md
和 page/[page].paths.js
来计算和生产分页信息。如 paths.js
的内容如下:
import { readdirSync } from 'fs'
const PAGE_SIZE = 10
// 计算文章数量
const data = readdirSync('./blog').filter(x => x.match(/\.md$/))
// 计算页面数量
const pageMax = Math.ceil(data.length / PAGE_SIZE)
// 创建分页参数
const pageParams = new Array(pageMax).fill(0)
.map((_, i) => ({ params: { page: i + 1 } }))
export default {
paths() {
return pageParams
}
}
这样在相应的渲染组件里就可以:
import { useData } from 'vitepress'
// 这里的 `./posts.data.js` 和 Vue 博客站的实现相同,所以就不赘述了
import { data as posts } from './posts.data.js'
const PAGE_SIZE = 10
const { params } = useData()
const currentPosts = computed(() => {
const { page } = params.value || { page: 1 }
const start = (page - 1) * PAGE_SIZE
const end = start + PAGE_SIZE
return posts.slice(start, end)
})
同理,对于归档页面来说,也是对应的实现 archives/page/[page].md
和 archives/page/[page].paths.js
,只不过 PAGE_SIZE
我定为了 100。
另外我实现了一个简单的 Pagination.vue
,用来实现文章列表页面上的分页器。代码很简单就不贴了。
接下来确保每一篇文章都能够被识别和渲染。因为我之前有些 blog 写得略随意,有些直接用 HTML 写的而不是 Markdown,且标签没有严格闭合和转码,还有些索性往文章里塞了一段内联的 <style>
和 <script>
直接出 demo。这些细节在 VitePress 里多多少少都引发了一些解析错误,我也都逐个修复了。
最后就是静态资源的迁移了。这个比较简单,直接把之前的静态文件拷贝到 public
目录就好了。
经过了这些粗枝大叶的迁移之后,网站差不多可以跑起来了,内容也都正常展示出来了。接下来就是一些细节的完善和优化了。
首先是把模板里不必要的信息注释掉或删掉。Vue Blog 的模板里有一些我这里不需要的信息,比如作者、头像不需要,因为就我一个人写;另外我在写作的时候越来越希望内容本身经得住时间考验,不论是什么时候写的都值得一读,所以发布时间之类的信息我个人觉得干扰阅读,为了极致的阅读场景我把展示这些信息的组件也都拿掉了。同时布局方面我也把侧边栏拿掉了。这完全是我的个人偏好。
还有一个小细节是关于文章列表中每一篇文章的摘要,之前 Hexo 是通过寻找内容中的 <!--more-->
记号并以此为分界来截取摘要的,但 VitePress 中是通过 ---
来识别的,并且在我迁移的那一刻还没有支持自定义分界记号。所以我只能手动 (当然是批量处理的) 把文章内容之前的分界记号全部都改成 ---
。不过好消息是我把这个问题提给了 VitePress 并很快得到了支持,最新的版本已经支持自定义分界记号了。大家如果再遇到这个问题就不用手动改了。相关 issue
再接下来是处理文章评论,这里有两部分,第一部分是早期我的博客基于 Typecho 的时候产生的评论,数据在 MySQL 里,当然这部分内容现在看是纯静态的了,不会再有更新了;第二部分是现在我的博客基于 Hexo 的时候产生的评论,数据在 GitHub issues 里,通过一个叫 gitalk 的库进行加载。后者相对容易,我把 gitalk 这个库导入评论组件的 <script setup>
里调用就可以了,并且监听路由改变,如果文章换了就重新初始化并加载对应的评论。
前者就比较麻烦了,因为我不想继续把 Typecho 的数据库挂在服务器上,所以我把数据导出成了 JSON 文件,然后在 transformPageData()
的时候把数据作为 earlyComments
灌入 frontmatter
元数据。这样在 build 的时候,每个页面都可以通过 VitePress 自带的 useData()
访问 useData().frontmatter.value.earlyComments
获取到这个数据,进而在页面上渲染对应的早期评论。
下一步是支持 open graph 之类的元数据。这里绝大多数信息我都是通过 config 中的 transformHead(context)
函数实现的。基本原理就是从 context.pageData
中分析出要展示的元数据,然后生成 <meta>
标签信息返回。但这里有三个特例:
description
:首先 pageData 里没有现成的信息,所以我自己写了个很简单粗暴的函数,从文章对应的源文件中读取内容,提取纯文本,然后截断前 200 个字符作为描述信息。不算特别严谨,但反正我平时写作时对格式对运用也比较规矩,所以足够了。大概的实现我提取了一个函数
import fs from 'fs'
import matter from 'gray-matter'
import { markdownToTxt } from 'markdown-to-txt'
export const genDescription = (filepath: string): string | undefined => {
if (fs.existsSync(filepath)) {
const content = fs.readFileSync(filepath, 'utf-8')
const data = matter(content)
const result = markdownToTxt(data.content.replace(/<[^>]+>/g, '')).replace(/\s+/g, ' ')
return result.length > 200 ? result.slice(0, 197) + '...' : result
}
}
description
这个元数据 VitePress 本身也会生成,并且在我迁移的那段时间是不支持合并或覆盖的,导致页面生成出来的 HTML 里会有两段 <meta name="description">
。这个问题估计你们猜到了,我也提 issue 了,最新版已经修复了。但我在这个 issue 被修复之前采取了另外一个临时的解决办法,就是通过 config 里的 transformHtml(code)
字段,把多余的 <meta name="description">
标签删掉。
async transformHtml(code) {
// dedupe <meta name="description">
const results = []
const regExp = /<meta name="description"[^>]+>/gi
while (regExp.exec(code)) {
results.push(regExp.lastIndex)
}
if (results.length > 1) {
return code.replace(/<meta name="description"[^>]+>/, '')
}
},
og:image
/ twitter:image
这两个字段目前社区最热门的实现方式是在服务端根据文章信息渲染一张图然后返回 URL (进而让用户觉得反正都已经上 Node 了不然就全站 SSR 吧,或者说至少这个功能跑不掉了那肯定得上 SSR)。说得好像不用这种服务博客都没法写了一样。我冷静的想了想好像也不必,选了个自己能接受的笨办法,就是找个离线工具生成自己想要的缩略图放到静态资源目录,然后在 frontmatter 里手写一个 manual_og_image 字段指向这个文件就好了。所以最终我的博客站用的依然是纯静态服务器。
最终的 transformHead(context)
实现大概是这样 (其中 genMeta
和 getIdFromFilePath
逻辑并不复杂,也不是这里讨论的重点,就不展开了):
async transformHead(context): Promise<HeadConfig[]> {
// add <meta>s
const description = genDescription(context.page)
const title = context.pageData.title
const url = `https://jiongks.name/${getIdFromFilePath(context.page)}`
const published = context.pageData.frontmatter.date
const updated = context.pageData.frontmatter.updated
const ogImage = context.pageData.frontmatter.manual_og_image
const tags = context.pageData.frontmatter.tags || []
const type = context.page.startsWith('blog/') ? 'article' : 'website'
const head: HeadConfig[] = [
// Basic
description ? genMeta('description', description) : undefined,
// Open Graph
description ? genMeta('og:description', description) : undefined,
genMeta('og:title', title),
genMeta('og:url', url),
genMeta('og:type', type),
ogImage ? genMeta('og:image', `https://jiongks.name/${ogImage}`): undefined,
// Twitter
description ? genMeta('twitter:description', description) : undefined,
genMeta('twitter:title', title),
genMeta('twitter:url', url),
ogImage ? genMeta('twitter:image', `https://jiongks.name/${ogImage}`): undefined,
genMeta('twitter:card', ogImage ? 'summary_large_image' : 'summary'),
// Article
published ? genMeta('article:published_time', published) : undefined,
updated ? genMeta('article:modified_time', updated) : undefined,
...tags.map((tag: string) => genMeta('article:tag', tag)),
].filter(Boolean)
return head
},
最终生成的代码如下:
<!DOCTYPE html>
<html lang="zh-CN" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>中文格式化小工具 zhlint 及其开发心得 | 囧克斯</title>
...
<meta name="description" content="介绍要给小工具给大家:**zhlint** zhlint logo 这个工具可以帮助你快速格式化中文或中英混排的文本。比如常见的中英文之间要不要用空格、标点符号要用全角字符之类的。 看上去这工具似乎和自己的工作和职业关系不大,但其实也是有一定由来的。 项目的由来 自己之前参与过一些 W3C 规范的翻译工作,这其中除了需要一定的词汇量、语法知识和表达技巧之外,最主要的部分应该就是格式了。因为大...">
<meta name="og:description" content="介绍要给小工具给大家:**zhlint** zhlint logo 这个工具可以帮助你快速格式化中文或中英混排的文本。比如常见的中英文之间要不要用空格、标点符号要用全角字符之类的。 看上去这工具似乎和自己的工作和职业关系不大,但其实也是有一定由来的。 项目的由来 自己之前参与过一些 W3C 规范的翻译工作,这其中除了需要一定的词汇量、语法知识和表达技巧之外,最主要的部分应该就是格式了。因为大...">
<meta name="og:title" content="中文格式化小工具 zhlint 及其开发心得">
<meta name="og:url" content="https://jiongks.name/blog/introducing-zhlint">
<meta name="og:type" content="article">
<meta name="og:image" content="https://jiongks.name/og/introducing-zhlint.png">
<meta name="twitter:description" content="介绍要给小工具给大家:**zhlint** zhlint logo 这个工具可以帮助你快速格式化中文或中英混排的文本。比如常见的中英文之间要不要用空格、标点符号要用全角字符之类的。 看上去这工具似乎和自己的工作和职业关系不大,但其实也是有一定由来的。 项目的由来 自己之前参与过一些 W3C 规范的翻译工作,这其中除了需要一定的词汇量、语法知识和表达技巧之外,最主要的部分应该就是格式了。因为大...">
<meta name="twitter:title" content="中文格式化小工具 zhlint 及其开发心得">
<meta name="twitter:url" content="https://jiongks.name/blog/introducing-zhlint">
<meta name="twitter:image" content="https://jiongks.name/og/introducing-zhlint.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="article:published_time" content="2020/04/26 03:53:59">
<meta name="article:modified_time" content="2020/04/27 12:29:56">
<meta name="article:tag" content="Chinese">
<meta name="article:tag" content="lint">
<meta name="article:tag" content="tool">
</head>
<body>
...
最后处理了两个 Vue Blog 样式上的小问题:
Dark mode (中文翻译是夜间模式?暗黑模式?深色模式?) 下 <mark>
标签内默认的文字配色有问题,我做了简单的修复。
/* fix color theme in <mark>s */
.dark\:prose-invert mark a,
.dark\:prose-invert mark strong,
.dark\:prose-invert mark code {
color: #111827;
}
为了避免过长的 URL 无法折行导致页面布局被破坏,我在必要的地方加了 word-break: keep-all
,对应的 Tailwind class 是 break-all
。
最后的最后支持了一下统计代码和 RSS 订阅文件 (这个 Vue Blog 就有,我做了些微调,沿用了我的博客站之前的输出格式),大功告成。
简单回顾一下,有几个 config 字段在整个迁移过程中起到了关键的作用,它们基本都在 build hooks 分类里。如果你也有类似的旧版本迁移或想给自己的网站定制一些特殊的功能,可以留意:
transformPageData()
:预处理页面数据,比如早期的评论transformHead()
:预处理 <head>
标签内的内容,比如 <meta>
transformHtml()
:后处理 <body>
标签内的内容,比如 <meta>
去重buildEnd()
:后处理全站的 HTML 内容,比如 RSS 订阅文件把该迁移的都迁移完毕过后,我又想了想,能不能顺便再给自己的网站加点什么呢?于是我又做了几个小功能:
manual_og_image
:这个字段是用来在 <meta>
里指向文章缩略图的,上一节其实已经提到过了,其实算是个新东西,之前没有仔细弄过。不重复介绍了。
View transitions:这是一个相对较新的 W3C 规范,用来定制页面跳转之间的动画。关键代码片段:
<script setup>
import { useRouter } from 'vitepress'
const router = useRouter()
router.onBeforePageLoad = async () => {
if ((document as any).startViewTransition) {
await (document as any).startViewTransition()
}
}
</script>
<template>...</template>
<style>
#app {
view-transition-name: app;
}
@keyframes fade-in {
from { opacity: 0; transform-origin: bottom center; transform: rotate(-5deg); }
}
@keyframes fade-out {
to { opacity: 0; transform-origin: bottom center; transform: rotate(5deg); }
}
::view-transition-old(app) {
animation-name: fade-out;
/* Ease-out Back. Overshoots. */
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
::view-transition-new(app) {
animation-name: fade-in;
/* Ease-out Back. Overshoots. */
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
</style>
动图效果:
Progress animations:这是一个更新的 W3C 规范,用来定制各种和滚动条进度自动绑定的动画效果。关键代码片段:
<template>
<div class="progress" />
</template>
<style>
/* progress animations */
.progress {
height: 4px;
background: transparent;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
transform-origin: 0 50%;
animation: scaleProgress auto linear, colorChange auto linear;
animation-timeline: scroll(root);
}
@keyframes scaleProgress {
0% {
transform: scaleX(0);
}
100% {
transform: scaleX(1);
}
}
@keyframes colorChange {
0% {
background-color: blue;
}
50% {
background-color: yellow;
}
100% {
background-color: red;
}
}
</style>
动图效果:
外链预览图:这是我效仿 WikiPedia 中词条链接的效果,用来在鼠标悬停在外部链接上时显示一个预览图,以方便用户更好地预判要不要将其打开。我用了 floating-vue 这个库,结合一个三方的缩略图生成服务实现的。实际体验够不够好还有待观察。
以上就是我近期对自己的网站做的一些小改动,希望能给你一些启发。如果你也有类似的需求,不妨参考一下我的做法。如果你有更好的想法,欢迎在评论区留言。
相关链接:
]]>大约一周之前,我突然接到父母亲的消息,说九十多岁高龄的奶奶身体突然变得非常不好,感觉很难坚持了,虽然立刻飞回去不太现实,但还是希望可以安排个远程视频,见一面。
我非常清楚这意味着什么,这一面就是最后一面了。
隔天早上,我们安排了视频见面。我们上一次见面也是视频见面,当时我们还能相互交谈个几句。这一次,奶奶的身体靠在家人身旁勉强可以坐起来,除了睁大眼睛看着屏幕对面的我,已经动弹不得,也说不出话了。我不确定她还能不能听到我说话,总之我努力说了很多。那一刻,自己心里特别难受——一是不愿看到这一幕,二是悔自己没有办法来得及回去亲自看看或身体力行帮着做点什么。
除了伤感,我决定写一点关于我奶奶的东西,让更多人认识和记住她。
]]>大约一周之前,我突然接到父母亲的消息,说九十多岁高龄的奶奶身体突然变得非常不好,感觉很难坚持了,虽然立刻飞回去不太现实,但还是希望可以安排个远程视频,见一面。
我非常清楚这意味着什么,这一面就是最后一面了。
隔天早上,我们安排了视频见面。我们上一次见面也是视频见面,当时我们还能相互交谈个几句。这一次,奶奶的身体靠在家人身旁勉强可以坐起来,除了睁大眼睛看着屏幕对面的我,已经动弹不得,也说不出话了。我不确定她还能不能听到我说话,总之我努力说了很多。那一刻,自己心里特别难受——一是不愿看到这一幕,二是悔自己没有办法来得及回去亲自看看或身体力行帮着做点什么。
除了伤感,我决定写一点关于我奶奶的东西,让更多人认识和记住她。
关于我奶奶最早的记忆,应该是自己很小还没有上学的时候,我们是个大家庭,从爷爷奶奶到我父母还有几个亲戚家都住得特别近,很多时候我们也直接住在爷爷奶奶家陪他们。白天我爸妈上班的时候,都是爷爷奶奶在家带我。那是一段其乐融融的日子,当时的街坊邻里也都关系很好,经常有不认识的叔叔阿姨来我们家串门,逗我玩送我小礼物什么的。虽然印象已经很模糊了,但我有一个细节一直到现在都记得,就是奶奶每天很早起床给一家人准备早餐。尤其在冬天的时候,天还没亮,奶奶就先起床了。然后我才会被叫醒吃饭,直到我后来上了学都一直这样。那个时候生活很简单,每天早上能饱饱得吃上一顿早餐,感觉一整天都是开心的。
渐渐的,我自己也慢慢长大,一点一点懂事,有了自己的想法和观点。我能回忆起的事情也具体了起来。我记得自己变得叛逆,不想每天生活在长辈们的教唆和监护之下。比如我吃东西的时候不喜欢别人看着我——现在想起来很荒唐不知道为什么——然而我的小孩现在也这样对我说过相同的话。作为家长,希望确定小孩吃完了吃饱了,然后就可以安排你出门上学或什么别的事情,连我自己现在都觉得理所应当,但是当时自己就是赌气不喜欢。有一次我早上上学前一个人吃早饭,我的奶奶为了迁就我的情绪,就坐得远远的,但是又放心不下,就一边远远坐着一边偷偷看我吃,结果被我发现了,我还因为这个发脾气,把气氛搞得非常不愉快;我自己出门上学,那是大概十分钟左右的路途,我奶奶总是在我出门之后站在阳台上看我,直到我走出从阳台能够看到的视野才罢休,我因为不喜欢这样,总是想办法躲开她的视线,每天贴着墙根走,但中间还是总会有一两段路会被她看到,于是我就每天尝试不同的走法,直到找到了一条路奶奶完全从阳台上看不到,然而晚上放学回家之后才听我爸妈说我奶奶就在阳台上看了一整天,一直放心不下不知道是不是我出了什么事。这大概就是我跟我的奶奶童年时期的典型互动模式,有点像猫捉老鼠,但现在回想起来又觉得自己的做法很幼稚。而这个循环似乎又延续到了我跟我的小孩之间。
再往后,我逐渐考到了一个离家比较远的高中,然后是大学,然后工作,跟家人见面的机会一点一点变少了,偶尔过年过节才回去。虽然见面的机会少了,但是每次回家见爷爷奶奶,有件事仍然格外重要,就是吃饭。奶奶总是在知道我要回去看他们的第一天就开始问我喜欢吃什么,一定要做给我吃,然后就开始每天盼我回去。所以每次我回去,总能吃到我最馋的家乡菜,什么莜面栲栳栳、小炒肉拌面、过油肉,到现在回忆起来,自己还是会流口水,怀念那些风味。我奶奶一直觉得不管是上学还是打工,出门在外就是吃得没有家里好,所以做一顿好饭就是对我最好的欢迎仪式。再有就是每次一顿饭下来,只要桌上的饭菜没吃干净,奶奶就会小声念叨说是不是做得不好吃;只要桌上的饭菜吃完了,奶奶又会小声念叨说今天东西做少了不够我们吃。总之永远有操不完的心。那时的我,虽然自己的味蕾得到了极大程度的满足,但还是有些不解风情,甚至有的时候觉得,自己在北京、杭州、还有其他工作和生活过的地方,其实吃的一点也不差。现在想想,老人家可能就是觉得别的也帮不上什么忙,也不一定懂,但是谈到吃的,老人家最自信了,而且确实拿手。也许在我们之间,这是唯一也是最重要的情感联结的纽带。不论念叨什么,也可能就是想找个话茬聊聊天吧,哪怕是尬聊。倒是自己那么当真,较劲。
渐渐的,奶奶年纪越来越大,家里人也不让奶奶亲自下厨了,我每次回家看爷爷奶奶,大家也就基本聊聊天寒轩个几句。奶奶还是忍不住会问吃得好不好——这真是个永恒的话题。再有就是我们每次家庭聚餐的时候,爷爷奶奶都会找我爸或我姑姑之类的亲戚,拿出相机或手机拍几张照片,录个视频什么的。说实话我到现在都没有完全接受这件事,因为人吃饭的时候难免嘴上手上弄得脏兮兮的,表情也难免怪怪的,就更别说我们这代人好多都是拍完照会 P 个半天直到所有人满意为止的。后来爷爷跟我说,现在他们年纪大了就特别怀旧,平时想见见我们没有什么机会,就喜欢看照片。而每次过年过节正是一大家人团聚的时候,就非常想借这个机会把大家的一举一动都尽可能记录下来,等我们各自回去工作上学了,他们可以拿出来看……
是啊,还有什么比这个更重要的呢。在我们自己每个月用手机拍上百张漂漂亮亮的照片的时候,爷爷奶奶只在乎有没有照片看,有就愿意津津有味得看。
从那以后,我虽然有时家庭聚会吃饭拍照还是有点抗拒,但同时也告诉自己,平时生活在外,要尽量拍些自己日常的生活照发送回去。而且给爷爷奶奶带了个 iPad 回去,这样看最新的照片和视频也方便。
现在回想起我的奶奶,脑海里浮现出来的,永远是她慈祥的笑容和无微不至的关怀。她老人家一生经历了很多我们这一代人无法想象和感同身受的事情,心有永远惦记着的是家庭,还有默默无私的付出。奶奶的想法也很传统,也会跟其他中国式家长一样,在你长大之后问你什么时候找对象,什么时候结婚,什么时候生小孩,要不要再生一个,也会说想让我们生个男的这样的很传统甚至今天大家觉得“政治不正确”的话。我觉得这就是他们这一代人极其朴素、务实而又单纯的一面。奶奶的眼里、心里,一直在盼着这个、盼着那个,照顾这个、照顾那个,我很少见到她谈论她自己,或者说她自己想要个什么东西,想去哪里玩,想过什么样的生活。奶奶自己很舍不得花钱,但是却每年都包大红包给我们晚辈们。她把自己完完全全奉献给了这个大家庭,奉献给了她身边的人,可能也就是除了想多看几张照片,到最后想见大家一面罢了。现在想起来,除了感动,还是感动。
我回忆和记录这些,其实也不只是为了我的奶奶,还包括我的爷爷、已故的姥姥 (外婆)、已故的姥爷 (外公)、爸爸妈妈、丈母娘、老丈人以及其他生活中的长辈们。不论走到哪里,身处何处,他们都是你的家人,都是你生活的一部分。他们有着跟我们不一样的人生经历、不同的想法、甚至跟我们有代沟,但在生活和生命面前,这些都只是插曲,永恒的是那些抹不去的情感记忆和最后对一切的和解。随着时代和科技的进步,我们感受着一些变化,也感受着一些变化。人们的距离被拉进,但也被疏远。每个人在不得不向前走的同时,也值得时不时回头看看吧。奶奶和其他长辈们与我之间这些共同的记忆,会时刻提醒着我,自己来自何方,又该向哪里而去。
如果还有机会,我会由衷的跟奶奶说,我今天吃得特别好,我们全家人都是。
最后,我很喜欢也相信一部动画片《Coco》里的设定:只要一直被人记得,你就在。我会一直记得奶奶,也相信她和我的姥姥、姥爷及其他已故的亲人们一样,一直都还在❤️
这是我在 B 站上找到的那部电影让我最感动的片段,也把电影里的这首歌送给我的奶奶和所有看到这篇文章的人。
]]>Remember me though I have to say goodbye
Remember me, don't let it make you cry
For even if I'm far away, I hold you in my heart
I sing a secret song to you each night we are apartRemember me though I have to travel far
Remember me each time you hear a sad guitar
Know that I'm with you the only way that I can be
Until you're in my arms againRemember me
这个工具可以帮助你快速格式化中文或中英混排的文本。比如常见的中英文之间要不要用空格、标点符号要用全角字符之类的。
看上去这工具似乎和自己的工作和职业关系不大,但其实也是有一定由来的。
]]>这个工具可以帮助你快速格式化中文或中英混排的文本。比如常见的中英文之间要不要用空格、标点符号要用全角字符之类的。
看上去这工具似乎和自己的工作和职业关系不大,但其实也是有一定由来的。
自己之前参与过一些 W3C 规范的翻译工作,这其中除了需要一定的词汇量、语法知识和表达技巧之外,最主要的部分应该就是格式了。因为大家对诸如空格、标点符号等细节的使用其实不太统一,这在团队协作的时候其实会变成问题,大家都花了一些不必要的时间在格式讨论和校对上。感觉这部分工作比较枯燥且机械,但又不得不做。只能花更多时间在上面。
后来因为接触 Vue.js 的关系。这个项目在早期并没有太多人知道它,而且当时社区普遍比较迷信像 Google 这种大厂官方推出的技术方案,对“野生”项目都不是很有兴趣,所以我希望可以把这个项目介绍给更多人认识。结合我之前的翻译经验,我觉得翻译文档是一个比较好的途径,于是就发起了 Vue 中文文档的翻译,结果没想到这件事一发不可收拾,我就不知不觉从 2014/2015 年做到了今天。随着 Vue 的不断发展,关注文档的人也越来越多,中间发生了很多故事,这些故事也让我自己逐步对翻译和中文格式的细节有了更多的认识。
真正触发我做这个项目的事情,是去年的一个翻译讨论:如何翻译 attribute 和 property。这个问题几乎从我接触技术翻译的第一天起就一直是个噩梦。我和周围的小伙伴尝试了各种译法,都不能让所有人满意,无奈之下通过刻意的区分和强化教育,把它们分别译成“特性”和“属性”。这个状态持续了很长一段时间,Vue 的文档也基本都是这么翻译的。直到去年的一段时间,我逐步意识到,也许这两个词不翻译会更好,索性直接保留英文原词,这样不会有歧义,同时随着整个社区的英文程度在提高,像这样的词不翻译大家应该也能顺畅的理解了,中英文混着读也逐渐可以接受了。所以就在 GitHub 开了个 issue,同时也扩散到了 W3C Web 中文兴趣组。没想到这次讨论大家的意见出奇的一致,几乎“全票通过”。看上去困扰我多年的问题终于要解开了……
然而在这之后,我意识到,如何对已经翻译好的大量文档做关键词批量替换并不是一件容易的事情——主要还是格式细节太多了。不能做简单粗暴的文本批量替换。
比如把“特性”换回“attribute”之后,如果“特性”一词的两边也都还是中文,那么“attribute”两边就都需要加一个空格,而如果是标点符号就不需要,而如果是英文,那理论上这个空格已经加过了。所以情况很多很复杂。你读到这里可能觉得那我们稍微加个正则表达式也许可以解决,那我会在告诉你,如果这个词的边上还可能有 HTML 标记或 Markdown 标记,那这个正则该如何写呢?或许也不那么容易了。
因此这个译法改动在去年就已经有定论了,但是实际上到今年上半年我才真正改好。原因是我觉得这次我不打算再靠蛮力去解决问题了,而打算通过工具来解决——这就是我做这个项目的由来和动机——没错我陆陆续续做了一年左右,最近终于做出一个比较文档的版本了,然后才完成了这次译法的替换。
另外一个促使我做这个工具的原因其实是我个人希望尝试一些语法分析之类的技术,因为觉得作为一个前端工程师,未来这个方向的可能性和空间比较大。如之前和很多人都聊到过的,现如今的前端框架全部都开始在编辑器这个环节大做文章。因为它可以帮助你突破一些 JS 语言的限制。所以大家武装到牙齿之后这部分是一定会碰的。我预测接下来这个趋势会从框架往上发展,逐步延申到前端工作的更多环节。做这个工具看上去似乎有那么一点可以积累到的知识和经验,于是就想先做个这个试试看。
接下来回到 zhlint 这个工具,介绍一下我设计的基本用法:
yarn global add zhlint
或 npm install -g zhlint
。这样 zhlint 就安装好了。zhlint <filepath>
,比如我们创建一个文件叫做 foo.md
,其中的文本内容是 中文English
。那么运行 zhlint foo.md
会收到一个错误提示,提醒你中英文之间应该有一个空格。zhlint foo.md --fix
,顾名思义这个命令会自动修复文件中的格式错误。所以运行之后文件 foo.md
内部的文本内容会变成 中文 English
。zhlint src/*.md
可以校验 src
目录下的所有 md 文件。同理也可以加 --fix
做批量自动修复。中文English中文
-> 中文 English 中文
中文 , 中文
-> 中文,中文
1+1=2
-> 1 + 1 = 2
中文, 中文.
-> 中文,中文。
Mr.
(不转换全角句号)2020/01/02 01:20:30
(在描述时间和日期的时候冒号和斜杠两边没有空格)33.30KB min+gzip
(这里的加号两边不会加空格,该 case 没有普遍规律)现在回顾之前的研发过程,首先是做得比较懒散,陆陆续续一点一点做,其次是返工了无数次,发现哪里走不通了就推倒重来,所以经历了太长的时间。
在最初的版本里,我想的比较简单,就只是把中文内容分为几个颗粒度去处理:char、token、full text。所以我当时只做了五件事:
大概的代码结构是这样的:
const checkCharType = char => {...}
const parse = str => {...}
const travel = (tokens, filter, handler) => {...}
const lint = (str, options) => {
// parse options
// travel and process tokens
// join tokens
}
然后我逐渐发现 lint 这个函数越写越大,逐渐失控。原因有这么几个:
我们需要 (先做一件事,然后再做一件事,最后再) 做一件事
。括号可以断在任意的地方,可以跨越多个句子,可以包含最前边或最后边的标点符号,也可以把它们留在外边,被截断的前后句子单独拿出来也未必是完整的。minute(s)
)、单引号可以用在英文缩写中 (doesn't
) 等等。再比如在描述时间和日期的时候,我们不太习惯在每个数字之间都加空格所以会省略空格 (2020年1月1日
而不是 2020 年 1 月 1 日
)。这些都导致设计之初通过简单的线性 token 机制处理很难做好这件事。“Travel and process” 这部分的代码越来越臃肿。逐渐我意识到,这里需要更多的结构化设计。于是我停下来考虑了一段时间。
之后我逐渐想到两个主意:
const rules = [...]; rules.reduce(processRule, str)
。这个思路其实我一开始想到过,但觉得把每条规则都抽象并独立出来是很有难度的,所以一直没有下定决心做。经过这次深思熟虑之后我鼓起勇气试了一下,看起来还是可行的,效果也还可以。于是我决定把之前的主分支退役,重新开启一个新的分支,开始以上述思路重构代码。
重构之后的处理流程更像是:
// separated files
const rules = [
(token, index, group, matched, marks) => {...},
(token, index, group, matched, marks) => {...},
(token, index, group, matched, marks) => {...},
// ...
]
// index.js
const checkCharType = char => {...}
const parse = str => {...}
const travel = (tokens, filter, handler) => {...}
const processRule = ({ tokens, marks, ... }, rule) => {...}
const join = (tokens) => {...}
const lint = (str, options) => {...}
有了这个结构,我就可以更加专注在格式规则的定义和实现上了。随着工作的深入,我也逐渐加入了一些务实的功能和设计。
截至目前,我们 lint 的假设性目标都是一个字符串——确切的说是单行字符串。但实际上我们需要处理的真实的文本内容是更复杂的。目前绝大多数待处理的文本内容都是 Markdown 格式,可能还夹带了一些 HTML 标记,而且是多行文本。
为了解决真实的问题,我稍微花了一些时间去了解如何解析 Markdown 语法。之前用到 Markdown 的地方基本都是从 Markdown 渲染出最终的 HTML 代码,但这次我们不太需要最终的 HTML 代码,而是 AST,也就是抽象语法树。最终我找到了一个叫做 unified.js 的库,它可以把各种格式的文本内容解析成为相应格式的 AST。其中 remark.js 就是在这个库的基础上用来解析 Markdown 语法的,其 AST 格式为 mdast。大致的用法如下:
const unified = require('unified')
const markdown = require('remark-parse')
const frontmatter = require('remark-frontmatter')
// the content
const content = '...'
const ast = unified().use(markdown).use(frontmatter).parse(content)
// process the Markdown AST
接下来就是根据 mdast 庖丁解牛的时刻了。经过研究 mdast 的文档,我发现在 Markdown 语法里,所有的语法节点都可以简单粗暴的区分为两大类:inline 和 block。而 zhlint 要处理的其实就是找出所有不能再拆解的 block,然后把其中的 inline 节点在 zhlint 中标注为我们之前提到过的 mark 类 token。当然其中 inline 节点还要再分为两类:一类是包含文本内容的 (例如加粗、斜体、链接等),需要继续 lint 处理;一类不包含 (例如图片),需要原文保留。对于代码片段,我们从自然语言分析的角度认为它不是文本内容,所以也算后者。更妙的是其实在 Markdown 的 parser 里其实是包含了对 HTML 标记的解析的,所以我们不需要额外引入 HTML parser 就可以完成对 HTML 标记的支持。
源代码中大致的语法节点分类如下:
// 不能再拆解的 block
const blockTypes = [
'paragraph',
'heading',
'table-cell'
]
// 包含文本的 inline
const inlineMarkTypes = [
'emphasis',
'strong',
'delete',
'footnote',
'link',
'linkReference'
]
// 不包含文本的 inline
const rawMarkTypes = [
'inlineCode',
'break',
'image',
'imageReference',
'footnoteReference',
'html'
]
这样我们就可以先把所有的文本中不可拆解的 block 找出来,同时对这些 block 内部出现的超文本做好 mark 标记,然后带着这些 mark 逐个 lint,最后再把这些结果填入之前的 block 所在的位置。大致思路如下:
const blocks = parseMarkdown(str).blocks
const blockResults = blocks.map(lintBlock)
const result = replaceBlocks(str, blocksResult.map(
// 意在强调主要处理的信息是处理后的结果和之前所在字符串中的位置
({ value, position }) => ({ value, position })
))
当然要想把 Markdown/HTML 语法处理好这还不算完,因为相应的 lint 规则也变得更加复杂了。举个例子,当我们处理空格的时候,希望空格始终出现在 inline mark 的外侧 (中文 [English](a-link-here) 中文
而不是 中文[ English ](a-link-here)中文
)。所以对已有规则处理上的复杂度相当于是指数级增长了一倍。而且实际上到最后还需要特别添加一些针对 Markdown/HTML 语法的规则。这里我其实在过程中反复做了各种尝试和搭配组合,才变成了现在的样子。现在的规则已经相对比较稳定了。同时我也在实现类似的规则过程中逐步积累了很多 util functions。所以拜托了一些低级别的重复性问题之后,整个研发过程越往后其实会变得越清晰越简单。
在设计和实现 lint 规则的过程中,自己也积累了一些心得,总体上所有的 lint 规则或选项被分为了四部分,分别对应四种需求:
min+gzip
的时候,之间没有空格。针对这部分规则我们提供了一种注释语法,可以被 zhlint 识别,从而在 join
的时候跳过。
<!-- zhlint ignore: min+gzip -->
。[prefix-,]textStart[,textEnd][,-suffix]
。这样用户就可以更灵活的使用这一功能。parseMarkdown
机制的基础上加入了 hyper parser chain 的机制,每段文本在真正运行每条 lint 规则之前,都会链式运行所有的 hyper parser。最终包括了 Markdown 解析、Hexo tag plugin 解析、还有被忽略的个别情况的注释解析。这次研发过程中,我比较早,也比较严格的实践了测试驱动开发。基于 Jest 写了很多用例,通过这些用例把工具的行为“卡死”,这样当后期引入更多复杂度的时候 (比如决定重构第二版的时候、或决定支持 Markdown 格式之后),可以通过锁住测试用例进行大胆的重构和尝试,并且在重构的时候一旦发现一些之前没有覆盖到的 edge case,就立刻补充进去,然后重构至这个 case 跑通为止再继续。久而久之整套测试用例也越来越见状。总体下来还是受益匪浅的,帮自己省了很多时间和脑细胞——上一次有这种感觉的项目是 Weex JS runtime 第一版。如一些朋友知道的,当时我们只有 2 个月时间,要从零写一个 JS 框架用在双十一移动主会场,所以除了测试用例我当时谁也没法相信。
完成上述核心功能之后,差不多已经过去一年时间了,最后的一些工作留给了下述这些“外包装”。
值得一提的是自己在打印日志的时候,想实现类似 TSC 或 Vue 3.0 模板编译的错误打印格式,即打印出错误所在的那一行代码,并且在再下一行的出错位置放一个小尖角字符 (^
) 以方便用户定位问题,例如这是 Vue 3.0 模板编译里的效果:
2 | <template key=\\"one\\"></template>
3 | <ul>
4 | <li v-for=\\"foobar\\">hi</li>
| ^^^^^^^^^^^^^^
5 | </ul>
6 | <template key=\\"two\\"></template>
但问题来到 zhlint 之后遇到了一些比较特别的问题:
为此我采取了一种不太一样的定位展示效果:
chalk
,这样就可以为日志上色。所以最终看到的效果,如果把特别的着色去掉的话,看到的效果是:
自动在中文和English之间加入空格
自动在中文和^
但实际效果中第二行的“自动在中文和”是看不到的,只会看到一条黑色矩形。运气好的话,如果你的命令行背景也是黑色的,那么就完全看不出差别了。
另外一个测试的时候的小技巧,如果你不希望日志打印把测试报告搞得乱七八糟,可以结合 Jest 的环境变量判断 + 自定义 Console 对象把日志打印到别的流,然后做二次处理或直接抛掉,代码类似:
let stdout = process.stdout
let stderr = process.stderr
let defaultLogger = console
// Jest env
if (global.__DEV__) {
const fs = require('fs')
const { Console } = require('console')
stdout = fs.createWriteStream('./stdout.log', { encoding: 'utf-8' })
stderr = fs.createWriteStream('./stderr.log', { encoding: 'utf-8' })
defaultLogger = new Console({ stdout, stderr })
}
// usage
defaultLogger.log(...)
最后推荐几个我用到的 npm 包,如果大家想做类似的事情,可以做个参考 (当然如果你有更好的推荐也可以):
目前 zhlint 已经集成到了 Vue 的中文文档项目中。通过简单的 CI 配置,就可以轻松做到为每个 PR 自动 lint 并返回处理结果。
有了这个工具之后,我们就可以比较没有心智负担地批量替换文本了,替换之后运行一遍类似 zhlint src/*.md --fix
的命令,即可把因批量替换产生的格式问题全部修复。
然后,我就立刻完成了对 attribute 和 property 的替换……
所以“为了批量替换两个单词的译法,我花了差不多一年的时间” (这原本是我设想的这篇文章标题党版本的标题)
之后我们在 Vue 文档中又陆续遇到了讨论 mutation、ref 译法的问题。产生的相应改动也都可以基于 zhlint 很容易的得以实现。
有趣的是,在我在微博和 Twitter 简单分享了这个小工具之后,也有人留言说其实写博客的时候这个工具也非常有用。或许这是 zhlint 可能的更多用途吧😉!
最后,关于心得体会和收获,我觉得有这么几个:
另外 zhlint 其实还没有做完,我已经想到了更多的 feature 和改进点,其实也已知了不少不理想不完美不够好的地方。所以会继续做下去。
以上
]]>vue-mark-display
。你可以用它把 markdown 格式的文本转换成幻灯片并在浏览器中播放和控制。
我自己工作中经常需要准备各式各样的幻灯片,所以逐渐觉得用 PowerPoint 或 Keynote 来做幻灯演示略微显得有些笨重。这体现在板式和样式设计、文件大小、打开、编辑和播放的方式等很多方面。加上我从事的就是前端开发的工作,对语义化的信息格式非常敏感,深刻的认为,那些你表面上想编辑的“样式”其实是信息的“类型”+“配套的样式”罢了。所以决定用 markdown 外加自己扩展的一些小功能,来撰写幻灯片,并研发了相应的工具,也就是最近开源的 vue-mark-display
。
最早这个工具是用 vue v0.10 写的,当时源代码里还有像 v-attr
, v-repeat
, v-transition
这样的“古董级”语法,而且还在依赖 Zepto。最近准备开源这个项目的时候,我也基于最新的前端知识和技能进行了重构。所以大家看到的是比较新的版本。
其实在此之前,我也写过很多类似的小工具了,但都没有坚持使用很久,这次开源的 vue-mark-display
我差不多持续使用了 5 年。经历了差不多这 5 年时间,准备过了无数的幻灯片和公开演讲,我想说基于 markdown 以及这些小功能撰写幻灯片真的很酷。如果你也有兴趣试一试用 markdown 为主体来撰写自己的幻灯片,那么不妨了解并体验一下 vue-mark-display
。
另外,事实上,如果只是想使用它,你是不需要学习任何关于 Vue 的知识的 —— 文章最后会提供一个不需要 Vue 知识的开箱即用的办法 —— 所以它也对 React、Angular 等社区的同学友好 —— 只要你会写 markdown 和简单的 HTML5 代码,你就可以使用 vue-mark-display
制作出非常精美的幻灯片。
vue-mark-display
。你可以用它把 markdown 格式的文本转换成幻灯片并在浏览器中播放和控制。
我自己工作中经常需要准备各式各样的幻灯片,所以逐渐觉得用 PowerPoint 或 Keynote 来做幻灯演示略微显得有些笨重。这体现在板式和样式设计、文件大小、打开、编辑和播放的方式等很多方面。加上我从事的就是前端开发的工作,对语义化的信息格式非常敏感,深刻的认为,那些你表面上想编辑的“样式”其实是信息的“类型”+“配套的样式”罢了。所以决定用 markdown 外加自己扩展的一些小功能,来撰写幻灯片,并研发了相应的工具,也就是最近开源的 vue-mark-display
。
最早这个工具是用 vue v0.10 写的,当时源代码里还有像 v-attr
, v-repeat
, v-transition
这样的“古董级”语法,而且还在依赖 Zepto。最近准备开源这个项目的时候,我也基于最新的前端知识和技能进行了重构。所以大家看到的是比较新的版本。
其实在此之前,我也写过很多类似的小工具了,但都没有坚持使用很久,这次开源的 vue-mark-display
我差不多持续使用了 5 年。经历了差不多这 5 年时间,准备过了无数的幻灯片和公开演讲,我想说基于 markdown 以及这些小功能撰写幻灯片真的很酷。如果你也有兴趣试一试用 markdown 为主体来撰写自己的幻灯片,那么不妨了解并体验一下 vue-mark-display
。
另外,事实上,如果只是想使用它,你是不需要学习任何关于 Vue 的知识的 —— 文章最后会提供一个不需要 Vue 知识的开箱即用的办法 —— 所以它也对 React、Angular 等社区的同学友好 —— 只要你会写 markdown 和简单的 HTML5 代码,你就可以使用 vue-mark-display
制作出非常精美的幻灯片。
首先,我们在 markdown 语法的基础上做了一个扩展:通过 ----
分割线把一整篇 markdown 文档划分成为若干张幻灯片。
# 这里是第一页
以及一些基本的自我介绍
----
### 这里是第二页
- 这里有内容
- 这里有内容
- 这里有内容
----
没了,讲完了
__谢谢__
例如上面的例子就会被生成为三张幻灯片。
这样你就可以利用 markdown 支持的 h1 - h6 标题、列表、表格、图片、链接、加粗等格式加分页符用极快的速度写出幻灯片了。
通常情况下,你再为自己的幻灯片设置一套全局的 CSS 样式并固化下来成为你的样式风格,基本就可以拿来演示了。以下是我自己喜欢的样式风格:
在这个基础上,我们对 markdown 格式做了一点点进一步的扩展 —— 通过在每一页幻灯片开头撰写 html 注释来设置这页幻灯片的特殊样式。
比如我们为第二页幻灯片换一个不一样的背景,同时正文文字颜色变成白色:
# 这里是第一页
以及一些基本的自我介绍
----
<!-- style: background: #4fc08d; color: white; -->
### 这里是第二页
- 这里有内容
- 这里有内容
- 这里有内容
----
没了,讲完了
__谢谢__
现在翻到第二页看看,背景和文字的颜色就已经改变了
当然在 markdown 中你也可以撰写任意 HTML5 代码,比如嵌入一段 HTML 甚至全局有效的 <style>
标签,都可以被解析:
# 这里是第一页
以及一些基本的自我介绍
<div class="notification">Welcome!</div>
----
<!-- style: background: #4fc08d; color: white; -->
### 这里是第二页
- 这里有内容
- 这里有内容
- 这里有内容
----
没了,讲完了
<div class="notification">Thanks!</div>
<style>
.notification {
position: absolute;
top: 20px;
right: 20px;
border-radius: 3px;
padding: 0.25em 1em;
background-color: yellow;
color: #666;
}
</style>
vue-mark-display
还提供了一些方便的 prop 配置项和 event 组件事件,如:
@setTitle({ title })
方便从组件外部获取当前幻灯片的主标题,也就是第一页幻灯片的第一行文字,你可以用这个事件来设置 document.title
autoFontSize
根据屏幕大小自动调节默认字号,这个字号是根据自己的演示经验设置的。保证不论屏幕大小,一页幻灯片可以放下的字数是相对稳定的,这样就基本杜绝了因为演示现场屏幕分辨率不一致而导致的适配问题。当然你如果对这个默认的适配结果不满意,也可以自己手动设置组件的 font-size
样式。supportPreview
可以在点击链接的时候,按住键盘上的 Alt 键,这样链接会从当前屏幕的一个 <iframe>
打开,并悬浮在屏幕上方,同时右上角有个关闭按钮可以将其关掉。如果你希望整个幻灯演示过程不被中途访问链接而打断,相信这个功能你会非常喜欢。urlHashCtrl
能自动把当前页码和网页的 URL hash 对应、keyboardCtrl
可以支持默认键盘左右键翻页、autoBlankTarget
所有链接都默认在新标签打开、baseUrl
能将链接和图片的相对路径改成你自己的网站等。同时 vue-mark-display
还可以方面得利用第三方手势库支持触摸屏的左右滑动翻页。
上述比较完整的用例可以看这里:
vue-mark-display
还支持直接导出成为 PDF 格式文件。因为我们的幻灯片有可能通过各式各样的方式进行传播:有可能是一个链接,也可能是一个文件。所以 vue-mark-display
利用 W3C 的 CSS Page Media 相关规范满足了这一需求,你只需要简单的打开浏览器的打印对话框,然后选择导出成为 PDF,就可以轻松的把自己的幻灯片发到钉钉好友、微信群、邮件附件等各个地方。
vue-mark-display
已经开源了 ​欢迎大家试用并提出宝贵的意见,如能一同参与建设和维护,我会更加感激。
同时我也把自己最近在社区分享过的幻灯片全部整理到了这个地址:
https://jinjiang.github.io/slides/
接下里这个项目还有一些规划中的特性会逐步实现并发布,尽请关注。
如果你不想学 Vue,希望开箱即用,直接把 markdown 导出成在线幻灯片:
来用 mark2slides
吧,一键导出!
https://www.npmjs.com/package/mark2slides
用法:
npm install --global mark2slides
m2s my-slides.md
然后去 dist
目录开启一个 web server:
cd dist
npx serve
打开 http://localhost:5000/
,完毕。
不过这个工具非常初期,还请大家多多提意见,接下来我也会逐步为这个工具增加更多贴心的功能。
]]>我发现越来越多人讨论说会议里的内容很多都不是独家的,会后还会把幻灯片和视频放网上,那去会场的意义何在?我觉得有这么几点:
这个问题我不用复述了,大家应该知道这是个什么问题,以我自己的角度和观察来讲:
质量是会议的最核心,相信这一点大家都认同,我觉得最重要的三个部分包括:内容质量、会务质量、社交质量。
技术会议的内容我总结绝大多数可归为三大类:
三类分享都有各自的价值和侧重点。有人说其实今天的分享还有第四种就是广告,我一半认同一半不认同,认同的部分是确实有广告,而且有很烦人甚至有很恶心的广告,不认同的部分是它们都是上述三种形式的广告。所以广告本身不是重点,甚至理论上所有的分享都是广义上的“广告”,重点是烦人、恶心、手段拙劣、违和的广告。
所以这个问题的重点来了,如何鉴定一个东西是不是这种狭义的广告?如果这个问题有答案,那么内容质量是不是就可以有进一步的保证?
我觉得这个更多是会议组织者要思考的问题,但答案来自参会者。我有一个建议的判断方式:你判断听众会从这个话题得到什么真正的价值。总体上征集来的话题都可以这样判断一下,而主办方主动联系到的讲师,我建议在沟通好大框架的前提下,尽可能的给予讲师们信任和尊重——至少我个人比较反感主办方安排的什么试讲、审稿之类的环节,这种会除非有什么我不得不参加的理由,否则我一定不参加。
除此之外,有些狭义的广告是会议组织者为了收支平衡有意加进去的,甚至有些会议是明示这个分享是来自赞助商的。说到这里你再想想之前门票和票价那个问题,如果你觉得技术会议没价值,是不是也有你自己的问题?是不是你自己总是图便宜去参加了那些廉价的会才有了这种感受?
会务质量我觉得说白了就是做会场体验吧,可能也包括讲师体验。现场的屏幕、话筒、灯光、座位这些硬件就不多说了,很多体验其实是体现在软件上,也就是一些人性化的细节。再详细的我也说不上来了,但是参加的会议越多,尤其是今年参加的这几个境外的会议,让我越发觉得,这是个很开放的话题,没有最好,只有更好。
我只有一个细节想展开谈一下,就是语言和翻译的问题。众所周知,很多优秀的技术都是来自于国外的,更准确的说是来自全球的。如果一个以中文为主的技术会议有“老外”的存在,那么很多大家习以为常的事情都会变成问题。有些问题很明显,比如参会者可能会听不懂“老外”在讲什么;有些问题不那么明显,比如“老外”听不懂其他讲师在讲什么;还有一些问题可能藏得更深,比如“老外”们需要长途跋涉来开会并且很有可能全天听不懂都别人在讲什么,白白浪费一天时间,但是还要愿意精心准备一个技术分享等等。
我想稍微分享一些参加几个境外会议发现的小细节:
我个人的经验是:对于一个周六全天的会来说,会议可能只是一白天的,但是社交活动可以是从前一天晚上开始到会议第二天白天才结束的。
大概就是这样。
最后我想鼓励大家多学习英文,多出去走走。
]]>这是绕不开的话题。也是来自小右的第一个大会分享。
坦白说这部分内容我之前是有所了解的,也参与过内部讨论和之前的一些宣讲。所以听的时候心态相对比较平静。我个人觉得比较值得关注的地方是:
observer
, compiler
, runtime-core
, runtime-web
, runtime-server
, ...当然其它贴心功能,包括模板支持 source map、time slicing、hooks、typescript 支持以及性能和包大小方面的进一步改进等,相信也让很多人为之兴奋。这里不一一列举。
另外分享一下我看到和理解的 Vue 3.0 相关计划背后的一些东西:
不出意外的话,接下来 3.0 会经历一个逐步迭代和打磨的过程。在这个过程中,可能还会有一些新的特性或变化产生。但总体上,我预测翻天覆地的变化不太可能出现,新版本的主体思想应该也不会变。我个人理解,近期前端开发的模式,尤其是框架这一层,有逐渐稳定的趋势,大家基本都不约而同的专注在组件系统和数据管理这两个大的部分。技术手段在语言特性本身没有 ES5、ES6 那两波重大革新和突破的前提下,也鲜有质的提升。所以这个时候上层生态的作用和意义会越来越凸显出来。这个时候,严把框架质量关 + 更多为上层生态服务是非常明智的选择。
当然再次强调这是我个人的观点和判断,接受反驳。
这个话题我之前跟分享者 寒老师 也有过私下讨论,当时讨论的内容我甚至认为要比 寒老师 会上分享的还要深入,包括延伸到了 MVVM 设计理念的一些细节,所以 Vue 模板的图灵完备性跟这些相比可能只是冰山一角了。比如我相信今天几乎每个前端工程,不仅限基于 Vue 开发的项目,都有很复杂的数据,也就是 MVVM 里的 Model 或 Flux 里的 Store,需要设计、维护和“定期治理”。我觉得从今天前端工程的角度看,MVVM 描述的有些抽象,和实际的场景和语言特性有距离;而 Flux 实践性更强,但是理论层面没有 MVVM 探讨得那么细致和严谨,所以走到某些特殊的业务逻辑的时候你还是会困惑。在和 寒老师 讨论之后,我有了一些新的思路,打算接下来花精力做一些试验。更具体的内容就要等以后有了什么新的结论再做分享了。
CLI 应该算是 Vue 最近一年时间里在工具层面最重要的一个发布了。在维护者 胖茶 分享的每一个设计细节中,我感触比较深的,一个是为 vue-loader 和 webpack config 解压,同时避免社区和生态在这个层面过度碎片化;另外 CLI 在项目的技术升级和配置模板等方面下了很多功夫,这是很多开发者有需求但是不敢乱动或越改越花的两个地方;最后想说 韦元悦 的闪电分享有些仓促,但有两个重点大家可能 get 到了:一个是 Vue CLI 的可扩展性是很高的,另一个是 Vue CLI 的 GUI 界面,因为随着前端工程化的发展,一个可视化的项目管理与配置变得越来越有价值了。
顺便提一下,除了 CLI,还有一个工具也值得推荐大家关注,就是 VuePress。而且这两个工具目前都是国人在维护。很赞。
下午的 And Design Vue、Electron-Vue、Hippy-Vue 三个分享都是 Vue 社区和生态的部分,分别对应了组件库、桌面应用、移动应用三个大的板块。虽然大家未必都用得到,但我理解这是 Vue 社区和生态广度的一个展现。
另外两个跟 Vue 的招牌特性实践有关:SFC 和 SSR。SFC 实践的分享来自淘宝,这算是淘宝对 Vue 的一个认可吧我觉得,前东家的东西我就不做更多解读和演绎了;SSR 实践的分享我和分享者 天翔 之前也聊过一些,其实这个分享没有很多对 SSR 本身特性的介绍,更多的是探讨在 SSR 业务场景下的数据管理问题,我意识到这个话题其实和 寒老师 的话题存在着某种诡异的联系,因为本质上都是 UI 和数据打交道。同样的今天先不展开了。
最后 CRDT 这个分享的内容坦白说是在我意料之外的,第一是我没想到 ai 哥直接从 PostCSS 跳到了看上去完全不搭嘎的新领域,来以分布式的视角探讨数据交互;第二是我没想到能够学到“从 CS 学科过去几十年的积累中寻求新问题灵感”的这种方法论。它带给我的启发远远大过了 CRDT 本身。
因为 Vue 3.0 会提供 ES class 形式的 API,所以与此相关的 ES 规范也会逐渐成为每一个 Vue 开发者关注的话题。关于 Hax 在会上提到的 ES 新规范,我个人比较明确的态度就是我不喜欢用 #
来表示私有成员,比如 this#foo
。不喜欢的原因实在太多了我都不知道该从哪条开始说了,而且 Hax 基本都提到过了,我就不复述了,另外你可能会说你不喜欢就给个喜欢的啊不然还废什么话,坦白说我现在也给不出更好的方案,但我觉得如果一个新规范讨论不到够好的话,我宁愿没有这个规范,这不是一个一定要立刻有结论的事情。另外现在可能更大的问题是它眼瞅着要被通过了,我们该怎么办?这可能也是 Hax 想表达的一个很重要的点。如果你有关注规范和语言特性的习惯的话,建议多了解一下这方面的内容。
总体上,我自己觉得这次 VueConf Hangzhou 非常棒!
当然也因为自己有幸参与开场的乐队表演,而且表演了自己参与创作的歌,觉得很荣幸。
本来这篇文章的标题打算定为《VueConf Hangzhou 见闻,以及一些对技术会议的看法》,因为这次 VueConf Hangzhou 不出意外是我今年参加的最后一个技术会议了,不过写到这里发现内容已经不少了,另外也是个人时间的关系,所以打算先写到这里了。对技术会议的看法我会将来再找机会写。
]]>其实除了自己的分享内容,这次我是带着很明确的目的参会的,因为有两个主题我特别关注,就是:
(两个分享的标题都被我稍微“演绎”了一下)
这两件事都是自己工作上正在特别关注的事情,一方面,我们很少从 API 的角度去理解一个组件的 CSS 该如何组织和管理,所以这个标题就特别吸引我,另一方面响应式组件的分享者是来自新加坡的前端工程师 Zell (我个人一直觉得国内的响应式都是在瞎搞,看了很多周围团队都没有认真做这件事,甚至不相信响应式的价值,从设计师到工程师),因此非常珍惜这个机会能近距离学习一些国外的同行们是怎么看待和实践响应式的。
所以尽管我们团队的差旅经费已经用完了,还是决定自费来厦门近距离交流一下。
现在证明这次真的不虚此行。
当然参加这种线下活动,“面基”的目的是一定有的……恩,这个不值一提。
]]>其实除了自己的分享内容,这次我是带着很明确的目的参会的,因为有两个主题我特别关注,就是:
(两个分享的标题都被我稍微“演绎”了一下)
这两件事都是自己工作上正在特别关注的事情,一方面,我们很少从 API 的角度去理解一个组件的 CSS 该如何组织和管理,所以这个标题就特别吸引我,另一方面响应式组件的分享者是来自新加坡的前端工程师 Zell (我个人一直觉得国内的响应式都是在瞎搞,看了很多周围团队都没有认真做这件事,甚至不相信响应式的价值,从设计师到工程师),因此非常珍惜这个机会能近距离学习一些国外的同行们是怎么看待和实践响应式的。
所以尽管我们团队的差旅经费已经用完了,还是决定自费来厦门近距离交流一下。
现在证明这次真的不虚此行。
当然参加这种线下活动,“面基”的目的是一定有的……恩,这个不值一提。
在谈这次分享内容给我的收获之前,我想说,实际上我不只是从几十分钟的分享中学习了响应式组件的东西。我这次去厦门的行程特地提前了两天,周四就到厦门了,就是希望能多一些分享的准备和现场交流。正好 Zell 也到的比较早,于是乎我在周末的会议之前就跟 Zell 聊了很多。真的是很难得的机会。
回到分享的内容,我听过 Zell 的分享之后简单整理了一些要点:
font-size
尽量使用 em
单位,而不是如 px
的绝对单位vw
、vh
、vmin
、vmax
单位,必要的时候可以配合 calc()
min-width
,以小屏幕为基础min-width
和 max-width
的交集,避免样式间不必要的相互干扰+
选择器处理最后一个元素多余的边距rem
作为边距的单位以避免被组件的字号影响布局实际上每一条都不是很难,也不是没有见到过,但是总结的非常有系统性,给了大家一些很好开始的着手点。
另外 Zell 在聊天过程中也提到了很多我非常认同的观点和细节,想分享给大家:
我记录了一些要点和自己的理解:
听过之后非常受启发。
这两份 slides 我也第一时间分享给了我们团队组件库的同学们了解学习了:)
这些内容可能真的是有一点“超前”了,因为很多浏览器都还没实现,而且规范本身也没有稳定下来。但是我迫不及待的想分享出来,是因为我也听到了一些说法,说“CSS 很久没有什么新闻了”。那 CSS Houdini 绝对是一个可以让 CSS 更上一层楼的“重量级”的东西。希望可以通过分享 CSS Houdini 让大家对 CSS 更有信心和期待。
同时很多技术的“风向”都是由最底层的东西决定的,我们从规范层面对 CSS 有更多的了解,一定会对我们的实际工作有很多指导和借鉴的价值。如果你同时还是一个有开源精神的人,那么你可以从今天开始就构思一些基于 CSS Houdini 的工具和库了对吧,用这些工具和库加速 CSS Houdini 的落地,同时也尽快把一些之前没有 CSS Houdini 的时候大家用起来很别扭很勉强的东西汰换掉,更在这个过程中体会 web 带给我们的乐趣:)
另外几个分享也各有特色,总体上,我觉得这次 CSSConf CN 同时包含了 CSS 的规范、理念、工具、技巧、动画、八卦、吐槽各个方面,应该是尽可能照顾到了大家的兴趣和需求了。还是觉得这样的会议非常的棒。
我觉得 CSS 和动画、SVG、字体设计、3D 图形学、可访问性、语义化的 web 等话题有着非常紧密的联系,有很多有意义的延伸,并且这些话题也很难有独立的 Conf 了吧我估计。再加上 CSSConf 的主办者们,尤其是 裕波,是非常懂前端开发者们的,他们经营 CSSConf 的理念和方式我一直非常认同和欣赏,所以也许未来有一天,CSSConf 会比 JSConf 更受人关注。
以上
]]>license: CC BY-SA 4.0
如何通过会话式的界面让数据收集更加人性化。
Web 表单是从纸质媒介进化而来的。即设计一组标签和线框来限制输入,同时让数据处理变得跟容易。
毕竟,表单的目的是收集数据,以便执行操作。为了执行该操作,我们需要把收集的数据统一汇总。我们在界面上设计了一些约束以便达到统一汇总的目的。表单旨在符合流程上的需求,而非用户本身。
表单经常给人的感觉是冷冰冰的,没有人情味。因此,我们得到的回应往往也是冷酷和不人性的。我们不深入细节,如果一个朋友问你相同的问题,你可能会多一些回复,但这是一台电脑。他想要的只是数据,别的不在乎。就好像你在跟人说话但是人家并没有在听。为什么没人听的话说出来会让人觉得烦呢?
Image by Ken Teegardin.
和许多数字化的东西一样,表单已经被之前的形态严重影响。我们之所以往线框里填东西是因为我们以前在纸上就是这么画的。
]]>license: CC BY-SA 4.0
如何通过会话式的界面让数据收集更加人性化。
Web 表单是从纸质媒介进化而来的。即设计一组标签和线框来限制输入,同时让数据处理变得跟容易。
毕竟,表单的目的是收集数据,以便执行操作。为了执行该操作,我们需要把收集的数据统一汇总。我们在界面上设计了一些约束以便达到统一汇总的目的。表单旨在符合流程上的需求,而非用户本身。
表单经常给人的感觉是冷冰冰的,没有人情味。因此,我们得到的回应往往也是冷酷和不人性的。我们不深入细节,如果一个朋友问你相同的问题,你可能会多一些回复,但这是一台电脑。他想要的只是数据,别的不在乎。就好像你在跟人说话但是人家并没有在听。为什么没人听的话说出来会让人觉得烦呢?
Image by Ken Teegardin.
和许多数字化的东西一样,表单已经被之前的形态严重影响。我们之所以往线框里填东西是因为我们以前在纸上就是这么画的。
我们在纸上主要的输入法是钢笔或铅笔。现在已经不一样了,我们被上百年的约束限制了自己。
技术已经从这些约束中解放了我们。我们已经拥有了创建更人性化的人机交互的工具。
Spike Jonze’s film “Her” provides an interesting prediction for how we might interact with computers in the future.
我们已经很接近在语音识别、自然语言处理和人工智能等方面与人类进行有意义的对话了。甚至我们的工具已经在构建足够优秀的体验了。
所以我们回到表单。我们该如何使用这些工具使得表单更人性化呢?
我们需要摆脱之前对于表单界面的预设。聚焦在通过技术构建一个更佳自然的体验,而不是去除操作层面的约束。
A conversation with Facebooks chat bot “Poncho”.
在过去几年中,我们已经看到了一些新产品致力于通过科技让我们的交互更自然。Siri、Alexa、chat bots 都让我们朝着正确的方向发展,但是我们还没有看到这些创新以某种方式融入到浏览器界面中。
我们有非常多的潜力在更加会话式的 web 界面上,当我们需要收集数据时,我们仍然从一堆输入框和下拉框中构建表单。
有些人在推动这件事。保险服务 Jack 最近发布了一个令人印象深刻的页面来收集保险报价所需的细节。
The “Get a Quote” page from withjack.co.uk
虽然回复依然是被约束的,但是这个收集数据的设计流程已经创造出了更加愉悦和友善的体验。
向用户展示一个标准的 web 表单因此而变得更加容易,但是这样的用户交互更像是一个会话的过程,Jack 已经创造出了更加自然的感受。
Adrian Zumbrunnen’s conversations website azumbrunnen.me
Adrian Zumbrunnen 在发布他的会话式的个人网站之后引起了互联网的关注。Adrian 设计了一个界面,通过一些回复选项来引导用户浏览他的 UI/UX 作品。Adrian 的网站巧妙的考虑到了用户如何到达他的网站并以此为信号来理解用户所处的情景。
我们的方向是对的,但感觉还是少了什么。从技术角度看,构建一个真实的会话式界面需要理解用户的意图和语境,而不仅仅是一些回复选项和位置摆放很聪明的文本框。我们需要基于已经做好的 chat bot 且开发出能够让人们用自然语言与其交流的界面。
界面甚至在开始之前就应该知道我们是谁。底层技术已经有现成的了,那就是浏览器的自动填表。你所有的细节都存在同一个地方,对于一个网站来说,一个简单的请求就可以访问。
该界面应该能够适配当前所处的情境。会话是你是从网站的帮助支持页面开始的还是从营销站点的首页开始的?这些信号可以帮助我们理解用户的语境并为其定制适当的系统回复。这些事情 Adrian 的网站并没有做。
让我们体验一下如何把用户注册流程变得更加会话式。
What would the sign up flow look like if we moved beyond forms?
我们今天拥有做这件事相应的数据,但是把所有的东西一起提供出来以创造这样一个自然的体验是真正难的地方。
再考虑牵连到可访问性、隐私、多语言支持、赋予情绪和同理心的设计。如果我们打算通过技术引入一个全新的更有意义的交互设计,这些都是我们今天要去面临和克服的挑战。
没想到我们已经走到这么远了,但是这里仍然有很多事情要做。未来就在不远处的转角,但我们要敢于去做才行。
]]>Jul 29, 2017
作者 Anil Dash 是 Fog Creek Software 的 CEO,致力于让科技变得更人性和道德一些,同时他也是 Medium 的顾问。
苹果在 Apple Park 这个漂亮的新办公楼上花了 50 亿美金,却犯了一个完全可以避免的极其昂贵的错误:让他们的程序员工作在一个开放式的格局中。这真让人惊讶。
我在 Fog Creek Software 工作,我们的联合创始人兼前 CEO Joel Spolsky 在至少 17 年前就已经针对开放式办公室对于程序员产能的糟糕影响撰文了。他在这方面的洞察基于了 Tom DeMarco 和 Tim Lister 的经典书籍《Peopleware》——该书已经出版了三十年。所以这其实不是什么全新的观点。当然在这数十年里,也已经有无数的学术研究确认了同一个结论:人们在开放式空间办公是烦躁的、注意力不集中的、常常不开心的。
这不是说开放式办公环境一无是处——它能够营造很好的协作和联络的氛围。对于市场或销售团队来说,共享空间是非常有意义的。但是对于需要身处某种工作流状态的任务来说呢?这个问题从科学的角度是有定论的。
那就是把门关上。
]]>Jul 29, 2017
作者 Anil Dash 是 Fog Creek Software 的 CEO,致力于让科技变得更人性和道德一些,同时他也是 Medium 的顾问。
苹果在 Apple Park 这个漂亮的新办公楼上花了 50 亿美金,却犯了一个完全可以避免的极其昂贵的错误:让他们的程序员工作在一个开放式的格局中。这真让人惊讶。
我在 Fog Creek Software 工作,我们的联合创始人兼前 CEO Joel Spolsky 在至少 17 年前就已经针对开放式办公室对于程序员产能的糟糕影响撰文了。他在这方面的洞察基于了 Tom DeMarco 和 Tim Lister 的经典书籍《Peopleware》——该书已经出版了三十年。所以这其实不是什么全新的观点。当然在这数十年里,也已经有无数的学术研究确认了同一个结论:人们在开放式空间办公是烦躁的、注意力不集中的、常常不开心的。
这不是说开放式办公环境一无是处——它能够营造很好的协作和联络的氛围。对于市场或销售团队来说,共享空间是非常有意义的。但是对于需要身处某种工作流状态的任务来说呢?这个问题从科学的角度是有定论的。
那就是把门关上。
现在,如果我们的工作或角色需要特定工作流的时候,那事情就能通过排除一切干扰而获益,编程或许就是这种绝无仅有的最好的例子。而苹果拥有一批这个世界上最顶尖的程序员,所以很显然应该给予他们非常好的环境。
这就是为什么华尔街日报的这篇醒目的文章里会出现这段关于苹果新总部的尤其刺眼的边注:
数以千计的 Apple Park 的雇员都会出现在 Ive 的办公视线内。很多人都将坐在开放空间,而不是以往的小办公室。程序员们都会担心他们的工作氛围太过嘈杂和注意力不集中。……
通常,公司会以预算为由把程序员安排在开放式办公室。确实让每个程序员都有一个自己的封闭办公室是一笔不小的开销。但是鉴于苹果已经在这个新园区投资了 50 亿美金,且用上了被 iPhone 设计影响了的定制抽水马桶,你很难确信这是因为省钱而做的决定。
取消私人办公室的另一个可能的原因是,也许公司并不知道它的员工们的喜好。但是这个问题是可测试的——我们不带任何倾向性的暗示,来询问一下大家希望办公室是什么样子的,看看大家的回应如何。
你曾经或现在拥有的办公室里的最好的特点是什么?
Anil Dash (@anildash)
2:47 AM - Jul 19, 2017
在数百则回复中,你会发现许多人在谈论他们多么高兴自己有一个,或希望自己有一个可以把门关上的私人办公室。
毫无疑问如果苹果和他们自己的团队交流过后,能够得到相同的回复。所以唯一剩下的可能就是在这个行业里,没有足够的人真正相信程序员值得拥有一个私人办公室。所以我们会继续大声呼吁这件事情。
来参观一下 Fog Creek 在纽约的总部
将近二十年前,我记得自己在 Joel 的博客上看到了有关 Fog Creek 新办公室的构造,后来当它完工的时候,我记得看到纽约时报热切的报道它的创新。在那时,我从未想象过我有一天会在那里工作。我看着那篇文章,脑海里幻想着自己在那里的样子,带有一点怀疑的问这些努力是否值得。
现在我得承认,当开始想象自己在 Fog Creek 拥有一间新办公室的时候,我一度被诱惑到了,我想知道通过像其它几乎每一家公司一样使用开放式办公室来省一些钱是否真的很好。这样做是很容易的。
现在 Fog Creek 已经改变了很多——我们公司大约三分之二的工作是远程进行的,尽管几乎所有人都在家中的办公室办公,你猜到了,都是关门的。同时像销售和客服这样的团队从开放式的办公区域中获益。我们可以讨论到所有人的意见一致为止。
不过即便是共享空间的团队也会因为我们在新办公室里提供了电话亭而兴奋,这会方便他们在一个私人空间里打电话。同时我们的技术人员仍然和以前一样拥有超高的工作效率,没有低效的时段,这可以归功于他们有适合他们工作的正确的环境。
最重要的是,专注于创造一个非常棒的工作环境,甚至让我们考虑新的想法,来帮助人们同时拥有两个最好的世界,犹如一辆“安静的汽车”置身于一个大型的会议室。这样的地方能让人们坐在一起但同时仍然可以享受安静和平静,灵感来源于大家最喜欢的 Amtrak amenity。
如果一家公司想把产品做得像苹果一样成功,我们永远无法给出建议。尽管我们为 Glitch 和 FogBugz 而无比的自豪,我们仍然对苹果近几十年来所做的一切深表敬意。但是我们不希望对这个有机会避免的错误袖手旁观而让其继续下去,因为这是一个苹果 (同时也是每一家自己有程序员的公司!) 不费吹灰之力就可以解决的缺点。
我们很高兴这么多公司知道在他们的雇员身上投资——从医疗保健到最新最棒的计算机硬件的一切东西。但是当他们需要集中精神的时候,也强求每个工作人员都要共享一个开放式的空间时,哪怕是最大最成功的公司也是时候 think different 了吧?
]]>原文写于 2012 年,至今已经有一段时间了,前段时间这篇文章又被大家翻出来热烈讨论,看过之后有些感触,所以翻译了一下
我是一个在美国待了 30 年的程序员,我有过一周工作超过 40 小时的经历,这在行业里面并不常见,但是我[很难][rarely]因此而得到更多的薪水。
总之,我现在发现整个做法[很恶心][nauseating]。
我并不是针对自营或创业等多干活儿就能得到更多回报的情况。我曾经在 80 年代中期到 90 年代开过两个小的软件公司,并且工作时间也很长,但是我们会共享全部的成果,而第二家公司我们在合同里就定好了多劳多得的规矩。当然这不是我们今天讨论的重点。
如果我为一家大公司工作并且谈好了薪水,那我的预期就是我在标准的时间内,即公认的 (至少在美国) 一天 8 小时一周 5 天,尽我所能完成工作。如果他们希望我每周工作 70 个小时或有些主管期望团队每天都来上班,现在的我是会拒绝的。为什么呢?
当我们决定工作赚钱的时候,我们假定工作的主要原因是为了换取我们生活所需的开销。雇员的预期是他们会获得等价于这笔薪水的产出。但问题在于,雇主概念中的价值经常和雇员的不一样,尤其在美国和亚洲。许多公司期望薪水是固定的,但是他们创造这些价值需要完成的工作是不确定的。雇主觉得只要提高对雇员的预期和要求,就能够获得更大的回报,这样他们就可以通过为每份薪水延长工作时间来降低实质的劳动成本。
这对于雇员来说意味着什么?如果你同意了,那么你实际上就认同了自己的工作更廉价。甚至这种工作其实就是无偿的。那么作为雇员你在这样的无偿工作中收获了什么呢?在绝大多数雇主面前,你什么也没有得到。如果你是一个主管,也许会得到晋升,但是作为程序员你职业发展的道路不只是做管理这一条。如果你连续几个月每周编码超过 80 个小时,通常情况下得到的回报和一周努力工作 40 小时差不多。
]]>原文写于 2012 年,至今已经有一段时间了,前段时间这篇文章又被大家翻出来热烈讨论,看过之后有些感触,所以翻译了一下
我是一个在美国待了 30 年的程序员,我有过一周工作超过 40 小时的经历,这在行业里面并不常见,但是我很难因此而得到更多的薪水。
总之,我现在发现整个做法很恶心。
我并不是针对自营或创业等多干活儿就能得到更多回报的情况。我曾经在 80 年代中期到 90 年代开过两个小的软件公司,并且工作时间也很长,但是我们会共享全部的成果,而第二家公司我们在合同里就定好了多劳多得的规矩。当然这不是我们今天讨论的重点。
如果我为一家大公司工作并且谈好了薪水,那我的预期就是我在标准的时间内,即公认的 (至少在美国) 一天 8 小时一周 5 天,尽我所能完成工作。如果他们希望我每周工作 70 个小时或有些主管期望团队每天都来上班,现在的我是会拒绝的。为什么呢?
当我们决定工作赚钱的时候,我们假定工作的主要原因是为了换取我们生活所需的开销。雇员的预期是他们会获得等价于这笔薪水的产出。但问题在于,雇主概念中的价值经常和雇员的不一样,尤其在美国和亚洲。许多公司期望薪水是固定的,但是他们创造这些价值需要完成的工作是不确定的。雇主觉得只要提高对雇员的预期和要求,就能够获得更大的回报,这样他们就可以通过为每份薪水延长工作时间来降低实质的劳动成本。
这对于雇员来说意味着什么?如果你同意了,那么你实际上就认同了自己的工作更廉价。甚至这种工作其实就是无偿的。那么作为雇员你在这样的无偿工作中收获了什么呢?在绝大多数雇主面前,你什么也没有得到。如果你是一个主管,也许会得到晋升,但是作为程序员你职业发展的道路不只是做管理这一条。如果你连续几个月每周编码超过 80 个小时,通常情况下得到的回报和一周努力工作 40 小时差不多。
在一些行业里,比如 AAA 游戏工作室,准备发布大型游戏这样的关键时刻的经历都是非常痛苦的。而你看了很多人们玩命工作然后发布没多久就下岗了的故事。当然你是可以选择休息的,但是付出的代价是多少?收益又是多少?
现在想象一下你自己是一个供应商 (我现在就是)。如果你要求在协议之上做更多的工作,那么公司付钱,供应商付出劳动。也许不会有更高的回报但不会比正常情况少。现在你是在为工作获取应有的回报。但奇怪之处在于,显然公司更倾向于根据时间付钱给你而不是你的实际产出,所以他们有的时候不会允许供应商加班。那他们为什么简单的要求雇员无偿工作或自告奋勇呢?
美国工人一般都有 10 天左右的年休假,有的时候还额外有几天病假;但是全职的美国工人评价一年只休息 5~7 天。在世界上很多地方,尤其是欧洲,政府授权 20~30 天年假,人们基本上都会把这些假期用掉。在很多国家加班并不普遍,无偿加班是极少的,甚至是非法的。人们配得上工作之外的生活,对于他们来说,只为雇主埋头工作是极其愚蠢的。而我们在美国 (以及亚洲很多地方) 很少这么思考问题。
我曾经有一个朋友,他的老板希望她的黑莓手机保持 24x7 待命状态。一年以后她拒绝并辞职了。她的老板为此大为恼火。而在那段时间她没有获得任何多余的回报。那我们为什么还这么做呢?
在欧美之间有一个很大的不同,美国的健康保险通常是和你的雇主绑定的,几乎没有其他地方给你实质的保障。如果你失业了,那么你得在有限的时间里支付一大笔 (COBRA) 费用,即便你找到了一份新工作,你的健康保险在 6 个月内也没法生效。所以对失业的恐惧感以及健康保险会让你更倾向于接受更长的无偿工作时间。感觉这个系统设计之初就想阻止你跳来跳去 (尤其是你成家之后)。在欧洲你的健康保险不会和雇主做任何绑定。如果公司想留住一个有价值的员工,他们就得采取一些积极的措施把你留住。很多欧洲国家 (和欧元区) 你很难期待或要求别人无偿加班。
另外一个无偿加班的副作用是更少的人被雇佣。如果你长期让你的雇员每周工作 60~80 小时,你就不需要雇佣更多的人。但是对于雇员来说他们收获了什么呢?基本是没有什么收获的。
我想说的重点是,如果你付钱给我,那么我为你好好工作 40 小时,如果其他人愿意工作 60 或 80 小时,他们就更有价值而我就贬值了?我就应该由于没有把人生的全部都放在工作上而被解雇?那些愿意工作两倍时间的人就真的交付了两倍于我交付的价值吗?你可以反驳如果公司是根据工作时间给员工回报的,那么工作 80 小时的人就得到两倍回报,但这只是从雇主的角度来看。而雇员创造了更多的价值 (为公司带来了更多的收入) 但是没有得到任何更多的回报。当然你可以无视我,找到更多这样的公司,但是我对这种现代的“奴隶制”并不感冒。
工作不能也不应该是一个人的生活的全部,这绝对是欧洲式的思维。生活对我来说也意味着更多。然而在美国有一种非常商业化的观点,就是如果你根据工作时间付钱,那么公司就不会成功;如果人们每年要休假 20 天,那么他们就会失败;一个雇员工作之外的生活一文不值。
我从 Steve Jobs 听到的一个有意思的故事是,在 iPhone 装备的几周前 Steve 要求他们把塑料屏幕换成玻璃的,所以他们通知中国的工厂,那边立刻把上千名工人叫起来,每人发一块饼干一杯茶水,让他们每天连续工作 12 小时,直到 iPhone 装配好。真是一个神奇的故事,但同时也是一个悲伤的故事。他们如此轻易的放弃了生活 (我相信他们还是根据工作时间得到了报酬) 甚至乐在其中,就为了有份工作。而且我从雇主这里听到用这个故事来激励大家做相同的事情——“如果你每周不工作 80 个小时,那么在中国某些人就会顶替你的工作”。而企业会通过这些引起笑声的国家取得成功。
经济是一门复杂的“科学”,我不想为此争辩太多。但是从一个个人工作者的角度看,就是一份付出一分回报。我有技能,公司有需求,我能作为一个有价值的工作者,但是这里是有度的。我不能对你或你的处境说什么,但是对我来说我的工作能力或工作期待是一个有限的范围。可能这是我的德国人的遗传,可能是因为我曾经在我的小公司每周工作 80 个小时的结果,可能我变老了也变聪明了,但是我更愿意享受工作和生活之间的平衡。
当我在 General Dynamics 的第一份工作时,我认识一位年轻的经理,他每周七天连续工作并且每天工作很长时间。有天在一个会上他突然猝死了。你说他没日没夜的工作最后得到了什么呢?
不为别人,也不为我自己。我努力工作,但到点该回家就会回家。你也应该这样。
]]>作为学生,毫无疑问是以学业为重。在校园里,大家不出意外都会把学习定为最大的目标,围着功课转。但是工作之后,你可能面对很多事情要处理,怎么有所成就?怎么赚钱?怎么照顾好自己?怎么照顾好家庭……除了目标本身不同之外,我觉得更大的不一样在于,在相当长且连续的学生生涯中,我们没有太多选择的余地和必要,即便是有什么目标,也基本上是被动接受或被灌输的。但是走出校园之后,你面对的是一个无比自由开放的社会。这个时候你需要的不是意见,而是主见。甚至你是否做好了准备,有了足够的本事从事一项工作,还是继续学习深造修炼自我,直到自己准备好为止再工作?这也需要你的主见 (并为这个主见承担相应的后果,某种角度上)。最终很多选择是开放的,权衡的,遵从自己内心的结果。
还有一点我想说的是,因为你拥有了新的目标,而且不再会有人逼着你学习,从外部给你学习上的压力,所以客观上学习的环境也没有那么好那么纯粹了,学习这件事情会逐渐变得让你渴望、珍惜、喜欢。希望你还没有因为繁重的学业对新事物新知识,尤其是表面上枯燥但实际上对你有很多帮助的东西,失去动力。我们在校园里更多的是学习知识,然后推导这些知识可以用在什么地方;走出校园之后更多的需要思考,我遇到一个问题想解决它,究竟有多少知识哪些知识可以为我所用?也许在未来的某一天你会突然觉得,当时在校园里,学习环境那么好,怎么没有多看两本书,多做些训练。所以最好不要丢下自己看书学习的习惯,给自己定个长期的学习计划,有空就多看两本书。
坦白地讲我觉得这在校园里并不是必备的技能。但是走出校园之后,它非常非常重要。这也是为什么几乎所有的公司都会给员工做沟通培训 (尽管那并不一定管用)。社交并不只是沟通而已,也不只是表达,更是聆听,是待人接物的每一个细节。我发现很多人在工作中,最简单的两件事情做不好,也不知道该怎么做,那就是:1 如何写邮件、2 如何开会。这表面上是职场礼数的范畴,也有很多文章介绍这些职场礼数,看上去就是个知识点罢了。但实际上一个人在理解和实践它的背后,反映的是修养,甚至是教养。这不是一两篇文章能够教会你的,是你平时为人处事方式的积累和感悟。
另外社交能力的重要性不止体现在工作中,体现在你步入社会之后可能会面对的各种场合的各种人,比如和同事下班之后一起组织些活动放松消遣一下,和老乡或老同学叙叙旧——这些也都是我刚毕业的时候经常会做的事情。但是这里我认为更重要的场景是:学会如何跟陌生人打交道,如何更主动的和陌生人交流,和这个社会交流。从最简单的跟陌生人问路、跟陌生的房东租房子、甚至搭讪对吧 ^_^,到跟不同性格的服务员、乘务员、售票员、公交车司机、出租车司机沟通或寻求帮助,再到你如何主动但又不会让对方和周围人尴尬的帮助别人,包括你是否会本能的路见不平挺身而出……抱歉这可能有点超出社交这个话题了。但这些都是有关联的不是吗?每个人在这个社会中都是一个独立的个体,但又是相互依赖相互依靠的一个集体社会。想真正融入这个社会,我觉得这些都是必须的。
最后,关于社交,还有一点很重要,就是在你刚刚加入一个公司或团队的时候,你周围的人一开始对你来说都是陌生人。这个社会上可能有很多适合你的机会,但它们不会主动找到你头上,这个时候需要你学会跟陌生人打交道。好的社交能力会让你的人生更有安全感,对自己办好一件事更有信心。和别人交流同样是一个完善自我认知的过程,尝试接受更多不同的观点,发现并理解不同的看问题的角度,有助于更认清自己,避免自以为是 (相信我,这种事情别人帮不上忙的,只能你自己领悟)。
我觉得找工作这件事情,更多的要从兴趣出发,而不是所谓的“前途”。我逐渐越来越认同和相信一句老话:“三百六十行,行行出状元”。首先,如果对这件事情没有兴趣,你很难“用心”做好它,这样的状态也不是长久的;第二,我们已经看得见摸得着的“前途”和“机会”,往往已经不是什么好机会了,尤其是如今互联网时代事情变化发展这么快。而且那些真正抓住机会的人往往都是在完全没人看好的时候就开始全身心的投入在这个行业或方向上,才能在“机会”来临的时候抓住它,我不觉得这些人只是更厉害的“投机主义者”,如果没有兴趣在背后趋势着这些人,他们又是怎样才能在枯燥 (往往还伴随着高风险和不确定性) 的领域里这么坚定的做到今天呢?所以我的个人建议是,找工作,就完全追随自己的兴趣和内心就好了,不要想太多“这个行业比较赚钱比较有前途”之类的——它最多是你的一个参考项。找到自己的兴趣所在,相信它,相信自己,保持专注,一定有最好的回报,剩下的东西交给运(时)气(间)就好了。
再有就是要相信专业的力量,对学问保有“敬畏之心”。这里的学问不只是纯职业技术,也包括做事方式方法等等一切社科类的研究。如果一个行业的专业性丢掉了,整个行业也就被毁掉了,最后大家会一起丢掉工作,一起失败。
这种东西我觉得跟很多已经被一份工作或长期不良型的环境所固化思想的“老家伙”们讲已经不一定有意义和效果了。但是对于打算或刚刚从校园步入社会,有理想,有抱负,承载着社会的未来的大学生们来说,我希望有机会在这方面多呼吁一下。尊重和相信专业的力量,耐得住性子,不要被一时的挫折、不走运或委屈左右,多多磨练自己,你一定不会后悔。
:)
]]>注:作者 Rand 是 Moz 的 CEO,文中反复出现两个词:IC (Individual Contributor) 和 PW (People Wrangler),分别翻译成了一线员工和经理人。
Geraldine 很喜欢她曾经在 Cranium 的工作 (西雅图的[桌游][board game]初创公司,在 Hasbro 收购他们并[裁员][layoffs][之前][prior])。她为桌游撰写问题,并为包装盒和营销材料撰写[文案][copy]。她很擅长这个。但是发生了一些奇怪的事情——他们想让她晋升。我记得她晚上回家后[非常的][endlessly][苦恼][fretting]。她不想让人们向她汇报。她不想在团队中拥有更大的责任。她只想写写东西。
这很奇怪。当我们审视一家公司的结构时,很容易发现团队需要很多高质量的一线员工 (IC) 以及少数高质量的[经理人][wranglers]。然而我们的[公司][corporate]文化和这个世界的“模式”已经让我觉得除非你要带人,否则你的影响力、薪水、利益、职位和自我价值都不会增长。
[这都什么乱七八糟的。][im calling bs]
我过去写过关于多样化成长轨迹的重要性——一线员工和经理人——但是我们最近在 Moz 花了大量的时间碰撞想法,很快会实施一个新的职位/团队的结构,最终付诸实践,我对此充满期待。
现在我会为一个在其工作岗位上做的很优秀的一线员工表达对管理的兴趣而担心。我担心这种渴望的很[重要的][significant][一部分][portion]不源自真正的管理责任感,而是因为他们想要在职业生涯和/或影响力上得到提高,并且认为这是唯一的办法。
我画了这张图来辅助[说明][illustrate]两种角色之间的不同:
(大图)
注:作者 Rand 是 Moz 的 CEO,文中反复出现两个词:IC (Individual Contributor) 和 PW (People Wrangler),分别翻译成了一线员工和经理人。
Geraldine 很喜欢她曾经在 Cranium 的工作 (西雅图的桌游初创公司,在 Hasbro 收购他们并裁员之前)。她为桌游撰写问题,并为包装盒和营销材料撰写文案。她很擅长这个。但是发生了一些奇怪的事情——他们想让她晋升。我记得她晚上回家后非常的苦恼。她不想让人们向她汇报。她不想在团队中拥有更大的责任。她只想写写东西。
这很奇怪。当我们审视一家公司的结构时,很容易发现团队需要很多高质量的一线员工 (IC) 以及少数高质量的经理人。然而我们的公司文化和这个世界的“模式”已经让我觉得除非你要带人,否则你的影响力、薪水、利益、职位和自我价值都不会增长。
我过去写过关于多样化成长轨迹的重要性——一线员工和经理人——但是我们最近在 Moz 花了大量的时间碰撞想法,很快会实施一个新的职位/团队的结构,最终付诸实践,我对此充满期待。
现在我会为一个在其工作岗位上做的很优秀的一线员工表达对管理的兴趣而担心。我担心这种渴望的很重要的一部分不源自真正的管理责任感,而是因为他们想要在职业生涯和/或影响力上得到提高,并且认为这是唯一的办法。
我画了这张图来辅助说明两种角色之间的不同:
(大图)
一线员工为他们自己及其工作负责。因为他们以一线员工的方式取得了长足的发展,所以他们的影响力变得更加广泛。一个在 Moz 的好的例子就是 Dr. Pete,他判断公司的战略指示并随之协力投入。他通过审查来协助大数据操作,通过战术指导和策略输入来辅助市场,发表如此高质量的博客和指南,甚至从头开始设计整个项目并基于他们的创意执行。他的影响遍及整个公司,横跨多个团队,和他们一同成长。他通过自己的影响力定义了这个角色,这比其它方式都好。
另一方面,优秀的经理人有义务让他们的团队开心、团结、自主,也要负责审查、指导等等。他们发展的越顺利,就越不需要“待在壕沟里”了。很多情况下,他们只会协助定义战略问题。剩下的定义范围、搜索相关答案、实现和执行统统都交给一线员工来做。一个在 Moz 的好的例子就是 Samantha Britney。她长期是一个一线员工,但现如今已经成为了经理人,帮助产品团队的几个一线员工,给予他们工作的自主性,通过工具、资源和协助把事做好,并提供作为一个经理人必要的辅导、一对一、回顾和 HR 工作。她的报告中从不会提及任何细枝末节,但总会驱动他们的项目向前。
基本上,如果你喜欢并且能够把这件事做好,那么你应该做一个一线员工。如果你喜欢 (且擅长) 把自主权交给其他人,帮助他们成长和成功,那么你应该做一个经理人。
这些一线员工和经理人之间有这样一些差别:
我和他人分享这些观点时,大部分情况是直观的。我遇到过的最大问题和一个简单的概念有关——战略战术的所有权。有一天一个 Mozzer 同事和我在这方面的看法就不一致。他觉得在 Moz 的历史上,有些团队的经理人掌握着战略和战术的所有权。个人开发者没有定义他们做什么,怎么做,如何衡量,也没有定义执行过程,他们只是接受命令。
是的这样做也行得通并且这种情况确实发生过。但是我不同意我的同事,这样做相比于,把更大的所有权交给一线员工,让他们决定做什么、何时何地、如何做,让经理人指决定谁来做已经为什么要做,效果不可能一样。诚然,很多初级经理人和一线员工之间会具有更大的内容重叠,而很多高级个人开发者会决定谁来做和为什么要做 (如上所述)。但是我强烈的相信,从长期来看,我们应该走这条路。人们的快乐便在此之上。
当 Daniel Pink 问道“是什么让我们的工作快乐?”时,答案已经很明显 (并且被很多其他学者和不太正式调查者证实):
如果个人开发者无法控制自己的工作内容并且能够掌握工作技能,他们中间优秀的人就会离开,去那些提供这种机会的公司。我们将只留下经理人,而且这会很快。
很奇怪,我是那种一线员工风格的 CEO (也许这并不都是奇怪)。我是一个高级别的一线员工,所以我和经理人有很大的职责重叠,但是我会服务所有的团队、工作和细节。我可能是最直接参与到产品和市场的人,我也经常让这些团队 Mozzer 们像对待资源和工具一样对待我。你让我写篇博客我就会去写,你让我答复一个客户我就会冲上去,你需要聊聊一个项目如何匹配更广泛的目标,以及如何改变你的做法,那就一起聊聊呗。我喜欢我汇报给这些 Moz 员工的感觉——而不是其它方式。我想这件事情永远也不会改变。
p.s. 我很喜欢 Phil Scarr 的这篇文章,它描述了自己从经理人转变为一线员工的经历以及为什么。Carin 从带领着我们的大数据团队,转变为一个产品团队的资深一线员工,我为之感到骄傲。
p.p.s. 如果我的思路不对 (或者对) 而你也有相关的经验,不妨也留言给我。我一定虚心学习——因为我一直在提醒自己,我是第一次当 CEO
]]>如果你浏览任何 Git 仓库的日志,你可能会发现那些提交信息多少有些[混乱][mess]。比如,看看这些我早年提交给 Spring 的精品:
$ git log --oneline -5 --author cbeams --before "Fri Mar 26 2009"
e5f4b49 Re-adding ConfigurationPostProcessorTests after its brief removal in r814. @Ignore-ing the testCglibClassesAreLoadedJustInTimeForEnhancement() method as it turns out this was one of the culprits in the recent build breakage. The classloader hacking causes subtle downstream effects, breaking unrelated tests. The test method is still useful, but should only be run on a manual basis to ensure CGLIB is not prematurely classloaded, and should not be run as part of the automated build.
2db0f12 fixed two build-breaking issues: + reverted ClassMetadataReadingVisitor to revision 794 + eliminated ConfigurationPostProcessorTests until further investigation determines why it causes downstream tests to fail (such as the seemingly unrelated ClassPathXmlApplicationContextTests)
147709f Tweaks to package-info.java files
22b25e0 Consolidated Util and MutableAnnotationUtils classes into existing AsmUtils
7f96f57 polishing
[呀][Yikes],比较一下这个仓库最近的提交:
$ git log --oneline -5 --author pwebb --before "Sat Aug 30 2014"
5ba3db6 Fix failing CompositePropertySourceTests
84564a0 Rework @PropertySource early parsing logic
e142fd1 Add tests for ImportSelector meta-data
887815f Update docbook dependency and generate epub
ac8326d Polish mockito usage
你更喜欢读哪个呢?
]]>如果你浏览任何 Git 仓库的日志,你可能会发现那些提交信息多少有些混乱。比如,看看这些我早年提交给 Spring 的精品:
$ git log --oneline -5 --author cbeams --before "Fri Mar 26 2009"
e5f4b49 Re-adding ConfigurationPostProcessorTests after its brief removal in r814. @Ignore-ing the testCglibClassesAreLoadedJustInTimeForEnhancement() method as it turns out this was one of the culprits in the recent build breakage. The classloader hacking causes subtle downstream effects, breaking unrelated tests. The test method is still useful, but should only be run on a manual basis to ensure CGLIB is not prematurely classloaded, and should not be run as part of the automated build.
2db0f12 fixed two build-breaking issues: + reverted ClassMetadataReadingVisitor to revision 794 + eliminated ConfigurationPostProcessorTests until further investigation determines why it causes downstream tests to fail (such as the seemingly unrelated ClassPathXmlApplicationContextTests)
147709f Tweaks to package-info.java files
22b25e0 Consolidated Util and MutableAnnotationUtils classes into existing AsmUtils
7f96f57 polishing
$ git log --oneline -5 --author pwebb --before "Sat Aug 30 2014"
5ba3db6 Fix failing CompositePropertySourceTests
84564a0 Rework @PropertySource early parsing logic
e142fd1 Add tests for ImportSelector meta-data
887815f Update docbook dependency and generate epub
ac8326d Polish mockito usage
你更喜欢读哪个呢?
过去的信息从长度到形式都很多样;最近的信息比较简洁且一致。过去的信息是一般情况下会发生的;最近的信息绝不是偶然发生。
虽然很多仓库的日志看起来像是过去的,但也有例外。Linux 内核和 Git 自身就是伟大的例子。再比如 Spring Boot 或其它由 Tim Pope 管理的仓库。
这些仓库的贡献者知道,对于一个开发同事来说 (其实对未来的自己也是一样),一条用心撰写的 Git 提交信息是用来沟通这则改动最好的上下文。一个 diff 会告诉你什么改变了,但是只有提交信息能正确的告诉你为什么。Peter Hutterer 阐述得非常好:
重建一段代码的上下文是非常费时费力的,这是无法完全避免的。所以我们应该努力尽可能的减少它。提交信息可以帮上这个忙,也正因为此,一个提交信息反应了一名开发者是不是个好的协作者。
如果你对于创建一个伟大的提交信息还没有想过太多,那说明你可能还没有在 git log
及相关的工具上花费太多的时间。这里有一个恶性循环:因为提交历史不成体系且不一致,我们就不会花更多的时间使用和关心它。因为它得不到使用和关注,所以它就一直不成体系且不一致。
但是用心写出来的日志是美丽且实用的。git blame
、revert
、rebase
、log
、shortlog
以及其它子命令就是生命的一部分。回顾其他人的提交和 pull requests 变成了值得去做的事情,并且可以快速独立完成。理解最近几个月或几年为什么发生了这些事情不止是可能的并且是高效的。
一个项目的长期成功靠的是其可维护性,以及一个拥有比项目的日志更强大的工具的维护者。这里值得花时间学习一下如何正确的考虑它。一开始可能是个麻烦的东西很快会变成习惯,并且最终变成一切投入的自豪和产能的源泉。
在这篇文章中,我只会致力于保障一个健康的提交历史的最基本要素:如何撰写一份个人提交信息。这里还有其它重要的实践比如压缩提交 (commit squashing) 就不是我在这里想说的。可能会为此再写一篇吧。
大多数编程语言都建立了良好的编码规约,以形成惯用的风格,比如命名、格式化等。当然在这些编码规约中有一些差异,但是大多数开发者赞同取其一并养成习惯好过每个人都选择自己的风格而发生混乱。
一个团队的提交日志方法应该是一致的。为了建立一个有用的修订历史,团队应该首先约定一个提交信息的规约,该规约至少定义以下三方面:
**样式。**标记句法、缠绕边距、语法、大小写、标点符号。把这些东西都找出来,去除猜测,把规则定的尽量简单可行。最终的产出将会是不同寻常的一致的日志,不只是乐于阅读,实际上也让阅读变成了一种习惯。
**内容。**提交信息的正文 (body) (如有) 应该包含什么样的信息?不应该包含什么?
**元数据。**Issue 追踪 ID、pull request 号等信息如何放进来?
幸运的是,这里有一些已经被良好建立的规约,用来创建惯用的 Git 提交信息。事实上,有些规约中很多都是以某种 Git 命令的方式工作的。不需要你重新发明任何东西。只需遵循下面七大法则,你就可以像专家一样进行提交:
比如:
Summarize changes in around 50 characters or less
More detailed explanatory text, if necessary. Wrap it to about 72
characters or so. In some contexts, the first line is treated as the
subject of the commit and the rest of the text as the body. The
blank line separating the summary from the body is critical (unless
you omit the body entirely); various tools like `log`, `shortlog`
and `rebase` can get confused if you run the two together.
Explain the problem that this commit is solving. Focus on why you
are making this change as opposed to how (the code explains that).
Are there side effects or other unintuitive consequences of this
change? Here's the place to explain them.
Further paragraphs come after blank lines.
- Bullet points are okay, too
- Typically a hyphen or asterisk is used for the bullet, preceded
by a single space, with blank lines in between, but conventions
vary here
If you use an issue tracker, put references to them at the bottom,
like this:
Resolves: #123
See also: #456, #789
在 git commit
的 manpage 手册中写到:
虽然不是必须的,但是你最好以一句少于 50 个字符的话简短概括你的改动,然后空一行,再深入描述。提交信息中空行之上的文本会被当作提交的标题,该标题在 Git 中到处都会用到。比如 Git-format-patch(1) 会把一个提交转换为一封电子邮件,它会把这个标题作为邮件的主题,其余的部分会作为邮件的正文。
首先,不是每一次提交都同时需要一个主题和一段正文。有的时候单独一行就可以了,尤其是当改动很简单没有更多必要的上下文的时候。比如:
Fix typo in introduction to user guide
无需说更多;如果读者好奇到底修复了什么 typo,她可以通过诸如 git show
或 git diff
或 git log -p
简单看看改动的内容就可以了。
如果你是在命令行中提交,则很容易使用 git commit
的 -m
选项:
$ git commit -m"Fix typo in introduction to user guide"
然而,当一个提交值得一些解释和上下文的时候,你需要撰写正文。比如:
Derezz the master control program
MCP turned out to be evil and had become intent on world domination.
This commit throws Tron's disc into MCP (causing its deresolution)
and turns it back into a chess game.
带正文的提交信息并不便于通过 -m
选项来撰写。你最好找一个合适的文本编辑器撰写信息。如果你并没有在命令行中为 Git 设置过编辑器,那么请移步阅读 Pro Git 的这个章节。
当你在任何情况下浏览日志的时候,都会觉得把主题从正文中分离出来是值得的。这里有一整段日志:
$ git log
commit 42e769bdf4894310333942ffc5a15151222a87be
Author: Kevin Flynn <kevin@flynnsarcade.com>
Date: Fri Jan 01 00:00:00 1982 -0200
Derezz the master control program
MCP turned out to be evil and had become intent on world domination.
This commit throws Tron's disc into MCP (causing its deresolution)
and turns it back into a chess game.
现在运行 git log --oneline
,这个命令只会打印主题行:
$ git log --oneline
42e769 Derezz the master control program
或者,git shortlog
,这个命令会把提交按照用户分组,同样出于简洁的考虑只会打印主题行:
$ git shortlog
Kevin Flynn (1):
Derezz the master control program
Alan Bradley (1):
Introduce security program "Tron"
Ed Dillinger (3):
Rename chess program to "MCP"
Modify chess program
Upgrade chess program
Walter Gibbs (1):
Introduce protoype chess program
在 Git 里还有一些其它的情况下,会区分主题行和正文——但是如果没有它们中间的空行的话是不会正常工作的。
50 个字符并不是一个严格的限制,只是个经验之谈。保持主题行的长度以确保它可读且促使作者考虑一下最简略的表达方式足矣。
提示:如果你做总结很艰难,你可能是一次性提交太多东西了。把原子提交从中剥离出来吧 (每个主题是一个独立的提交)。
GitHub 的 UI 都会提醒这些规约。如果你输入超过 50 个字符的限制,它会警告:
而且会主题行超过 75 个字符的部分会被截断,留下一个省略号:
所以奔着 50 个字符去写,但是 72 个字符是底线。
如题。比如:
而不是:
主题行结尾的标点符号用法不是必要的。而且,当你打算控制在 50 个字符以内时,连空格都是很宝贵的。比如:
而不是:
*祈使句*就是指“说起来或写起来像是在发号施令”。举几个例子:
其实这七大法则的每一条读起来都是祈使句的 (“正文在 72 个字符处折行”等)。
祈使句听起来有一点粗鲁;这也是我们为什么不常用它的原因。但是这非常适合写在 Git 提交的主题行中。其中一个的原因就是 Git 本身就是根据你的意志命令式的创建一个提交的。
例如,使用 git merge
的默认信息读起来是这样的:
Merge branch 'myfeature'
而用 git revert
的时候是:
Revert "Add the thing with the stuff"
This reverts commit cc87791524aedd593cff5a74532befe7ab69ce9d.
再或者在一个 GitHub pull request 上点击“Merge”按钮时:
Merge pull request #123 from someuser/somebranch
所以当你以祈使句撰写你的提交信息时,你遵循了 Git 自己内建的规约。比如:
这样撰写一开始会觉得有点怪怪的。我们更多的在说话的时候使用陈述句来陈述事实。这是为什么提交信息经常读起来像:
有的时候提交信息写起来像是对于其内容的描述:
为了避免混淆,这里有一个简单原则,可以用在每一个地方。
一个 Git 提交的主题行的准确的格式应该始终完全遵循下面的句式:
比如:
注意非祈使句在这里别扭的地方:
注意:使用祈使句只在主题行中至关重要。当你撰写正文的时候就可以放下这些限制了。
Git 不会自动给文本折行。当你为一个提交撰写消息正文的时候,你必须意识到它正确的边距,并且手动折行。
这里推荐在 72 个字符处折行,这样 Git 有足够的空间,即便缩进文本也可以保证所有东西在 80 个字符以内。
一个好的文本编辑器是可以帮上忙的。比如在 Vim 中配置在 Git 提交的 72 个字符处折行非常容易。然而传统的 IDE 在给提交信息文本折行方面提供的智能支持很糟糕 (尽管 IntelliJ IDEA 在最近的版本中终于在这方面做得好一些了)。
这个来自比特币核心的提交是一个非常好的解释改动是什么和为什么的例子:
commit eb0b56b19017ab5c16c745e6da39c53126924ed6
Author: Pieter Wuille <pieter.wuille@gmail.com>
Date: Fri Aug 1 22:57:55 2014 +0200
Simplify serialize.h's exception handling
Remove the 'state' and 'exceptmask' from serialize.h's stream
implementations, as well as related methods.
As exceptmask always included 'failbit', and setstate was always
called with bits = failbit, all it did was immediately raise an
exception. Get rid of those variables, and replace the setstate
with direct exception throwing (which also removes some dead
code).
As a result, good() is never reached after a failure (there are
only 2 calls, one of which is in tests), and can just be replaced
by !eof().
fail(), clear(n) and exceptions() are just never called. Delete
them.
看一眼完整的 diff,想一下作者此时此刻通过提供这样的上下文为同事以及未来的提交者节省了多少时间。如果他不这样做,这些信息可能永远找不回来了。
在很多情况下,你可以忽略这个改动发生时的各种细节。从这个角度看,代码自己会说话 (如果代码很复杂以至于需要长篇大论的解释,那也是代码注释该做的事情)。请首先专注于弄清你产生这个改动的理由——改动前的工作方式,改动后的工作方式 (以及这样做哪里不对),以及为什么你决定以这样的方式解决问题。
你将来某一天维护它的时候也许会感激今天的你!
[和 Git 子命令同样多的原因][For-as-many-reasons-at-there-are-Git-subcommands],拥抱命令行是明智的。Git 是超级强大的;IDE 也一样,但是套路不同。我每天都使用 IDE (IntelliJ IDEA) 也用过很多其它的 (Eclipse),但是我从未见到 IDE 对 Git 的集成能够配得上命令行的易用和强大 (一旦你意识到这一点)。
某些 Git 相关的 IDE 功能是非常宝贵的,比如当你删除一个文件时调用 git rm
、当你重命名一个文件时完成相应的 git
命令。但是当你尝试提交、合并、rebase、或通过 IDE 做复杂的历史分析时,事情就分崩离析了。
当你想发挥出 Git 全部的能量的时候,命令行始终是不二之选。
记住不论你是用的是 Bash 还是 Z shell,都有 tab 补全脚本减轻忘记子命令和开关的痛苦。
按照 Doug Gwyn 的话说:“Unix 不会阻止你做愚蠢的事情,因为那会同样阻止你做聪明的事情”。C 是一个非常强大的工具,但使用它的时候需要非常小心和[自律][discipline]。学习这些纪律是绝对值得的,因为 C 是所有程序语言中最优秀的。一个自律的 C 程序员将会……
]]>按照 Doug Gwyn 的话说:“Unix 不会阻止你做愚蠢的事情,因为那会同样阻止你做聪明的事情”。C 是一个非常强大的工具,但使用它的时候需要非常小心和自律。学习这些纪律是绝对值得的,因为 C 是所有程序语言中最优秀的。一个自律的 C 程序员将会……
喜欢可维护性。不要在不必要的地方自作聪明。取而代之的是,找出最简单最易懂的满足需求的方案。诸如性能之类考量是放在第二位的。你应该为你的代码做一个性能预算,并自在的支配它。
随着你对这门语言越来越了解,掌握了越来越多能够从中获益的特性,你也应该学会什么时候不能使用它们。相比用到了很多新奇的方式去解决问题,易于新手理解是更重要的。最好是让一个新手理解你的代码并从中有所收获。像你大概去年就在维护它一样去撰写代码。
避免使用魔法。不要使用宏 (macros)——尽管用它定义常量是没问题的。不要使用 typedef 来隐藏指针或回避撰写“结构”。避免撰写复杂的抽象。保持你的构建系统简单透明。不要因为一个愚蠢的 hacky 的废物解决问题的方式酷炫就使用它。你的代码在行为之下应该是明显的,甚至不需要上下文。
C 最大的优势之一就是透明和简单。这应该被信奉,而不是被颠覆。但是 C 的优良传统是给你足够的空间施展自己,所以你可以为了一些魔术般的目的使用它。但最好还是不要这样,做个麻瓜挺好的。
辨识并回避危险的模式。不要使用固定尺寸的 buffers (有人指出这种说法并不是完全正确。我之前打草稿的时候提到了这些,但还是删掉了)——始终计算你需要分配的空间。阅读你使用的函数的 man 手册并掌握他的成功有出错模式。立刻把不安全的用户输入转换为干净的 C 结构。如果你之后会把这些数据展现给用户,那么尽可能把 C 结构保持到最后。要学会在使用例如 strcat 的敏感函数时多加留意。
撰写 C 有的时候像握着一把枪。枪是很重要的工具,但是和枪有关的事故都是非常糟糕的。你对待枪要非常小心:不要用枪指着任何你喜爱的东西,要有好的用枪纪律,把它当作始终上膛一样谨慎。而就像枪善于拿来打孔一样,C 也善于用来撰写内核。
**用心组织代码。**永远不要把代码写到 header 里。永远不要使用 inline
关键字。把独立的东西分开写成不同的文件。大量使用静态方法组织你的逻辑。用一套编码规范让一切都有足够的空间且易于阅读。当目的显而易见的情况下使用单字符变量名,反之则使用描述性的变量名。
我喜欢把我的代码组织成目录,每个目录实现一组函数,每个函数有属于自己的文件。这些文件通常会包含很多静态函数,但是它们全部用于组织这个文件所要实现的行为。写一个 header 允许这个模块被外部访问。并使用 Linux 内核编码规范,该死。
只使用标准的特性。不要把平台假设为 Linux。不要把编译器假设为 gcc。不要把 libc 假设为 glibc。不要把架构假设为 x86 的。不要把核心工具假设为 GNU。不要定义 _GNU_SOURCE
。
如果你一定要使用平台相关的特性,为这样的特性描述一个接口,然后撰写各自平台相关的支持代码。在任何情况下都不要使用 gcc 扩展或 glibc 扩展。GNU 是枯萎的,不要让它传染到你的代码。
使用严谨的工作流。也要有严谨的版本控制方法。撰写提交记录的时候要用心——在第一行简短解释变动,然后在扩展提交记录中加上改变它的理由。在 feature 分支上工作要明确定义目标,不要包含和这个目标不相关的改动。不要害怕在 rebase 时编辑你的分支的历史,它会让你的改动展示得更清晰。
当你稍后不得不回退你的代码时,你将会感激你之前详尽撰写的提交记录。其他人和你的代码互动时也同样会心存感激。当你看到一些愚蠢的代码时,也可以知道这个白痴当时是怎么想的,尤其是当这个白痴是你自己的时候。
严格测试和回顾。找出你的改动可能会经过的代码路径。测试每条路径的行为是正确的。给它不正确的输入。给它“永远不可能发生”的输入。对有错误倾向的模式格外小心。寻找可以简化代码的地方并让过程变得更清晰。
接下来,把你的改动交给另外一个人进行回顾。这个人应该运用相同的程序并签署你的改动。而且回顾要严格,标准始终如一。回顾的时候应该想着,如果由于这些代码出了问题,自己会感到耻辱。
从错误中学习。首先,修复 bug。然后,修复实际的 bug:你的流程允许里这个错误的发生。拉回顾你代码的人讨论——这是你们共同的过错。严格的检查撰写、回顾和部署这些代码的流程,找出根源所在。
解决方案可以简单,比如把 strcat 加入到你的触发“认真回顾”条件反射的函数列表。它可以通过电脑进行静态分析,帮你检测到这个问题。可能这些代码需要重构,这样找出问题变得简单容易。疏于避免未来的错误才是真的大错。
重要的是记住规则就是用来打破的。可能有些情况下,不被鼓励的行为是有用的,被鼓励的行为是应该被忽视的。你应该力争把这些情况当作例外而不是常态,并当它们发生时仔细的证明它们。
C 是狗屎。我爱它,并希望更多的人可以学到我做事的方式。祝好运!
]]>原文:https://medium.com/the-vue-point/vue-2-0-is-here-ef1f26acf4b8#.6r9xjmu6x
今天我非常兴奋的官宣 Vue.js 2.0 的发布:Ghost in the Shell。历经 8 个 alpha 版本、8 个 beta 版本和 8 个 rc 版本 (矮油好巧!),Vue.js 2.0 已经为生产环境准备好了!我们的官方教程 vuejs.org/guide 也已经全面更新。
2.0 的工作自今年 4 月启动以来,核心团队为 API 设计、bugfix、文档、类型声明做出了很重要的贡献,社区中的同学们也反馈了很多有价值的 API 建议——在此为每一位参与者致以大大的感谢!
]]>原文:https://medium.com/the-vue-point/vue-2-0-is-here-ef1f26acf4b8#.6r9xjmu6x
今天我非常兴奋的官宣 Vue.js 2.0 的发布:Ghost in the Shell。历经 8 个 alpha 版本、8 个 beta 版本和 8 个 rc 版本 (矮油好巧!),Vue.js 2.0 已经为生产环境准备好了!我们的官方教程 vuejs.org/guide 也已经全面更新。
2.0 的工作自今年 4 月启动以来,核心团队为 API 设计、bugfix、文档、类型声明做出了很重要的贡献,社区中的同学们也反馈了很多有价值的 API 建议——在此为每一位参与者致以大大的感谢!
基于第三方 benchmark,数值越低越好
2.0 用一个 fork 自 snabbdom 的轻量 Virtual DOM 实现对渲染层进行了重写。在其上层,Vue 的模板编译器能够在编译时做一些智能的优化处理,例如分析并提炼出静态子树以避免界面重绘时不必要的比对。新的渲染层较之 v1 带来了巨大的性能提升,也让 Vue 2.0 成为了最快速的框架之一。除此之外,它把你在优化方面需要做的努力降到了最低,因为 Vue 的响应系统能够在巨大而且复杂的组件树中精准的判断其中需要被重绘的那部分。
还有个值得一提的地方,就是 2.0 的 runtime-only 包大小 min+gzip 过后只有 16kb,即便把 vue-router 和 vuex 都包含进去也只有 26kb,和 v1 核心的包大小相当!
尽管渲染层全面更新,Vue 2.0 兼容了绝大部分的 1.0 模板语法,仅废弃掉了其中的一小部分。这些模板在背后被编译成了 Virtual DOM 渲染函数,但是如果用户需要更复杂的 JavaScript,也可以选择在其中直接撰写渲染函数。同时我们为喜欢 JSX 的同学提供了支持选项
渲染函数使得这种基于组件的开发模式变得异常强大,并打开了各种可能性——比如现在新的 transition 系统就是完全基于组件的,内部由渲染函数实现。
Vue 2.0 支持服务端渲染 (SSR),并且是流式的,可以做组件级的缓存,这使得极速渲染成为可能。同时,vue-router 和 vuex 2.0 也都支持了可以通用路由和客户端状态“hydration”的服务端渲染。你可以通过 vue-hackernews-2.0 的 demo app 了解到它们是如何协同工作的。
官方支持的库和工具——vue-router、vuex、vue-loader 和 vueify——都已经升级并支持 2.0 了。vue-cli 现在已经默认生成 2.0 的脚手架了。
特别之处在于,vue-router 和 vuex 在它们的 2.0 版本中都已经有了很多改进:
vue-router
<router-view>
<router-link>
组件改进了导航功能vuex
它们各自的 2.0 文档里有更多的细节:
中国最大的在线订餐平台饿了么的团队已经基于 Vue 2.0 构建了一套完整的桌面 UI 组件库。不过还没有英文文档,但是他们正在为此而努力!
很多其他社区的项目也都在为 2.0 做兼容——请移步到 awesome-vue 搜索关键字“2.0”。
如果你是一个 Vue 的新同学,现在就可以“无脑”使用 Vue 2.0 了。最大的问题其实是目前 1.0 的用户如何迁移到新的版本。
为了帮助大家完成迁移,团队已经在配合 CLI 迁移辅助工具制作非常详实的迁移教程。这个工具不一定捕获每一处被废弃的东西,但相信能帮你开个好头。
中国最大的电商公司阿里巴巴的工程师们已经发起了一个叫做 Weex 的项目,通过 Vue-inspired 语法在移动端渲染 native UI 组件。但是很快,“Vue-inspired” 将会成为 “Vue-powered”——我们已经启动了官方合作,让 Vue 2.0 真正成为 Weex 的 JavaScript 运行时框架。这让用户能够撰写横跨 Web、iOS 和 Android 的通用 Vue 组件!我们的合作才刚刚开始,这将会是 2.0 发布后未来我们专注的重点,请大家拭目以待!
Vue 从一个不起眼的 side project 开始如今已经有了长足的发展。今天它已经是社区资助的,被实际广泛认可的,并且根据 stats.js.org 统计在所有 JavaScript 库中增势最强劲的一个。我们相信 2.0 会走得更远。这是 Vue 自启动以来最大的一次更新,我们期待大家用 Vue 创造出更多好产品!
]]>仅从我个人角度跟大家分享一下自己参与 Weex 开源这几个月以来的感受,中间可能会有写观点是偏颇的或者片面的,希望大家指正,另外不论怎样,这些都是我心里真实的想法和感受。
]]>仅从我个人角度跟大家分享一下自己参与 Weex 开源这几个月以来的感受,中间可能会有写观点是偏颇的或者片面的,希望大家指正,另外不论怎样,这些都是我心里真实的想法和感受。
有两个关键字:加速、共赢
我们提出来要开源的时候,在网上被很多人质疑过。有人质疑说这是个“KPI项目”,作者折腾完要“弃坑”了,所以就把它开源了;也有人质疑它的成色,也有人质疑“电商”的标签,是不是只有你们阿里用得到,别人都不太用得到。
我觉得开源最大的意义在于找到志同道合的人做出更伟大的事情,如果我们只是为了“弃坑”,那显然在4个月之前我们的工作就完成了,也不会有接下来的研发迭代、宣传、开发者服务和社区经营——最起码我自己从 Weex 还没有开源甚至还没有这个名字的时候就参与其中,一直参与到现在。我喜欢这个项目,也愿意接受这个项目带给我的各种刺激和挑战,他一直让我不断进步,有满满的收获。
话说回来,我确实看到很多社区的开源项目,自己厂的、友商的、个人的,确实有“弃坑”的意味,感觉源代码丢到 github 就没事了。我觉得这种开源不是没有价值,但价值是约等于 0 的。这段时间关注奥运会,也一下子想起奥林匹克之父顾拜旦老人家的一句名言:“生活的本质不在于索取,而在于奋斗!”我觉得这句话在开源社区更是如此。把代码开源出去然后撒手不管等着别人来捡,这实际上是索取,是没有意义的。关注开源项目的开发者表面上是索取,但是开发者提交的每一个 pull request、每一条 issue、甚至每一句评论和吐槽,也是在为项目做贡献。作为开源项目的参与者或作者,一定要在这方面有一个健康的心态,才能真正做出好的项目。
我还记得自己 2010 年参加 WebRebuild 交流会的时候,蒋定宇 的分享 让我印象深刻,他其中一句话我到今天还记得:
“最好的 solution 是讨论出来的”
所以如果想做出优秀的开源项目,除了摆正自己的心态,还要有一颗和别人 (甚至竞争对手和讨厌你的人) 一起共赢的心。尽可能团结一切可以团结的力量。让这个项目变得更好!
团队内部从去年双十一之后宣布开源计划,到从4月份 QCon 开始邀请开发者陆续参与进来,再到6月底正式全面开源,一共经历了大概了大半年的时间。团队是把 Weex 开源这件事情当做一个工程来认真对待的,这里可以跟大家分享一些我们背后做的准备工作:
这是集团很早就定下来的规矩,我理解这件事情更大程度上是“怕出事”,不要不小心把不该公开的信息公开出去导致集团不必要的商业损失。我觉得这理所当然,同时这只是个最低要求。
当然集团今天对待开源已经不是“怕出事”这么简单了,我自己能够感觉到,集团新成立的开源委员会,除了通过这个流程帮助开发者打消不必要的顾虑之外,更多的希望我们能够通过开源的方式让一件事加速和共赢。这是我参与 Weex 开源过程中明显感受到和之前不一样的地方。
除了上面的“硬性”准备工作之外,对 Weex 团队更大的挑战在于工作方式的转变。在阿里有句土话,“能电话不邮件”,讲求的是密切沟通、快速响应,阿里的很多团队也是因此具有其他团队不曾想象的做事决心和执行力。在开源社区,面对海量的开发者一起参与,还要做好开发者服务,这种工作方式是不合适的。我们需要大量依赖线上的、异步的、远程的、开放的工作模式。
在团队内部,我们有意识的把所有的工作讨论和任务安排,能公开出来的,就全部公开在 github issue 里。随着团队成员的增多,我们的团队有杭州、北京、广州的,大家分散办公,然后在线上沟通,把不需要当面或同步沟通的工作大方的区分出来。表面上看,异步沟通增加了团队的沟通成本,但实际上,异步沟通让能够参与进来的人变多了,而且不仅限于杭州的某一个办公区或会议室的人,彼此也可以更自在灵活的安排自己的工作和行程。这给了项目组很多想象和发挥的空间。
甚至不只是 Weex 这个项目,我希望集团层面都可以更多的尝试远程协作和异步沟通,这是一种有魔力的体验。
在筹备期间,我有幸和集团的几位开源的前辈聊到过我们的开源设想,印象最深刻的一个问题就是:
“你是否确定,如果有一天你的 KPI 里没有这个项目了,甚至你有一天不在阿里工作了,你会发自内心的去投入和维护它吗?”
我听完觉得前辈把话说到我们心坎儿里了。项目组核心团队里的每一个人,是把 Weex 简单当一份工作,还是发自内心的认同,做出来的东西我相信是完全不一样的。你会全职参与到一个项目里,还是兼职,有的时候兼职的效果更好,更健康。尤其是当我们从长计议的时候,对这几方面更加有感触。我们有大量的已经开源的项目,更新频度是大于半年的。这样的项目出发点都是很好的,但是结果很可惜。
我们在后期组建团队让更多人参与进来的时候特别思考了这个问题,今天在集团内部,Weex 的很多东西都是业务的同学在帮忙打理的。从项目组的角度,工作压力得到了分担和缓解;从个人的角度,在支持业务的同时,能够把一些比较解耦的工作拿来业余时间独自承担,松散的参与一些技术讨论,有自己的收获。这是两全其美的事情。
另外团队的既定工作安排是非饱和的,我们鼓励团员主动寻找值得参与和付出的地方,把项目的方方面面打理好,毕竟项目是完全对外的嘛,要“出去见人”总得把自己“打扮的漂漂亮亮的”。这是每个人都会有的心态。坦白讲这方面我们还不算做得特别好。所以有很多工作要继续做,也有很多空间给到团队。
我们主动联系了很多和 Weex 有共同志向或相关联的团队和事业部,大家在不同的角度能够看到更多不同层次的问题,也有各自擅长的领域和空间。我们希望在 Weex 项目组之外,把一个围绕着 Weex 的生态建立起来,他会让 Weex 变得更丰富饱满,更有意义,更有价值。
今天在阿里,Weex 杭州的团队已经只是参与 Weex 的所有人中一小部分了,不同的业务方,不同的技术层次上,都有不同的小伙伴在参与。
经过两个多月的筹备,我们于6月30日晚把项目正式开源了,我们在微博上做了个简单的宣传,但实际上团队当时内部压力是蛮大的,大家都很辛苦,所以我们搞了个小的 party,煞有介事的搬来一个“重大决策按钮”,很有仪式感的让大家一起把这个按钮按下去,把项目开源出来,尽量把这个过程搞得轻松愉悦一点。
开源之前大概就是这样,团队紧接着要面对的,是开源之后的漫长之路。
我总结的开源社区经营就是一个“帽子戏法”的过程,就像开淘宝店是一样的,有三顶帽子你需要轮流得把他带到自己头上:
如果你要开店,那么你首先需要备货,拥有用户满意的商品;然后找入口买流量,让别人看到你的商品;用户发现你的商品之后,你要有很好的承接和售后服务;等到商品卖出去了,用户肯定会给你沟通、评价和建议,这会作为你拥有更好商品的筹码。每个环节之间都是紧密联系的,哪个做得不够平衡都会很痛苦。
做开源项目也是一样,首先你要做出好的技术产品;然后通过各种技术宣讲机会介绍给别人;当开发者来到项目的首页或 github 仓库时,要做好服务,帮助开发者解答参与过程中的疑惑;然后在这个过程中收集到用户的反馈和意见再做产品的迭代改进。
包括4月份我们参与的 QCon 北京在内,团队先后参加了大大小小的很多场技术交流分享活动,同时在线上我们也在陆续写一些介绍 Weex 的技术文章,在宣传 Weex 的技术设想和理念的同时,也鼓励开发者更多的参与进来。
现在回想起来,最早接触开发者的时候,团队对自己还是太过自信了,心想我们一起研发并且准备了这么久,开发者过来看过一定觉得很厉害。没想到遇到了大家的各种挑战。而且被问到最多的问题是完全没有想到的:
“怎么让程序跑起来?”
然后就发现了一堆问题:比如 Windows 环境下的命令行问题、路径分隔符问题、Node 版本问题、Android 环境翻墙的问题、npm/cocoapods 镜像的问题、NDK 的问题、x86 模拟器的问题等等……
这里面有些是团队自己知道的,只是觉得太顺理成章了,没觉得应该写清楚,结果就让开发者们误解了;有些确实是自己的工作环境很单一,而社区里开发者们的工作环境是千差万别的;还有些是交代得不够清楚,明明知道也写了,但是没能让开发者很好的充分理解。
后来我才留意到技术社区里一个流传很久的笑话:
“所有的开源软件都有一个特点:根据官方文档的步骤是跑不起来的。”
原来 Landing Page、README 和 文档这么重要,这给了团队当头一棒,大家认为最简单的问题都折腾得很狼狈。看起来搞开源真的“不是你一片赤诚就能够面对的”
最早期我们和开发者所有的沟通基本都是通过 github issues 来进行的,这也蛮正常的,看人家开源项目都在 issues 上讨论的火热,好有气氛好羡慕,巴不得有人在我们自己的 issues 上多聊个两句,哪怕是闲聊,总比冷冷清清无人问津的好。
后来发现完全不是我们想象的那样,我们真的是想多了,实际情况是各种 issue 洪水猛兽版袭来,大家的 github 账号默认都是可以收到每个 issue 的邮件提醒的,然后瞬间邮箱就被炸瘫痪了,正常的研发迭代也被应付这些 issue 变得支离破碎。
后来我们发现其实 issue 其实并不都适合处理所有的问题,有些使用上的小问题,在 issue 上几个来回讨论清楚,一个小时甚至一上午就这样过去了。而且问题多了之后,把 issues 上正常的工作内容讨论和安排都给淹没了。
这个时候就有非常热心的开发者帮我们建立了 QQ 群、微信群等社区,这种沟通方式更直接简单,回合更快,开发者遇到一个编译不通过的问题,问题抛出来在线等个几分钟就有人帮忙回应了。这样 github issues 的压力暂时得到了缓解。
后来经过几个同学的调研,我们最终把疑难杂症的解答和及时的线上讨论放到了 gitter 上,把需要跟进的事项、发现的 bug、值得追踪探讨的话题留在了 github issues 里。这样差不多是今天团队和社区开发者们协作的最终方式了。
我们根据开发者的参与度划分了几个维度:随便看看、试一试、用起来、交流互动、参与贡献。背后的需求和服务方式应该是不一样的
随着 Weex 社区参与者的增多,我们也不需要鼓励大家有事没事写个 issue 打肿脸充胖子了,而是比较自然而合理的做各种事情。我们看到两个很好的势头:
一个是开发者在 issues 里参与了很多基于 proposal 的新功能讨论,之前团队在迭代新功能的时候是自行设计排期研发实现的,逐渐的,我们把整个技术设计的过程也透明出来并且有意识的在这个阶段放慢节奏,让这个功能经过足够充分的讨论之后,再付诸实现;
另一个是很多开发者开始在集团内网和 github articles 下写了越来越多对 Weex 的理解和相关讨论,很多文章团队自己看完都私下表示开发者们写得比我自己写得都好 [偷笑],这也让团队的每一个人更受鼓舞,也更愿意跟社区分享自己的想法和真知灼见。
这两方面不论哪一方面,对 Weex 社区来说都是很好的迹象,也都一定程度鼓舞了 Weex 团队本身做得更好!
就像开源之后第一段提到的,Weex 团队除了宣传和服务开发者之外,还在保持有条不紊的版本迭代。去年 Weex 初期启动的时候,是一个7人左右的团队,通过不那么标准的 Scrum 的敏捷方式快速迭代。双十一过后,团队的规模扩大了,同时也有了非常专业的项目经理为团队保驾护航。我们基本保持着每个月一次迭代,每两个迭代发布一个版本的节奏,所以我们于5月份发布了0.5版本,7月份发布了0.6版本。
从0.7版本开始,随着团队默契度的提升,再加上整个社区逐步成型,也通过 proposal 讨论等机会给了团队很多回馈,我们加快了迭代频率,现在每个月都会完成一个新的版本,所以本月初我们发布了0.7版本。目前0.8版本也已经启动,正在紧锣密鼓的迭代过程中。
同时今年的双十一也要邻近了,团队针对今年双十一提出了更高的目标,具体内容这里不详细提及了,先卖个关子,请大家拭目以待。
今天,Weex 在近4个月的开源之路后,累计了5000+个star,并且保持着比较高的迭代速度和社区活跃度。我们在欣喜的同时,更多的是感恩,觉得自己应该对得起大家的这份关注和信任,继续做出更好的产品给大家。除了之前提到的各方面细节和感触,将来我们还有很多地方值得改进
借近期参加开源中国源创汇和JSConf的活动,也给了我一个机会从开源经历的角度重新审视了一些自己和团队做的事情。同时 Weex 在 github 的 star 也即将迈过 6000 大关,有一些感触,分享给大家。未来我们会继续努力,用自己的实际行动。
]]>Weex 的技术架构和传统的客户端渲染机制相比有一个显著的差别,就是引入了 JavaScript,通过 JS Runtime 完成一些动态性的运算,再把运算结果和外界进行通信,完成界面渲染等相关操作指令。而客户端面对多个甚至可能同时共存的 Weex 页面时,并没有为每个 Weex 页面提供各自独立的 JS Runtime,相反我们只有一个 JS Runtime,这意味着所有的 Weex 页面共享同一份 JS Runtime,共用全局环境、变量、内存、和外界通信的接口等等。这篇文章会循序渐进的介绍 Weex JS Runtime 这部分的内容,大概的章节设计是这样的:
Weex 的技术架构和传统的客户端渲染机制相比有一个显著的差别,就是引入了 JavaScript,通过 JS Runtime 完成一些动态性的运算,再把运算结果和外界进行通信,完成界面渲染等相关操作指令。而客户端面对多个甚至可能同时共存的 Weex 页面时,并没有为每个 Weex 页面提供各自独立的 JS Runtime,相反我们只有一个 JS Runtime,这意味着所有的 Weex 页面共享同一份 JS Runtime,共用全局环境、变量、内存、和外界通信的接口等等。这篇文章会循序渐进的介绍 Weex JS Runtime 这部分的内容,大概的章节设计是这样的:
如果只用一个词来回答,那就是“性能”
如果要用一段话来回答:手机上的资源是很宝贵的,包括CPU、内存、电量等等,而 Weex 团队从设计初期就决定以页面为单位对产品实现进行划分,一个完整的应用是多个相互独立解耦的页面通过一定的路由规则和链接跳转互联起来组合而成。所以为每个页面都单独提供一份 JS Runtime 代价还是比较昂贵的,这会引起大量的资源开销,手机发烫,反应迟钝,甚至应用或操作系统的崩溃。尤其是在国内一些中低端机型上面,反应尤其明显。
从另外一个角度讲,我们通过同一个 JS Runtime,可以更直接方便的做一些运行时的资源共享,比如 JS Framework 的初始化过程,只需要应用启动的时候执行一次就可以了,不必每个页面被打开的时候才进行。目前 JS Framework 的启动过程一般会在几百毫秒不等,相当于每个页面打开的时候,这几百毫秒都被节省下来了。
首先不同的 Weex 页面肯定需要执行各自的 JavaScript 运算,完成各自的 native 指令收发。所以如何避免多个 Weex 页面在同一个 JS Runtime 里相互“打架”就变得至关重要。
这里的“打架”有以下几个细节:
除了“打架”的问题之外,传统 HTML5 页面里,每个 JS Runtime 的生命周期是对应页面本身的生命周期的,相对是个短效的实例,而且一旦页面被关闭,对应这个页面的 JS Runtime 就可以大方的 kill 掉,没有任何后顾之忧;而 Weex 的 JS Runtime 需要在应用被开启之后至始至终存在并不间断工作,所以长期运转的内存管理也变成了一个不得不正视的问题。
createInstance(id, code, config, data)
:创建一个新的 Weex 页面,通过一整段 Weex JS Bundle 的代码,在 JS Runtime 开辟一块新的空间用来存储、记录和运算sendTasks(id, tasks)
:从 JS Runtime 发送指令到 native 端receiveTasks(id, tasks)
:从 native 端发送指令到 JS Runtime形如:
// old version of Weex JS Runtime
function createInstance(id, code) {
const customComponents = {}
function define(name, definition) {
// todo: register a weex component in this Weex instance
...
customComponents[name] = definition
...
}
function bootstrap(name) {
// todo: start to render this Weex instance from a certain named component
...
sendTasks(id, [...])
...
}
// run
eval(code)
}
我们在闭包中设置了这么几个东西,保障隔离效果:
define
: 用来自定义一个复合组件bootstrap
: 用来以某个复合组件为根结点渲染页面这样的话,假设有一个 Weex 页面,它的代码是这样的:
// 伪代码,并不能实际运行
// A Weex JS Bundle File
// define a component named `foo`
define('foo', {
type: 'div',
children: [
{ type: 'text', attr: { value: 'Hello World' }}
]
})
// render the page with `foo` component
bootstrap('foo')
那么 Weex 页面里的 define
和 bootstrap
表面上是全局方法,实际上只会针对当前的 Weex instance 在一个更小的作用域下执行,而不会干扰或污染全局环境或其它 Weex 页面。
这是我们最初的版本的形态。
随着 Weex JS Framework 代码的不断演进,功能也逐渐丰富起来,上层的 Weex 页面也写得越来越复杂,之前简单的 define
+ bootstrap
已经满足不了工程上的需求和设想了。这个时候我们需要引入前端资源包管理的概念,而且拥抱现有的各种成熟的包管理规范和工具。这其中包括 AMD、CMD、CommonJS、ES6 Modules 等等。这个时候 define
和 bootstrap
这两个名字就显得起得有点太大了,尤其是 define
,和 AMD 里的语法重叠,所以和很多兼容 AMD 语法的打包工具都会产生冲突。所以我们逐步把这些方法转变成了带有 Weex 特殊前缀的方法:
__weex_define__
: define
的别名,用来自定义一个复合组件__weex_bootstrap__
: bootstrap
的别名,用来以某个复合组件为根结点渲染页面同时我们可以借助各种打包工具把 Weex 页面拆成多个文件开发和维护,然后打包成一个文件完成发布和运行,以 webpack 为例,上述的例子会打包生成类似:
// 伪代码,并不能实际运行
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports) {
__weex_define__('foo', {
type: 'div',
children: [
{ type: 'text', attr: { value: 'Hello World' }}
]
})
__weex_bootstrap__('foo')
/***/ }
/******/ ]);
eval
到 new Function
​之后我们在最终执行 Weex JS Bundle 代码时,从略显简陋的 eval
命令改写成了 new Function
,即:
// old version
function define() {...}
function bootstrap() {...}
eval(code)
// new version
import { aaa, bbb } from 'xxx' // place and name your methods as you like
const fn = new Function('define', 'bootstrap', code)
fn(aaa, bbb)
用 new Function
的前几个参数定义了即将执行的 Weex JS Bundle 中“伪装”的几个全局变量或全局方法,然后运行的时候把那些背后的“伪装”传递进去,形式上更灵活,运行时更安全。
同时也是因为闭包中需要准备的变量和方法也逐渐多起来了,new Function
的写法更便于清晰的管理和对应这些内容。
可能很多同学注意到了,不论是 eval
还是 new Function
其实效率都是不高的,为什么还要这样用呢?主要的原因还是因为我们需要动态的为每个 Weex 页面创造这样的闭包。后来在 native 端我们还想到了一些变通的优化办法,即在 native 端将 Weex JS Bundle 代码包装在一个闭包里,再丢给 JavaScript 去执行。所以,如果一个 Weex JS Bundle 大代码如下:
// 伪代码,并不能实际运行
__weex_define__('foo', {
type: 'div',
children: [
{ type: 'text', attr: { value: 'Hello World' }}
]
})
__weex_bootstrap__('foo')
而客户端现在要基于这个 Weex JS Bundle 创建一个页面,instance id 为 x
,那么客户端会先为这段代码加上特殊的头和尾:
// 伪代码,并不能实际运行
// 特殊的头部代码
(function (global) {
const env = global.prepareInstance('x')
(function (__weex_define__, __weex_bootstrap__) {
// 特殊的头部代码
__weex_define__('foo', {
type: 'div',
children: [
{ type: 'text', attr: { value: 'Hello World' }}
]
})
__weex_bootstrap__('foo')
// 特殊的尾部代码
})(env.define, env.bootstrap)
})(this)
// 特殊的尾部代码
这样的话我们先通过 prepareInstance('x')
创建一个属于 x
这个 id 的方法,然后通过 function (__weex_define__, __weex_bootstrap__)
创造一个闭包,把 JS Bundle 的源代码放进去,效果和之前的实现是等价的,但是由于没有用到 eval
和 new Function
,性能有了一定的提升,在我们实验室数据中,JavaScript 运算的时间缩短了 10%~25%。
当然,由于在浏览器的环境下,我们没有机会在执行 JavaScript 之前对内容进行高性能的处理,所以 HTML5 Renderer 还没有办法通过这样的改造提升执行效率,在这方面我们还会继续探索。
包括上述提到的 createInstance()
接口在内,JS Runtime 还提供了以下几个和 native 通信的方式:
首先是 native 配置的导入:
registerComponents(components)
registerModules(modules)
这两个 API 用来让 JS Runtime 知道当前 Weex 支持哪些原生组件和原生的功能模块,及其相关的细节。
其次是实例的生命周期管理:
createInstance(id, code, ...)
refreshInstance(id, data)
destroyInstance(id)
这三个 API 用来让 JS Runtime 知道每一个页面的创建和销毁的时机,特别的,我们还提供了一个 refreshInstance
的接口,可以便捷的更新这个 Weex 页面的“顶级”根组件的数据。
最后,每个 Weex 页面在具体工作的时候会更频繁的使用到下面这两个 API
sendTasks(id, tasks)
:从 JS Runtime 发送指令到 native 端receiveTasks(id, tasks)
:从 native 端发送指令到 JS Runtime这其中,sendTasks
中的指令会以 native 的功能模块进行分类和标识,比如 DOM 操作 (dom
模块)、弹框操作 (modal
模块) 等,每个功能模块又提供了多种方法可以调用,一个指令其实就是由指定的功能模块名、方法名以及参数决定的。比如一个 DOM 操作的指令 sendTasks(id, [{ module: 'dom', method: 'removeElement', args: [elementRef]}])
。
receiveTasks
中的指令一共有两种,一种是 fireEvent
,相应客户端在某个 DOM 元素上触发的事件,比如 fireEvent(titleElementRef, 'click', eventObject)
;而另一种则是 callback
,即前面功能模块调用之后产生的回调,比如我们通过 fetch
接口向 native 端发送一个 HTTP 请求,并设置了一个回调函数,这个时候,我们会先在 JavaScript 端为这个回调函数生成一个 callbackId
,比如字符串 "x"
——其实我们实际上发送给 native 端的是这个 callbackId
,当请求结束之后,native 需要把请求结果返还给 JS Runtime,为了能够前后对得上,这个回调最终会成为类似 callback(callbackId, result)
的格式。
至此,我们就拥有了 7 个主要的接口,来完成 native 和 JS Runtime 之间的通信,同时可以做到多实例之间的隔离。
篇幅有限,所有问题不能一一展开,这里提几个我们比较有心得的地方
绝对的避免和杜绝是很难的,我们通过以下集中方式尝试缓解和回避这种现象出现,部分想法还在论证当中:
sendTasks
的时候返回一个特殊的值来提示 JS 代码可以省去后续的计算,让整个 JS 阻塞的状态立即恢复。openURL
这个打开网址的命令是个很长的链路,但如果我们支持 <a href>
这样的标签,用户点击链接的时候,页面可以不经过 JS 运算直接跳走,这样回避了 JS 阻塞带来的问题。更多的思路和想法等待大家的挖掘和探讨。
其实现在在 Weex 页面里,不经过声明给一个变量赋值还是会产生全局环境的污染,我们短期只能通过宣导的方式,教育开发者避免使用全局变量——这在传统的 HTML5 和 JavaScript 开发中都是特别不推荐的做法。
长期来看,我们可以提供一些发布前的语法检测工具,帮助开发者更好的驾驭自己的代码。
从隐私性的角度,如果你的客户端是多个团队共同研发的,相互之间希望不被打扰,我们也可以考虑引入浏览器中比较广泛实践的“同源策略”,根据 JS Bundle URL 的域名区分对待。
让 Weex 能够支持多种 Framework 共存,既是满足多方业务团队不同技术栈和需求的一个重要决定,同时也是尊重前端社区固有的开放自由的精神,更是让 Weex 在快速更迭的前端技术栈中立于不败之地的基础。
早期的 Weex 是重度依赖我们自身研发的 JS Framework 的,它基于 Vue 1.x 的数据监听机制,配合 Weex virtual-DOM APIs 进行数据绑定,并沿用了 mustache 的经典模板语法。现如今,Vue 2.0 迎来了很多颠覆式的革新和改进、React 也被越来越多的工程师所接受,Angular、Zepto/jQuery、VanillaJS 也都有众多的前端开发者在使用。所以我们在支持 native 端多实例指令分发的同时,也支持了多 JS Framework 本地部署并相互隔离,可以支持不同的 Weex 页面基于各自的 JS Framework 开发运行。
首先我们约定,每个 Weex 页面的 JS Bundle 第一行需要出现一行特殊格式的注释,比如:
// { "framework": "Vue" }
...
...
...
它能够识别当前 Weex 页面所对应的 JS Framework,比如这个例子是需要 Vue 来解析的。如果没有识别出合法的注释,则被认为对应到默认的 Weex JS Framework。
然后把每个 Weex 页面及其对应的 JS Framework 名称的关联关系记录下来。
最后把上面提到的 JS 和 native 通信的 createInstance
, refreshInstance
, destroyInstance
, sendTasks
, receiveTasks
等接口在每个 JS Framework 中都封装一遍,然后每次这些全局方法被调用的时候,JS 都可以根据记录下来的页面和 JS Framework 的对应关系找到相应的 JS Framework 封装的方法,并完成调用。
这样每个 JS Framework,只要:1. 封装了这几个接口,2. 给自己的 JS Bundle 第一行写好特殊格式的注释,Weex 就可以正常的运行基于各种 JS Framework 的 页面了。
这篇文章主要介绍了 Weex 在 JS Runtime 这个环节的一些现状,以及它的来龙去脉,同时介绍了一些心得经验和特别的地方。篇幅有限,有些东西描述的还是比较简略,感兴趣的同学可以移步我们的 github 了解更多细节,同时欢迎大家一起参与到我们的开源项目建设当中来!
谢谢
]]>SPA 往往和 Router、页面间通信、页面间数据共享这些词汇联系在一起,不少同学直接问到这些词汇,实际上都是以 SPA 为前提的,因为脱离 SPA 的概念,这些词汇将失去它原有的意义,或者变成了完全不同的东西。
那 SPA 不是理所当然、天经地义、不容挑战的吗?干嘛要脱离 SPA 的概念讨论这些问题?
我觉得不是
SPA 背后的命题是如何管理复杂的页面关系,最终构成一个产品的整体。传统的页面之间是通过简单粗暴的“页面跳转”和“浏览器前进/后退”建立联系的,SPA 提出的观念是在浏览器中模拟页面的跳转、切换和前进/后退,相关的很多周边命题也随之而生。
(其实你不觉得这也是个"全家桶"么……)
所以我们先把问题回归到如何管理复杂的页面关系
]]>SPA 往往和 Router、页面间通信、页面间数据共享这些词汇联系在一起,不少同学直接问到这些词汇,实际上都是以 SPA 为前提的,因为脱离 SPA 的概念,这些词汇将失去它原有的意义,或者变成了完全不同的东西。
那 SPA 不是理所当然、天经地义、不容挑战的吗?干嘛要脱离 SPA 的概念讨论这些问题?
我觉得不是
SPA 背后的命题是如何管理复杂的页面关系,最终构成一个产品的整体。传统的页面之间是通过简单粗暴的“页面跳转”和“浏览器前进/后退”建立联系的,SPA 提出的观念是在浏览器中模拟页面的跳转、切换和前进/后退,相关的很多周边命题也随之而生。
(其实你不觉得这也是个"全家桶"么……)
所以我们先把问题回归到如何管理复杂的页面关系
我觉得有以下几个子命题 —— 在这之前,我想先把接下来所有的讨论,从形态上收敛到手机应用,PC 端的暂时不涉及。
怎么把一个完整的产品以页面为维度进行拆分,这里是有学问的。
其中最关键的一个认知问题就是页面的颗粒度是否就是整个手机屏幕同时可以呈现的所有内容?
如果是,那么页面本身的结构就是简单的一维模型,即所有的页面都可以完全独立工作运行。
如果不是,那么有可能某个页面是另外一个页面的一部分,两者之间有包含关系。这样的话页面结构就变成了多维的。
在 PC 上,答案显然是后者,因为 PC 的屏幕比手机要大得多,所有页面都要整屏更换和依赖,显然是不合理的,但是在手机屏幕上,我们有机会简化为前者。
也可以从另外一个角度讲,不说怎么解耦,而是不同的页面之间保留了哪些耦合。
这个地方的设计直接决定了大规模并行研发产品的可能性和实际的效率效果
<a>
链接、location
设置、历史管理 API等手机淘宝的工程传统是一种非常简单粗暴直接有效的理念 —— 可能你看下来会有这种感觉
首先我们没有实践 SPA —— 准确的讲鲜有成功实践的 SPA 案例,但解决上述问题有一些自己细节上的思考
<iframe>
可以使用 Web Messaging API 来进行通信,但总体上确实场景非常少所以大家会发现,我们为了更好的工程实践和大规模并行研发方面做了一定的取舍
经过上述分析和论述,我们也逐步滤清了 Weex 在这个复杂问题上的思路:如何解决 Weex 中路由管理的问题?如何在 Weex 上进行 SPA 实践?如何让 Weex 页面之间通信或共享数据?其实面对这么多看似复杂混乱的问题,只要从上述几个角度抓住重点问题,提出关键解法,就可以把复杂问题逐一解开。
<web>
/<embed>
这样的组件,可以管理子页面,但这里我们延续了手机淘宝对待子页面状态定位的看法,不做中心化的设计,每个页面可以自由定义识别规则 —— 当然这个规则范围就不包含 path 了 —— 因为这需要模拟页面的跳转和切换,所以只有 query 和 hash 可以识别<a>
链接和 openURL()
方法,对于 <web>
, <embed>
中产生的跳转和切换命令,我们目前还没有具体的设计和实现,将来可以提供类似 a[target]
的配置项,让页面跳转和切换的时候可以指定父页面或子页面作为目标最后总结下来,如果大家认可我们上述的设想和取舍的话,Weex 在应用级别的工程实践上还欠缺这么几个地方:
a[target]
的配置项,在页面跳转或切换时可以指定目标页面这里首先谈了谈个人对 SPA 的认识,同时觉得 SPA 背后的命题本质上是“如何管理复杂的页面关系”。然后列出了几个关键的维度,包括如何拆分页面、如何管理耦合、如何跳转和切换页面、页面间如何通信和数据共享等等,并对比了 SPA 在这方面的表现,和手机淘宝传统的实践经验和取舍判断,最后按照相同的思维模型得出了 Weex 在这方面的选择,列出了接下来的 Actions。在整个过程中,我们可能还是会在细节问题上展开更具体的讨论,届时我们可以再伺机探讨。
谢谢
]]>之前 review 业务代码的时候就一直想说写一篇自己对 Flux 的理解和看法,不知不觉也过去蛮久了,于是这周末打起精神写了这么一篇。
这篇文章将谈一些我对 Flux 的理解和个人看法。如果您还不太了解什么是 Flux,请先移步这里。
另外文中没有特别大段的代码,以讨论架构设计和背后的道理为主,可能会显得有点枯燥,大家可以选个不太困的时候耐心读读看:)
这是 Flux 官方提供的一张说明图:
图中有四个名词:
下面逐个以我的角度做个讲解:
]]>之前 review 业务代码的时候就一直想说写一篇自己对 Flux 的理解和看法,不知不觉也过去蛮久了,于是这周末打起精神写了这么一篇。
这篇文章将谈一些我对 Flux 的理解和个人看法。如果您还不太了解什么是 Flux,请先移步这里。
另外文中没有特别大段的代码,以讨论架构设计和背后的道理为主,可能会显得有点枯燥,大家可以选个不太困的时候耐心读读看:)
这是 Flux 官方提供的一张说明图:
图中有四个名词:
下面逐个以我的角度做个讲解:
首先 View 是视图,是用户看得见摸得着的地方,同时也是产生主要用户交互的地方,这个概念在 MVC 和 MVVM 架构中都是有的,有些观点认为虽然这几种架构里都有 View,但是定义不太一致,有细微的差别,我自己觉得这种差异确实是存在的,但在一开始这并不妨碍我们理解 View 这个名词。
然后是 Store,它对应我们传统意义上的 Data,和 MVC、MVVM 里的 Model 有一定对应关系。你问我它们为啥不直接叫 Data 算了,那这就是文化人和小老百姓表达方式的差别。当然了我只是想尽量降低理解成本,尝试用比较通俗的说法把问题说清楚。
然后是 Action,这看上去是一个新概念,实际上我还是能找到一些帮助大家理解的名词,叫做 Event。就是一个结构化的信息,从一个地方传递到另一个地方,整个过程就是一个 Action/Event。
最后是 Dispatcher,多说一句,我觉得正是因为有了 Dispatcher 才让前面三个名词变得有新鲜感。也是理解 Flux 的关键。言归正传,Dispatcher 算是从 Action 触发到导致 Store 改变的镇流器。比一般架构设计里直接在“Event”逻辑中修改“Data”更“正规”。所以土得掉渣的 Event 变成了 Action,土得掉渣的 Data 变成了 Store,土得掉渣的 View 仍然是土得掉渣的 View。
因为“正规”
传统 MVC 被 Flux 团队吐槽最深的,表面上是 Controller 中心化不利于扩展,实际上是 Controller 需要处理大量复杂的 Event 导致。这里的 Event 可能来自各个方向是 Flux 吐槽的第二个点,所以不同的数据被不同方向的不同类型的 Event 修改,数据和数据之间可能还有联系,难免就会乱。
所以和 Dispatcher 配合的 Store 只剩下了一个修改来源,和 Dispatcher 配合的 Action 只剩下了约定好的有限几种操作。一下子最混乱的地方变得异常“正规”了。架构复杂度自然就得到了有效的控制。
另外还有一个蛮好理解的点是:Action 不仅仅把修改 Store 的方式约束了起来,同时还加大了 Store 操作的颗粒度,让琐碎的数据变更变得清晰有意义。
另外,这两个地方抽象之后数据操作变得“无状态”了,所以可以根据 Action 的历史记录确定 Store 的状态。这个让很多撤销恢复管理等场景成为了可能。
综上所述,在 Flux 架构中,数据修改的颗粒度变大,更有语义;上层数据操作的行为更抽象化,碎片化程度降低。
不是,只要在传统架构的基础上注重对数据操作和用户/客户端/服务器行为的抽象定义,Flux 架构中提到的各种好处大家都享受得到。
我们就拿被 Flux 黑得最惨的那个“一大堆 V 和一大堆 M 只有一个 C”的例子好了,图中每个 View 找到不一样 Model 进行操作时,我们把这些操作抽象成 Action,然后通过中心化的逻辑找到相应的 Model 完成修改,其实就是 Flux 了。这里抽象出来的 Action 一定要和图中 Controller 能够接受到 Action 一样,没有什么特殊的地方。
基于这样的理解,Redux 提出了另外的对 Flux 架构的理解:
本质上同样是对数据操作和上层行为的抽象,另外从实现层面更加 functional。
Vuex 是基于 Vue.js 的架构设计,稍后再展开说我的看法。
(咳咳咳~~~ 这个问题我得谨慎回答)
我觉得 Flux 架构没有把一个事实告诉大家,就是它的 Store 是中心化的,Flux 用中心化的 Store 取代了它吐槽的中心化的 Controller。
我看了一些基于 Flux/Redux/Vuex 架构的实现,基本上多个 Store 之间完全解耦不建立任何联系是不可能的——除非它们完全从数据行为各方面都是解耦的——这种程序用什么架构都无所谓的坦白讲。
为什么中心化的 Store 无人吐槽呢?因为中心化的数据复杂度绝对低于中心化的行为控制。你甚至没有意识到它是中心化的,这其实从另外一个侧面就证实了这一点。
所以我觉得透过 Flux 看架构的本质:这里不算是坑或吐槽,我更想说的是,放下 Flux 这把锤子,我们该怎么看世界,怎么看待自己每天在设计和架构的软件。
在这几个方面,如果一个架构师能够做到极致,去TM的各种架构缩写,用哪个都一样。
我先说我觉得 Vue.js 怎么样,Vue.js 天生做了几件事:
所以 Vue.js 本身已经提供了很多很好的架构实践。但这在 Flux 看来还不够纯粹,它缺 2 点:
所以 Vuex 需要做的事情很简单:
这样在 Vue 的基础上,再加上如虎添翼的 Vuex,开发者就可以享受到类似 Flux 的感觉了。
是的,我觉得这是一个被用烂的词,以至于很多人在求职面试的时候一被问到 Flux 就脱口而出“单向数据流”,几乎当做 Flux 这个词的中文翻译在回答。就好像一说到 Scrum 就脱口而出“看板”一样……
我觉得单向数据流的讲法太过表面,不足够体现出 Flux 的设想和用意。现在一提单向数据流,我脑中第一个浮现的画面其实是这个:
这是数据操作颗粒度变大之后的名词。我觉得它只是个名词,为什么这样说?
所为“时空穿梭”,本质就是记录下每一次数据修改,只要每次修改都是无状态的,那么我们理论上就可以通过修改记录还原之前任意时刻的数据。
大家设想一下,其实我们每次对数据最小颗粒度的、不能再分解的、最直接的操作基本 (比如赋值、删除、增减数据项目等) 都是无状态的,其实我们如果写个简单的程序,把每次直接修改数据的操作记录下来,同样可以很精细的进行“时空穿梭”,但没有人提这个词,因为它颗粒度太细了,没有语义,没有人愿意在这样琐碎的数据操作中提炼“时空”。因为数据操作的颗粒度变大了,所以变得直观,有语义,易于理解,对我们的功能研发和调试有实际帮助,所以才有了“时空穿梭”这个概念。
这是我最后想说的,首先不管有没有 Flux/Vuex,一个好的架构实践已经足以满足日常的研发需求,尤其是在手机上,界面、数据和行为都不会特别复杂。
其次,如果基于 Vue 2.0 来开发 Weex 页面或应用的话,Vuex 是天生支持的,不需要额外做什么。大家如果已经在浏览器中,不论是桌面还是手机上实践过 Vuex,应该是感觉不到任何不一样的。
最后,上周我简单写了个 Vuex 的复刻版,能够在 Weex 的 JS Framework 上工作,这里不想占太多篇幅介绍。坦白讲我希望大家更多的精力在理解 Flux 和 Vue 上。其它问题都是顺理成章的。
这篇文章整理了我个人对 Flux 的理解和个人看法,首先解释一下 Flux 核心的四个名词:View, Store, Action, Dispatcher,然后提出 Dispatcher 在 Flux 架构中的关键位置,并解释为什么 Dispatcher 让其他三者变得更好更“正规”,然后是一些我通过了解 Flux 认识到的背后倡导的架构设计的最佳实践的提炼。
真的没有代码……
……好吧如果一定要看代码可以看看这里
谢谢
]]>之前 Vue 2.0 发布技术预览版 到现在差不多三个月了,之前写过一篇简单的 code review,如今三个月过去了,Vue 2.0 在这个基础之上又带来了不少更新,这里汇总 beta 以来 (最新的版本是 beta 4) 的主要更新,大家随意学习感受一下
首先 Vue 2.0 对 alpha、beta 有自己的理解和设定:alpha 版本旨在完善 API、考虑所需的特性;而来到 beta 版则会对未来的正式发布进行充分的“消化”,比如提前进行一些必要的 breaking change,增强框架的稳定性、完善文档和周边工具 (如 vue-router 2.0 等)
Vue 本身的语法基础这里就不多赘述了,网上有很多资料可以查阅,我们已经假定你比较熟悉 Vue 并对 2.0 的理念和技术预览版的状态有一定的了解。
]]>之前 Vue 2.0 发布技术预览版 到现在差不多三个月了,之前写过一篇简单的 code review,如今三个月过去了,Vue 2.0 在这个基础之上又带来了不少更新,这里汇总 beta 以来 (最新的版本是 beta 4) 的主要更新,大家随意学习感受一下
首先 Vue 2.0 对 alpha、beta 有自己的理解和设定:alpha 版本旨在完善 API、考虑所需的特性;而来到 beta 版则会对未来的正式发布进行充分的“消化”,比如提前进行一些必要的 breaking change,增强框架的稳定性、完善文档和周边工具 (如 vue-router 2.0 等)
Vue 本身的语法基础这里就不多赘述了,网上有很多资料可以查阅,我们已经假定你比较熟悉 Vue 并对 2.0 的理念和技术预览版的状态有一定的了解。
ref 的写法由 <comp v-ref:foo>
变成了 <comp ref="foo">
,更加简单,同时动态数据的写法是 <comp :ref="x">
支持 functional components,这个特性蛮酷的,可以把一个组件的生成过程完全变成一个高度自定义的函数执行过程,比如:
Vue.component('name', { functional: true, props: ['x'], render: (h, props, children) { return h(props.tag, null, children) } })
你可以在 render()
函数里写各种特殊的逻辑,这样标签的含义和能力都得到了非常大的扩展,在后续的几次更新中,你马上会感受到一些 functional components 的威力
另外剧透一下,h
方法里的第二个参数如果是 null
就可以省略,这个改动出现在了 beta 1
可以设置特殊的 keyCode,比如 Vue.config.keyCodes.a = 65
,然后你就可以写 <input @keyup.a="aPressed">
了
init
改成了 beforeCreated
(大家可以在 Vuex 的源码里看到对应的改变哦)Vue.transition
的 hook 支持第二个参数,把 vm 传递进去如:
Vue.transition('name', {
onEnter (el, vm) {
...
}
})
update
的触发时机发生了变化,由于 functional component 等概念的引入,一个 directive 的变更的颗粒度也不完全是 directive 本身引起的,所以这里做了一个更具有通用性的调整;同时 hook 名 postupdate
也相应的更名为 componentUpdated
——如果你想让 update
保持原有的触发时机,可以加入一句 binding.value !== binding.oldValue
即可。Vue.traisition
的 hook 名做了简化
onEnter
-> enter
onLeave
-> leave
server.getCacheKey
更名为 serverCacheKey
,避免多一层结构嵌套createRenderer
/createBundleRenderer
方法不会强制应用 lru-cache
,而是开发者手动选择<transition>
标签来了!
其实这个玩意儿我之前在 polymer 等其他框架里也见到过,不过看到 Vue 的语法设计,还是觉得巧妙而简洁:
<transition>
<div v-if="...">...</div>
</traisition>
<transition-group tag="ul">
<li v-for="...">...</li>
</traisition-group>
更牛掰的在这里,还记得 functional components 吧,你今天可以这样抽象一个动画效果的标签:
Vue.component('fade', {
functional: true,
render (h, children) {
return h('transition', {
props: {...},
on: {
beforeEnter,
afterEnter
}
}, children)
}
})
然后
<fade>...</fade>
就可以实现高度自定义的动画效果了,这个我个人觉得是非常赞的设计和实现!
<comp @click.native="..."></comp>
,beta 3 的时候终于看到它被实现了,嘿嘿,有点小激动<div :xxx.prop="x">
和 <div v-bind:prop="{ xxx: x }">
来对 DOM 的 property 进行绑定,最近我自己也在思考一些在 virtual-DOM 上支持 properties 而不只是 attributes 的想法,这个设计让我也多了一些新的思路。2 天前发布的,其实这个版本以 bugfix 为主
以上是近期 Vue 2.0 的一些更新,让我自己比较兴奋的主要是 functional component 以及基于这个设计的 <transition>
和 <transition-group>
标签和自定义 transition 标签的能力拓展,还有就是久违的 <comp @click.native="..."></comp>
最后希望大家可以多多试用,有更大兴趣的可以多多学习 Vue 的源码!
]]>文字介绍稍后抽空再补补
]]>首先,当我第一次看到 Vue 2.0 的真面目的时候,我的内心是非常激动的
来个简单的 demo,首先把 dist/vue.js
导入到一个空白的网页里,然后写:
当然,在大家阅读下面所有的内容之前,先想象一下,这是一个运行时 min+gzip 后只有 12kb 大小的库
<script src="./dist/vue.js"></script>
<div id="app">
Hello {{who}}
</div>
<script>
new Vue({
el: '#app',
data: {who: 'Vue'}
})
</script>
你将看到 "Hello Vue"
然后再看一个神奇的:
<script src="./dist/vue.js"></script>
<div id="app"></div>
<script>
new Vue({
el: '#app',
render: function () {
with (this) {
__h__('div',
{staticAttrs:{"id":"app"}},
[("\n Hello "+__toString__(who)+"\n")],
''
)
}
}
data: {who: 'Vue'}
})
</script>
这个是 compile 过后的格式,大家会发现首先 #app
下不需要写模板了,然后 <script>
里多了一个 render
字段,Vue 在运行时其实是会把模板内容先转换成渲染方法存入 render
字段,然后再执行,如果发现 render
已经存在,就跳过模板解析过程直接渲染。所以在 Vue 2.0 中写一段模板和写一个 render
option 是等价的。为什么要这样设计,稍后会我们会涉及到。
首先,当我第一次看到 Vue 2.0 的真面目的时候,我的内心是非常激动的
来个简单的 demo,首先把 dist/vue.js
导入到一个空白的网页里,然后写:
当然,在大家阅读下面所有的内容之前,先想象一下,这是一个运行时 min+gzip 后只有 12kb 大小的库
<script src="./dist/vue.js"></script>
<div id="app">
Hello {{who}}
</div>
<script>
new Vue({
el: '#app',
data: {who: 'Vue'}
})
</script>
你将看到 "Hello Vue"
然后再看一个神奇的:
<script src="./dist/vue.js"></script>
<div id="app"></div>
<script>
new Vue({
el: '#app',
render: function () {
with (this) {
__h__('div',
{staticAttrs:{"id":"app"}},
[("\n Hello "+__toString__(who)+"\n")],
''
)
}
}
data: {who: 'Vue'}
})
</script>
这个是 compile 过后的格式,大家会发现首先 #app
下不需要写模板了,然后 <script>
里多了一个 render
字段,Vue 在运行时其实是会把模板内容先转换成渲染方法存入 render
字段,然后再执行,如果发现 render
已经存在,就跳过模板解析过程直接渲染。所以在 Vue 2.0 中写一段模板和写一个 render
option 是等价的。为什么要这样设计,稍后会我们会涉及到。
废话不说,来看仓库
哎呀好东西太多我都不知道该先讲哪个啦!
package.json
- - ​https://github.com/vuejs/vue/blob/next/package.json
先看这里,我个人习惯是拿到仓库之后除了 README (它没写) 就先看这个。和 1.x 相比,开发工具链还是以 rollup + webpack + karma 为主,开发的时候用 webpack 加 watch;打包的时候用 rollup 快速而且可以自动删掉没用到的代码片段;测试的时候用 karma 各种组合,包括 e2e、spec、coverage、sauce等。语法检查用了 eslint 这个似乎没什么争议和悬念。另外我发现了两个新东西:nightwatch 和 selenium-server
另外你们就选眼睛再迟钝也会看到 ssr 这个词吧!对,就是服务端渲染 Server-Side Rendering!先不急,这个最后说,你们可以先去 high 一会儿
src
​作为一个见证了一小段 Vue 2.0 成长过程的脑残粉,我得跟大家从时间线的角度介绍一下这个文件夹:
compiler
+ runtime
​早些时候 Vue 2.0 的代码还是这样分的,一半运行时,一半(预)编译时,中间会通过一个 JavaScript 的格式严格划清界限,即源代码 template + JavaScript 经过编译之后变成了一段纯 JavaScript 代码,然后这段纯 JavaScript 的代码又可以在运行时被执行渲染。
这里面奇妙的地方是:编译时的代码完全可以脱离浏览器预执行,也可以在浏览器里执行。所以你可以把代码提前编译好,减轻运行时的负担。
由于 Vue 2.0 对 template 的解析没有借助 DOM 以及 fragment document,而是在 John Resig 的 HTML Parser 基础上实现的,所以完全可以在任何主流的 JavaScript 环境中执行,这也为 ssr 提供了必要的基础
Vue 最早会打包生成三个文件,一个是 runtime only 的文件 vue.common.js,一个是 compiler only 的文件 compiler.js,一个是 runtime + compiler 的文件 vue.js,它们有三个打包入口,都放在了 entries
目录下,这是 src
里的第三个文件夹,第四个文件夹是 shared
,放置一些运行时和编译时都会用到的工具方法集。
compiler
+ runtime
+ platforms
​Wahahaha~
这要说到 Vue 2.0 的第二个优点:virtual-DOM!virtual-DOM 有很多优点,也被很多人热议,而 Vue 2.0 里面的 virtual-DOM 简直是把它做到了极致!代码非常简练,而且性能超高 (据说秒杀 React,我自己没试过,大家可以自己比比看)。在这一点上编译器的前置起到了非常重要的作用,而且很多 diff 算法的优化点而且是在运行时之前就准备好的。
另外 virtual-DOM 的另一个优点当然就是可以对渲染引擎做一般化的抽象,进而适配到更多类型的终端渲染引擎上!所以在我的怂恿下,小右把本来在 runtime
下的 runtime/dom
文件夹挪到了一个名叫 platforms
的新文件夹下,改名叫 platforms/web/runtime
,把本来 compiler
文件夹下 web 相关的 modules
挪到了 platforms/web/compiler
!
(是的没错,今天在 Weex 的子仓库里已经有另外一个 platforms/weex
文件夹了耶)
compiler
+ runtime
+ platforms
+ server
​是的没有错!Vue 2.0 既然已经有了 virtual-DOM,也有了运行环境无关的 compiler,为什么不能 ssr 呢?!Vue 2.0 不只是简单的把预渲染拿到服务端这么简单,Vue 2.0 ssr 除了提供传统的由源文件编译出字符串之外,还提供了输出 stream 的功能,这样服务端的渲染不会因为大量的同步字符串处理而变慢。即:createRenderer()
会返回 renderToString()
和 renderToStream()
两个方法。同时,在 platforms/web
文件夹下除了 runtime
和 compiler
之外又多了一个 server
目录,这样编译器、服务端流式预渲染、运行时的铁三角架构就这样达成了!
test
​说到测试,我惊奇的发现,在带来了这么多颠覆性的改变之后,Vue 2.0 竟然完好保留了绝大多数 1.0 的 API 设计,而且更快更小巧延展性更强。Vue 2.0 在前期研发阶段主要是通过粗线条的 e2e 测试进行质量保障的,因为版本延续性做得非常好,所以这部分在 1.x 的积累已经帮上很大忙了。现在 Vue 2.0 逐渐的在从 feature 的角度在进一步覆盖测试用例,对每个 API 和每个流程进行测试。目前以我个人的感觉主要的常见的链路都已经比较畅通了,具体功能细节上偶尔还是会遇到 bug 待修复,不过作为一个新兴的 Vue 2.0 来说,相信这已经远远超过大家的预期了!
我觉得 Vue 2.0 在编译器和运行时的解耦上做得超级棒!中间格式设计得也非常巧妙,把静态的部分在编译时就分析出来,而且通过非常简单的 __h__
, __renderList__
等方法就搞定了几乎所有的逻辑控制和数据绑定。之前我个人在实践 Weex 的时候也是会把 template 提前 compile,但只是 compile 成一段 JSON,逻辑分析还是在运行时做的,当时和小右交流的时候就在讨论,能不能把分析过程也前置,无奈自己功力不够啊,一直没搞出来。看到 2.0 横空出世,简直是泪流满面有木有!!
还有一件事情也是之前跟小右聊到过,就是目前 Vue 提供的很多 directive 包括 filter 也都是有机会前置处理的,所以在 Vue 2.0 里,有相当一部分 directive 是前置处理成一般格式的,运行时只是针对各端的渲染机制保留了 attr, style, class, event 等几个最基础简单的解析过程,比如 if, for, else 都直接在 compile 的时候被解开了。而且 Vue 2.0 把这部分内容抽象得如此清晰,除了赞叹还是赞叹!!
还有就是,你们去看看 Vue 2.0 的提交记录,300+ 次提交,上万行高效优质的代码,总共花了差不多两周的时间,而且提交时间几乎遍布二十四个小时……
别的不多啰嗦了,我觉得大家还是亲自看过 Vue 2.0 的源码,会对这些内容有更深刻的了解。从今天起,fork + clone Vue 2.0,写写 demo、写写测试、练练英文 XD go!
]]>今天我们非常激动的首发 Vue 2.0 preview 版本,这个版本带来了很多激动人心的改进和新特性。我们来看看这里面都有些什么!
]]>今天我们非常激动的首发 Vue 2.0 preview 版本,这个版本带来了很多激动人心的改进和新特性。我们来看看这里面都有些什么!
Vue.js 始终聚焦在轻量和快速上面,而 2.0 把它做得更好。现在的渲染层基于一个轻量级的 virtual-DOM 实现,在大多数场景下初试化渲染速度和内存消耗都提升了 2~4 倍 (详见这里的 benchmarks)。从模板到 virtuel-DOM 的编译器和运行时是可以独立开来的,所以你可以将模板预编译并只通过 Vue 的运行时让你的应用工作起来,而这份运行时的代码 min+gzip 之后只有不到 12kb (提一下,React 15 在 min+gzip 之后的大小是 44kb)。编译器同样可以在浏览器中工作,也就是说你也可以写一段 script 标签然后开始你的工作,就像以前一样。而即便你把编译器加进去,build 出来的文件 min+gzip 之后也仅有 17kb,仍然小于目前的 1.0 版本。
现在 virtual-DOM 有点让人听腻了,因为社区里有太多种实现,但是 Vue 2.0 的实现有与众不同的地方。和 Vue 的响应式系统结合在一起之后,它可以让你不必做任何事就获得完全优化的重渲染。由于每个组件都会在渲染时追踪其响应依赖,所以系统精确地知道应该何时重渲染、应该重渲染哪些组件。不需要 shouldComponentUpdate
,也不需要 immutable 数据 - it just works.
除此之外,Vue 2.0 从模板到 virtuel-DOM 的编译阶段使用了一些高阶优化:
它会检测出静态的 class 名和 attributes 这样它们在初始化渲染之后就永远都不会再被比对。
它会检测出最大静态子树 (就是不需要动态性的子树) 并且从渲染函数中萃取出来。这样在每次重渲染的时候,它就会直接重用完全相同的 virtual nodes 同时跳过比对。
这些高阶优化通常只会在使用 JSX 时通过 Babel plugin 来做,但是 Vue 2.0 即使在使用浏览器内的编译器时也能做到。
新的渲染系统同时允许你通过简单的冻结数据来禁用响应式转换,配以手动的强制更新,这意味着你对于重渲染的流程实际上有着完全的控制权。
以上这些技术组合在一起,确保了 Vue 2.0 在每一个场景下都能够拥有高性能的表现,同时把开发者的负担和成本降到了最低。
开发者对于用模板还是 JSX 有很多的争执。一方面,模板更接近 HTML - 它能更好地反映你的 app 的语义结构,并且易于思考视觉上的设计、布局和样式。另一方面,模板作为一个 DSL 也有它的局限性 - 相比之下 JSX/hyperscript 的程序本质使得它们具有图灵完备的表达能力。
作为一个兼顾设计和开发的人,我喜欢用模板来写大部分的界面,但在某些情况下我也希望能拥有 JSX/hyperscript 的灵活性。举例来说,当你想在一个组件中程序化的处理其子元素时,基于模板的 slot 机制会显得比较有局限性。
那么,为什么不能同时拥有它们呢?在 Vue 2.0 中,你可以继续使用熟悉的模板语法,但当你觉得受限制的时候,你也可以直接写底层的 virtual-DOM 代码,只需用一个 render
函数替换掉 template
选项。你甚至可以直接在你的模板里使用一个特殊的 <render>
标签来嵌入渲染函数!一个框架,两全其美。
既然迁移到了 virtual-DOM,Vue 2.0 自然支持服务端渲染和客户端的 hydration(直接使用服务端渲染的 DOM 元素)。当前服务端渲染的实现有一个痛点,比如在 React 里,渲染是同步的,所以如果这个 app 比较复杂的话它会阻塞服务器的 event loop。同步的服务端渲染在优化不当的情况下甚至会对客户端获得内容的速度带来负面影响。Vue 2.0 提供了内建的流式服务端渲染 - 在渲染组件时返回一个可读的 stream,然后直接 pipe 到 HTTP response。流式渲染能够确保服务端的响应度,也能让用户更快地获得渲染内容。
基于新的架构,我们还有更多的可能性有待开发 - 比如在手机端渲染到 native 界面。目前我们正在探索一个 Vue.js 2.0 的端,它会用 weex:一个由中国最大科技公司之一的阿里巴巴的工程师们维护的项目,作为一个 native 的渲染层。同时从技术角度 Vue 2.0 运行在 ReactNative 上也是可行的。让我们拭目以待!
Vue.js 2.0 仍然处在 pre-alpha 阶段,但是你可以来这里 查看源代码。尽管 2.0 是一个完全重写的项目,但是除了一些有意废弃掉的功能,API 和 1.0 是大部分兼容的。看看 2.0 中一模一样的官方例子 - 你会发现几乎没有什么变化!
对于部分功能的废弃,本质上是为了提供更简洁的 API 从而提高开发者的效率。你可以移步这里 查看 1.0 和 2.0 的特性比对。如果你在现有的项目中大量地使用着一些被废弃的特性,这意味着会有一定的迁移成本,不过我们在未来会提供更详实的升级指导。
现在我们还有很多工作没有完成。一旦我们达到了令人满意的测试覆盖率,我们将会推出 alpha 版本,同时我们希望能在五月底六月初推出 beta 版。除了更多的测试之外,我们也需要更新相关库(如 vue-router, Vuex, vue-loader, vueify...)的支持。目前只有 Vuex 在 2.0 下可以直接使用,但是我们会确保在 2.0 正式发布时所有东西都会顺畅地工作。
我们不会因此而忘记 1.x 哦!1.1 将会和 2.0 beta 独立发布,提供六个月 critical bug fixes 和九个月安全升级的长效服务 (LTS)。同时 1.1 还会包含可选的废弃特性警告,让你为升级到 2.0 做好充足的准备。尽请期待!
]]>我自己发现周围的同学越来越把一句话挂在嘴边,那就是:我们就这么些人,想搞定这件事情是远远不够的
说白了,大家觉得“人多好办事”,“规模”这个词很多情况下几乎就是个褒义词。
但是今天有些变化正在悄然发生,比如人类的学习能力越来越强,同时信息技术越来越发达,学习新知识的门槛越来越低,比如各技术领域的成熟度越来越高,比如移动时代前所未有的技术挑战,在有限的硬件性能和网络带宽的情况下,越是“规模”的东西越难以维持;比如体力和知识都逐渐远离核心竞争优势等等
一件事搞得起来搞不起来,逐渐不取决于我们的团队有多少人,而取决于我们有没有让这件事情发生在最广阔的舞台上,因为一个技术团队再多人,哪怕一百多人,相比起任何一个知名技术社区都是九牛一毛,而一个集团、一个国家、一门语言的社区,相比起全球的互联网社区,也都是渺小的。所以今天,我们想在技术上成就一件事,未必需要很大的团队,未必需要很多人在身边
同时,我们也不能小看个体在科技发展中所起到的关键作用。技术的不断融合和碰撞也加速了这件事情。所以更重要的不是团队有多少人,而是团队有没有能够真正起到关键作用的个体,找到对的人,比组建一支上百人的团队要重要得多
我敢说,抛开具有真正技术驱动力的公司,绝大多数公司的技术工作都能够在社区找到现成并且适合的方案,也都不太需要最高精简的科研探索。一个务实的技术方案,一定是在大量的社区成熟技术基础上建立起来的,再加上一点点针对自身业务独特性的技术实践。不过看上去“没什么自己的东西”罢了,如果大家很介意这东西“得是我自己的”,那么你需要继续考虑这个问题:自己搞出来的东西能不能比今天社区的更好,能不能发展成更好的社区,是否可以在社区中的深度参与从而把“别人的”变成“自己的”
然而社区不应该只有索取,还应该有奉献,如果你发现有件事情是别人也会再次遇到的那种事情,但社区没有合适现成的东西,那么恭喜你,赶紧开工吧
有的时候觉得社区化的技术发展也有它残酷的一面,同类的方案或工具库基本上也就全球数一数二的几个能生存下来,并且体现出可观的价值,稍微差一点的,也许绝对实力没有差很多,但只要不是第一,所产生的价值和影响力就少得可怜了。所以想掂量掂量自己,看看是不是这块料,不妨放到社区里跟大家一起来一场真正的PK吧!也别自己憋着,别觉得“等我弄得再完善一点再给你们看”,因为在社区化的技术发展中,这样做只会让自己越来越没有价值和勇气把它拿出来了。当然也不必贪多,哪怕是一个小小的功能,如果做到最好,能够被全球的开发者使用,那也是极好的
我们在工作中逐渐对人的评价,会由评价其绝对的技术实力,转向评价其运用各方面技术解决实际问题的能力。这两者之间有一个比较形象的比喻,就好比我们学生时期背单词,拿出任何一个单词来,都能立刻说出它的含义、发音、常用短语、同义词、反义词、各种形态,但面对具体的对话场景不知道该用哪个词最合适最得体,这是比较典型的一种现象。其实前者就像是一个人的技术实力,后者就像是一个人的解决实际问题的能力。所以新知识新技术不光要学,还要学以致用,如何合理运用技术解决实际问题才是我们真正希望看到的。
现在回到“小而美的务实方案”这件事情上。我们认为看上去很重要、工作量很大、需要很多人来做的工作,是可以充分拥抱社区,汲取最好的技术,同时发挥关键角色的关键作用,并把技术合理运用在实际需求上。整件事情的成败有很多因素,但是“规模”这个因素显然不在考前的位置了
基于这样的思考,我觉得,自己喜欢的团队,是一个小而美的团队
]]>Hi HN (Hacker News)! 如果你还不熟悉 Vue.js 的话,可以通过这篇文章 (英文)对其有个总体印象。
在经历了 300+ 次提交、8 次 alpha、4 次 beta 和 2 次 rc 之后,今天我很荣幸的向大家宣布 Vue.js 1.0.0 Evangelion 正式发布了!非常感谢所有参与 API 重设计的同学们——没有来自社区的贡献这是不可能完成的。
]]>Hi HN (Hacker News)! 如果你还不熟悉 Vue.js 的话,可以通过这篇文章 (英文)对其有个总体印象。
在经历了 300+ 次提交、8 次 alpha、4 次 beta 和 2 次 rc 之后,今天我很荣幸的向大家宣布 Vue.js 1.0.0 Evangelion 正式发布了!非常感谢所有参与 API 重设计的同学们——没有来自社区的贡献这是不可能完成的。
1.0 的模板语法解决了很多微小的一致性问题,并使得 Vue 的模板更加简洁且易于阅读。最值得留意的新特性就是 v-on
和 v-bind
的语法简写:
<!-- 简写版 v-bind:href -->
<a :href="someURL"></a>
<!-- 简写版 v-on:click -->
<button @click="onClick"></button>
当使用一个子组件的时候,v-on
用来监听自定义事件,v-bind
用来绑定属性 (props)。这些简写让子组件变得更易用:
<item-list
:items="items"
@ready="onItemsReady"
@update="onItemsUpdate">
</item-list>
Vue.js 1.0 的总体目标是使其适用于更大型的项目。这也是很多 API 被弃用的原因。在被弃用的 API 中,除了很少被用及之外,最常见的理由就是它会导致可维护性被破坏。特别是,我们弃用了难以维护的功能,并把组件提炼隔离开,使其不会对项目其它部分产生影响。
比如,在 0.12 中默认资源 (asset) 方案会隐性降级到组件树中的父级。这使得组件中的可用资源非常不确定,并且取决于在运行时的用法。在 1.0 中,所有的资源都基于严格模式进行解析,也没有了隐性降级。inherit
选项也被移除了,因为它很容易导致组件强耦合,无法提炼。
1.0 用 v-for
指令 (directive) 取代了 v-repeat
。除了提供相同的功能和更直观的作用域之外,v-for
将初始化渲染大列表和大表格时的性能提升了 100%!
在 Vue.js core 之外,还有很多令人激动的东西:vue-loader 和 vueify 的新升级,包括:
组件热加载。当一个 *.vue
组件被编辑之后,其所有活跃实例都可以在页面不刷新的情况下完成热转换。这意味着你不需要重新加载 app 就可以完成诸如样式或模板的小调整;而 app 本身及其被转换的组件的状态可以被保留,这大大提升了开发体验。
局部 CSS。通过在你的 *.vue
组件的 style 标签上简单加入一个 scoped
特性,该组件的模板和最终生成的 CSS 就会被奇妙的重写以保证组件的样式只被应用在其自身的元素上。最重要的是,父组件的特殊样式不会泄露到嵌套的子组件当中。
默认支持 ES2015。JavaScript 一直在进化。你可以用最新的语法写出更简洁生动的代码。vue-loader
和 vueify
现在会直接转换你的 *.vue
组件中的 JavaScript,无需额外的设置。今天,就来写未来的 JavaScript 吧!
结合 vue-router,Vue.js 现在不只是一个库了——它提供了一个构建复杂单页应用的稳固基础。
如一般 1.0.0 所提倡的,核心 API 将会保持稳定服务于可预见的未来,库也做好了产品级别的准备。未来的开发会专注于:
改善 vue-loader
并使其做好产品级别的准备。
捋顺开发体验,比如更好的开发者工具和 Vue.js 项目/组件脚手架的 CLI。
提供更多诸如教程和示例的学习资料。
看过之后非常有感触,很多观点都是自己长期非常坚持和认同的,所以翻译出来分享给更多的前端同学!
最近我收到一封读者来信让我陷入了思考,信是这么写的:
Hi Philip,您是否介意我问您是如何成为一名卓越 (great) 的前端工程师的?对此您有什么建议吗?
我不得不承认,我很惊讶被问这样的问题,因为我从来不觉得自己是个很卓越的前端工程师。甚至我入行头几年时并不认为自己可以做好这一行。我只确定自己比自己想象中还才疏学浅,而且大家面试我的时候都不知道从何问起
话虽这么说,我到现在做得还算不错,而且成为了团队中有价值的一员。但我最终离开 (去寻求新的挑战——即我还不能够胜任的工作) 的时候,我经常会被要求招聘我的继任者。现在回看这些面试,我不禁感叹当我刚开始的时候自己在这方面的知识是多么的匮乏。我现在或许不会按照我自己的模型进行招聘,即便我个人的这种经历也有可能成功。
我在 web 领域工作越长时间,我就越意识到区分人才和顶尖人才的并不是他们的知识——而是他们思考问题的方式。很显然,知识在很多情况下是非常重要而且关键的——但是在一个快速发展的领域,你前进和获取知识的方式 (至少在相当长的一段时间里) 会比你已经掌握的知识显得更加重要。更重要的是:你是如何运用这些知识解决每天的问题的。
这里有许许多多的文章谈论你工作中需要的语言、框架、工具等等。我希望给一些不一样的建议。在这篇文章里,我想谈一谈一个前端工程师的心态,希望可以帮助大家找到通往卓越的道路。
]]>看过之后非常有感触,很多观点都是自己长期非常坚持和认同的,所以翻译出来分享给更多的前端同学!
最近我收到一封读者来信让我陷入了思考,信是这么写的:
Hi Philip,您是否介意我问您是如何成为一名卓越 (great) 的前端工程师的?对此您有什么建议吗?
我不得不承认,我很惊讶被问这样的问题,因为我从来不觉得自己是个很卓越的前端工程师。甚至我入行头几年时并不认为自己可以做好这一行。我只确定自己比自己想象中还才疏学浅,而且大家面试我的时候都不知道从何问起
话虽这么说,我到现在做得还算不错,而且成为了团队中有价值的一员。但我最终离开 (去寻求新的挑战——即我还不能够胜任的工作) 的时候,我经常会被要求招聘我的继任者。现在回看这些面试,我不禁感叹当我刚开始的时候自己在这方面的知识是多么的匮乏。我现在或许不会按照我自己的模型进行招聘,即便我个人的这种经历也有可能成功。
我在 web 领域工作越长时间,我就越意识到区分人才和顶尖人才的并不是他们的知识——而是他们思考问题的方式。很显然,知识在很多情况下是非常重要而且关键的——但是在一个快速发展的领域,你前进和获取知识的方式 (至少在相当长的一段时间里) 会比你已经掌握的知识显得更加重要。更重要的是:你是如何运用这些知识解决每天的问题的。
这里有许许多多的文章谈论你工作中需要的语言、框架、工具等等。我希望给一些不一样的建议。在这篇文章里,我想谈一谈一个前端工程师的心态,希望可以帮助大家找到通往卓越的道路。
很多人埋头写 CSS 和 JavaScript 直到程序工作起来了,然后就去做别的事情了。我通过 code review 发现这种事经常发生。
我总会问大家:“为什么你会在这里添加 float: left
?”或者“这里的 overflow: hidden
是必要的吗?”,他们往往答道:“我也不知道,可是我一删掉它们,页面就乱套了。”
JavaScript 也是一样,我总会在一个条件竞争的地方看到一个 setTimeout
,或者有些人无意中阻止了事件传播,却不知道它会影响到页面中其它的事件处理。
我发现很多情况下,当你遇到问题的时候,你只是解决当下的问题罢了。但是如果你永远不花时间理解问题的本源,你将一次又一次的面对相同的问题。
花一些时间找出为什么,这看上去费时费力,但是我保证它会节省你未来的时间。在完全理解整个系统之后,你就不需要总去猜测和论证了。
前后端开发的一个主要区别在于后端代码通常都运行在完全由你掌控的环境下。前端相对来说不那么在你的掌控之中。不同用户的平台或设备是前端永恒的话题,你的代码需要优雅掌控这一切。
我记得自己 2011 年之前曾经阅读某主流 JavaScript 框架的时候看到过下面这样的代码 (简化过的):
var isIE6 = !isIE7 && !isIE8 && !isIE9;
在这个例子中变量 IE6 为了判断 IE 浏览器版本是否是 6 或更低的版本。那么在 IE10 发布时,我们的程序判断还是会出问题。
我理解在真实世界特性检测并不 100% 工作,而且有的时候你不得不依赖有 bug 的特性或根据浏览器特性检测的错误设计白名单。但你为此做的每一件事都非常关键,因为你预见到了不再有 bug 的未来。
对于我们当中的很多人来说,我们今天写的代码都会比我们的工作周期要长。有些我写的代码已经过去 8 年多了还在产品线上运行。这让人很满足又很不安。
浏览器有 bug 是很难免的事,但是当同一份代码在两个浏览器渲染出来的效果不一样,人们总会不假思索的推测,那个“广受好评”的浏览器是对的,而“不起眼”的浏览器是错的。但事实并不一定如此,当你的假设出现错误时,你选取的变通办法都会在未来遭遇问题。
一个就近的例子是 flex 元素的默认最小尺寸问题。根据规范的描述,flex 元素初始化的 min-width
和 min-height
的值是 auto
(而不是 0),也就是说它们默认应该收缩到自己内容的最小尺寸。但是在过去长达 8 个月的时间里,只有 Firefox 的实现是准确的。[1]
如果你遇到了这个浏览器兼容性的问题并且发现 Chrome、IE、Opera、Safari 的效果相同而 Firefox 和它们不同时,你很可能会认为是 Firefox 搞错了。事实上这种情况我见多了。很多我在自己 Flexbugs 项目上报的问题都是这样的。而且这些解决方案的问题会在两周之后 Chrome 44 修复之后被体现出来。和遵循标准的解决方案相比,这些方案都伤害到了正确的规范行为。[2]
当同一份代码在两个或更多浏览器的渲染结果不同时,你应该花些时间确定哪个效果是正确的,并且以此为标准写代码。你的解决方案应该是对未来友好的。
额外的,所谓“卓越”的前端工程师是时刻感受变化,在某项技术成为主流之前就去适应它的,甚至在为这样的技术做着贡献。如果你锻炼自己看到规范就能在浏览器支持它之前想象出它如何工作的,那么你将成为谈论并影响其规范开发的那群人。
出于乐趣阅读别人的代码可能并不是你每周六晚上会想到的娱乐项目,但是这毫无疑问是你成为优秀工程师的最佳途径。
自己独立解决问题绝对是个不错的方式,但是这不应该是你唯一的方式,因为它很快就会让你稳定在某个层次。阅读别人的代码会让你开阔思维,并且阅读和理解别人写的代码也是团队协作或开源贡献必须具备的能力。
我着实认为很多公司在招聘新员工的时候犯的最大错误是他们只评估应聘者从轮廓开始写新代码的能力。我几乎没有见过一场面试会要求应聘者阅读现有的代码,找出其中的问题,并修复它们。缺少这样的面试流程真的非常不好,因为你作为工程师的很多时间都花费在了在现有的代码的基础上增加或改变上面,而不是搭建新的东西。
我印象中的很多前端开发者 (相比于全职工作来说) 都是自由职业者,有同类想法的后端开发者并没有那么多。可能是因为很多前端都是自学成才的而后端则多是学校里学出来的。
不论是自我学习还是自我工作,我们都面对一个问题:你并没有机会从比你聪明的家伙那里学到什么。没有人帮你 review 代码,也没有人与你碰撞灵感。
我强烈建议,最起码在你职业发展的前期,你要在一个团队里工作,尤其是一个普遍比你聪明而且有经验的团队里工作。
如果你最终会在你职业发展的某个阶段选择独立工作,一定要让自己投身在开源社区当中。保持对开源项目的活跃贡献,这会给你团队工作相同甚至更多的益处。
造轮子在商业上是非常糟糕的,但是从学习的角度是非常好的。你可能很想把那些库和小工具直接从 npm 里拿下来用,但也可以想象一下你独立建造它们能够学到多少东西。
我知道有些人读到这里是特别不赞成的。别误会,我并没有说你不应该使用第三方代码。那些经过充分测试的库具有多年的测试用例积累和已知问题积累,使用它们绝对是非常明智的选择。
但在这里我想说的是如何从优秀到卓越。我觉得这个领域很多卓越的人都是我每天在用的非常流行的库的作者或维护者。
你可能不曾打造过自己的 JavaScript 库也拥有一个成功的职业发展,但是你从不把自己手弄脏是几乎不可能淘到金子的。
在这一行大家普遍会问的一个问题是:我接下来应该做点什么?如果你没有试着学一个新的工具创建一个新的应用,那不妨试着重新造一个你喜欢的 JavaScript 库或 CSS 框架。这样做的一个好消息是,在你遇到困难的时候,所有现成的库的源代码都会为你提供帮助。
最后,但丝毫不逊色的是,你应该把你学到的东西记录下来。这样做有很多原因,但也许最重要的原因是它强迫你更好的理解这件事。如果你无法讲清楚它的工作原理,在整个过程中它会推动你自己把并不真正理解的东西弄清楚。很多情况下你根本意识不到自己还不理解它们——直到自己动手写的时候。
根据我的经验,写作、演讲、做 demo 是强迫自己完全深入理解一件事的最佳方式。就算你写的东西没有人看,整个过程也会让你受益匪浅。
注:本文摘自阿里内网的无线前端博客《无线前端的图片相关工作流程梳理》。其实是一个月前写的,鉴于团队在中国第二届 CSS Conf 上做了《手机淘宝 CSS 实践启示录》的分享,而图片工作流程梳理是其中的一个子话题,故在此一并分享出来,希望仍可以给大家一些经验和启发。另外,考虑到这是一篇公开分享,原版内容有部分删节和调整,里面有一些经验和产出是和我们的工作环境相关的,不完全具有普遍性,还请见谅。
今天很荣幸的跟大家分享一件事情,就是经过差不多半年多的努力,尤其是最近 2 周的“突击扫尾”,无线前端团队又在工具流程方面有了一个不小的突破:我们暂且称其为“图片工作流”梳理。
要说最近 1 年里,无线前端开发的一线同学最“难搞”的几件事,图片处理绝对可以排在前三。
这里面“难搞”在哪些地方呢?我们逐一分析一下:
所以可能把这些东西画成一张图表的话:
在最近半年的一段时间里,无线前端团队先后发起了下面几项工作,从某个点上尝试解决这些问题:
]]>注:本文摘自阿里内网的无线前端博客《无线前端的图片相关工作流程梳理》。其实是一个月前写的,鉴于团队在中国第二届 CSS Conf 上做了《手机淘宝 CSS 实践启示录》的分享,而图片工作流程梳理是其中的一个子话题,故在此一并分享出来,希望仍可以给大家一些经验和启发。另外,考虑到这是一篇公开分享,原版内容有部分删节和调整,里面有一些经验和产出是和我们的工作环境相关的,不完全具有普遍性,还请见谅。
今天很荣幸的跟大家分享一件事情,就是经过差不多半年多的努力,尤其是最近 2 周的“突击扫尾”,无线前端团队又在工具流程方面有了一个不小的突破:我们暂且称其为“图片工作流”梳理。
要说最近 1 年里,无线前端开发的一线同学最“难搞”的几件事,图片处理绝对可以排在前三。
这里面“难搞”在哪些地方呢?我们逐一分析一下:
所以可能把这些东西画成一张图表的话:
在最近半年的一段时间里,无线前端团队先后发起了下面几项工作,从某个点上尝试解决这些问题:
首先,我们和 UED 团队共同协商约定了一套 REM 方案 (后更名为 flexible 方案,进而演进为 lib.flexible 库),通过对视觉稿的产出格式的约定,从工作流程的源头把控质量,同时在技术上产出了配套的 lib.flexible 库,可以“抹平”不同设备屏幕的尺寸差异,同时对清晰度进行了智能判断。这部分工作前端的部分是 @wintercn 寒老师和 @terrykingcha 共同创建的。
其次,我们于去年 12 月开始启动了一个“视觉稿工具效率提升”的开放课题,由团队的 @songsiqi 负责牵头,我们从课题的一开始就确立了 KPI 和 roadmap,经过一段时间的调研和落实,收罗了很多实用的辅助工具帮助我们提升效率,同时布道给了整个团队。比如 cutterman、parker、Size Marks 等
在 @hongru 去年主持完成的一系列 One-Request 前端工具集当中,有一个很有意义的名叫 or-uploadimg
的图片上传工具。它把 TPS 的图片上传服务命令化了。这给我们对图片上传工作批量化、集成化提供了一个非常重要的基础!这个工具同时也和淘宝网前端团队的另一个 TPS 图片上传工具有异曲同工之妙。大概用法是这样的,大家可以感受一下:
var uploader = require('@ali/or-uploadimg');
// 上传 glob 多张图
uploader('./**/*.jpg', function (list) {
console.log(list)
});
// 上传多张
uploader(['./1.jpg', './3d-base.jpg'], function (list1, list2) {
console.log(list1, list2);
})
// 上传单张
uploader('./3d-base.jpg', function (list1) {
console.log(list1)
})
随后团队又出现了这一工具的 gulp 插件,可以对图片上传的工作流程做一个简单的集成,具体集成方式是分析网页的 html/css 代码,找到其中的相对图片地址并上传+替换 CDN URL。
var gulp = require('gulp');
var imgex = require('@ali/gulp-imgex');
gulp.task('imgex', function() {
gulp.src(['./*.html'])
.pipe(imgex())
.pipe(gulp.dest('./'));
gulp.src('./css/*.css')
.pipe(imgex({
base64Limit: 8000, // base64化的图片size上限,小于这个size会直接base64化,否则上传cdn
uploadDest: 'tps' // 或者 `mt`
}))
.pipe(gulp.dest('./css'));
});
lib.img
是团队 @chenerlang666 主持开发的一个基础库,它是一套图片自动处理优化方案。可以同时解决屏幕尺寸判断、清晰度判断、网络环境判断、域名收敛、尺寸后缀计算、画质后缀计算、锐化度后缀计算、懒加载等一系列图片和性能相关的问题。这个库的意义和实用性都非常之高,并且始终保持着快速的业务响应和迭代周期,也算是无线前端团队的一个明星作品,也报送了当年度的无线技术金码奖。
px2rem 是 @songsiqi 主持开发的另一个小工具,它因 lib.flexible 方案而生,因为我们统一采用 rem 单位来最终记录界面的尺寸,且对于个别1像素边框、文本字号来说,还有特殊的规则作为补充 (详见 lib.flexible 的文档)。
同样的,它也有 gulp / browser 的各种版本。
img4dpr
则是一个可以把 CSS 中的 CDN URL 自动转成 3 种 dpr 下不同的尺寸后缀。算是对 lib.img 的一个补充。如果你的图片不是产生在 <img>
标签或 JavaScript 中,而是写在了 CSS 文件里,那么即使是 lib.img 恐怕也无能为力,img4dpr 恰恰是在解决这个问题。
看上去,团队为团队做了很多事情,每件事情都在单点上有所突破,解决了一定的问题。
但我们并没有为此停止思考
有一个很明显的改进空间在这里:今天我们的前端开发流程是一整套工程链路,每个环节之间都紧密相扣, 解决了单点的问题并不是终点,基于场景而不是功能点的思考方式,才能够把每个环节都流畅的串联起来,才能给前端开发者在业务支持的过程当中提供完美高效畅通无阻的体验——这是我们为之努力的更大的价值!也是我认为真正“临门一脚”的最终价值体现!
这种思维方式听上去很玄幻,其实想做到很简单,我们不要单个儿看某个工具好不好用,牛不牛掰,模拟真实工程场景,创建个新项目,从“切图”的第一步连续走到发布的最后一步,看看中间哪里断掉了?哪里衔接的不自然?哪里不完备?哪里重复设计了?哪里可以整合?通常这些问题都会变得一目了然。
首先,在 Photoshop 中“切图”本身的过程对于后续的开发流程来说是相对独立的,所以这里并没有做更多的融合 (从另外一个角度看,这里其实有潜在的改造空间,如何让“切图”的工作也能集成到前端工具链路中,这值得我们长期思考)
然后,从图片导出产生的那一刻起,它所经历的场景大概会是这么几种:
images
文件夹
[src]
-> webpack require -> hash filename (upload time) -> file-loader[data-src]
-> lib.img (auto resize)[data-src]
data[src]
(manually resize)element.style.background
-> lib.img (manually resize)background
-> postcss (upload time) -> px2rem, img4dpr其中 (upload time)
指的是我有机会在这个时机把图片上传到 CDN 并把代码里的图片地址替换掉;(* resize)
指的是我有机会在这个时机把图片的域名收敛/尺寸/画质/锐化度等需求处理掉。
经过这样一整理,我们很容易发现问题:
package.json
和一份 gulpfile.js
)在完善场景的“最后一公里”,我们做了如下的工作:
images
目录下约定一个名为 _cdnurl.json
的文件,记录图片的 hash 值和线上 CDN 地址,并写了一个 @ali/gulp-img-uploader
的 gulp 插件,每次运行的时候会便利 images
文件夹中的图片,如果出现新的 hash 值,就自动上传到 CDN,并把相应生成的 CDN URL 写入 _cdnurl.json
_cdnurl.json
中记录的本地图片路径和线上地址的对应关系_cdnurl.json
的信息引入以做准备上述几件事我们于上周一做了统一讨论和分工,这里要感谢 @mingelz @songsiqi @chenerlang666 的共同努力!!
我在这个过程中,融入了之前一段时间集中实践的 vue 和 webpack 的工程体系,在 vue 的基础上进行组件化开发,在 webpack 的基础上管理资源打包、集成和发布,最终合并在了最新的 just-vue 的 adam template 里面。
之前不是在文章的最后卖了个“最后一公里”的关子吗,这里介绍的图片工作流改进就是其中的一部分:)
同时,我基于 lib.img 的思路,结合 vue.js 自身的特点,写了一个 v-src
的 directive,在做到 lib.img 里 [data-src]
相同目的的同时,更好的融入了 vue.js 的体系,同时加入了更高集成度的功能,稍后会再介绍。
夹带了私货之后是不是我就没法用了?
最后我想强调的是,除了自己的这些“私货”之外,上面提到的几个改进点和这些个人的内容是完全解耦的,如果你不选择 vue.js 或 webpack 而是别的同类型工具或自己研发的一套工具,它依然可以灵活的融入你的工作流程中。
我们在团队内部把这些工作流程以脚手架的方式进行了沉淀,并放在了团队内部叫做 adam
的 generator 平台上 (后续会有介绍) 取名叫做 just-vue
(时间仓促,adam 和相关的 generator 未来会在适当的时机开放出来)。大致用法:
安装 adam 和 just-vue 模板:
tnpm install -g @ali/adam
adam tmpl add <just-vue git repo>
交互式初始化新项目:
$ adam
? Choose a template: just-vue
? Project Name: y
? Git User or Project Author: ...
? Your email address: ...
Awesome! Your project is created!
|--.gitignore
|--components
|--|--foo.vue
|--gulpfile.js
|--images
|--|--_cdnurl.json
|--|--logo.png
|--|--one.png
|--|--taobao.jpg
|--lib
|--|--lib-cdnurl.js
|--|--lib-img.js
|--|--vue-src.js
|--package.json
|--README.md
|--src
|--|--main.html
|--|--main.js
|--|--main.vue
然后大家会看到项目目录里默认就有:
gulpfile.js
,里面默认写好了图片批量上传并更新 _cdnurl.json
、webpack 打包、htmlone 合并 等常见任务images
目录,里面放好了关键的 _cdnurl.json
,还有几张图片作为示例,它们的 hash 和 CDN URL 已经写好了src/main.*
,主页面入口,包括一个 htmlone 文件 (main.html
),一个 webpack 文件 (main.js
) 和一个 vue 主文件 (main.vue
),默认引入了需要的所有样式和脚本,比如 lib.img, lib.flexible, lib.cdnurl, _cdnurl.json, v-src.js 等,我们将来主要的代码都会从 main.vue
写起——额外的,我们为 MT 模板开发者贴心的引入了默认的 mock 数据的 <script data-mt-variable="data">
标签,不需要 MT 模板开发环境的将其删掉即可components
目录,这里会把我们拆分下来的子组件都放在这里,我们示范性的放了一个 foo.vue
的组件在里面,并默认引入了 lib.cdnurl 库lib
这里默认放入了 lib.img, lib.cdnurl, v-src.js 几个库,这几个库在未来逐步稳定之后都会通过 tnpm + CommonJS 的方式进行管理,目前团队 tnpm + CommonJS 的组件整合还需要一定时间,这里是个方便调整迭代的临时状态。然后,我们来看一看 main.vue
里的细节,这才是真正让你真切感受到未来开发体验的地方。
首先,新产生任何图片,尽管丢到 images
目录,别忘了起个好理解的文件名
然后,在 main.vue
的第 11 行看到了一个 CSS 的 background-image 的场景,我们只是把 url(../images/taobao.jpg)
设为其背景图片:
background-image: url(../images/taobao.jpg);
完成了!就这样!你在发布之前不需要再关注额外的事情了。没有手动上传图片、没有另外的GUI、没有重命名、没有 CDN 地址替换、没有图片地址优化、没有不可读的代码
我们再来看看 HTML 里的图片,来到 39 行:
<img id="test-img" v-src="../images/one.png" size="cover">
一个 [v-src]
特性搞定!就这样!你在发布之前不需要再关注额外的事情了 (这里 [size]
特性提供了更多的图片地址优化策略,篇幅有限,大家感兴趣可以移步到 lib/vue-src.js
看其中的实现原理)。
最后再看看在 JavaScript 里使用图片,来到 68 行:
this.$el.style.backgroundImage = 'url(' + cdn('../images/logo.png') + ')'
只加入了一步 cdn(...)
的图片生成,也搞定了!就这样!你在发布之前不需要再关注额外的事情了。
那有人可能会怀疑: “那你都说发布之前很方便,发布的时候会不会太麻烦啊?”
好问题,发布就两行命令:
# 图片增量上传、webpack 打包、htmlone 合并,最终生成在 dist 目录
gulp
# 交互式上传到 awp
awp
正常的命令行反应是类似这样的:
$ gulp
[04:46:48] Using gulpfile ~/Sites/alibaba/samples/y/gulpfile.js
[04:46:48] Starting 'images'...
uploaded ../images/logo.png e1ea82cb1c39656b925012efe60f22ea http://gw.alicdn.com/tfscom/TB1SDNqIFXXXXaTaXXX7WcCNVXX-400-400.png
uploaded ../images/one.png 64eb2181ebb96809c7202a162b9289fb http://gw.alicdn.com/tfscom/TB1G7JHIFXXXXbTXpXX_g.pNVXX-400-300.png
uploaded ../images/taobao.jpg 4771bae84dfc0e57f841147b86844363 http://gw.alicdn.com/tfscom/TB1f2xSIFXXXXa1XXXXuLfz_XXX-1125-422.jpg
[04:46:48] Finished 'images' after 46 ms
[04:46:48] Starting 'bundle'...
[04:46:49] Version: webpack 1.10.1
Asset Size Chunks Chunk Names
main.js 17.1 kB 0 [emitted] main
main.js.map 23.5 kB 0 [emitted] main
[04:46:49] Finished 'bundle' after 1.28 s
[04:46:49] Starting 'build'...
"htmlone_temp/cdn_combo_1.css" downloaded!
"htmlone_temp/cdn_combo_0.js" downloaded!
[04:46:57] >> All html done!
[04:46:57] Finished 'build' after 8.07 s
[04:46:57] Starting 'default'...
done
[04:46:57] Finished 'default' after 130 μs
$ awp (交互式过程略)
你甚至可以写成一行:
gulp && awp
最终这个初始化工程的示例页面的效果如下
这条链路是我们之前最不愿意面对的,今天,我们来看看这条链路变成了什么,假设有一张设计图要换:
images
文件夹gulp && awp
就这样!
额外的,如果尺寸有变化,就加一步:更改相应的 CSS 尺寸代码
在整个团队架构的过程中,大家都在不断尝试,如何以更贴近开发者真实场景的方式,还原真实的问题,找出切实有效的解决方案,而不仅仅是单个功能或特性。这样我们往往会找到问题的关键,用最精细有效的方式把工作的价值最大化。其实“基于场景的思维方式”不只是流程设计的专利,我们业务上的产品设计、交互设计更需要这样的思维。我个人也正是受到了一些产品经理朋友们的思维方式的影响,把这种方式运用在了我自己的工作内容当中。希望我们产出的这套方案能够给大家创造一些价值,更是向大家传递我们的心得体会,希望这样的思维方式和做事方式可以有更多更广的用武之地。
]]>译自:How to Minimize Politics in Your Company via www.bhorowitz.com
更新:跟身边一些朋友讨论之后,觉得之前翻译的标题“杜绝”言过了,还是规规矩矩翻译成了“最小化”
Who the f@#k you think you f$&kin’ with
I’m the f%*kin’ boss—Rick Ross, Hustlin'
在我所有的从商经历中,我从没听过有人说:“我喜欢办公室政治”。但在我们的周围,令人深恶痛绝的政治又到处都是,甚至自己的公司就是如此。既然大家都不喜欢政治,那为什么它无处不在呢?
政治行为几乎都源自 CEO。也许你会觉得:“我讨厌政治,我也不关心政治,但是我的周围充满了政治气味。这显然不是我造成的。”很遗憾,你并不需要怎么关心政治就会让你的周围充斥政治手段。实际上,很少关心政治的 CEO 才会让办公室充斥政治手段。不关心政治的 CEO 们往往会直接助涨政治行为。
我这里说的政治,就是指员工追求自我职业发展多于价值产出和贡献。也许还有别样的政治类型,但是这类政治行为真的很烦。
]]>译自:How to Minimize Politics in Your Company via www.bhorowitz.com
更新:跟身边一些朋友讨论之后,觉得之前翻译的标题“杜绝”言过了,还是规规矩矩翻译成了“最小化”
Who the f@#k you think you f$&kin’ with
I’m the f%*kin’ boss—Rick Ross, Hustlin'
在我所有的从商经历中,我从没听过有人说:“我喜欢办公室政治”。但在我们的周围,令人深恶痛绝的政治又到处都是,甚至自己的公司就是如此。既然大家都不喜欢政治,那为什么它无处不在呢?
政治行为几乎都源自 CEO。也许你会觉得:“我讨厌政治,我也不关心政治,但是我的周围充满了政治气味。这显然不是我造成的。”很遗憾,你并不需要怎么关心政治就会让你的周围充斥政治手段。实际上,很少关心政治的 CEO 才会让办公室充斥政治手段。不关心政治的 CEO 们往往会直接助涨政治行为。
我这里说的政治,就是指员工追求自我职业发展多于价值产出和贡献。也许还有别样的政治类型,但是这类政治行为真的很烦。
CEO 无意识的激励甚至有时刺激了政治行为,办公室政治由此而生。举个非常简单的例子,我们想象一下薪酬决策。作为一个 CEO,资深的员工会反复找你索要加薪。他们会提醒你自己得到的回报已经比市场行情低多了。他们甚至已经手握外面的 offer 了。你大可给他们加个薪。这听起来没什么问题,但你就这样强烈刺激了大家的政治行为。
尤其是你在为一些对你的业务毫无价值的东西做奖励,员工会在你主动为他们的杰出表现嘉奖之前赚取更多的回报。为什么这样做很糟糕?我们仔细分析一下:
现在我们来到一个更复杂的例子。你的 CFO 找到你说他希望在管理方面更进一步。他说他希望最终成为 COO,想了解自己需要具备什么样的技能才能胜任公司的这一职位。作为一个积极的主管,你可能会鼓励他实现自己的梦想。你告诉他你觉得他将来一定会是一个合格的 COO,并且应该开发某些方面的技能。额外的,你告诉他应该在管理上变得够强,这样其他高管 (executes) 才会愿意为他工作。一周之后,你的另一个高管就来找你诉苦了。她说 CFO 问她愿不愿意为他工作。她说你看好他成为最终的 COO。你之前遇到过这种事情吗?恭喜你摊上大事儿了。
避免政治往往会觉得不自然。它挑战了诸如开明思想或鼓励员工发展等管理最佳实践。
管理高层和初级雇员的不同好比跟业余选手和职业拳手过招的不同。如果你跟一个普通人交手,你尽可自然为之不必担心。如果你想退一步,你可以先抬起你迈在前面的一只脚。如果你对垒一位专业拳手,估计就被击到了。职业拳手经过了年复一年的训练,他们善于利用你的每一处微小的失误。先抬起你迈在前面的一只脚向后退会让你在那一瞬间失去重心,这就是你的对手一直等待的机会。
同样的,如果你管理一名初级雇员,他们跟你探讨职业发展的时候,你大可忘掉那些顾虑随性作答。但就像我们之前看到的,在对待那些高度敏感的老家伙时就不一样了。为了不被政治手段击倒,你需要在这方面提炼自己的技巧。
我作为一个 CEO 发展至今,我发现三条非常有效的将政治最小化的秘诀
1. 雇佣有正确目标的人
我之前描述的例子可能卷入了有目标,但本质并不关心政治的人。并不是所有的情况都是这样的。毫无疑问,把你的公司政治搞成美国参议院级别的方式就是雇佣错误目标的人。正如 Andy Grove 所说,正确的目标是把主管的个人成功和公司成功和公司产品的胜利息息相关。错误的目标是把主管的个人成功和公司的收入划清界限。
2. 为潜在的政治行为建立严格的机制并坚持贯彻
某些行动会助涨政治,比如这三点:
我们来审视在每一种情况下,你该如何制定程序来杜绝不好的行为和政治的动机。
绩效管理和薪酬调整
公司的绩效管理和薪酬调整通常都有一些滞后。这并不意味着他们没有认可员工或不给员工加薪,这仅仅是因为他们仓促特许此事在政治手段面前是非常脆弱的。通过规范合理的结构、正规的绩效评估和薪酬评估,你会在更高的高度明确薪水和股票的涨幅情况。尤其对高管的薪酬调整尤为重要,因为这样做会杜绝政治。在上面的例子中,CEO 应该有一套滴水不漏的绩效和薪酬政策,并且跟高管明确他的薪酬会被其他所有人评估。理想状态下,高管的薪酬体系应该有董事会的参与。这会 a) 有助于更好的管理 b) 让意外更难出现。
组织结构设计与调整
如果你管理高级员工,他们会一次又一次希望扩展自己的职责范围。在上面的例子中,CFO 希望成为 COO。在其它情形下,市场的一把手都希望把销售和市场一起运作起来,或工程的一把手希望把研发和产品管理都握在手上。当有些人向你提出类似的要求时,你要非常小心作答,因为你所讲的每一句话都会变成定时炸弹。一般情况下最好什么都别说。最多问问为什么,并且要牢记不要对对方提及的原因做出任何回应和解释。如果你表明了你的想法,那它一定会被传出去的,谣言会变得到处都是,你的周围会被业务无关的讨论所淹没。你应该基于常规的考虑评估你的组织结构,为了做出正确的决定,你可以获取必要的信息,但不要把你的计划透露或暗示出去。一旦你做了决定,那么就立刻执行组织结构调整:别让谣言先传到园区里。
晋升
每次你的公司提拔某些人的时候,其他同级别的人都会对此指指点点,探讨这个人是因为业绩好还是会来事儿才得到晋升的。如果答案是后者,那么其他人的反应无外乎是下面这三种:
很明显哪种行为你都不希望看到。因此你必须有一个正式的、透明的、有正当理由的晋升流程来决定每个人的晋升。通常这个流程是由其他团队成员参与的 (一般晋升流程要让其他主管参与,这些主管的工作性质和这个人类似,高管的晋升流程里应该有董事会的参与)。这个流程的目的是两面的。一方面它会让组织相信公司至少是基于业绩进行晋升评估的,另一方面流程的结果足以充分解释你的晋升。
3. 小心别人打来的“小报告”
一旦你的组织壮大到一定规模,团队成员就会不断相互投诉和抱怨。有时这些批评是非常激进的。要非常小心留意你听到的话以及它背后传递的信息。如果单纯的没有任何防备的解答员工提出的问题,你很容易把你内心认同的信息传递出去。如果大家在公司认为你觉得某个高管不够好,这样的信息会迅速传播开来,并且不会有人求证真相的。最后,大家都不再相信那个“问题高管”,做事也不再有效率。
这里有两种典型的你会听到的抱怨:
对于第一种问题,一般最好的处理方式就是找投诉和被投诉的双方高管拉到一个小黑屋里当面把事情解释清楚。通常一个当面的沟通就可以挽回冲突和错误 (如有)。不要试图隔空解决问题,那样只会带来问题和政治。
第二种问题会更少见同时处理起来也更复杂。如果你的一个高管鼓起勇气质疑他同伴的能力,那么很有可能,这两个人之间有很严重的问题。如果你遇到了这种问题,你一般会得到下面两种回复中的一种:a) 你会从他们那里得知一些你已经知道的事情,或者 b) 他们会给你“惊喜”。
如果他们告诉你的是你已经知道的事情,那么最主要的问题就是你已经让这个问题存在太久了。不论你迟迟不解决问题的理由是什么,你把这件事拖太久了导致现在你的团队已经向这名主管发难。你必须用最快速度解决这个问题。基本上你是要解雇这名高管了,因为我看到过的高官们只有提升得自己业绩和技能的,却从没有重拾起团队的信任和支持的。
而如果你收到的是信息是你从未了解过的,那么你必须立刻打断他,让这位抱怨别人的高管明确,你没办法确定这个评判。你不希望在重新审视他的表现之前就做处理。你也不希望大家觉得投诉是万能的。一旦你终止了谈话,你必须立刻重新审视这名被抱怨的员工。如果你发现他表现的很好,那么你必须找出抱怨他的人的动机并把它处理好,不要让这种言论占上风。如果你发现这个员工确实有问题,那么再回到抱怨他的人提供的信息,这时你应该明确处理表现不好的人。
作为 CEO,你必须系统的考虑自己的一言一行导致的结果。开放,负责,目标导向才是王道,尽量避免错误的激励方式。
]]>Vue.js 是一个非常典型的 MVVM 的程序结构,整个程序从最上层大概分为
这里面大部分内容可以直接跟 Vue.js 的官方 API 参考文档对应起来,但文档里面没有且值得一提的是构造函数的设计,下面是我摘出的构造函数最核心的工作内容。
整个实例初始化的过程中,重中之重就是把数据 (Model) 和视图 (View) 建立起关联关系。Vue.js 和诸多 MVVM 的思路是类似的,主要做了三件事:
v-text="message"
被解析之后 (这里仅作示意,实际程序逻辑会更严谨而复杂):
this.$data.message
,以及node.textContent = this.$data.message
所以整个 vm 的核心,就是如何实现 observer, directive (parser), watcher 这三样东西
]]>Vue.js 是一个非常典型的 MVVM 的程序结构,整个程序从最上层大概分为
这里面大部分内容可以直接跟 Vue.js 的官方 API 参考文档对应起来,但文档里面没有且值得一提的是构造函数的设计,下面是我摘出的构造函数最核心的工作内容。
整个实例初始化的过程中,重中之重就是把数据 (Model) 和视图 (View) 建立起关联关系。Vue.js 和诸多 MVVM 的思路是类似的,主要做了三件事:
v-text="message"
被解析之后 (这里仅作示意,实际程序逻辑会更严谨而复杂):
this.$data.message
,以及node.textContent = this.$data.message
所以整个 vm 的核心,就是如何实现 observer, directive (parser), watcher 这三样东西
Vue.js 源代码都存放在项目的 src
目录中,我们主要关注一下这个目录 (事实上 test/unit/specs
目录也值得一看,它是对应着每个源文件的测试用例)。
src
目录下有多个并列的文件夹,每个文件夹都是一部分独立而完整的程序设计。不过在我看来,这些目录之前也是有更立体的关系的:
api/*
目录,这几乎是最“上层”的接口封装,实际的实现都埋在了其它文件夹里instance/init.js
,如果大家希望自顶向下了解所有 Vue.js 的工作原理的话,建议从这个文件开始看起
instance/scope.js
:数据初始化,相关的子程序 (目录) 有 observer/*
、watcher.js
、batcher.js
,而 observer/dep.js
又是数据观察和视图依赖相关联的关键instance/compile.js
:视图初始化,相关的子程序 (目录) 有 compiler/*
、directive.js
、parsers/*
directives/*
、element-directives/*
、filters/*
、transition/*
util/*
目录,工具方法集合,其实还有一个类似的 cache.js
config.js
默认配置项篇幅有限,如果大家有意“通读” Vue.js 的话,个人建议顺着上面的整体介绍来阅读赏析。
接下来是一些自己觉得值得一提的代码细节
this._eventsCount
是什么? ​一开始看 instance/init.js
的时候,我立刻注意到一个细节,就是 this._eventsCount = {}
这句,后面还有注释
for $broadcast optimization
非常好奇,然后带着疑问继续看了下去,直到看到 api/events.js
中 $broadcast
方法的实现,才知道这是为了避免不必要的深度遍历:在有广播事件到来时,如果当前 vm 的 _eventsCount
为 0
,则不必向其子 vm 继续传播该事件。而且这个文件稍后也有 _eventsCount
计数的实现方式。
这是一种很巧妙同时也可以在很多地方运用的性能优化方法。
前阵子有很多关于视图更新效率的讨论,我猜主要是因为 virtual dom 这个概念的提出而导致的吧。这次我详细看了一下 Vue.js 的相关实现原理。
实际上,视图更新效率的焦点问题主要在于大列表的更新和深层数据更新这两方面,而被热烈讨论的主要是前者 (后者是因为需求小还是没争议我就不得而知了)。所以这里着重介绍一下 directives/repeat.js
里对于列表更新的相关代码。
首先 diff(data, oldVms)
这个函数的注释对整个比对更新机制做了个简要的阐述,大概意思是先比较新旧两个列表的 vm 的数据的状态,然后差量更新 DOM。
第一步:遍历新列表里的每一项,如果该项的 vm 之前就存在,则打一个 _reused
的标 (这个字段我一开始看 init.js
的时候也是困惑的…… 看到这里才明白意思),如果不存在对应的 vm,则创建一个新的。
第二步:遍历旧列表里的每一项,如果 _reused
的标没有被打上,则说明新列表里已经没有它了,就地销毁该 vm。
第三步:整理新的 vm 在视图里的顺序,同时还原之前打上的 _reused
标。就此列表更新完成。
顺带提一句 Vue.js 的元素过渡动画处理 (v-transition
) 也设计得非常巧妙,感兴趣的自己看吧,就不展开介绍了
[keep-alive]
特性 ​Vue.js 为其组件设计了一个 [keep-alive]
的特性,如果这个特性存在,那么在组件被重复创建的时候,会通过缓存机制快速创建组件,以提升视图更新的性能。代码在 directives/component.js
。
如何监听某一个对象属性的变化呢?我们很容易想到 Object.defineProperty
这个 API,为此属性设计一个特殊的 getter/setter,然后在 setter 里触发一个函数,就可以达到监听的效果。
不过数组可能会有点麻烦,Vue.js 采取的是对几乎每一个可能改变数据的方法进行 prototype 更改:
但这个策略主要面临两个问题:
length
,导致 arr.length
这样的数据改变无法被监听arr[2] = 1
这样的赋值操作,也无法被监听为此 Vue.js 在文档中明确提示不建议直接角标修改数据
同时 Vue.js 提供了两个额外的“糖方法” $set
和 $remove
来弥补这方面限制带来的不便。整体上看这是个取舍有度的设计。我个人之前在设计数据绑定库的时候也采取了类似的设计 (一个半途而废的内部项目就不具体献丑了),所以比较认同也有共鸣。
首先要说 parsers
文件夹里有各种“财宝”等着大家挖掘!认真看一看一定不会后悔的
parsers/path.js
主要的职责是可以把一个 JSON 数据里的某一个“路径”下的数据取出来,比如:
var path = 'a.b[1].v'
var obj = {
a: {
b: [
{v: 1},
{v: 2},
{v: 3}
]
}
}
parse(obj, path) // 2
所以对 path
字符串的解析成为了它的关键。Vue.js 是通过状态机管理来实现对路径的解析的:
咋一看很头大,不过如果再稍微梳理一下:
也许看得更清楚一点了,当然也能发现其中有一点小问题,就是源代码中 inIdent
这个状态是具有二义性的,它对应到了图中的三个地方,即 in ident
和两个 in (quoted) ident
。
实际上,我在看代码的过程中顺手提交了这个 bug,作者眼明手快,当天就进行了修复,现在最新的代码里已经不是这个样子了:
而且状态机标识由字符串换成了数字常量,解析更准确的同时执行效率也会更高。
首先是视图的解析过程,Vue.js 的策略是把 element 或 template string 先统一转换成 document fragment,然后再分解和解析其中的子组件和 directives。我觉得这里有一定的性能优化空间,毕竟 DOM 操作相比之余纯 JavaScript 运算还是会慢一些。
然后是基于移动端的思考,Vue.js 虽确实已经非常非常小巧了 (min+gzip 之后约 22 kb),但它是否可以更小,继续抽象出常用的核心功能,同时更快速,也是个值得思考的问题。
第三我非常喜欢通过 Vue.js 进行模块化开发的模式,Vue 是否也可以借助类似 web components + virtual dom 的形态把这样的开发模式带到更多的领域,也是件很有意义的事情。
Vue.js 里的代码细节还不仅于此,比如:
cache.js
里的缓存机制设计和场景运用 (如在 parsers/path.js
中)parsers/template.js
里的 cloneNode
方法重写和对 HTML 自动补全机制的兼容自己也在阅读代码,了解 Vue.js 的同时学到了很多东西,同时我觉得代码实现只是 Vue.js 优秀的要素之一,整体的程序设计、API 设计、细节的取舍、项目的工程考量都非常棒!
总之,分享一些自己的收获和代码的细节,希望可以帮助大家开阔思路,提供灵感。
]]>“团队时间线”是个可视化展示团队所有同学时间分配/管理的平台。每个人都可以在“我的时间管理”页面极简的记录自己的时间,比如从某天到另外一天做了一个项目、或者昨天开了一个重要的会等等。
]]>“团队时间线”是个可视化展示团队所有同学时间分配/管理的平台。每个人都可以在“我的时间管理”页面极简的记录自己的时间,比如从某天到另外一天做了一个项目、或者昨天开了一个重要的会等等。
(下面这段阐述偏管理思考,只对技术感兴趣的同学可以跳过)
为什么要做这件事情呢?是从一个我观察到的团队现状开始的:
我们通常,也应该做事专注在事情上,围绕着任务、业务目标,安排不同的人参与进来,在一起协作。但这样的方式给前端团队带来了很多管理上的死角,我们并没有以人为本,关注人的状态。张三在参与的两个项目里工作安排都是合理的,但是两份工作叠加在一个人身上的时候就不一定了,但任何单个项目团队的负责人都看不到这件事。
所以作为补充,我希望引入“团队时间线”,从人的维度换个角度来看问题。
还有一个和时间线概念很接近的东西是“甘特图”,市面上也有不少现成的组件库,我也参与开发过一个叫 jquery.gantt 的插件。但是同样的,甘特图是专注在团队共同做好一件事情上的,而不是以人为中心的,所以每一行基本是一个子任务,像瀑布一样一步步排下来,一件事情就能占满整个屏幕的宽度和高度。而我希望时间线是可以同时让大家一眼看到前端团队全局的工作状态的。所以甘特图其实不是我想要的东西。
汇总一下自己的想法:
这就是“团队时间线”想法的由来
(下面这段描述请设计师和艺术家们轻拍……)
前两天看优设哥 (优秀网页设计) 有一篇《术语小科普!聊聊线框稿、视觉稿与原型的区别》的文章,很多人可能会觉得这都是“别人家的设计”,没见过周围有人设计界面先画手稿的啊?不都是直接PS么?而且很多产品和交互文档都是拿同类产品截图来的,做完项目都不知道这文档是我们写出来的还是竞争对手写出来的……
我自己觉得,也许对于牛掰的设计师来说,他们只是因为太牛掰了以至于可以脑补那些初级的设计手段罢了。反正自己没那个专业能力,还是照猫画虎中规中矩一点,所以就试着先画框线图,然后实现原型功能,再做出高保真视觉 (视觉稿),再做出高保真原型来。走一遍自己认为比较“规矩“的流程
当然,凑巧我找到了一个利器——53 Paper + 53 Pencil,它可以把 iPad 变成你的画板,而且 app 本身的品质是非常之高的!我现在不光拿它来画框线图,而且开会也可以拿它当会议记录本。
然后趁热打铁,把组件划分出来,并定下来基本的几个组件名和方法/属性名
整个的时间大概用了半个小时
之后,我快速在 web 端实现了一个可交互的体验版本 (UI 很“抽象”,几乎没有美感可言),大概用了一个晚上。包括时间线是可以在表头点击左右滚动的,且有平移的动画效果。
坦白讲,做到这个程度花费的时间比我想象中的少了很多,因为我用到了另外的开发利器,晚些时候介绍:)
有了这样的一个可操作版本,基本上就可以把设计好的功能和操作流程都走通一遍了。
我把做好的这个抽象版本发给某同事,得到这样的评价:
作为一个死码农,觉得这代码比UI漂亮,哈哈~
好吧,我紧接着就做了第三步:视觉设计,我直接利用团队已有的 Bootstrap 皮肤 + 浏览器就完成了这件事,用时 1 个小时,然后又发给了刚才那个同事:
什么鬼,为什么突然就这么好看了
bootstrap,果然
但其实这个设计出来的页面是不能操作的,只是个静态效果。最后我又花了差不多半个小时时间,把静态效果套到之前的“抽象”界面中。第四部也完成了。前后差不多经过了 1 天时间。
前面已经卖了个关子了,能够用一个晚上搞定这个界面的全套功能,包括每个组件的完备性、交互反映、动画效果。Vue + webpack 功不可没,前几天我刚博客介绍过一些 vue + webpack 的内容,没错,就是用这一套技术基础快速搭建起来的。再加上前期产品设计的时候已经把组件划分和主要属性/方法命名都确定过了,所以整个开发过程完全是自顶向下的。大应用拆成子组件,子组件再拆成更小的组件;大函数写成几行伪代码,每行伪代码再拆成不同的子函数……非常之顺畅,几乎没有返工。很多地方的逻辑都是一次浏览器刷新就通过的 (那天工作状态也确实不错)
除了之前博客介绍到的 vue 和 webpack 以及 gulpfile.js 我又加入了 htmlone 打包和 awp 发布等更高集成化的工作方式。让调试、打包、发布都非常简单省时。
就在我正准备搭 Node 服务的时候,刚好找到了 @子之 子大爷,跟他聊起这件事,他也非常认同,后端的服务器设备、数据库环境和身份验证等机制都是现成的,于是大家一拍即合,迅速敲定了数据格式和服务器接口,数据基本都是通过 GET
、POST
、PUT
、DELETE
四个 HTTP 方法完成查询、增加、更新、删除某个工作任务的。
@子之 当时同时在处理的事情也比较多,但开发效率也是奇高的,早上跟他说了想法,晚上接口就准备好了。
又经过几轮松散的调试,过了不到一周时间吧。想法、设计、前端、后端都已经完成了,我们对程序进行了部署。并开始在无线前端的导购产品小组和卖家产品小组中先试用起来。
如上所述,我在大概一周的时间里,参与并见证了“团队时间线”的从无到有。它给我带来的收获,一方面是从团队管理角度一个更好的方式,另一方面实践了很多自己专业范围以外的流程和事情,再一方面也实践了 vue + webpack 的项目流程设计。一举三得。
希望这些产品、经验和收获可以在未来产生更大的价值,尤其是后两者
]]>首先,我会先简单介绍一下 vue 和 webpack:
(当然如果你已经比较熟悉它们的话前两个部分可以直接跳过)
Vue.js 是一款极简的 mvvm 框架,如果让我用一个词来形容它,就是 “轻·巧” 。如果用一句话来描述它,它能够集众多优秀逐流的前端框架之大成,但同时保持简单易用。废话不多说,来看几个例子:
<script src="vue.js"></script>
<div id="demo">
{{message}}
<input v-model="message">
</div>
<script>
var vm = new Vue({
el: '#demo',
data: {
message: 'Hello Vue.js!'
}
})
</script>
首先,代码分两部分,一部分是 html,同时也是视图模板,里面包含一个值为 message
的文本何一个相同值的输入框;另一部分是 script,它创建了一个 vm 对象,其中绑定的 dom 结点是 #demo
,绑定的数据是 {message: 'Hello Vue.js'}
,最终页面的显示效果就是一段 Hello Vue.js
文本加一个含相同文字的输入框,更关键的是,由于数据是双向绑定的,所以我们修改文本框内文本的同时,第一段文本和被绑定的数据的 message
字段的值都会同步更新——而这底层的复杂逻辑,Vue.js 已经全部帮你做好了。
首先,我会先简单介绍一下 vue 和 webpack:
(当然如果你已经比较熟悉它们的话前两个部分可以直接跳过)
Vue.js 是一款极简的 mvvm 框架,如果让我用一个词来形容它,就是 “轻·巧” 。如果用一句话来描述它,它能够集众多优秀逐流的前端框架之大成,但同时保持简单易用。废话不多说,来看几个例子:
<script src="vue.js"></script>
<div id="demo">
{{message}}
<input v-model="message">
</div>
<script>
var vm = new Vue({
el: '#demo',
data: {
message: 'Hello Vue.js!'
}
})
</script>
首先,代码分两部分,一部分是 html,同时也是视图模板,里面包含一个值为 message
的文本何一个相同值的输入框;另一部分是 script,它创建了一个 vm 对象,其中绑定的 dom 结点是 #demo
,绑定的数据是 {message: 'Hello Vue.js'}
,最终页面的显示效果就是一段 Hello Vue.js
文本加一个含相同文字的输入框,更关键的是,由于数据是双向绑定的,所以我们修改文本框内文本的同时,第一段文本和被绑定的数据的 message
字段的值都会同步更新——而这底层的复杂逻辑,Vue.js 已经全部帮你做好了。
我们还可以加入更多的 directive,比如:
<script src="vue.js"></script>
<div id="demo2">
<img title="{{name}}" alt="{{name}}" v-attr="src: url">
<input v-model="name">
<input v-model="url">
</div>
<script>
var vm = new Vue({
el: '#demo2',
data: {
name: 'taobao',
url: 'https://www.taobao.com/favicon.ico'
}
})
</script>
这里的视图模板加入了一个 <img>
标签,同时我们看到了 2 个特性的值都写作了 {{name}}
。这样的话,图片的 title
和 alt
特性值就都会被绑定为字符串 'taobao'
。
如果想绑定的特性是像 img[src]
这样的不能在 html 中随意初始化的 (可能默认会产生预期外的网络请求),没关系,有 v-attr="src: url"
这样的写法,把被绑定的数据里的 url
同步过来。
没有介绍到的功能还有很多,推荐大家来我(发起并)翻译的Vue.js 中文文档
最后要介绍 Vue.js 对于 web 组件化开发的思考和设计
如果我们要开发更大型的网页或 web 应用,web 组件化的思维是非常重要的,这也是今天整个前端社区长久不衰的话题。
Vue.js 设计了一个 *.vue
格式的文件,令每一个组件的样式、模板和脚本集合成了一整个文件, 每个文件就是一个组件,同时还包含了组件之间的依赖关系,麻雀虽小五脏俱全,整个组件从外观到结构到特性再到依赖关系都一览无余 :
并且支持预编译各种方言:
这样再大的系统、在复杂的界面,也可以用这样的方式庖丁解牛。当然这种组件的写法是需要编译工具才能最终在浏览器端工作的,下面会提到一个基于 webpack 的具体方案。
从功能角度,template, directive, data-binding, components 各种实用功能都齐全,而 filter, computed var, var watcher, custom event 这样的高级功能也都洋溢着作者的巧思;从开发体验角度,这些设计几乎是完全自然的,没有刻意设计过或欠考虑的感觉,只有个别不得已的地方带了自己框架专属的 v-
前缀。从性能、体积角度评估,Vue.js 也非常有竞争力!
webpack 是另一个近期发现的好东西。它主要的用途是通过 CommonJS 的语法把所有浏览器端需要发布的静态资源做相应的准备,比如资源的合并和打包。
举个例子,现在有个脚本主文件 app.js
依赖了另一个脚本 module.js
// app.js
var module = require('./module.js')
... module.x ...
// module.js
exports.x = ...
则通过 webpack app.js bundle.js
命令,可以把 app.js
和 module.js
打包在一起并保存到 bundle.js
同时 webpack 提供了强大的 loader 机制和 plugin 机制,loader 机制支持载入各种各样的静态资源,不只是 js 脚本、连 html, css, images 等各种资源都有相应的 loader 来做依赖管理和打包;而 plugin 则可以对整个 webpack 的流程进行一定的控制。
比如在安装并配置了 css-loader 和 style-loader 之后,就可以通过 require('./bootstrap.css')
这样的方式给网页载入一份样式表。非常方便。
webpack 背后的原理其实就是把所有的非 js 资源都转换成 js (如把一个 css 文件转换成“创建一个 style
标签并把它插入 document
”的脚本、把图片转换成一个图片地址的 js 变量或 base64 编码等),然后用 CommonJS 的机制管理起来。一开始对于这种技术形态我个人还是不太喜欢的,不过随着不断的实践和体验,也逐渐习惯并认同了。
最后,对于之前提到的 Vue.js,作者也提供了一个叫做 vue-loader 的 npm 包,可以把 *.vue
文件转换成 webpack 包,和整个打包过程融合起来。所以有了 Vue.js、webpack 和 vue-loader,我们自然就可以把它们组合在一起试试看!
回到正题。今天要分享的是,是基于上面两个东西:Vue.js 和 webpack,以及把它们串联起来的 vue-loader
Vue.js 的作者以及提供了一个基于它们三者的项目示例 (链接已失效)。而我们的例子会更贴近实际工作的场景,同时和团队之前总结出来的项目特点和项目流程相吻合。
<components>
组件目录,一个组件一个 .vue
文件
a.vue
b.vue
<lib>
如果实在有不能算组件,但也不来自外部 (tnpm) 的代码,可以放在这里
foo.css
bar.js
<src>
主应用/页面相关文件
app.html
主 htmlapp.vue
主 vueapp.js
通常做的事情只是 var Vue = require('vue'); new Vue(require('./app.vue'))
<dist>
(ignored)<node_modules>
(ignored)gulpfile.js
设计项目打包/监听等任务package.json
记录项目基本信息,包括模块依赖关系README.md
项目基本介绍通过 gulpfile.js
我们可以设计整套基于 webpack 的打包/监听/调试的任务
在 gulp-webpack 包的官方文档里推荐的写法是这样的:
var gulp = require('gulp');
var webpack = require('gulp-webpack');
var named = require('vinyl-named');
gulp.task('default', function() {
return gulp.src(['src/app.js', 'test/test.js'])
.pipe(named())
.pipe(webpack())
.pipe(gulp.dest('dist/'));
});
我们对这个文件稍加修改,首先加入 vue-loader
tnpm install vue-loader --save
.pipe(webpack({
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue'}
]
}
}))
其次,把要打包的文件列表从 gulp.src(...)
中抽出来,方便将来维护,也有机会把这个信息共享到别的任务
var appList = ['main', 'sub1', 'sub2']
gulp.task('default', function() {
return gulp.src(mapFiles(appList, 'js'))
...
})
/**
* @private
*/
function mapFiles(list, extname) {
return list.map(function (app) {return 'src/' + app + '.' + extname})
}
现在运行 gulp
命令,相应的文件应该就打包好并生成在了 dist
目录下。然后我们在 src/*.html
中加入对这些生成好的 js
文件的引入:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Main</title>
</head>
<body>
<div id="app"></div>
<script src="../dist/main.js"></script>
</body>
</html>
用浏览器打开 src/main.html
这时页面已经可以正常工作了
监听更加简单,只要在刚才 webpack(opt)
的参数中加入 watch: true
就可以了。
.pipe(webpack({
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue'}
]
},
watch: true
}))
当然最好把打包和监听设计成两个任务,分别起名为 bundle
和 watch
:
gulp.task('bundle', function() {
return gulp.src(mapFiles(appList, 'js'))
.pipe(named())
.pipe(webpack(getConfig()))
.pipe(gulp.dest('dist/'))
})
gulp.task('watch', function() {
return gulp.src(mapFiles(appList, 'js'))
.pipe(named())
.pipe(webpack(getConfig({watch: true})))
.pipe(gulp.dest('dist/'))
})
/**
* @private
*/
function getConfig(opt) {
var config = {
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue'}
]
}
}
if (!opt) {
return config
}
for (var i in opt) {
config[i] = opt
}
return config
}
现在你可以不必每次修改文件之后都运行 gulp bundle
才能看到最新的效果,每次改动之后直接刷新浏览器即可。
打包好的代码已经不那么易读了,直接在这样的代码上调试还是不那么方便的。这个时候,webpack + vue 有另外一个现成的东西:source map 支持。为 webpack 加入这个配置字段 devtool: 'source-map'
:
var config = { module: { loaders: [ { test: /.vue$/, loader: 'vue'} ] }, devtool: 'source-map' }
再次运行 gulp bundle
或 gulp watch
试试看,是不是开发者工具里 debug 的时候,可以追踪断点到源代码了呢:)
完整的 javascript 代码如下:
var gulp = require('gulp')
var webpack = require('gulp-webpack')
var named = require('vinyl-named')
var appList = ['main']
gulp.task('default', ['bundle'], function() {
console.log('done')
})
gulp.task('bundle', function() {
return gulp.src(mapFiles(appList, 'js'))
.pipe(named())
.pipe(webpack(getConfig()))
.pipe(gulp.dest('dist/'))
})
gulp.task('watch', function() {
return gulp.src(mapFiles(appList, 'js'))
.pipe(named())
.pipe(webpack(getConfig({watch: true})))
.pipe(gulp.dest('dist/'))
})
/**
* @private
*/
function getConfig(opt) {
var config = {
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue'}
]
},
devtool: 'source-map'
}
if (!opt) {
return config
}
for (var i in opt) {
config[i] = opt[i]
}
return config
}
/**
* @private
*/
function mapFiles(list, extname) {
return list.map(function (app) {return 'src/' + app + '.' + extname})
}
做出一个 vue + webpack 的 generator,把这样的项目体验分享给更多的人。目前我基于团队内部在使用的轻量级脚手架工具写了一份名叫 just-vue
的 generator,目前这个 generator 还在小范围试用当中,待比较成熟之后,再分享出来
其实上面提到的 just-vue
脚手架已经远不止文章中介绍的东西了, 我们在业务落地的“最后一公里”做了更多的沉淀和积累,比如自动图片上传与画质处理、rem单位自动换算、服务端/客户端/数据埋点接口的梳理与整合、自动化 htmlone 打包与 awp 发布等等。它们为支持业务的开发者提供了更简单高效的工作体验。 篇幅有限,更多内容我也希望将来有机会再多分享出来。
最后再次希望大家如果有兴趣的话可以来玩一下,无线前端组内的同学我都愿意提供一对一入门指导:)
Just Vue!
]]>晒一下自己用 Koa next generation web framework for node.js 写的一个 web 服务
这个 web 服务主要是做内容的列表展示和搜索的 (可能说得比较抽象,但确实是 web 服务最常需要做的事情) 主要的文件一共就2个:
app.js
主程序lib/model.js
数据层其中 model.js
是和具体业务逻辑相关的,就不多介绍了,这也不是 Koa 的核心;而 app.js
的代码可以体现 Koa 的很多优点,也使得代码可以写得非常简练而去清晰——这是我自己都完全没有想到的事情
晒一下自己用 Koa next generation web framework for node.js 写的一个 web 服务
这个 web 服务主要是做内容的列表展示和搜索的 (可能说得比较抽象,但确实是 web 服务最常需要做的事情) 主要的文件一共就2个:
app.js
主程序lib/model.js
数据层其中 model.js
是和具体业务逻辑相关的,就不多介绍了,这也不是 Koa 的核心;而 app.js
的代码可以体现 Koa 的很多优点,也使得代码可以写得非常简练而去清晰——这是我自己都完全没有想到的事情
// resources
var koa = require('koa')
var app = koa()
var logger = require('koa-logger')
var route = require('koa-route')
var fs = require('fs')
var path = require('path')
var extname = path.extname
var views = require('co-views')
var render = views('./views', {
map: { html: 'ejs' }
})
var model = require('./lib/model')
其中:
koa
是最核心的库,app
是 koa
生成的 web 服务主程序koa-logger
和 koa-route
都是koa官方开发的“中间件”,分别用来打印日志和路由设置,路由设置稍后还会提到fs
和 path
都是 Node 的官方包,用来进行本地文件和路径相关的处理,辅助性质的co-views
是用来渲染模板的库,而 render
是它生成的实例,这个用法也跟传统用法不太一样,稍后会提及// workflow
app.use(logger())
app.use(route.get('/', list))
app.use(route.get('/page/:page', list))
app.use(route.get('/search/:keywords', search))
app.use(route.get('/search/:keywords/:page', search))
app.use(function *(next) {
if (!this.path.match(/^\/assets\//)) {
yield* next
return
}
var path = __dirname + this.path
var fstat = yield stat(path)
if (fstat.isFile()) {
this.type = extname(path)
this.body = fs.createReadStream(path)
}
})
app.use(function *(next) {
if (this.needRendered) {
this.body = yield render(this.templateView, {cache: false, data: this.templateModel})
}
yield* next
})
// utils
function stat(file) {
return function (done) {
fs.stat(file, done)
}
}
这部分代码是用来规划服务器工作流的,从请求被接受到响应被发出,整个过程都在这段代码里一览无余。工作流设计的主要的用法是 app.use(...)
。里面的参数其实就是一个 generator。
list
和 search
其实都是在利用 lib/model
在生成数据,准备给模板进行渲染。这里的原理也有特殊之处,稍后会看到app.use
,分别是处理静态资源目录 assets
和对模板+数据进行渲染所以完整的工作流可以理解为:
logger
-> 路由分发 -> list
或 search
-> 模板渲染 -> 回应logger
-> 找到对应的 assets
文件 -> 回应function *() {}
和 yield
是啥? ​这个其实是 Koa 的精髓所在,在介绍它之前,我们先把 list
和 search
的代码也贴出来:
// routes
function *list(page, next) {
next = arguments[arguments.length - 1]
this.templateView = 'page'
this.templateModel = yield model.list({page: page})
this.needRendered = true
yield *next
}
function *search(keywords, page, next) {
next = arguments[arguments.length - 1]
this.templateView = 'search'
this.templateModel = yield model.search({keywords: keywords, page: page})
this.needRendered = true
yield *next
}
大家会发现,首先 app.use(...)
和 route.get(path, ...)
传入的参数都是一种写得很像函数的东西,但不同之处是函数的写法是 function foo() {...}
,而这里的写法多了一个星号,即 function *foo() {}
。这种写法其实就是 ES6 里的 generator。而 yield
正是配合这个写法的一种语法。
有关 ES6 generator 的基础知识,建议大家来 @兔哥 的这个 ES6 教程网页来学习,这里不做原理方面的赘述。但我想说的是,由于 web 服务的处理本身就是“一层一层”的,并且有些处理是可以同步的,有些是只能异步的,我们不免要精心设计很多中间件并保障它的可扩展性,同时尽量简化异步操作的写法保障它的可读性。
有了 ES6 generator 和 yield
之后,我们的每一层中间件都可以从流程上看成一个以 yield *next
语句切分出来的 “三明治”:
function *(next) {
// 下一步之前的操作
yield *next // 进行下一步
// 所有逻辑处理完之后的补充操作
}
而且这个“下一步”是不介意是不是异步行为,都可以这样简单描述清楚的。
后头看我们设计的整个工作流的实现:
我们这里的逻辑基于全部是出现在 yield *next
之前的,但是如果你需要在临发出响应之前做点什么,就可以写在其后面了
co-views
的用法 ​co-views
其实是对通用模板引擎渲染平台 consolidate 的封装,consolidate 应该算是 express.js 时代非常重要的一个库,它支持包括 ejs, mustache, swig 等各种模板渲染并提供统一的 api 调用方法。根据对 co-views
源码的分析,它把 consolidate 统一的 api 又封装成了 return function (done) {...}
的形态,这样源代码中的 yield render(view, model)
就能够融入 generator 的逻辑之中。
值得一提的是,源代码中 yield render(view, model)
这里的 model
传入了一个 {cache: false}
的参数,这会意味着模板不会被缓存,每次修改模板文件之后,在不重启服务的情况下,刷新页面就可以看到最新的效果。这个选项是针对开发环境设置的,为了保障线上环境的运行性能和效率,这个选项应该是不需要的。
lib/model
的用法 ​同上,我们在 lib/model.js
里封装的 yield model.list({page: page})
和 yield model.search({keywords: keywords, page: page})
也都会生成形如 return function (done) {...}
的返回值,以融入 generator 的逻辑之中。
// listen
app.listen(3000)
console.log('listening on port 3000')
That's it
在首次尝试用 generator 的方式编写 web 服务的时候,我自己一开始总会把 yield
的位置、yield
后面要不要加星号、function
后面要不要加星号、app.use()
的调用顺序这几件事情弄得乱糟糟的,可能还是对 generator 和 koa 的理解不够深入,不过逐渐写着写着,感受到了更多的爽和快感。到最后用如此简单的一个 js 文件完成了全部的功能和逻辑串联,还是觉得很兴奋的。大家如果感兴趣也可以搞来玩一玩,写点自己平时用得到用不到的小玩意儿体验一下:)
我觉得把东西开源出来之前,有几件事要准备好,不然除了自己刷存在感之外,真的没意义。比如:
印象中我见到的优秀的开源项目,基本都在被大家广泛认识之前,都已经把这些事情打理好了——这也是我一直推崇的。
好吧很惭愧,这几点我还都没有做到……
不过在这之前,我愿意在此分享一些自己开发中的心得,跟大家一起探讨相关的话题。
-- 以上是一些比较啰嗦的铺陈 --
在开发大型应用的时候,难免要用到一些组件化的分解方式。比如:把一个相册浏览界面分解成:“相册列表”和“大图预览”两个区域,“相册列表”又由一个个“相册缩略图”组成,每个“相册缩略图”包含了一个“小图片”以及“预览按钮”、“删除按钮”、“排序按钮”等操作按钮……
而如何管理和划分组件逐渐变成了前端工程里的一门学问。
]]>我觉得把东西开源出来之前,有几件事要准备好,不然除了自己刷存在感之外,真的没意义。比如:
印象中我见到的优秀的开源项目,基本都在被大家广泛认识之前,都已经把这些事情打理好了——这也是我一直推崇的。
好吧很惭愧,这几点我还都没有做到……
不过在这之前,我愿意在此分享一些自己开发中的心得,跟大家一起探讨相关的话题。
-- 以上是一些比较啰嗦的铺陈 --
在开发大型应用的时候,难免要用到一些组件化的分解方式。比如:把一个相册浏览界面分解成:“相册列表”和“大图预览”两个区域,“相册列表”又由一个个“相册缩略图”组成,每个“相册缩略图”包含了一个“小图片”以及“预览按钮”、“删除按钮”、“排序按钮”等操作按钮……
而如何管理和划分组件逐渐变成了前端工程里的一门学问。
最简单的分解方式是树形分解,自上而下。比如刚才的那个相册浏览界面的例子。
同时,我们会发现,树形的最末端往往存在着有共性的组件,比如按钮、文本框之类的组件,它们无处不在。这时,就有了所谓的“基础组件”和“业务组件”之分。“基础组件”是共享的,树形结构中的任何一个结点(“业务组件”)都可以直接使用这些“基础组件”。
如果程序的结构再复杂,那么就在“业务”和“基础”之间分更多的层,每一层有自己明确的职能范围,同时,较高层的组件可以自由调用较低层的组件。
配置信息大多是在组件在兼顾通用性抽象和特殊性业务时出现的。好的配置设计可以避免大量重复的组件设计和实现。
较简单的配置信息通常都是组件本身的一些属性 (properties) 或特性 (attributes),在 webcomponents (polymer) 的场景下,就是:
<polymer-element name="x-person" attributes="name, age, gender, avatar, ...">
...
</polymer-element>
进一步的,有时候我们需要把上层组件的配置信息带到更下层的组件:
<polymer-element name="x-person-avatar" attributes="avatar">
...
</polymer-element>
<polymer-element name="x-person" attributes="name, age, gender, avatar, ...">
<template>
...
<x-person-avatar avatar="{{avatar}}"></x-person-avatar>
...
</template>
...
</polymer-element>
如果一个程序的组件层次太深,则可能出现下面两个问题:
于是顺着这个思路,我们发现,有一种不太起眼的办法在很早的时候就被忽略掉了——这就是全局配置信息。
通常情况下,和整体应用所处环境相关,同时和上层组件无关的配置适合做全局配置。
举一个实践中的例子:在开发图片上传组件的时候,我们发现,图片上传组件往往需要一个上传图片的服务器地址,这个地址在固定的用户、固定的应用之中,通常是一致的,只是在不同的应用中,图片可能需要长传到不同的服务器地址。
这种情况下,通过组件的配置字段一层一层向下传递找到图片上传组件显然是很繁琐的。于是我们想了个简单的办法:
<body>
里面) 写入一些 <input type="hidden">
的标签,注明服务器相关配置2015-03-31 追加说明:感谢民工哥 @民工精髓 在微博上的指点。也是因为很多服务器的配置是后端同学决定的,所以我们创造了这种对传统后端配置友好的 <input type="hidden">
写法。这样的写法对于后端的友好之处我就不一一列举了。如果是纯前端程序,配置来自前端,确实直接定义全局变量就好,不必这么麻烦。
于是整个程序变成了:
<polymer-element ...>
<script>
Polymer({
ready: function () {
var inputList = document.querySelectorAll('input[type="hidden"]');
...
}
});
</script>
</polymer-element>
...
<body>
<input type="hidden" name="uploadUrl" value="/pathToUpload.do">
...
</body>
这样不管图片上传组件用在哪里,其它组件都不会因此而产生负担,同时这些配置的管理也变得很清晰——这甚至和前端工程师平时和后端工程师协作的流程是完全吻合的:前端负责写好 components,后端负责把 <input type="hidden">
配置好。
我们顺着上面的思路继续想:如果程序中很多组件都有类似的配置需求,那么:
<input type="hidden">
不能被滥用于是,就有了 Zorro 现在的一个组件:<z-config>
。它的大致功能如下:
<body>
最外层的 <input type="hidden">
配置信息——这显然是和后端工程师约定过的自从有了这个组件,很多配置相关的问题在 webcomponents 中都显得很轻松了。毫不夸张的说前端工程师和后端工程师的关系也因此不像之前那么紧张了。
后来,我们在配置管理的基础上,加入了更多的实用信息。比如:
location.href
中的字段信息localStorage
一样提供一块全局共享的键值对空间,方便组件之间共享状态信息<z-config>
在被创建时,根据不同的场景设置一些初始化数据等以上这些,构成了今天的 <z-config>
组件
实现机制并不复杂,这里就暂不贴代码出来了,但终归是会开源的,我保证。
说回配置管理本身,它实际上是一种信息在程序和组件之间的流通方式。我们基于对 webcomponents 自身特点和形态的理解,加上业务实践中的一些体会,设计了这样的一个标签。希望给大家一些启发,同时也欢迎大家的讨论和观点。
]]>今天晚上,改完了自己名下的最后一个bug,也算是给2014简单收个尾吧,也突然觉得自己有一点时间回顾一下了
先从13年说起吧,最大的变化就是我换工作了,也搬家了,从北京搬到了杭州,从此过上了南方人的生活:
经过了1年多的时间,我想说,我很喜欢杭州这个地方。
然后说说工作吧。
这一年多时间我其实没做太多事情,更多的还是在熟悉环境,熟悉人、熟悉业务、熟悉流程、熟悉沟通方式、熟悉做事风格。阿里确实是个巨大的集团,有超过十年的历史和沉淀。我希望可以帮助团队做得更好,但首当其冲的事情是要先弄清楚游戏规则,努力找到问题的症结,谨慎的提出可实施方案,然后循序渐进的往前走。可以说每一步都要走得小心谨慎,稍有疏漏,可能就前功尽弃了,这一点在阿里可能体现的尤为明显。我想这也正是我之前的工作经验可以体现出价值的地方。
从工程和管理的角度,我今年主要是以培训或布道的方式为大家分享一些系统而又务实的最佳实践,包括如何高效开会、合作、项目管理、代码管理、时间管理,也包括组织团队讨论编码规范、项目目录结构、文档格式、工具链使用等。希望可以全面的提升团队的工作效率和效果。这些工作会继续延续到2015年。
团队的技术视野也是一个亟待提高的地方,这同时也和团队每一位同学的职业发展有着密切的联系,这也是在新的一年我希望可以做好的一件事。
技术上,今年我主要的精力聚焦在了2件事情上:前半年花了一些时间在触摸屏幕操作上,研究了几个手势库,也尝试自己写,不过没有太像样的成果,算是个失败的过程吧,我今天回顾这件事情,主要是没有抓到重点,上来就研究多点复杂操作,也许这些东西在未来可能会显得更有价值,还是决定放一放,什么时候有了更成熟的思路,再考虑拿起来;后半年花了更多时间在 web components 上,还有 polymer 框架,我觉得这是我们看得见的未来,非常值得投入进去看一看,我和几个同事一起做了些实践,也有很多心得。
在全年技术探索和实践的过程中,我有一个深刻的感悟,是和“造轮子”这件事有关系的,在今天这个技术世界里,与其说“不要重复造轮子”,不如说“造轮子”的和“用轮子”的已经是两种性质的工作了。我们今天很多人选择的工作,首要任务是造汽车而不是造轮子。我觉得这是很多讨论“到底要不要造轮子”的问题的根源。造汽车和造轮子有各自的技术含量,有各自需要认真思考的问题,如果没有认清这个问题,明明是造汽车的,结果觉得造轮子更牛掰而去造轮子,必定没有好结果,或者事倍功半;如果真想造轮子,应该好好找个造轮子的地方,才会造出最好的轮子,不然也只会做个半调子。篇幅有限,这事儿暂不细说了……
技术方面还有一件事,就是开始组织 W3C 中文兴趣组的技术讨论,我们这一年组织了几次《ig在线talk》,也发起了一些标准翻译,也讨论了一些诸如首屏渲染的提案,还有中文字体和排版的制定,有的时候电话会议的时间已经超了,但是大家还会津津有味的在技术的领域里讨论闲聊,这种感觉是很少有的,相信经历过这些讨论的同学们也感受得到。
另外自己在2014年初暗自下决心要看10本以上的书——这对于我这种平时没有看书习惯的人来说其实并不容易——这个目标算是已经达成了。这里面《习惯的力量》、《合作的进化》、《反脆弱》、《rework》、《remote》都是令我印象深刻的书,看过之后有非常多的收获。
2015年,我有这么几个简单的想法:
到明年这个时候,我会把这篇blog翻出来看看完成的怎么样:)
]]>p.s. 这 2 年真的经历了不少事情
港版风景
]]>p.s. 这 2 年真的经历了不少事情
港版风景
唐老鸭
春天的气球
旋转木马
快到推车里来
山西博物馆
索菲亚大教堂
年轮
晚霞
杭州生活
杭州一角
圣诞
阿里巴巴滨江园区
我的世界杯
手机屏保 之 淘宝城
手机屏保 之 龙井品茶
虾米音乐节
双11演唱会
朋友的婚礼
]]>之前参加过 2 次,今年的 D2 是我第一次以“自己人”的身份参加的。和往年一样,受益匪浅,但也有了一些不一样的想法。
]]>之前参加过 2 次,今年的 D2 是我第一次以“自己人”的身份参加的。和往年一样,受益匪浅,但也有了一些不一样的想法。
纵览这次 D2 的主题
- 张可竞:《指尖上的数据》
- 苏 千:《支付宝前后端分离的思考与实践》
- 林 楠:《nodejs一小步 前端开发一大步》
- 祝 犁:《Listen to the buzz of Angular.JS — 阿里云控制台AngularJS实践》
- 周 杰:《第三方开发前端实践》
- 不 四:《企业级 NPM 服务在阿里的实践》
- 贝 勒:《面向多端的蘑菇街前端技术架构》
- 弘 树:《航旅无线H5技术体系成长之路》
- 刘 威:《京东前端工业化实践之路》
- 一 位:《淘宝前端工程与自动化体系》
- 贺师俊:《透过ES6看JS未来》
- 邓 钢:《架构与IBM前端》
- 张克军:《豆瓣的前端发展思路》
我们不难发现,这次的主题方向有一个很明显的趋势,就是工程体系化和整体架构。大家不约而同的在这里摸索、实践、分享,我觉得这不是一件偶然的事情。我记得自己 2011 年的时候写过一篇关于当时如火如荼的 HTML5 的文,我当时的一个观点是:
我觉得HTML5需要更多模式化和工程化的思维,而不仅仅是技术上实现某个效果的可能性……唯有更多更丰富的上层建筑,才会让HTML5真正发挥威力……最后,优秀的规范、工具、函数库、平台、引擎、理念,才会催生真正优秀的HTML5作品甚至是HTML5产业。
今天,我们已经不光拘泥于一份实用的新规范、一个酷炫的前端效果、一个满足需求设计合理的代码库,想得更远了,站得更高了。或许这一次的“绽放”过后,就是开花结果的时节了。
同时我也觉得,我们今天已经有了这么大篇幅的主题了。分享有余,讨论不足。我认为分享和讨论应该是结合在一起才最有意义的,分享让人身临其境,讨论则会碰撞出更大的火花,产生更大的效应。我们是否可以在各自的“独门手艺”中找到灵感和认同,进而融合、演进,产生质的飞跃,共同提升到一个更高的高度,最后发现工程与架构的普世真理,这是我由这次 D2 想到的第一件事。
第二件事是前端团队。
大家在技术分享之余聊得最多的应该就是这个话题了。怎么找工作,怎么招聘,怎么应付老板,怎么带团队。
对于前两个问题,我个人的观点是要注重技术基础和学习能力。这两点主要看个人追求和造化,而经验与工程能力是更易于在工作过程中积累和培养的。那么问题也来了:我们假设有一部分,或者说相当一部分人来参加 D2 或业余时间任何别的技术交流会,是为了自我提升而来,我们是否应该更多保留一些技术本身的话题呢?
我不禁想到一个极端的情况,就是我们每个人的“格局”都很大,看得都很远,规划得也很好,但是真正做起事情来,需要动手了,就手忙脚乱了,这似乎不太好吧。也许基础的东西是更通用的、直接受益的、可以举一反三的——尽管它不那么光鲜亮丽。
我也不相信我们今天纯技术的层面都很好了、也没有值得深入琢磨的玩意儿了。如果我们在这个地方都有长足的进步,我不担心大家找不到合适的工作,老板也不担心找不到合适的人。
而对于后两个问题,我的感觉是,实际工作中,总会有一些让人觉得脏、累、烦,谁都不想处理的事情。很多团队的问题都出自如何优雅的处置这些“dirty work”。强势的主管向下施加压力,结果员工疲于奔命对主管失去信心;心软的主管仍凭大家随波逐流结果没有业绩员工也失去了信心。似乎是个解不开的锁。这确实是需要很多智慧的。那么问题又来了,**我希望 D2 今后可以有一些关于团队、关于人的主题。我们的技术能搞上去,业绩能搞上去,人,怎么样?**大家不妨一起聊聊看 :)
所以,我最终想说的是,把大家茶歇、酒会环节,私下交流最多的东西找出来,可以变成我们搬到台面上,认认真真探讨的话题。
这些,都是我由今年 D2 前端论坛活动所想到的。
当然最后,D2 也让我重逢了很多老朋友,结实了很多新朋友。期待与大家的再次见面。
]]>找到的这篇文章算是对我之前写的 《标签?ID?还是CLASS?》 的再深入。我当时写那篇文章的时候,就有朋友提出了“非语义化”的 class 命名的问题,我当时确实觉得很纠结,简单的想法是“框架性质的表象 class 我没异议……框架的实质是通过降低灵活性达成更广泛的共识,我们个人不要再创造这样的样式就好了”,但没有想到特别好的“套路”,更多的是在实际情况中再分辨。看过这篇文章,我似乎找到了更好的答案。同时顺着文中提到的 Nicolas 那篇文章看下去,也对 OOCSS、BEM 之类的提法有了更多的认同感。特译给大家参考。
]]>找到的这篇文章算是对我之前写的 《标签?ID?还是CLASS?》 的再深入。我当时写那篇文章的时候,就有朋友提出了“非语义化”的 class 命名的问题,我当时确实觉得很纠结,简单的想法是“框架性质的表象 class 我没异议……框架的实质是通过降低灵活性达成更广泛的共识,我们个人不要再创造这样的样式就好了”,但没有想到特别好的“套路”,更多的是在实际情况中再分辨。看过这篇文章,我似乎找到了更好的答案。同时顺着文中提到的 Nicolas 那篇文章看下去,也对 OOCSS、BEM 之类的提法有了更多的认同感。特译给大家参考。
这并不是一篇有关 CSS 架构的文章,也并不是一篇有关命名规范的文章,而关乎我们如何定位元素,关乎命名本身,关乎我们如何把元素及其相关的一段样式连接起来。
10 个开发者里有 9 个都同意:在撰写 CSS 中命名什么的部分是最难的了。因为我们无法预知未来。一个 class 名可以在今天完美的应景,但是明天设计发生改变了,可能就不适用了。所以我们需要提炼应景的标记和样式。嗷~
如何面对这一状况呢?那便是让命名尽量显得不太会改的样子。
我们通常会根据三类情况给定一个 class 名:
这几类 class 名是趋向于稳定特质的。如果我们遵循这些命名原则,就会显得更明智,而且我们的 CSS 会更好的适应未来的改变。
<button class="positive-button">Send Message</button>
功能性 class 名例如 positive-button
、important-text
或 selected-tab
。这些元素的样式是基于其功能或含义的。所以其 class 名、样式及这样引用样式的理由,都是强连接的。因此 class 名和样式是相关的。
因为有这些强连接,所以样式是几乎不会被改变的。如果你真的要改变一个 positive-button
的样子,那这个改变也是每个肯定语气的按钮都要改变的。如果你的设计师的想法是改变“肯定语气的按钮”,而不是设置菜单里“增加用户”的按钮,那么这件事就很轻松且易于维护。你考虑的不是哪个独立的页面,而是整个系统。
功能性的 class 名很棒。只要有这个可能,这应该就是你想要撰写样式的方式。但是功能性的 class 名不是所有情况都适用的。
设计并不一定都有逻辑性。当我们讨论按钮的时候,给出一个功能性的 class 名是很容易的。大部分情况下其功能和样式也紧密相关。但是我们撰写的其它 99% 的样式都不太容易给出这样的例子。有的时候这个块区域需要一个内阴影,因为这样看起来很漂亮;有的时候图标需要在 hover 的时候长大一点因为这样很 cute;有的时候文本需要是橙色的,好吧,因为它就是橙色的。一个网站不是每个视觉的部分背后都有功能性的理由,这无可厚非。
所以开发者该怎么应对呢?我们站在了这个十字路口。如果我们不能想出一个合适的功能性 class 名,那么我们不妨基于内容、或者展现给它起个名字。它们也各自暗示着不同的可维护性。
<button class="submit-button">Send Message</button>
基于内容的 class 名是描述它们包含的内容的 class 名。如果你曾经见到过类似 submit-button
、intro-text
或 profile-photo
的 class 名,那些名字就是基于内容起的。
这些 class 名感觉很干净。它们让你的内容 (HTML) 和样式 (CSS) 之间保持简洁的分离。理论上,这可以让你完全改版网站的样式和感官而无需触碰到 HTML。CSS Zen Garden 就是这样的。
现在我们回到最初的问题:“这样做的好处是什么?”
我从来没有被要求改版一个网站而不触碰 HTML。变化是存在各种可能的。当然一些 HTML 可以作为后台系统的集成成果固定下来,但是随着你对 HTML 的失控,这时你还是需要写一些非理想化的 hacky CSS 来应付设计的变化。想想看,CSS Zen Garden 更多的是一个 CSS 技术演示而不是一个可维护的 CSS 的例子。没有必要一味追求在改版的时候只改 CSS。
当你开发一个小网站的时候,内容性的 class 名非常好用。而随着你的网站不断成长,它就感觉越来越不合适了。它们并不易于样式重用。如果你的 login-button
和 submit-button
看起来一样该怎么处理呢?在你的 CSS 架构里该如何展示这些东西?为保持展现样式块,你不得不写一堆用逗号分隔开的选择器,或者通过预处理器展开。这些组织方式对于大型的项目来说都比较困难。
除非有更好的方式重用样式块……
<button class="green-button">Send Message</button>
展示性 class 名用诸如 green-button
、big-text
或 squiggle-border
的方式描述一个元素。其名字本身就是对样式的描述。
这些 class 是有助于代码复用的。它们不关心是否用在产品标题上还是名户名或页头。它们只知道这会让文字变大加粗。同时这样的方式还有一个好处是可以优雅的扩展。如你开发一个新组件的时候,你可以把现成的样式贴在你的新标签上。你无须担心在已有的架构中产生并适配新的样式,因为你使用的都是已有的样式。
展示性 class 名也非常易于自我描述。一个开发者在审查代码的时候,round-image
会比 profile-photo
更多的推断出这个元素的样子。
会有争议认为展示性 class 增加了维护成本。因为它模糊了标记和展现之间的界限,很多设计的改变都将会导致 HTML 的改变。如果你预见到了这方面的问题,那么请谨慎的使用。
展示性 class 的名声并不好。尽管很多人回避它们因为它们“不是语义化的”,但这里是存在误区的,也被 Nicolas Gallagher 质疑。重要的是区分“语义化的 HTML”和“语义化的 class”。Nicolas 说的非常好:
撰写“语义化的 HTML”的原则是现代化、专业化的前端开发的基础。大部分的语义化都关乎现有的或预期的内容的本质…… ……不过并不是所有的语义化都需要源自内容的。Class 名可以是“非语义化的”。不管使用什么名字,它们都有意义,都有目的。Class 名的语义化是不同于那些 HTML 元素的。
如我写的这些,“非语义化”这个词下面是有红色波浪线的。非语义化的 class 名并不是问题。每个 class 名都有背后的意义。在你写 class 名时,不必刻意追求它是最“语义上适合的” class 名,而要创建为开发者和未来的你提供尽量多信息的 class 名。
功能性 class 名通常是你的最佳选择。当你能够使用它们的时候就尽量使用。如果你无法提取出完全功能性的名字,可以考虑你的项目的本质及其发展。原则上,内容性 class 名更适合小型站点;而展示性 class 名更适合大型站点。
开发者会很在意这种用法。没有人希望一个项目变得难以维护,但是每个人都有不同的 想法通过 class 名来应对这些特殊情况。这时不妨思考一下我们使用的不同类型 class 名的本质,问问自己这样做是否更好的帮助你的项目达成目标。
]]>本文将展示我一年前在自己的项目中成功运用的开发模型。我一直打算把这些东西写出来,但总是没有抽出时间,现在终于写好了。这里介绍的不是任何项目的细节,而是有关分支的策略以及对发布的管理。
在我的演示中,所有的操作都是通过 git 完成的。
]]>本文将展示我一年前在自己的项目中成功运用的开发模型。我一直打算把这些东西写出来,但总是没有抽出时间,现在终于写好了。这里介绍的不是任何项目的细节,而是有关分支的策略以及对发布的管理。
在我的演示中,所有的操作都是通过 git 完成的。
为了了断 git 和中心源代码控制系统的比较和争论,请移步这里看看 链接1 链接2。作为一个开发者,我喜欢 git 超过其它任何现有的工具。Git 真正改变了开发者对于合并和分支的认识。在传统的 CVS/SVN 里,合并/分支总是有点令人害怕的(“注意合并冲突,它们会搞死你的”)。
但是 git 中的这些操作是如此的简单有效,它们真正作为你每天工作流程的一部分。比如,在 CVS/SVN 的书籍里,分支和合并总是最后一个章节的讨论重点(对于高级用户),而在每一本 git 的书里 链接1 链接2 链接3,这些内容已经被包含在第三章(基础)里了。
因为它的简单直接和重复性,分支和合并不再令人害怕。版本控制工具比其它任何东西都支持分支/合并。
有关工具就介绍到这里,我们现在进入开发模型这个正题。我要展现的模型本质上无外乎是一个流程的集合,每个团队成员都有必要遵守这些流程,来达到管理软件开发流程的目的。
我们的分支模型中使用良好的代码库的设置方式,是围绕一个真实的中心代码库的。注意,这里的代码库仅仅被看做是一个中心代码库(因为 git 是 DVCS,即分散版本控制系统,从技术层面看,是没有所谓的中心代码库的)。我们习惯于把这个中心代码库命名为 origin
,这同时也是所有 git 用户的习惯。
每一位开发者都向 origin
这个中心结点 pull 和 push。但是除此之外,每一位开发者也可以向其它结点 pull 改变形成子团队。比如,对于两个以上开发者同时开发一项大的新特性来说,为了不必过早向 origin
推送开发进度,这就非常有用。在上面的这个例子中,Alice 和 Bob、Alice 和 David、Clair 和 David 都是这样的子团队。
从技术角度,这无非意味着 Alice 定义一个名为 Bob
的 git remote,指向 Bob 的代码库,反之亦然。
该开发模型的核心基本和现有的模型是一样的。中心代码库永远维持着两个主要的分支:
master
develop
在 origin
上的 master
分支和每个 git 用户的保持一致。而和 master
分支并行的另一个分支叫做 develop
。
我们认为 origin/master
是其 HEAD
源代码总是代表了生产环境准备就绪的状态的主分支。
我们认为 origin/develop
是其 HEAD
源代码总是代表了最后一次交付的可以赶上下一次发布的状态的主分支。有人也把它叫做“集成分支”。该源代码还被作为了 nightly build 自动化任务的来源。
每当 develop
分支到达一个稳定的阶段,可以对外发布时,所有的改变都会被合并到 master
分支,并打一个发布版本的 tag。具体操作方法我们稍后讨论。
因此,每次改动被合并到 master
的时候,这就是一个真正的新的发布产品。我们建议对此进行严格的控制,因此理论上我们可以为每次 master
分支的提交都挂一个钩子脚本,向生产环境自动化构建并发布我们的软件。
我们的开发模型里,紧接着 master
和 develop
主分支的,是多种多样的支持型分支。它们的目的是帮助团队成员并行处理每次追踪特性、准备发布、快速修复线上问题等开发任务。和之前的主分支不同,这些分支的生命周期都是有限的,它们最终都会被删除掉。
我们可能会用到的不同类型的分支有:
每一种分支都有一个特别的目的,并且有严格的规则,诸如哪些分支是它们的起始分支、哪些分支必须是它们合并的目标等。我们快速把它们过一遍。
这些“特殊”的分支在技术上是没有任何特殊的。分支的类型取决于我们如何运用它们。它们完完全全都是普通而又平凡的 git 分支。
develop
develop
master
、develop
、release-*
或 hotfix-*
的任何名字Feature 分支(有时也被称作 topic 分支)用来开发包括即将发布或远期发布的新的特性。当我们开始开发一个特性的时候,发布合并的目标可能还不太确定。Feature 分支的生命周期会和新特性的开发周期保持同步,但是最终会合并回 develop
(恩,下次发布的时候把这个新特性带上)或被抛弃(真是一次杯具的尝试啊)。
Feature 分支通常仅存在于开发者的代码库中,并不出现在 origin
里。
当开始一个新特性的时候,从 develop
分支派发出一个分支
$ git checkout -b myfeature develop
Switched to a new branch "myfeature"
完成的特性可以合并回 develop
分支并赶上下一次发布:
$ git checkout develop
Switched to a new branch "develop"
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
Deleted branch myfeature (was 05e9557)
$ git push origin develop
-no-ff
标记使得合并操作总是产生一次新的提交,哪怕合并操作可以快速完成。这个标记避免将 feature 分支和团队协作的所有提交的历史信息混在主分支的其它提交之后。比较一下:
在右边的例子里,我们不可能从 git 的历史记录中看出来哪些提交实现了这一特性——你可能不得不查看每一笔提交日志。恢复一个完整的特性(比如通过一组提交)在右边变成了一个头疼事情,而如果使用了 --no-ff
之后,就变得简单了。
是的,这会创造一些没有必要的(空的)提交记录,但是得到的是大量的好处。
不幸的是,我还没有找到一个在 git merge
时默认就把 --no-ff
标记打上的办法,但这很重要。
develop
develop
和 master
release-*
Release 分支用来支持新的生产环境发布的准备工作。允许在最后阶段产生提交点(dotting i's)和交汇点(crossing t's)。而且允许小幅度的问题修复以及准备发布时的meta数据(比如版本号、发布日期等)。在 release
分支做了上述这些工作之后,develop
分支会被“翻篇儿”,开始接收下一次发布的新特性。
我们选择(几近)完成所有预期的开发的时候,作为从 develop
派发出 release
分支的时机。最起码所有准备构建发布的功能都已经及时合并到了 develop
分支。而往后才会发布的功能则不应该合并到 develop
分支——他们必须等到 release
分支派发出去之后再做合并。
在一个 release
分支的开始,我们就赋予其一个明确的版本号。直到该分支创建之前,develop
分支上的描述都是“下一次”release 的改动,但这个“下一次”release 其实也没说清楚是 0.3 release 还是 1.0 release。而在一个 release 分支的开始时这一点就会确定。这将成为有关项目版本号晋升的一个守则。
Release 分支派发自 develop
分支。比如,我们当前的生产环境发布的版本是 1.1.5,马上有一个 release 要发布了。develop
分支已经为“下一次”release 做好了准备,并且我们已经决定把新的版本号定为 1.2 (而不是 1.1.6 或 2.0)。所以我们派发一个 release 分支并以新的版本号为其命名:
$ git checkout -b release-1.2 develop
Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2
Files modified successfully, version bumped to 1.2.
$ git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed, 1 insertions(+), 1 deletions(-)
创建好并切换到新的分支之后,我们完成对版本号的晋升。这里的 bump-version.sh
是一个虚构的用来改变代码库中某些文件以反映新版本的 shell 脚本。(当然你也可以手动完成这些改变——重点是有些文件发生了改变)然后,晋升了的版本号会被提交。
这个新的分支会存在一段时间,直到它确实发布出去了为止。期间可能会有 bug 修复(这比在 develop
做更合理)。但我们严格禁止在此开发庞大的新特性,它们应该合并到 develop
分支,并放入下次发布。
当 release 分支真正发布成功之后,还有些事情需要收尾。首先,release 分支会被合并到 master
(别忘了,master
上的每一次提交都代表一个真正的新的发布);然后,为 master
上的这次提交打一个 tag,以便作为版本历史的重要参考;最后,还要把 release 分支产生的改动合并回 develop
,以便后续的发布同样包含对这些 bug 的修复。
前两部在 git 下是这样操作的:
$ git checkout master
Switched to branch 'master'
$ git merge --no-ff release-1.2
Merge made by recursive
(Summary of changes)
$ git tag -a 1.2
现在发布工作已经完成了,同时 tag 也打好了,用在未来做参考。
补充:你也可以通过 -s
或 -u <key>
标记打 tag。
为了保留 release 分支里的改动记录,我们需要把这些改动合并回 develop
。git 操作如下:
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
这一步有可能导致冲突的发生(只是有理论上的可能性,因为我们已经改变了版本号),一旦发现,解决冲突然后提交就好了。
现在我们真正完成了一个 release 分支,该把它删掉了,因为它的使命已经完成了:
$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).
master
develop
和 master
hotfix-*
Hotfix 分支和 release 分支非常类似,因为他们都意味着会产生一个新的生产环境的发布,尽管 hotfix 分支不是先前就计划好的。他们在实时的生产环境版本出现意外需要快速响应时,从 master
分支相应的 tag 被派发。
我们这样做的根本原因,是为了让团队其中一个人来快速修复生产环境的问题,其他成员可以按工作计划继续工作下去而不受太大影响。
Hotfix 分支创建自 master
分支。例如,假设 1.2 版本是目前的生产环境且出现了一个严重的 bug,但是目前的 develop
并不足够稳定。那么我们可以派发出一个 hotfix 分支来开始我们的修复工作:
$ git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1"
$ ./bump-version.sh 1.2.1
Files modified successfully, version bumped to 1.2.1.
$ git commit -a -m "Bumped version number to 1.2.1"
[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)
别忘了在派发出分支之后晋升版本号!
然后,修复 bug,提交改动。通过一个或多个提交都可以。
$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)
当我们完成之后,对 bug 的修复需要合并回 master
,同时也需要合并回 develop
,以保证接下来的发布也都已经解决了这个 bug。这和 release 分支的完成方式是完全一样的。
首先,更新 master
并为本次发布打一个 tag:
$ git checkout master
Switched to branch 'master'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive
(Summary of changes)
$ git tag -a 1.2.1
补充:你也可以通过 -s
或 -u <key>
标记打 tag。
然后,把已修复的 bug 合并到 develop
:
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive
(Summary of changes)
这个规矩的一个额外之处是:如果此时已经存在了一个 release 分支,那么 hotfix 的改变需要合并到这个 release 分支,而不是 develop
分支。因为把对 bug 的修复合并回 release 分支之后,release 分支最终还是会合并回 develop
分支的。(如果在 develop
分支中立刻需要对这个 bug 的修复,且等不及 release 分支合并回来,则你还是可以直接合并回 develop
分支的,这是绝对没问题的)
最后,删掉这个临时的分支:
$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).
其实这个分支模型里没有什么新奇的东西。文章开头的那张大图对我们的项目来说非常有用。它非常易于团队成员理解这个优雅有效的模型,并在团队内部达成共识。
这里还有一份那张大图的 高清PDF版本,你可以把它当做手册放在手边快速浏览。
补充:还有,如果你们需要的话,这里还有一份 Keynote 版本
]]>这篇文章算是 A List Apart 系列文章中,包括滑动门在内,令我印象最深刻的文章之一。最近有时间翻译了一下,分享给更多人,希望对大家有所帮助!
我们已经面对到了这一窘境:一开始我们写的 JavaScript 只有区区几行代码,但是它的代码量一直在增长,我们不断的加参数、加条件。最后,粗 bug 了…… 我们才不得不收拾这个烂摊子。
如上所述,今天的客户端代码确实承载了更多的责任,浏览器里的整个应用都越变越复杂。我们发现两个明显的趋势:1、我们没法通过单纯的鼠标定位和点击来检验代码是否正常工作,自动化的测试才会真正让我们放心;2、我们也许应该在撰写代码的时候就考虑到,让它变得可测试。
神马?我们需要改变自己的编码方式?是的。因为即使我们意识到自动化测试的好,大部分人可能只是写写集成测试(integration tests)罢了。集成测试的侧重点是让整个系统的每一部分和谐共存,但是这并没有告诉我们每个独立的功能单元运转起来是否都和我们预期的一样。
这就是为什么我们要引入单元测试。我们已经准备好经历一段痛苦的撰写单元测试的过程了,但最终我们能够撰写可测试的 JavaScript。
]]>这篇文章算是 A List Apart 系列文章中,包括滑动门在内,令我印象最深刻的文章之一。最近有时间翻译了一下,分享给更多人,希望对大家有所帮助!
我们已经面对到了这一窘境:一开始我们写的 JavaScript 只有区区几行代码,但是它的代码量一直在增长,我们不断的加参数、加条件。最后,粗 bug 了…… 我们才不得不收拾这个烂摊子。
如上所述,今天的客户端代码确实承载了更多的责任,浏览器里的整个应用都越变越复杂。我们发现两个明显的趋势:1、我们没法通过单纯的鼠标定位和点击来检验代码是否正常工作,自动化的测试才会真正让我们放心;2、我们也许应该在撰写代码的时候就考虑到,让它变得可测试。
神马?我们需要改变自己的编码方式?是的。因为即使我们意识到自动化测试的好,大部分人可能只是写写集成测试(integration tests)罢了。集成测试的侧重点是让整个系统的每一部分和谐共存,但是这并没有告诉我们每个独立的功能单元运转起来是否都和我们预期的一样。
这就是为什么我们要引入单元测试。我们已经准备好经历一段痛苦的撰写单元测试的过程了,但最终我们能够撰写可测试的 JavaScript。
撰写集成测试通常是相当直接的:我们单纯的撰写代码,描述用户如何和这个应用进行交互、会得到怎样的结果就好。Selenium 是这类浏览器自动化工具中的佼佼者。而 Capybara 可以便于 Ruby 和 Selenium 取得联系。在其它语言中,这类工具也举不胜举。
下面就是搜索应用的一部分集成测试:
def test_search
fill_in('q', :with => 'cat')
find('.btn').click
assert( find('#results li').has_content?('cat'), 'Search results are shown' )
assert( page.has_no_selector?('#results li.no-results'), 'No results is not shown' )
end
集成测试对用户的交互行为感兴趣,而单元测试往往仅专注于一小段代码:
当我伴随特定的输入调用一个函数的时候,我是否收到了我预期中的结果?
我们按照传统思路撰写的程序是很难进行单元测试的,同时也很难维护、调试和扩展。但是如果我们在撰写代码的时候就考虑到我将来要做单元测试,那么这样的思路不仅会让我们发现测试代码写起来很直接,也会让我们真正写出更优质的代码。
我们通过一个简单的搜索应用的例子来做个示范:
当用户搜索时,该应用会向服务器发送一个 XHR (Ajax 请求) 取得相应的搜索结果。并当服务器以 JSON 格式返回数据之后,通过前端模板把结果显示在页面中。用户在搜索结果中点“赞”,这个人的名字就会出现在右侧的点“赞”列表里。
一个“传统”的 JavaScript 实现大概是这个样子的:
// 模板缓存,缓存的内容均为 jqXHR 对象
var tmplCache = {};
/**
* 载入模板
* 从 '/templates/{name}' 载入模板,存入 tmplCache
* @param {string} name 模板名称
* @return {object} 模板请求的 jqXHR 对象
*/
function loadTemplate (name) {
if (!tmplCache[name]) {
tmplCache[name] = $.get('/templates/' + name);
}
return tmplCache[name];
}
/**
* 页面主要逻辑
* 1. 支持搜索行为并展示结果
* 2. 支持点“赞”,被赞过的人会出现在点“赞”列表里
*/
$(function () {
var resultsList = $('#results');
var liked = $('#liked');
var pending = false; // 用来标识之前的搜索是否尚未结束
// 用户搜索行为,表单提交事件
$('#searchForm').on('submit', function (e) {
// 屏蔽默认表单事件
e.preventDefault();
// 如果之前的搜索尚未结束,则不开始新的搜索
if (pending) { return; }
// 得到要搜索的关键字
var form = $(this);
var query = $.trim( form.find('input[name="q"]').val() );
// 如果搜索关键字为空则不进行搜索
if (!query) { return; }
// 开始新的搜索
pending = true;
// 发送 XHR
$.ajax('/data/search.json', {
data : { q: query },
dataType : 'json',
success : function (data) {
// 得到 people-detailed 模板
loadTemplate('people-detailed.tmpl').then(function (t) {
var tmpl = _.template(t);
// 通过模板渲染搜索结果
resultsList.html( tmpl({ people : data.results }) );
// 结束本次搜索
pending = false;
});
}
});
// 在得到服务器响应之前,清空搜索结果,并出现等待提示
$('<li>', {
'class' : 'pending',
html : 'Searching …'
}).appendTo( resultsList.empty() );
});
// 绑定点“赞”的行为,鼠标点击事件
resultsList.on('click', '.like', function (e) {
// 屏蔽默认点击事件
e.preventDefault();
// 找到当前人的名字
var name = $(this).closest('li').find('h2').text();
// 清除点“赞”列表的占位元素
liked.find('.no-results').remove();
// 在点“赞”列表加入新的项目
$('<li>', { text: name }).appendTo(liked);
});
});
我的朋友 Adam Sontag 称之为*“自己给自己挖坑”的代码:展现、数据、用户交互、应用状态全部分散在了每一行代码里。这种代码是很容易进行集成测试的,但几乎不可能针对功能单元*进行单独的测试。
单元测试为什么这么难?有四大罪魁祸首:
$(document).ready()
回调里进行的,而这一切在一个匿名函数里,它在测试中无法暴露出任何接口。pending
在一个闭包里,所以我们没有办法测试在每个步骤中这个状态是否正确。$.ajax
成功的回调函数不应该依赖 DOM 操作。首当其冲的是把我们代码的逻辑缕一缕,根据职责的不同把整段代码分为几个方面:
在之前的“传统”实现里,这四类代码是混在一起的,前一行我们还在处理界面展现,后两行就在和服务器通信了。
我们绝对可以写出集成测试的代码,但我们应该很难写出单元测试了。在功能测试里,我们可以做出诸如“当用户搜索东西的时候,他会看到相应的搜索结果”的断言,但是无法再具体下去了。如果里面出了什么问题,我们还是得追踪进去,找到确切的出错位置。这样的话功能测试其实也没帮上什么忙。
如果我们反思自己的代码,那不妨从单元测试写起,通过单元测试这个角度,更好的观察,是哪里出了问题。这进而会帮助我们改进代码,让代码变得更易于重用、易于维护、易于扩展。
我们的新版代码遵循下面几个原则:
作为起步,我们有必要搞清楚,该如何把应用分解成不同的部分。我们有三块展现和交互的内容:搜索框、搜索结果和点“赞”列表。
我们还有一块内容是从服务器获取数据的、一块内容是把所有的内容粘合在一起的。
我们从整个应用最简单的一部分开始吧:点“赞”列表。在原版应用中,这部分代码的职责就是更新点“赞”列表:
var liked = $('#liked');
var resultsList = $('#results');
// ...
resultsList.on('click', '.like', function (e) {
e.preventDefault();
var name = $(this).closest('li').find('h2').text();
liked.find( '.no-results' ).remove();
$('<li>', { text: name }).appendTo(liked);
});
搜索结果这部分是完全和点“赞”列表搅在一起的,并且需要很多 DOM 处理。更好的易于测试的写法是创建一个点“赞”列表的对象,它的职责就是封装点“赞”列表的 DOM 操作。
var Likes = function (el) {
this.el = $(el);
return this;
};
Likes.prototype.add = function (name) {
this.el.find('.no-results').remove();
$('<li>', { text: name }).appendTo(this.el);
};
这段代码提供了创建一个点“赞”列表对象的构造函数。它有 .add()
方法,可以在产生新的赞的时候使用。这样我们就可以写很多测试代码来保障它的正常工作了:
var ul;
// 设置测试的初始状态:生成一个搜索结果列表
setup(function(){
ul = $('<ul><li class="no-results"></li></ul>');
});
test('测试构造函数', function () {
var l = new Likes(ul);
// 断言对象存在
assert(l);
});
test('点一个“赞”', function () {
var l = new Likes(ul);
l.add('Brendan Eich');
// 断言列表长度为1
assert.equal(ul.find('li').length, 1);
// 断言列表第一个元素的 HTML 代码是 'Brendan Eich'
assert.equal(ul.find('li').first().html(), 'Brendan Eich');
// 断言占位元素已经不存在了
assert.equal(ul.find('li.no-results').length, 0);
});
怎么样?并不难吧 😃 我们这里用到了名为 Mocha 的测试框架,以及名为 Chai 的断言库。Mocha 提供了 test
和 setup
函数;而 Chai 提供了 assert
。测试框架和断言库的选择还有很多,我们出于介绍的目的给大家展示这两款。你可以找到属于适合自己的项目——除了 Mocha 之外,QUnit 也比较流行。另外 Intern 也是一个测试框架,它运用了大量的 promise 方式。
我们的测试代码是从点“赞”列表这一容器开始的。然后它运行了两个测试:一个是确定点“赞”列表是存在的;另一个是确保 .add()
方法达到了我们预期的效果。有这些测试做后盾,我们就可以放心重构点“赞”列表这部分的代码了,即使代码被破坏了,我们也有信心把它修复好。
我们新应用的代码现在看起来是这样的:
var liked = new Likes('#liked'); // 新的点“赞”列表对象
var resultsList = $('#results');
// ...
resultsList.on('click', '.like', function (e) {
e.preventDefault();
var name = $(this).closest('li').find('h2').text();
liked.add(name); // 新的点“赞”操作的封装
});
搜索结果这部分比点“赞”列表更复杂一些,不过我们也该拿它开刀了。和我们为点“赞”列表创建一个 .add()
方法一样,我们要创建一个与搜索结果有交互的方法。我们需要一个点“赞”的入口,向整个应用“广播”自己发生了什么变化——比如有人点了个“赞”。
// 为每一条搜索结果的点“赞”按钮绑定点击事件
var SearchResults = function (el) {
this.el = $(el);
this.el.on( 'click', '.btn.like', _.bind(this._handleClick, this) );
};
// 展示搜索结果,获取模板,然后渲染
SearchResults.prototype.setResults = function (results) {
var templateRequest = $.get('people-detailed.tmpl');
templateRequest.then( _.bind(this._populate, this, results) );
};
// 处理点“赞”
SearchResults.prototype._handleClick = function (evt) {
var name = $(evt.target).closest('li.result').attr('data-name');
$(document).trigger('like', [ name ]);
};
// 对模板渲染数据的封装
SearchResults.prototype._populate = function (results, tmpl) {
var html = _.template(tmpl, { people: results });
this.el.html(html);
};
现在我们旧版应用中管理搜索结果和点“赞”列表之间交互的代码如下:
var liked = new Likes('#liked');
var resultsList = new SearchResults('#results');
// ...
$(document).on('like', function (evt, name) {
liked.add(name);
})
这就更简单更清晰了,因为我们通过 document
在各个独立的组件之间进行消息传递,而组件之间是互不依赖的。(值得注意的是,在真正的应用当中,我们会使用一些诸如 Backbone 或 RSVP 库来管理事件。我们出于让例子尽量简单的考虑,使用了 document
来触发事件) 我们同时隐藏了很多脏活累活:比如在搜索结果对象里寻找被点“赞”的人,要比放在整个应用的代码里更好。更重要的是,我们现在可以写出保障搜索结果对象正常工作的测试代码了:
var ul;
var data = [ /* 填入假数据 */ ];
// 确保点“赞”列表存在
setup(function () {
ul = $('<ul><li class="no-results"></li></ul>');
});
test('测试构造函数', function () {
var sr = new SearchResults(ul);
// 断言对象存在
assert(sr);
});
test('测试收到的搜索结果', function () {
var sr = new SearchResults(ul);
sr.setResults(data);
// 断言搜索结果占位元素已经不存在
assert.equal(ul.find('.no-results').length, 0);
// 断言搜索结果的子元素个数和搜索结果的个数相同
assert.equal(ul.find('li.result').length, data.length);
// 断言搜索结果的第一个子元素的 'data-name' 的值和第一个搜索结果相同
assert.equal(
ul.find('li.result').first().attr('data-name'),
data[0].name
);
});
test('测试点“赞”按钮', function() {
var sr = new SearchResults(ul);
var flag;
var spy = function () {
flag = [].slice.call(arguments);
};
sr.setResults(data);
$(document).on('like', spy);
ul.find('li').first().find('.like.btn').click();
// 断言 `document` 收到了点“赞”的消息
assert(flag, '事件被收到了');
// 断言 `document` 收到的点“赞”消息,其中的名字是第一个搜索结果
assert.equal(flag[1], data[0].name, '事件里的数据被收到了' );
});
和服务器直接的交互是另外一个有趣的话题。原版的代码包括一个 $.ajax()
的请求,以及一个直接操作 DOM 的回调函数:
$.ajax('/data/search.json', {
data : { q: query },
dataType : 'json',
success : function( data ) {
loadTemplate('people-detailed.tmpl').then(function(t) {
var tmpl = _.template( t );
resultsList.html( tmpl({ people : data.results }) );
pending = false;
});
}
});
同样,我们很难为这样的代码撰写测试。因为很多不同的工作同时发生在这一小段代码中。我们可以重新组织一下数据处理的部分:
var SearchData = function () { };
SearchData.prototype.fetch = function (query) {
var dfd;
// 如果搜索关键字为空,则不做任何事,立刻 `promise()`
if (!query) {
dfd = $.Deferred();
dfd.resolve([]);
return dfd.promise();
}
// 否则,向服务器请求搜索结果并把在得到结果之后对其数据进行包装
return $.ajax( '/data/search.json', {
data : { q: query },
dataType : 'json'
}).pipe(function( resp ) {
return resp.results;
});
};
现在我们改变了获得搜索结果这部分的代码:
var resultList = new SearchResults('#results');
var searchData = new SearchData();
// ...
searchData.fetch(query).then(resultList.setResults);
我们再一次简化了代码,并通过 SearchData
对象抛弃了之前应用程序主函数里杂乱的代码。同时我们已经让搜索接口变得可测试了,尽管现在和服务器通信这里还有事情要做。
首先我们不是真的要跟服务器通信——不然这又变成集成测试了:诸如我们是有责任感的开发者,我们已经确保服务器一定不会犯错等等,是这样吗?为了替代这些东西,我们应该“mock”(伪造) 与服务器之间的通信。Sinon 这个库就可以做这件事。第二个障碍是我们的测试应该覆盖非理想环境,比如关键字为空。
test('测试构造函数', function () {
var sd = new SearchData();
assert(sd);
});
suite('取数据', function () {
var xhr, requests;
setup(function () {
requests = [];
xhr = sinon.useFakeXMLHttpRequest();
xhr.onCreate = function (req) {
requests.push(req);
};
});
teardown(function () {
xhr.restore();
});
test('通过正确的 URL 获取数据', function () {
var sd = new SearchData();
sd.fetch('cat');
assert.equal(requests[0].url, '/data/search.json?q=cat');
});
test('返回一个 promise', function () {
var sd = new SearchData();
var req = sd.fetch('cat');
assert.isFunction(req.then);
});
test('如果关键字为空则不查询', function () {
var sd = new SearchData();
var req = sd.fetch();
assert.equal(requests.length, 0);
});
test('如果关键字为空也会有 promise', function () {
var sd = new SearchData();
var req = sd.fetch();
assert.isFunction( req.then );
});
test('关键字为空的 promise 会返回一个空数组', function () {
var sd = new SearchData();
var req = sd.fetch();
var spy = sinon.spy();
req.then(spy);
assert.deepEqual(spy.args[0][0], []);
});
test('返回与搜索结果相对应的对象', function () {
var sd = new SearchData();
var req = sd.fetch('cat');
var spy = sinon.spy();
requests[0].respond(
200, { 'Content-type': 'text/json' },
JSON.stringify({ results: [ 1, 2, 3 ] })
);
req.then(spy);
assert.deepEqual(spy.args[0][0], [ 1, 2, 3 ]);
});
});
出于篇幅的考虑,这里对搜索框的重构及其相关的单元测试就不一一介绍了。完整的代码可以移步至此查阅。
当我们按照可测试的 JavaScript 的思路重构代码之后,我们最后用下面这段代码开启程序:
$(function() {
var pending = false;
var searchForm = new SearchForm('#searchForm');
var searchResults = new SearchResults('#results');
var likes = new Likes('#liked');
var searchData = new SearchData();
$(document).on('search', function (event, query) {
if (pending) { return; }
pending = true;
searchData.fetch(query).then(function (results) {
searchResults.setResults(results);
pending = false;
});
searchResults.pending();
});
$(document).on('like', function (evt, name) {
likes.add(name);
});
});
比干净整洁的代码更重要的,是我们的代码拥有了更健壮的测试基础作为后盾。这也意味着我们可以放心的重构任意部分的代码而不必担心程序遭到破坏。我们还可以继续为新功能撰写新的测试代码,并确保新的程序可以通过所有的测试。
看完这些的长篇大论你一定会说:“纳尼?我多写了这么多代码,结果还是做了这么一点事情?”
关键在于,你做的东西早晚要放到网上的。同样是花时间解决问题,你会选择在浏览器里点来点去?还是自动化测试?还是直接在线上让你的用户做你的小白鼠?无论你写了多少测试,你写好代码,别人一用,多少会发现点 bug。
至于测试,它可能会花掉你一些额外的时间,但是它到最后真的是为你省下了时间。写测试代码测出一个问题,总比你发布到线上之后才发现有问题要好。如果有一个系统能让你意识到它真的能避免一个 bug 的流出,你一定会心存感激。
这篇文章只能算是 JavaScript 测试的一点皮毛,但是如果你对此抱有兴趣,那么可以继续移步至:
对于一个给定的版本号 MAJOR.MINOR.PATCH (主、次、补丁),其变化的规律是:
我们还可以根据预发布、构建元数据 (build metadata) 的实际需求,在 MAJOR.MINOR.PATCH 格式之上扩展出额外的标记。
在软件管理领域,存在一个叫做“dependency hell (依赖地狱)”的坑。随着系统越变越大,你集成了越多的软件包,也越发觉得,有一天,你会陷入绝望。
对于有很多依赖关系的系统来说,发布新版本的软件包会迅速变成一场噩梦。如果依赖性规定得太紧,你会陷入 version lock (版本锁,即每次软件包的升级无法产生新的版本)。如果依赖性规定得太松,你会不可避免的面对 version promiscuity (版本泛滥,假设未来版本是需要考虑兼容性的)。当 version lock 和 version promiscuity 让你的项目无法安全而又轻松的向前推进时,这就是所谓的 dependency hell。
作为一种解决问题的办法,我提出了一套简单的规则和要求来表明版本号该如何确定和增加。这套规则基于但不仅限用于已经广泛存在的开源闭源软件的一般实践。为了让这个系统工作起来,你首先需要声明一个公有的 API,它可以由文档组成或在代码层面强制实现,且必须是清晰准确的。一旦你标识了你的公有 API,你就可以通过不同的版本号的增加来交流 API 的各种改变。设想一个形如 X.Y.Z 的版本,不影响 API 的 bug 修复会增大补丁版本,向下兼容的 API 增加或改变会增大次版本,而不兼容的 API 改变会增大主版本。
我把这套系统称作“语义化版本管理”。在这套系统之下,版本号及其改变传递了代码背后的含义,以及每个相邻版本之间的变化。
]]>对于一个给定的版本号 MAJOR.MINOR.PATCH (主、次、补丁),其变化的规律是:
我们还可以根据预发布、构建元数据 (build metadata) 的实际需求,在 MAJOR.MINOR.PATCH 格式之上扩展出额外的标记。
在软件管理领域,存在一个叫做“dependency hell (依赖地狱)”的坑。随着系统越变越大,你集成了越多的软件包,也越发觉得,有一天,你会陷入绝望。
对于有很多依赖关系的系统来说,发布新版本的软件包会迅速变成一场噩梦。如果依赖性规定得太紧,你会陷入 version lock (版本锁,即每次软件包的升级无法产生新的版本)。如果依赖性规定得太松,你会不可避免的面对 version promiscuity (版本泛滥,假设未来版本是需要考虑兼容性的)。当 version lock 和 version promiscuity 让你的项目无法安全而又轻松的向前推进时,这就是所谓的 dependency hell。
作为一种解决问题的办法,我提出了一套简单的规则和要求来表明版本号该如何确定和增加。这套规则基于但不仅限用于已经广泛存在的开源闭源软件的一般实践。为了让这个系统工作起来,你首先需要声明一个公有的 API,它可以由文档组成或在代码层面强制实现,且必须是清晰准确的。一旦你标识了你的公有 API,你就可以通过不同的版本号的增加来交流 API 的各种改变。设想一个形如 X.Y.Z 的版本,不影响 API 的 bug 修复会增大补丁版本,向下兼容的 API 增加或改变会增大次版本,而不兼容的 API 改变会增大主版本。
我把这套系统称作“语义化版本管理”。在这套系统之下,版本号及其改变传递了代码背后的含义,以及每个相邻版本之间的变化。
原文中的关键字 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" (必须、禁止、要求、应该、不应该、推荐、可以、可选的) 在 RFC 2119 中有相应的解释和描述。
这并不是什么新的或革命性的东西。事实上你的做事习惯可能已经很接近它了。但问题是“接近”是不够的。如果不接受一些正式的规范,版本号对依赖性管理是没有实际意义的。基于上述想法,给定名词和定义,它就变得易于交流。一旦这些意图变得清晰,灵活 (且不过分的) 的依赖性规范就会最终产生。
一个简单的例子就可以向人们展示语义化版本管理会使依赖地狱成为历史。想像一个叫做“消防车”的库,它需要一个名叫“梯子”的经过语义化版本管理的软件包。当消防车被创建时,梯子的版本是 3.1.0。因为消防车使用一些在 3.1.0 被首次引入的功能,你可以安全的制定梯子的依赖关系为大于 3.1.0 且小于 4.0.0。现在当梯子的版本 3.1.1 和 3.2.0 可用时,你可以把它们发布到你的软件包管理系统之中并很清楚它们可以和现存的依赖性软件和平共处。
作为一个有责任感的开发者,你一定想要验证任何被公示的软件包的功能升级。在现实世界中这是一个混乱的地方,我们对此需要警惕但又无能为力。我们能做的就是让语义化版本管理提供给你一个清晰的路线,去发布和升级软件包,无需在依赖性软件包的不同版本中翻滚,省去你的时间和烦恼。
如果这一切是你所渴望的,你需要做的就是声明你开始遵循上述规则来进行语义化的版本管理。在你的 README 中附带这个网站的链接,让其他人了解这个规则,并从中获益。
最简单的事情就是当你开始初始化开发时,从 0.1.0 开始,然后在后续的发布过程中不断增加次版本。
如果你的软件已经用到了产品环境,它可能应该已经是 1.0.0 了。如果你有一个稳定的用户依赖的 API,它应该是 1.0.0 了。如果你担心很多向下兼容的问题,它可能应该已经是 1.0.0 了。
主版本 0 是快速开发时期。如果你每天都在改变 API 你应该还处在 0.y.z 或一个独立的开发分支中,这是为下一个主版本服务的。
这是一个开发责任感和长远意识的问题。不兼容的改变不应该很轻松就被引入到一个被大量依赖的软件当中。这必定导致升级的代价昂贵。改变主版本才可以发布不兼容的改变也在变相的促使你思考这一变化带来的影响及其投入产出比。
作为一名开发者,撰写软件文档以供其他人使用是你的责任。管理软件复杂度是保障一个项目高效运作的及其重要的部分,如果没人知道如何使用你的软件,什么方法使用起来比较安全,项目将会变得很困难。长期来看,语义化版本管理以及一个被良好定义的公有 API 能够保障每个人每件事都运转顺利。
当你意识到你打破了语义化版本管理规范之后,立即修复这个问题并且发布一个新的次版本去修复此问题并恢复向下兼容性。甚至在这个周期里,不要接受任何其它版本的发布。如果方便合适的话,记录下出错的版本并向你的用户告知这一问题以便他们警惕这个出错的版本。
这回被认为是兼容的,因为它并没有影响到公有 API。软件显式依赖你的软件包相同的依赖,应该有自身的以来规范,作者自然会注意任何冲突。决定这个改变是一个补丁级别还是次级别,取决于你把依赖关系的改变用在了修复一个bug上还是用在了引入新功能上。我通常会在后期期待额外的代码,很明显这是一个次级别的增大。
做出做合理的判断。如果你有一大群用户,因为有意把行为改回到公有 API 会收到剧烈的影响,那么最好发布一个主版本,尽管实际的改动也许只是发布一个补丁。记住,语义化版本管理就是通过版本号的变化传递信息。如果这些改变对用户很重要,就用版本号去通知他们。
废弃已存在的功能是软件开发的一个正常部分。而且它经常需要提前行动。当你废弃部分你的公有 API 时,你应该做两件事:(1) 更新你的文档,让用户知道这一变化,(2) 创建一个此版本发布的任务。当你发布主版本完全移除功能之前,至少要有一个次版本发布,该发布包含废弃的动作,以便让用户可以平稳的过度到新的 API。
没有限制,但要合理使用。比如一个 255 字符的版本字符串就算是有点长了。同样的,规范系统可以实行自己的字符串长度限制。
语义化版本管理规范由 Gravatars 发明者、Github 的联合创始人 Tom Preston-Werner 撰写。 如果你想留下宝贵意见,请来 Github 开一个 issue 吧。
你曾否需要调节一张图片的亮度?或者增强红色通道让它变得温暖一些?
这是我之前两篇文章“如何通过HTML5 Canvas处理图片酷效”和“如何创建一个HTML5的大头贴应用”的后续。在之前的那些文章里,我提供了一些可分离的颜色滤镜代码:灰度、灰褐色、红色、变亮、变暗等。这些滤镜都是经典的颜色滤镜,每个像素点的颜色都是独立运算的,互不影响。我们的可以将其建模成一个单独数据驱动的称为颜色矩阵滤镜(Color Matrix Filter)的东西。这一概念将会遍布本文。这种滤镜将会以一个包含权重(即系数)的颜色矩阵作为输入,并决定输出的颜色组件(color component)如何和输入的颜色组建相对应。
]]>你曾否需要调节一张图片的亮度?或者增强红色通道让它变得温暖一些?
这是我之前两篇文章“如何通过HTML5 Canvas处理图片酷效”和“如何创建一个HTML5的大头贴应用”的后续。在之前的那些文章里,我提供了一些可分离的颜色滤镜代码:灰度、灰褐色、红色、变亮、变暗等。这些滤镜都是经典的颜色滤镜,每个像素点的颜色都是独立运算的,互不影响。我们的可以将其建模成一个单独数据驱动的称为颜色矩阵滤镜(Color Matrix Filter)的东西。这一概念将会遍布本文。这种滤镜将会以一个包含权重(即系数)的颜色矩阵作为输入,并决定输出的颜色组件(color component)如何和输入的颜色组建相对应。
这个应用实例允许你在一个表格里编辑颜色矩阵,并立即把矩阵应用到当前加载的图片中。下图的表格展示了灰褐色滤镜的矩阵:
通过这个例子,每个像素的新红色组件r’
都将会根据给定的r
、g
、b
、a
进行如下计算:
r' = 0.393r + 0.769g + 0.189b + 0
作为每个颜色组件值额外的系数i
被加载了表格的最后,用来变量或变暗最终的计算值。同理新的g’
、b’
、a’
也进行相似的计算。下面的代码展示了等同于灰褐色颜色矩阵的JavaScript数组:
var sepiaMatrix =
[
0.393, 0.769, 0.189, 0, 0,
0.349, 0.686, 0.168, 0, 0,
0.272, 0.534, 0.131, 0, 0,
0, 0, 0, 1, 0,
];
下面这段代码展示了等同于灰度特效矩阵的JavaScript数组:
var grayscaleMatrix =
[
0.33, 0.34, 0.33, 0, 0,
0.33, 0.34, 0.33, 0, 0,
0.33, 0.34, 0.33, 0, 0,
0, 0, 0, 1, 0,
];
颜色矩阵滤镜的代码如下:
colorMatrixFilter = function (pixels, m) {
var d = pixels.data;
for (var i = 0; i < d.length; i += 4) {
var r = d[i];
var g = d[i + 1];
var b = d[i + 2];
var a = d[i + 3];
d[i] = r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4];
d[i+1] = r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9];
d[i+2] = r * m[10]+ g * m[11]+ b * m[12]+ a * m[13]+ m[14];
d[i+3] = r * m[15]+ g * m[16]+ b * m[17]+ a * m[18]+ m[19];
}
return pixels;
};
我希望你已经乐在其中了。颜色矩阵提供了一个应用颜色滤镜的强大通用工具。
]]>关于如何巧妙提高V8 JavaScript性能的话题,Daniel Clifford在Google I/O上做了一次非常精彩的分享。Daniel鼓励我们“追求更快”,认真的分析C++和JavaScript之间的性能差距,根据JavaScript的工作原理撰写代码。在Daniel的分享中,有一个核心要点的归纳,我们也会根据性能指导的变化保持对这篇文章的更新。
最重要的是要把任何性能建议放在特定的情境当中。性能建议是附加的东西,有时一开始就特别注意深层的建议反而会对我们造成干扰。你需要从一个综合的角度看待你的Web应用的性能——在关注这些性能建议之前,你应该找PageSpeed之类的工具大概分析一下你的代码,也算是跑个分先。这会防止你过度优化。
对Web应用的性能优化,几个原则性的建议是:
为了完成这几个步骤,理解V8如何优化JS是一件很重要的事情,这样你就可以根据其对JS运行时的设计撰写代码。同样重要的是掌握一些帮得上忙的工具。Daniel也交代了一些开发者工具的用法,它们刚好抓住了一些V8引擎设计上最重要的部分。
OK。开始V8小贴士。
]]>关于如何巧妙提高V8 JavaScript性能的话题,Daniel Clifford在Google I/O上做了一次非常精彩的分享。Daniel鼓励我们“追求更快”,认真的分析C++和JavaScript之间的性能差距,根据JavaScript的工作原理撰写代码。在Daniel的分享中,有一个核心要点的归纳,我们也会根据性能指导的变化保持对这篇文章的更新。
最重要的是要把任何性能建议放在特定的情境当中。性能建议是附加的东西,有时一开始就特别注意深层的建议反而会对我们造成干扰。你需要从一个综合的角度看待你的Web应用的性能——在关注这些性能建议之前,你应该找PageSpeed之类的工具大概分析一下你的代码,也算是跑个分先。这会防止你过度优化。
对Web应用的性能优化,几个原则性的建议是:
为了完成这几个步骤,理解V8如何优化JS是一件很重要的事情,这样你就可以根据其对JS运行时的设计撰写代码。同样重要的是掌握一些帮得上忙的工具。Daniel也交代了一些开发者工具的用法,它们刚好抓住了一些V8引擎设计上最重要的部分。
OK。开始V8小贴士。
JavaScript限制编译时的类型信息:类型可以在运行时被改变,可想而知这导致JS类型在编译时代价昂贵。那么你一定会问:JavaScript的性能有机会和C++相提并论吗?尽管如此,V8在运行时隐藏了内部创建对象的类型,隐藏类相同的对象可以使用相同的生成码以达到优化的目的。
比如:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(11, 22);
var p2 = new Point(33, 44);
// At this point, p1 and p2 have a shared hidden class
// 这里的p1和p2拥有共享的隐藏类
p2.z = 55;
// warning! p1 and p2 now have different hidden classes!
// 注意!这时p1和p2的隐藏类已经不同了!
在我们为p2添加“z”这个成员之前,p1和p2一直共享相同的内部隐藏类——所以V8可以生成一段单独版本的优化汇编码,这段代码可以同时封装p1和p2的JavaScript代码。我们越避免隐藏类的派生,就会获得越高的性能。
当类型可以改变时,V8使用标记来高效的标识其值。V8通过其值来推断你会以什么类型的数字来对待它。因为这些类型可以动态改变,所以一旦V8完成了推断,就会通过标记高效完成值的标识。不过有的时候改变类型标记还是比较消耗性能的,我们最好保持数字的类型始终不变。通常标识为有符号的31位整数是最优的。
比如:
var i = 42; // 这是一个31位有符号整数
var j = 4.2; // 这是一个双精度浮点数
为了掌控大而稀疏的数组,V8内部有两种数组存储方式:
最好别导致数组存储方式在两者之间切换。
a = new Array();
for (var b = 0; b < 10; b++) {
a[0] |= b; // 杯具!
}
//vs.
a = new Array();
a[0] = 0;
for (var b = 0; b < 10; b++) {
a[0] |= b; // 比上面快2倍
}
同样的,双精度数组会更快——数组的隐藏类会根据元素类型而定,而只包含双精度的数组会被拆箱(unbox),这导致隐藏类的变化。对数组不经意的封装就可能因为装箱/拆箱(boxing/unboxing)而导致额外的开销。比如:
var a = new Array();
a[0] = 77; // 分配
a[1] = 88;
a[2] = 0.5; // 分配,转换
a[3] = true; // 分配,转换
下面的写法效率更高:
var a = [77, 88, 0.5, true];
因为第一个例子是一个一个分配赋值的,并且对a[2]的赋值导致数组被拆箱为了双精度。但是对a[3]的赋值又将数组重新装箱回了任意值(数字或对象)。第二种写法时,编译器一次性知道了所有元素的字面上的类型,隐藏隐藏类可以直接确定。
尽管JavaScript是个非常动态的语言,且原本的实现是解释性的,但现代的JavaScript运行时引擎都会进行编译。V8(Chrome的JavaScript)有两个不同的运行时(JIT)编译器:
在V8中,完全编译器会以最快的速度运行在任何代码上,快速生成优秀但不伟大的代码。该编译器在编译时几乎不做任何有关类型的假设——它预测类型在运行时会发生改变。完全编译器的生成码通过内联缓存(ICs)在程序运行时提炼类型相关的知识,以便将来改进和优化。
内联缓存的目的是,通过缓存依赖类型的代码进行操作,更有效率的掌控类型。当代吗运行时,它会先验证对类型的假设,然后使用内联缓存快速执行操作。这也意味着可以接受多种类型的操作会变得效率低下。
如果一个操作的输入总是相同类型的,则其为单态操作。否则,操作调用时的某个参数可以跨越不同的类型,那就是多态操作。比如add()的第二个调用就触发了多态操作:
function add(x, y) {
return x + y;
}
add(1, 2); // add中的+操作是单态操作
add("a", "b"); // add中的+操作变成了多态操作
V8有一个和完全编译器并行的优化编译器,它会重编那些最“热门”(即被调用多次)的函数。优化编译器通过类型反馈来使得编译过的代码更快——事实上它就是使用了我们之前谈到的ICs的类型信息!
在优化编译器里,操作都是内联的(直接出现在被调用的地方)。它加速了执行(拿内存空间换来的),同时也进行了各种优化。单态操作的函数和构造函数可以整个内联起来(这是V8中单态操作的有一个好处)。
你可以使用单独的“d8”版本的V8引擎来获取优化记录:
d8 --trace-opt primes.js
(其会把被优化的函数名输出出来)
不是所有的函数都可以被优化,有些特性会阻止优化编译器运行一个已知函数(bail-out)。目前优化编译器会排除有try/catch的代码块的函数。
function perf_sentitive() {
// 把性能敏感的工作放置于此
}
try {
perf_sentitive()
} catch (e) {
// 在此处理异常
}
这个建议可能会在未来发生改变,因为我们会在优化编译器里开启try/catch代码块。你可以通过使用上述的d8选项“--trace-opt”得到更多有关这些函数的信息来检验优化编译器如何排除这些函数。
d8 --trace-opt primes.js
最终,编译器的性能优化是有针对性的——有时它的变现并不好,我们就不得不回退。“取消优化”的过程实际上就是把优化过的代码扔掉,恢复执行完全编译器的代码。重优化可能稍后再打开,但是短期内性能会下降。尤其是取消优化的发生会导致其函数的变量的隐藏类的变化。
你可以像其它优化一样,通过V8的一个日志标识来取消优化。
d8 --trace-deopt primes.js
顺便提一下,你还可以在Chrome启动时传递V8跟踪选项:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --js-flags="--trace-opt --trace-deopt"
额外使用开发者工具分析,你可以使用d8进行分析:
% out/ia32.release/d8 primes.js --prof
它通过内建的采样分析器,对每毫秒进行采样,并写入v8.log。
重要的是认识和理解V8引擎如何处理你的代码,进而为优化JavaScript做好准备。再次强调我们的基础建议:
这意味着你应该通过PageSpeed之类的工具先确定你的JavaScript中的问题,在收集指标之前尽可能减少至纯粹的JavaScript(没有DOM),然后通过指标来定位瓶颈所在,评估重要程度。希望Daniel的分享会帮助你更好的理解V8如何运行JavaScript——但是也要确保专注于优化你自身的算法!
现在满大街都是视觉差(parallax)网站了,我们随便看几个:
也许你对这玩意儿还不太熟,视觉差其实就是它的视觉结构会随着页面的滚动而变化。通常情况下页面里的元素会根据页面的滚动位置而缩放、旋转或移动。
我们的视觉差demo的完整效果
不管你喜不喜欢视觉差网站,有一件事毫无疑问,它是一个性能的黑洞。因为当页面滚动时,浏览器的优化都倾向于新内容随滚动而出现于屏幕的最上方或最下方的情况。一般来说,内容改变得越少浏览器性能越高。而对于一个视觉差网站来说,在页面滚动时,好多元素都在发生改变,大多数情况下整个页面的大块可视元素都在发生变化,所以浏览器不得不重绘整个页面。
我们有理由这样归纳一个视觉差的网站:
建议大家先阅读我们之前介绍过的滚动性能来改进你的app的响应速度。本篇文章是基于那篇文章所写的。
所以文字是如果你在建立一个视觉差网站,那么你是否受困于高昂的重绘开销?有没有别的改进建议使得性能最大化?让我们看看这几个方案:
]]>现在满大街都是视觉差(parallax)网站了,我们随便看几个:
也许你对这玩意儿还不太熟,视觉差其实就是它的视觉结构会随着页面的滚动而变化。通常情况下页面里的元素会根据页面的滚动位置而缩放、旋转或移动。
我们的视觉差demo的完整效果
不管你喜不喜欢视觉差网站,有一件事毫无疑问,它是一个性能的黑洞。因为当页面滚动时,浏览器的优化都倾向于新内容随滚动而出现于屏幕的最上方或最下方的情况。一般来说,内容改变得越少浏览器性能越高。而对于一个视觉差网站来说,在页面滚动时,好多元素都在发生改变,大多数情况下整个页面的大块可视元素都在发生变化,所以浏览器不得不重绘整个页面。
我们有理由这样归纳一个视觉差的网站:
建议大家先阅读我们之前介绍过的滚动性能来改进你的app的响应速度。本篇文章是基于那篇文章所写的。
所以文字是如果你在建立一个视觉差网站,那么你是否受困于高昂的重绘开销?有没有别的改进建议使得性能最大化?让我们看看这几个方案:
这是很多人默认采取的方案。页面里有一大堆元素,任何时候只要触发滚动事件,这些元素就会进行各种变换来完成视觉上的更新。我已经用这个方式写好了一个demo页面。
如果你打开开发者工具的时间线的帧模式的话,滚动页面,你会发现各种全屏重绘,这个代价是很高的。如果你滚动多一些,你会发现在一个单个帧里出现了好多滚动事件,每个事件都会触发布局操作。
开发者工具展示了一个单个帧里的大块绘制以及多个由事件触发的布局操作
需要铭记的要点是为了达到60fps(匹配传统显示器60赫兹的刷新频率),我们需要在16毫秒之内搞定一切。在这个版本中我们使得每次滚动事件都造成了视觉上的变化。但是我们之前的文章用requestAnimationFrame做出更经济实惠的动画和滚动性能已经讨论过,这样做和浏览器的更新机制并不相符,所以我们要么会错过帧,要么会在同一帧里做了多余的工作。这样的网站无法给人一种纯天然不刺激的感觉,用户就会不爽。
让我们把更新界面的代码从滚动事件里拿出来,放到requestAnimationFrame
的回调函数里吧,滚动事件只是简单的不惑滚动的值。我们的第二个demo页面在此。
如果你重复滚动测试,你可能会注意到一个轻微的改进,尽管不算明显。原因是因滚动而触发的布局操作并不总是代价昂贵了,但在其他用例中它很可能是。现在至少我们把布局操作限制在了每帧一次。
开发者工具展示了一个单个帧里的大块绘制以及多个由事件触发的布局操作
现在我们可以每帧绑定一个也可以绑定一百个滚动事件,但我们只记录requestAnimationFrame
回调函数运行时最近的值并更新到视图上。这里的重点是之前每次滚动事件触发时都强制更新视图,现在则是请求浏览器提供一个合适的窗口来做这件事。怎么样?不错吧!
这个方式的主要问题在于,不论requestAnimationFrame
与否,整个页面基本上是一个层。通过移动周围的这些可视元素,我们需要大块的重绘。通俗地讲,绘制是一个阻塞操作(虽然这已经在改变),也就是说浏览器无法做任何其它的工作,我们经常会超出每帧16毫秒的预算,页面还是无法纯天然不刺激。
不同于绝对定位的另一个方案是我们可以将元素应用到3D变换当中。在这种情形下,我们将这些应用3D变换的元素视为一个新的层,并且在WebKit浏览器中,它通常会导致一个硬件层面的转变。在方案1种,相比之下,我们有一个大的重绘的层,这个层的任何改变都会在CPU中绘制和组合。
也就是说,在这个方案中,一切变得不一样了:我们为应用3D变换的任何元素提供一个潜在的层。如果我们从这一点出发进行元素的变换那么我们无需重绘这个层,而GPU可以处理这些元素的移动并组合成最后的页面。
这里有另一个demo展示了3D变换的使用。如果你滚动页面你将会发现效果得到了大幅度的改善。
人们多次使用-webkit-transform: translateZ(0);
做hack并看到惊人的性能提升,但今天看来有几个问题:
如果你谨慎使用3D变换的话,这确实是一个临时解决方案!理想化的讲,我们可以在2D变换时看到和3D同样的渲染特性。浏览器正在以惊人的速度一步一步发展,所以希望这就是我们将会看到的。
最后,你应该针对性的避免绘制任何你可以在页面内简单移动的元素。举个视觉差网站通用的例子,固定div的高度并改变齐背景的位置来提供视觉差效果。不行的是这个元素需要在每次运动的时候都进行重绘,这会带来性能的损耗。取而代之的是,如果可以,你应该创建元素(有必要的话将其包裹在一个overflow: hidden
的div中)并对齐进行简单的移动。
我们考虑的终极方案,就是使用一个固定位置的canvas放在页面最底层,把我们想要绘制的各种变换图形都画在里面。一眼看上去这并不像是最优方案,但是这个方案确实有它的一些优势:
使用canvas元素给了我们一个新的层,但是仅此一个层,而在方案2种,我们实际是为每个应用3D变换的元素都创建了一个新的层。所以我们需要组合所有的层到一起,这是一个会增长的工作量。鉴于不同浏览器对变换的不同实现,这同时也是跨浏览器兼容性最好的方案。
如果你看看基于这个方案的这个demo,在开发者工具里测试一下,你会发现性能非常好。这个方案我们简单的使用了canvas的drawImage
API调用,并且我们将其背景图片和每个色块都绘制在屏幕上正确的位置。
/**
* Updates and draws in the underlying visual elements to the canvas.
*/
function updateElements () {
var relativeY = lastScrollY / h;
// Fill the canvas up
context.fillStyle = "#1e2124";
context.fillRect(0, 0, canvas.width, canvas.height);
// Draw the background
context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));
// Draw each of the blobs in turn
context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));
// Allow another rAF call to be scheduled
ticking = false;
}
/**
* Calculates a relative disposition given the page’s scroll
* range normalized from 0 to 1
* @param {number} base The starting value.
* @param {number} range The amount of pixels it can move.
* @param {number} relY The normalized scroll value.
* @param {number} offset A base normalized value from which to start the scroll behavior.
* @returns {number} The updated position value.
*/
function pos(base, range, relY, offset) {
return base + limit(0, 1, relY - offset) * range;
}
/**
* Clamps a number to a range.
* @param {number} min The minimum value.
* @param {number} max The maximum value.
* @param {number} value The value to limit.
* @returns {number} The clamped value.
*/
function limit(min, max, value) {
return Math.max(min, Math.min(max, value));
}
当你处理大图片(或其它可方便绘制到canvas中的元素)的时候,这个方案效果不错,但是处理大块文字的时候这个方案会遇到更多的挑战,但还是可以根据你的网站的情况成为最合适的方案。如果你不得不在canvas里处理文本,你可以使用fillText
API方法,但是它的可访问性会打折扣(你把文字转成了位图!)并且你不得不处理文字的折行等一些列细节。如果你可以避免它,你真的应该,也更有可能更好的使用上面的变换方案。
既然我们尽可能往远了想,那么没有理由断定视觉差的工作应该在一个canvas元素内完成。如果浏览器支持的话,我们可以使用WebGL。这里的关键在于WebGL有最直接的显卡API调用方式,也是你最有可能达到60fps的方式,尤其在网站效果比较复杂的时候。
你立刻觉得使用WebGL有点过于夸张了,或者WebGL尚未被广泛的支持,但是如果你使用类似Three.js的工具,你总是可以降级到使用canvas元素同时你的代码被抽象为了一致且友好的形态。所有我们需要的是使用Modernizr检查相关的API支持情况:
// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
renderer = new THREE.CanvasRenderer();
}
然后使用Three.js的API替换掉我们对上下文的处理。这里的demo同时支持了两个渲染方式,假设你的浏览器也会如此!
作为这个方案的最终思考,如果你不会在页面里放太多额外的元素的话,你可以总是使用canvas作为背景元素,这Firefox和基于WebKit的浏览器中都可以。很明显这确实不是无处不在的,所以我们平时使用的时候要小心谨慎。
开发者默认更多使用绝对定位的元素实现视觉差的主要原因其实就是其特性的支持程度。这在某种程度上是幻觉,因为老的目标浏览器很可能提供的是一个极其糟糕的渲染体验。甚至在今天的现代浏览器中,使用绝对定位元素还是无法保障好的性能。
3D变换为你提供了直接操作DOM元素的能力,并可以达到不错的帧率。成功的关键就是在你简单的移动周围元素时避免了绘制。一定记住,WebKit浏览器在这个过程中创建了层,但这和其它浏览器并不相关,所以要在提交方案之前一定要测试确认。
如果你只是定位于顶级浏览器,且可以通过canvas渲染网站,拿canvas可能是你最好的选择。当然如果你使用Three.js,你应该可以根据你需要的支持情况选择在不同的渲染方式之间进行切换。
我们已经评估了几个视觉差网站的实现方案,从决定定位元素到使用固定位置的canvas。当然,你需要的实现方式,依赖于你希望达到的特定的设计效果,但有这几个可选方案总是好的。
还是那句话,不论你用哪个方案:别妄加猜测,试试就知道了。
]]>本次开发者工具的改进中有几项新特性是针对性能的:
本次开发者工具的改进中有几项新特性是针对性能的:
持续绘制模式是开发者工具设置中的一个选项(渲染>开启持续页面绘制),这个选项可以帮助你识别单个元素或CSS样式的渲染开销。
通常Chrome只在响应一个布局或样式的变化时绘制屏幕,并且只是绘制屏幕中需要更新的区域。当你开启持续页面绘制选项时,整个屏幕都会不断的重绘。一个置顶的界面会展示Chrome在绘制页面时所花费的时间,以及近期绘制时间的分布图。穿过整个直方图的那条横线代表16.6毫秒标记线。
这样做的好处是你可以走遍DOM树中的元素面板,隐藏单个元素(隐藏当前选中元素的快捷键是H)或关闭一个元素的CSS样式。通过留意页面绘制时间的变化,你可以看到单个元素或样式为页面渲染所增加的“负担”。如果隐藏一个元素使得绘制时间明显下降,那么你要重点关照一下这个元素的样式或构造了。
开启持续绘制模式的方法:
注意:如果你看不到这个设置项,请打开about:flags,打开在所有页面中使用GPU合成,并重启Chrome。
更多信息,请移步至:用开发者工具的持续绘制模式进行长绘制时间的性能分析
另一个开发者工具的选项是展示正在被绘制的矩形区域(设置>渲染>展示绘制矩形)。比如,在下面这个屏幕截图中,一个矩形正在被绘制,在这里,CSS悬停效果被应用到了紫色图形中。
你得回避导致整个界面被重绘的设计实践与开发实践。比如,在下面这个屏幕截图中,用户正在滚动页面。一个绘制矩形覆盖在了滚动条上,另有一个绘制矩形覆盖在了整个页面的剩余部分。它的罪魁祸首是body元素的背景图片。该背景图片是fixed定位的,它要求Chrome每次滚动页面的时候都得重绘整个页面。
每秒帧数测量仪显示了页面当前的帧率、最小帧率和最大帧率、一个展示帧率随时间变化的条形图、以及不同帧率分布的直方图。
开启每秒帧数测量仪的方法:
你可以通过打开about:flags,然后开启每秒帧数计数器并重启Chrome,来强制每秒帧数测量仪始终显示。
为了最大化渲染性能,Chrome通常会在应用程序中批处理布局变化请求,并制定一个日程来异步计算和渲染这些变化请求。尽管如此,当一个应用程序获取依赖于布局的属性值的时候(比如offsetHeight或offsetWidth),Chrome会强制立刻同步渲染页面布局。我们称之为强制同步布局。这会明显的降低渲染的性能,在大DOM树中重复运行时尤为明显。这种情形也被称之为“layout thrashing”。
当我们检测到一个强制同步布局的时候,时间线记录中会有警告,它会在相应的时间线记录边上显示一个黄色的警告图标。鼠标悬停在这些记录上会看到无效的布局的代码堆栈记录、以及造成强制布局的代码堆栈记录。
该弹泡同时展示了需要布局的结点数量、重新布局的树的尺寸、布局的范围和布局的根。
更多信息,请移步至:时间线Demo:诊断强制同步布局
对象分配跟踪是一个新型的内存描述资料,它可以实时展示内容分配的情况。当你开始分配跟踪时,开发者工具实时持续生成堆的快照。堆分配的描述资料展示了对象在哪里被创建,且识别被保留的路径。
跟踪对象分配的方法:
相关新闻:东亚杯-王永珀2球孙可建功 中国两球落后3-3日本
当我再一次看到这样的标题的时候,我就知道,言外之意是国足的状况一定非常糟糕。
精气神儿是个什么东西?我觉得是一种最基本的态度,它只是个精神层面的很虚的过程。我举个例子,当你一无所有的时候,你只能说:哦,至少我还有节操。——这就是拿精气神儿说事儿的节奏。
国足说我们努力了,大家说其实人家从上到下还是很努力的,这一点我们还是要认可的……好吧你们确实真的很努力,但为时已晚。而且国足真的很差,现在才知道努力有个屁用?未雨绸缪的事情怎么从来没见足协做过?
以前国足被叫作头球队,叫热身赛之王,如今头球也没了,热身赛也能输个精光,连博彩公司的小伙伴们都惊呆了。 以前几年赢不了韩国就说恐韩,如今15年不胜日本了,也没人造出个什么恐日了,因为觉得跟人家比不自量力,丢不起那人。 以前国足98年,当时还叫东亚四强赛,国足在日本的主场2比0羞辱对手,范志毅还踢飞丢了一个点球,不然就是3比0的大胜(对,两边都是成人队,而且都是男足)。如今在一片铺天盖地的唾骂声中,国足才开始努力,开始打出精气神儿,勉强在最后时刻,逼平了日本二线队。
国足的努力掩盖不了一个事实,那就是战绩糟糕,排名持续下滑,多年无缘各项国际大赛。 这有什么可高兴的?
所以请别扯淡!拿成绩说话!!
看看今天的国足,还剩下什么?答:“只剩下两滴冰冷的泪水:一滴化斗酒添一份麻醉;一滴沉落于岁月的潮水。”
好。我这里对国足的吐槽完毕。
其实我没想太多聊国足。
接下来,请把“国足”二字换成你看到“其实很努力”之后首先联想到的事物,然后把上面全文的“国足”二字换掉重读一遍。相信你会很有乐趣和感悟。
]]>今年,在老罗锤子手机一路跳票、累不死手机活蹦乱跳、魅族手机版本号即将输给小米、iOS被拍扁、Windows Phone在卖萌、安卓在卖身等诸多鸟事相继雷到众生之后,人们无不感叹,手机这个行业还有救吗?站在智能和愚蠢的十字路口,我们该何去何从?囧rz
就在大家迷茫之时,Nokia发布了它的又一力作——1050!整个业界犹如刮来了一股春风,无不感到清新舒畅。大家纷纷感叹,那个曾经的科技巨人就要王者归来鸟!这个夏天,This summer,最受瞩目的大事件,big event!就是:
Nokia 1050 的发布!!!
作为一个反智能化手机操作系统的支持者,我很自豪的宣布,经历了前两轮的预定失败之后,我终于在上周成功订到了这款神机——要不要卖得这么好啊 - -
我之前用的手机是Nokia 1202,2008年的神机,其实也不算太老,要不是1050发布,它也其实已经是一个非常现代化、非常新款的愚蠢手机了,无奈在这个1050的时代趋势下也不得不接受停产的命运。
1050基本上继承了1202所有的成员函数和成员变量,小巧大方,简单易用,低碳环保,超长待机,便宜实惠,这已经足以吊起广大消费者的胃口了。然而真正的1050到底表现如何?它能否在1202的光环之下更进一步再创辉煌?带着这些疑问,记者走访了不少xxx,挖掘出了很多珍贵的xxxx,也听到了各种xxxxxxxx……
OK 马上开始
今年,在老罗锤子手机一路跳票、累不死手机活蹦乱跳、魅族手机版本号即将输给小米、iOS被拍扁、Windows Phone在卖萌、安卓在卖身等诸多鸟事相继雷到众生之后,人们无不感叹,手机这个行业还有救吗?站在智能和愚蠢的十字路口,我们该何去何从?囧rz
就在大家迷茫之时,Nokia发布了它的又一力作——1050!整个业界犹如刮来了一股春风,无不感到清新舒畅。大家纷纷感叹,那个曾经的科技巨人就要王者归来鸟!这个夏天,This summer,最受瞩目的大事件,big event!就是:
Nokia 1050 的发布!!!
作为一个反智能化手机操作系统的支持者,我很自豪的宣布,经历了前两轮的预定失败之后,我终于在上周成功订到了这款神机——要不要卖得这么好啊 - -
我之前用的手机是Nokia 1202,2008年的神机,其实也不算太老,要不是1050发布,它也其实已经是一个非常现代化、非常新款的愚蠢手机了,无奈在这个1050的时代趋势下也不得不接受停产的命运。
1050基本上继承了1202所有的成员函数和成员变量,小巧大方,简单易用,低碳环保,超长待机,便宜实惠,这已经足以吊起广大消费者的胃口了。然而真正的1050到底表现如何?它能否在1202的光环之下更进一步再创辉煌?带着这些疑问,记者走访了不少xxx,挖掘出了很多珍贵的xxxx,也听到了各种xxxxxxxx……
OK 马上开始
从体型上看,1050比1202略大了一圈,基本上没有怎么影响便携性,因为它已经真的足够小巧了,放包里、裤子口袋里、憋在身体的各种地方都没有负担。相比之下,1050不论是蓝色还是黑色更时尚一些,尤其是她的背影,但略略略显厚重点点点。
充电器还是和以前一样,很小巧,绝对耐用耐看
电池和1202想比,由860mAh减少到了800mAh
毫无疑问,1050到1202最大的变化就是从白屏到彩屏了,1202的屏幕更宽更扁一些,1050则是更方更高一些,1202正常情况下可以显示8x5个汉字,而1050则是在8x5的基础上还能在上面放一行小标题和信号、电量的信息。这样的话,1202之前只能在具体功能页面中隐藏信号、电量信息、然后上面显示一行标题,下面显示左右两个按钮,正文只有3三行;而1050不但可以始终显示信号、电量信息,还可以显示4行正文,这比之前是有提升的。但另一方面,其实屏幕的绝对精度是提高了,所以屏幕高度只有略微的增加,而且宽度还小了一些。这是屏幕上的变化。
正常情况下,两款手机的主界面基本一样,无非是1050在时钟下面多空了一行,不过主界面的变化还是很明显的:
先说待机,1202在待机的时候是关闭背景灯光,并始终显示时钟;而1050由于彩屏的缘故,可能会更耗电一些,所以待机的时候背景灯光和时钟全部都是关掉的。在导致的体验差异是:如果你需要看表,1202在白天可以直接拿起来看,1050则需要按任意键点亮屏幕。我觉得这一点上体验是下降的,当然这也可以理解为彩屏的代价吧
在众多智能手机还在为如何解锁、会不会有专利问题伤透脑筋的时候,Nokia系列愚蠢手机在就完美解决了这个问题:“开锁键->星号键”,pretty simple!什么?怕忘?没关系,系统在必要的时候都有提示的。
说到提示,这是我想说的一个重点:1202对于解锁的提示是一个非常强的提示,在锁屏状态下,只要你不是在键入“开锁键->星号键”,就会有一个全屏提示说:“要想解锁请按开锁键再按星号键”,但其实我有的时候只是想在光线比较暗的场合看一下时间,这其实是很令人恼火的一件事。1050抓住了这个用户痛点!它在待机状态下只接受开锁键和开机键这两个事件,开机键用来点亮屏幕,这个时候可以直接看表,除非用户再按了别的键,才会出现那个提示。
1050多了两个非常实用的快捷键!
1050的主菜单相比较1202的主菜单也发生了视觉上的变化
1202的主菜单是一个图片轮播一样的界面,同时只显示一个命令,外加左右键的提示
1050的主菜单则为用户提供了两种选择,一种是列表,每屏可以显示两个命令,可以上下翻阅,它的特点是字大看得清楚;另一种则是launchpad一样的图标列表,它的好处是对图形敏感的用户可以快速的找到最下面的命令,不必在列表模式下一直翻到最下面才知道有什么。
要说一个手机应该做什么事?这回到一个非常严肃的问题:什么叫智能手机?什么叫愚蠢手机?(此处省略1000字)
一个手机最应该做好的,当然是电话、短信、通讯录了!1020和1050在这方面做得非常出色,它可以在如此小巧的一亩三分田里做到智能手机才能做到的事。1050更是在1202的基础上做出了多项功能改进。
1202和1050的电话功能基本一致,不过在国内这种嘈杂的环境中,这两款神机的嗓门显得有点力不从心。1202的听筒音量根据我多年的使用经验其实是可以的,1050的听筒音量比1202更小一些,我有的时候索性打开免提模式了,那个音量在人多嘈杂的环境中是刚好的。但这两款手机有个不太好的地方是周围的人同样会听得很清楚,尤其是1202,貌似声音是直接通过扬声器传出来的,没有特别的听筒发音,所以手机正反面发出去的音量差不多,有一定的隐私问题。如果有什么私密事宜要在电话里谈,建议选好地方。
这个就值得多说一说了,1050在短信上的改进还是很贴心的。
首先是由于屏幕由3行正文变成了4行正文,在输入短信的时候有了更多的预览空间,这是很明显的一个改进。
第二,在选择发送人时加入了一个最近发出人的列表界面。
之前只有两个选择:
现在的流程变成:
这样的话,发给常发的人变得更快捷了。我个人的使用经验是90%的短信都是直接在这个列表里选的。真的省了很多时间。
不过我在这里还有一点点改进的建议,因为选择联系人发送的步骤其实由3步操作变成了4步操作,我建议把选择联系人的选项直接放在选择最近发出人列表的最上面作为默认选项,选择输入号码放在第二选项或最下面,保持3步操作
第三,一键回复短信。
在Nokia的键盘设计理念里,每一个按键都有很鲜明的特色和传统。说到这里不得不系统的介绍一下:
介绍这个是因为在最新的设计里,发送短信如果按下确认键,会打开菜单,提供发送等各种功能选择,然后才跳入我们刚才介绍的发送短信的流程;如果按下接听键,可以直接来到这个流程。
而更贴心的是,如果我是在回复一则短信而不是发送一条新短信,这时按下接听键,系统会直接把短信发出去——没错就直接发给来短信的这个号码。怎么样,是不是感觉很贴心呢 ^_^
第四,支持调整短信文字大小。
你也许不会相信,这种简朴的屏幕居然还能调“分辨率”?!没错,1050支持三种文字大小——汉字!如果你眼神不好,可以选择最大字号,如果你想看到更多内容,可以选择最小字号。老少皆宜!感受一下
其实除此之外,1202也已经有很多优质的特性了,1050也同样支持——这就是传承!有底蕴的科技公司!!
Nokia的通讯录管理也是很经典的,我有点不打算多介绍了,我只想说一点:把通讯录存在SIM卡上!秒杀一切所谓的云同步,它们全都是炒概念,想挖你的数据,像Siri一样。如果三方服务你都信不过,那这个绝对是最实用最可靠的!
说到这里,先大笑三声:哈~哈~哈~哈~
在1202里有一个软肋,就是它不计时间,只要电池拔下来,再装上,就要重新设定时间。它们真是会压缩成本啊 - -
1050不光解决了这一顽疾,而且支持“自动更新日期和时间”,你能想象吗?这是多么先进的理念!
1050做到了!10个字:贴心!
而且绑定备忘录、支持公历农历快速查询——这是1202和1050都具备的
1202和1050都支持情景模式,按下挂断键,就会看到这个选项列表
刚才介绍了,在1050中,还支持长按井号键快速切换到静音模式,其实是对这个功能的一个进化
没错!1050有主题切换哦,只需简单的2-3步操作——这可是iOS需要在6和7之间升级/降级才做得到的事情哦!
one more thing:你还可以修改配色方案的细节!天呐,这是神马神机呢!200块的手机可以如此DIY,我三观都改变了!!
1050的手机铃声有点不如1202了,可能是我个人品味的问题吧,Nokia的默认手机铃声其实旋律是很经典的,但是音色一直在变化,从最早的蜂鸣声开始。1202是钢琴音色,自己听着很舒服,而1050换了个说不上来的音色,我个人还是比较喜欢钢琴音色的那个版本。无奈之下我换了另外一个铃声,不过也算不错了,Nokia的铃声选择可比iOS的丰富多了,并且震动可以根据铃声的节奏震动。这个特性连HTML5都还没讨论出结论呢。
再有就是有个小乐趣消失了,就是自创铃声。1202是支持的,1050把这个功能去掉了。不过平心而论,我很少用这个,所以不太在乎了。不知道大家是不是这种音乐发烧友,如果是的话,建议还是不要升级新版本了,1202也不错。
我觉得nokia推出的各种“实用工具”虽然在我看来是不务正业,但这是对智能手机最大的讽刺。你们烧硬件、烧系统、烧应用、各种烧,不就是做了这点事么?
苹果从上周的iOS7开始才意识到手机可以用来当手电,我觉得这好讽刺,因为他们没有重新定义手电,而是在视图定义手机的时候不小心做成了手电。我觉得Nokia才是重新定义了手电,它诚意十足,它不是闪光灯的副产品,而是真的有意配了个灯泡给你当手电啊亲!!Tim-Cock你不觉得惭愧吗?
在临床作业中,我们发现,1050的手电筒效果比1202的光线更集中,发光效果更好,更符合手电筒的使用需求。细节!细节!!
足球场上人墙的距离为嘛是9.15米呢?iPhone用户该怎么办?打开Safari,在搜索框里艰难的输入问题,然后在一系列的网络不给力之后,比赛结束了,你还在思考人生。
看看Nokia是什么态度!内嵌各种单位转换,无需网络。
你也许会说,就那么几个写死的单位换算,一点都不高级。错!我要拿截图和亲身经历告诉你Nokia的单位转换是有API的!!只要你知道公式,想换算什么都可以!!
OMG!别挖财了,看看Nokia是怎么给你提供解决方案的。
我很想把Nokia手机里的游戏秀给你们看,真的。早就不是神马贪吃蛇数独之辈了。It's AMAZING!It's SO GREAT!That is FANTASTIC!UNBELIEVABLE!我觉得这个应该是一个真正的诺基亚用户独享的东西,所以,想即刻拥有来自诺基亚的经典游戏么?还在等什么!赶紧去排队预购吧!!
我要崩溃了……连这个都有
美中不足是需要耳机啊,要是能功放就更完美了,不然这绝对是在地铁里公交车上秀给那些iOS安卓屌丝们的最佳利器!!
常用功能的快捷方式都在这里了,可以自由定制,比如我就把收件箱、闹钟、未接电话和已接来电放到了这里
这个我没测,愚蠢手机也是手机啊!不能浪费的好吧。诸位大神谁帮忙测一下呗,绝对的业界良心!
我觉得Nokia要是够聪明,就适可而止吧,做成这样刚刚好。这些功能也不用做太多,做个意思,输出一下自己的价值观就足够了。另外该操作系统不提供截屏功能,这给它的市场传播带来了一定的困难。希望将来Nokia可以支持截图上传微博的功能,这个就真心碉堡了,毕竟Nokia还是一家老牌的有实力的科技公司。
从总体的性能情况看,就像之前介绍到的,1050比1202各方面响应速度,在同样合理的范围内,要略慢一些,有两种可能,一种可能是彩屏拖慢了速度,另一种可能就是Nokia其实也在成本和性能之间找寻平衡。从这一点上讲,1202的硬件配置其实是很奢侈的,1202才是一台真正的超值高性能主机。难怪它会停产啊,我为曾经用过这样的神机而骄傲!所以,1050不但可以比1202做得更好用,甚至在商业利益上也超越了前作。
从续航能力看,实际实用时间没有理论待机时间35天那么长,充一次电差不多可以正常使用1周,和1202差不多。但实际上它的电池容量从860mAh降到了800mAh。我们不得不再次感叹,Nokia在传说中如此成熟的直板机制造业,还能不断突破自我,勇往直前!换个图标把UI拍平了神马的就敢说自己突破自我绝对是百事可乐喝多了有木有
综上所述。Nokia 1050的最大变化来自于彩屏,其实细细想来,整款手机的一些列变化其实都是围绕着屏幕展开的。作为一个环保主义者,我其实觉得彩屏是个很奢侈的改变,它几乎违背了Nokia科技以人为本的办学理念。但是它由此带来的改进和便捷却是感同身受的。另外可以看出来Nokia在什么是手机、手机应该是什么样子、手机的未来在哪里这三个问题上有着非常睿智的独到见解。这家老牌的手机厂商、曾经的移动终端霸主,在经历了一段短暂的迷茫之后,终于找到了正确的发展方向。从前两轮预购都被秒光的态势来看,我们有理由相信,1050将引领一波轰轰烈烈的反智能化手机的热潮,并有理由期待Nokia的未来带给我们的更多惊喜。
完
]]>中国队输球之后回到更衣室,卡马乔甩下一句中国国骂就离去了,翻译一时糊涂还翻译成了西班牙国骂,随后也尴尬离去,气氛很凝重,大家都闷不吭声……30秒后,秦升终于忍不住先开口了:“哎呀今天这球输了都算我的,晚上我请客,咱们去三里屯不醉不归!”,顿时更衣室云开雾散,大家又快快乐乐的在一起了^_^
博君一笑 (配图皆来自于互联网)
]]>远远关注Sass很久了,今天终于鼓起勇气写了我的第一个Sass文件
一种CSS的预处理程序,基于Ruby运行。安装过程和相关的准备工作非常简单:
gem install ruby
,必要的环境下需要在命令前加上sudo
sass-convert style.css style.sass
,把我的css文件先转换成sass文件sass --watch style.sass:style.css
,使得程序自动把style.sass
文件接下来的任何改动自动同步转换到style.css
这时,新的Sass文件就创建完毕了!^_^ 去碎觉……
呵呵,开个玩笑。其实这样的Sass文件虽然格式上没有任何问题,但和直接撰写CSS几乎没区别。而Sass除了可以让我们少写几个花括号和分号之外,其实还有很多实用的特性是我们真正需要的。
无论如何,现在的这个Sass文件是一个整理的基础,接下来,我们就来一步一步整理这个文件,同时也一步一步熟悉Sass的特性。
]]>远远关注Sass很久了,今天终于鼓起勇气写了我的第一个Sass文件
一种CSS的预处理程序,基于Ruby运行。安装过程和相关的准备工作非常简单:
gem install ruby
,必要的环境下需要在命令前加上sudo
sass-convert style.css style.sass
,把我的css文件先转换成sass文件sass --watch style.sass:style.css
,使得程序自动把style.sass
文件接下来的任何改动自动同步转换到style.css
这时,新的Sass文件就创建完毕了!^_^ 去碎觉……
呵呵,开个玩笑。其实这样的Sass文件虽然格式上没有任何问题,但和直接撰写CSS几乎没区别。而Sass除了可以让我们少写几个花括号和分号之外,其实还有很多实用的特性是我们真正需要的。
无论如何,现在的这个Sass文件是一个整理的基础,接下来,我们就来一步一步整理这个文件,同时也一步一步熟悉Sass的特性。
我把CSS文件中通用的字体、颜色等等属性值归纳了出来,并找到其中的相关性,比如文本框的边框颜色始终比链接的文字颜色亮一些,即:
a {
color: #0c0;
}
input:not([type]),
input[type="text"],
input[type="password"] {
border: 2px lime solid;
}
在Sass中将其改写为:
$color-link: #0c0
a
color: $color-link
input:not([type]),
input[type="text"],
input[type="password"]
border: 2px lighten($color-link, 10%) solid
即可,输出的结果不变。
把嵌套着的选择器添加不同的缩进,同时把重复表达的外层选择器去掉。如:
h2 {
margin: 0.75em 0;
padding: 0.25em 0.5em;
background-color: rgba(0, 204, 0, 0.4);
color: #339933;
}
h2 strong {
color: white;
}
h2 a {
color: #009900;
}
h2 a:hover {
background: yellow;
color: black;
}
可以转换为:
h2
margin: 0.75em 0
padding: 0.25em 0.5em
background-color: rgba($color-link, 0.4) // 链接颜色,透明度40%
color: desaturate($color-link, 50%) // 链接颜色,饱和度减少50%
strong
color: white
a
color: darken($color-link, 10%) // 链接颜色,变暗10%
&:hover
background: $color-mark // 特殊标记的颜色
color: black
这个特性是我花最多时间整理的,整理过后发现整个文件结构真的清晰很多。
比如我的博客主题中,3D的标题和按钮就是两套集成度重用性都很高的样式,我创建了@mixin title-3d
和@mixin button-3d
两个大的属性集合:
@mixin title-3d
...
&::before,
&::after
...
...
@mixin button-3d
...
&::before,
&::after
...
&:hover::before
...
&:hover::after
...
...
随后我把这些属性集合应用到了标题和按钮上:
h2
@include title-3d
button,
input[type="button"],
input[type="submit"],
.button-3d
@include button-3d
整理完上面这两个大的集合之后,我发现,其实3d的标题和按钮样式上其实也有很多想通的地方,于是我进一步抽象出了一些3d模型的通用集合:
@mixin short-transform
-webkit-transition: -webkit-transform 0.3s
@mixin transform-origin-0
-webkit-transform-origin: 50% 50%
@mixin transform-origin-1
left: 0
top: 0
-webkit-transform-origin: 0% 0%
@mixin transform-origin-2
right: 0
top: 0
-webkit-transform-origin: 100% 0%
@mixin transform-origin-3
right: 0
bottom: 0
-webkit-transform-origin: 100% 100%
@mixin transform-origin-4
left: 0
bottom: 0
-webkit-transform-origin: 0% 100%
@mixin init-3d
@include short-transform
@include transform-origin-0
-webkit-transform-style: preserve-3d
@mixin p-element
position: absolute
content: ""
然后到之前的集合把这些通用集合抽象出来:
@mixin title-3d
@include init-3d
...
&::after,
&::before
@include p-element
@include short-transform
background-color: lighten($color-text, 30%)
&::before
height: 100%
width: $button-3d-height*2
@include transform-origin-1
...
&::after
height: 100%
width: $button-3d-height*2
@include transform-origin-2
...
@mixin box-sizing($sizing: border-box)
-moz-box-sizing: $sizing
box-sizing: $sizing
@mixin transform($value...)
-webkit-transform: $value
transform: $value
@mixin transform-origin($value)
-webkit-transform-origin: $value
transform-origin: $value
@mixin transform-3d($value: preserve-3d)
-webkit-transform-style: $value
transform-style: $value
@mixin transform-perspective($value: 350px)
-webkit-perspective: $value
perspective: $value
@mixin transition($value...)
-webkit-transition: $value
transition: $value
@mixin transition-transform($duration: 0.3s)
-webkit-transition: -webkit-transform $duration
transition: transform $duration
...
其实这个特性和mixin很多时候可以二选一使用,比如这里的.button-3d
选择器可以不必和button
、input[type="button"]
、input[type="submit"]
写在一起,可以写成:
.button-3d
...
button,
input[type="button"],
input[type="submit"]
@extend .button-3d
不过我这里这样写的必要性不太大,所以就没有实际的例子可以分享了。
最终我的第一个Sass文件就这样整理完毕了。(p.s.当然这个文件未来可能还是会有改动,届时可能会和本篇文章描述的内容不符)
经过上面这几轮代码的整理,这个Sass文件才真的很Sass了。回顾这次整理的过程,先后用到了变量、运算、嵌套、重用属性等Sass的特性,简单明了,而且sass --watch
命令、sass-convert
命令可以方便的对文件进行监听和格式转换。我几乎在感觉不到学习成本的情况下提高了开发效率。这里也推荐大家试试看。
Connect是基于Node的中间件框架(middleware framework),提供超过18种官方中间件以及更多的第三方中间件。
示例:
var app = connect()
.use(connect.logger('dev'))
.use(connect.static('public'))
.use(function(req, res){
res.end('hello world\n');
})
.listen(3000);
安装方式:
$ npm install connect
依次介绍官方中间件
]]>Connect是基于Node的中间件框架(middleware framework),提供超过18种官方中间件以及更多的第三方中间件。
示例:
var app = connect()
.use(connect.logger('dev'))
.use(connect.static('public'))
.use(function(req, res){
res.end('hello world\n');
})
.listen(3000);
安装方式:
$ npm install connect
依次介绍官方中间件
服务器请求日志,支持自定义格式,支持传入 options
选项对象或 format
字符串。
format
表示日志格式的字符串,由各种记号(token)组合而成stream
表示输出到哪里。默认是 stdout
buffer
表示缓冲的时间间隔,默认为 1000msimmediate
是否在请求(request)的时候立即写日志,而不是在回应(response)的时候default
、 short
、 tiny
其中 default
代表的格式是:
:remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"
另外还有 dev
格式,可以着色输出响应状态,开发时适用。
记号和格式都是可以自定义更多的,通过
connect.logger.token(name, function (req, res) {...})
和connect.logger.format(name, stringOrFunction)
更多细节请移步至此
默认情况下该中间件会生成一个名为“_csrf”的记号,该记号可以作为请求的状态、表单提交的隐藏属性值或查询字符串等等,并在服务器端与 req.session._csrf
属性进行核对。如果核对出错,则会出现403错误。
默认的 value
函数会以此核对 bodyParser()
中间件生成的 req.body
、 query()
生成的 req.query
以及名为“X-CSRF-Token”的头信息。
该中间件需要会话支持,因此必须出现在 session()
和 cookieParse()
中间件之后。
默认的 defaultValue()
实现如下:
function defaultValue(req) {
return (req.body && req.body._csrf)
|| (req.query && req.query._csrf)
|| (req.headers['x-csrf-token']);
}
更多细节请移步至此
Gzip压缩的中间件
支持的方法都在 connect.compress.methods
中,通过 connect.compress.filter(req, res)
方法判断文件是否需要压缩,默认压缩Content-Type含json、text或javascript的文件。
更高级的操作是可以将具体压缩方法的参数通过options参数传进去:
connect.compress({
chunkSize: ..., // default 16*1024
windowBits: ...,
level: ..., // 0-9
memLevel: ..., // 1-9
strategy: ...
})
更多细节请移步至此
connect.basicAuth(function (user, pass) {...})
,如果这个回调函数返回 true
,则获得访问权限。connect.basicAuth(function (user, pass, callback))
connect.basicAuth('username', 'password')
更多细节请移步至此
可扩展的解析器,对请求的body进行解析。支持_application/json_、application/x-www-form-urlencoded、multipart/form-data
其等同于:
app.use(connect.json());
app.use(connect.urlencoded());
app.use(connect.multipart());
更多细节请移步至此
_application/json_解析器,并将结果放至 req.body
strict
是否严格解析,当值为 false
时,理论上 JSON.parse()
能解析的数据都是被允许的reviver
用作 JSON.parse()
方法的第二参数limit
字节数限制,默认不开启更多细节请移步至此
_application/x-www-form-urlencoded_解析器,并将结果放至 req.body
limit
字节数限制,默认不开启更多细节请移步至此
_multipart/form-data_解析器,并将结果放至 req.body
和 req.files
limit
字节数限制,默认不开启defer
延时处理并不等 end
事件触发就调用 req.form.next()
展示大表单。该选项在需要绑定 progress
事件时可用。更多细节请移步至此
用法: connect.timeout(ms)
。如果请求超时则指向408错误。
另, req
对象会多一个 req.clearTimeout()
方法,用来在必要的情况下取消计时。
更多细节请移步至此
解析头中的_Cookie_并将结果放至 req.cookies
。你还可以通过 connect.cookieParser(secret)
中的 secret
参数对cookie进行加密。该密码可以通过 req.secret
进行取值。
更多细节请移步至此
详情略。
更多细节请移步至此
connect.cookieSession({ secret: 'tobo!', cookie: { maxAge: 60 * 60 * 1000 }});
key
cookie名,默认是 connect.sess
secret
密码cookie
会话cookie的设置,默认是 { path: '/', httpOnly: true, maxAge: null }
proxy
信任反向代理req.session = null;
更多细节请移步至此
当检查到方法重载的时候,把原方法存入 req.originalMethod
,检查的字段可以通过参数 key
设置,默认为 _method
connect.methodOverride(key)
更多细节请移步至此
计算响应时间并展示为 X-Response-Time
头
更多细节请移步至此
在内存中建立static中间件的缓存。默认最大缓存对象为128个,每个对象的最大体积是256k,总共大约32mb。
maxObjects
最大缓存对象个数,默认128个maxLength
最大缓存对象体积,默认256kb更多细节请移步至此
为给定的 root
路径提供静态文件服务,例如
connect.static(__dirname + '/public', {maxAge: 86400000})
maxAge
浏览器缓存时间,默认是 0
hidden
是否允许访问隐藏文件,默认是 false
redirect
路径是目录时是否在结尾自动加 /
,默认是 true
展示MIME模块,可读写
connect.static.mime
更多细节请移步至此
列出目录的文件列表
hidden
是否显示点(.)开头的文件,默认是 false
icons
是否显示文件图标,默认是 false
filter
过滤文件的函数,默认是 false
图标文件在 lib/public/icons/
目录中
connect.directory.html()
输出html格式的内容connect.directory.json()
输出json格式的内容connect.directory.plain()
输出文本格式的内容更多细节请移步至此
例如:
connect()
.use(connect.vhost('foo.com', fooApp))
.use(connect.vhost('bar.com', barApp))
.use(connect.vhost('*.com', mainApp))
更多细节请移步至此
默认图标为 lib/public/favicon.ico
,可更改,调用方式:
connect.favicon('public/favicion.ico', {maxAge: 86400000})
maxAge
过期时间,默认是1天(86400000)更多细节请移步至此
限制请求的body字节数,可传入一个数字或代表容量大小的字符串,比如: 5mb
、 200kb
、 1gb
connect.limit('5.5mb')
更多细节请移步至此
自动解析查询字符串,生成 req.query
更多细节请移步至此
灵活的错误处理机制,开发环境下提供出错信息和栈追踪,回应信息支持纯文本、HTML和JSON
{ "error": error }
更多细节请移步至此
(完)
]]>Node.JS这东西说起来也是3年前就听说的东西了,最近一段时间才真正拿它做东西。这种感觉既熟悉又陌生,熟悉在听大家谈过无数次,陌生在自己没怎么亲自动过手。这回的一些尝试和项目实践,让我更多的了解了这门技术,也更好的了解了我自己。
分享的在线链接在此:/slides/node-js-practice,这里就不重复其中的内容了。
我想更多说的是,我们有幸在HTML5快速发展的时代,这里几乎每天都会有新的规范、新的工具、新的库、新的框架、新的理论。玲琅满目,目不暇接。也许我们每天走马灯似的看它们,都不一定看得过来。我感觉自己就长期处于这种状态。可是当这些东西都只是我们眼前的匆匆过客,来不及细细体验、品味其中的内涵,那和从来都没看过相比,有多少实质上的差别呢?
我觉得这里有两件事情值得考虑:第一,要再多些时间在新技术的关注和尝试上;第二,有选择性的深入其中。
先说第一点,我们不能始终沉醉在对现有技术或业务的娴熟之中,同时要相信技术和产品、设计、市场、商务一样,可以驱动业务的发展和进步。我们唯有带着这样的信念去工作,去和同事交流,去和领导沟通,去在团队里一起探讨问题,才会令自己有这种空间和时间。凡事都有难处,但总要走出这第一步。
第二,前端这个词逐渐由html/css/js三门语言和基本ps技巧的集合,变成一个无限宽泛的概念。我们在鼓励全面发展的同时,也不能一把抓,也需要量力而行,循序渐进,找到属于自己的突破口,找到和自身工作最佳的结合点。最关键的是,要有实践的机会。
人生有时难免会站在命运的十字路口,左右为难。最后发现,真正让自己踏实下来的,往往是这些真材实料的东西。
]]>r.js 的介绍中明确写道它是 RequireJS 项目的一部分,和 RequireJS 协同工作。但我发现,RequireJS Optimizer 提供了丰富的配置参数,可以让我们完全跳出 AMD 和 RequireJS 程序的束缚,为我们的前端程序服务。
]]>r.js 的介绍中明确写道它是 RequireJS 项目的一部分,和 RequireJS 协同工作。但我发现,RequireJS Optimizer 提供了丰富的配置参数,可以让我们完全跳出 AMD 和 RequireJS 程序的束缚,为我们的前端程序服务。
首先,简单介绍一下 RequireJS Optimizer 的“正派”用法 (以 NodeJS 环境为例):
事先写好一个配置文件,比如 config.js
,它是 JSON 格式的,常用属性有:
{
// 程序的根路径
appDir: "some/path/trunk",
// 脚本的根路径
// 相对于程序的根路径
baseUrl: "./js",
// 打包输出到的路径
dir: "../some/path/release",
// 需要打包合并的js模块,数组形式,可以有多个
// name 以 baseUrl 为相对路径,无需写 .js 后缀
// 比如 main 依赖 a 和 b,a 又依赖 c,则 {name: 'main'} 会把 c.js a.js b.js main.js 合并成一个 main.js
modules: [
{name: 'main'}
...
]
// 通过正则以文件名排除文件/文件夹
// 比如当前的正则表示排除 .svn、.git 这类的隐藏文件
fileExclusionRegExp: /^\./
}
然后运行:
node r.js -o config.js
这时 RequireJS Optimizer 就会:
modules
下的所有模块建立好完整的依赖关系,再把相应的文件打包合并到 dir
目录css
文件中,使用 @import
语法的文件自动打包合并到 dir
目录dir
目录,比如图片、附件等等我已经把 RequireJS 和 r.js 整套东西用到了 H5Slides 上。觉得蛮方便的。
不过工作中的前端开发工作并不是绝对理想化的,有些旧的项目,并不是 AMD 的模块化开发方式,而是传统的 js 程序,开发一个页面时可能需要一口气引入三到五个 css 文件、十来个 js 文件…… 上线的时候为了减少流量及 HTTP 请求数又需要把代码尽可能重用和合并。这个时候就需要一个方便快捷的打包工具帮助我们了,下面就介绍一下 RequireJS Optimizer 是如何完成这项工作的。
说到这里,必须要额外介绍几个 RequireJS Optimizer 的参数了:
modules: [
{
name: "main",
include: ["d", "e"]
}
]
这里的 include 字段提供了“强制建立依赖关系”的功能,也就是说,即使在 main.js 的代码里没有依赖 d.js 和 e.js,它们也会在合并代码的时候插入到 main.js 的前面
在介绍这个参数之前需要说明的是,RequireJS Optimizer 有一个很智能的功能,就是为没有写明 define(...) 函数的模块代码自动将其放入 define(...) 之中。如果我们写明:
skipModuleInsertion: true
则这种处理将会被取消。
这个参数可以定义一个函数,在处理每个 js 文件之前,会先对文件的文本内容进行预处理。比如下面这个例子里,我会把 main.js 里的代码全部清除:
onBuildRead: function (moduleName, path, contents) {
if (moduleName === 'main') {
contents = '/* empty code */';
}
return contents;
}
这时,我们的资源已经足够了。比如我现在的项目有:
1 个 html
代码:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Index</title>
<link rel="stylesheet" href="css/a.css">
<link rel="stylesheet" href="css/b.css">
</head>
<body>
...
<script src="js/a.js"></script>
<script src="js/b.js"></script>
<script src="js/c.js"></script>
</body>
</html>
2 个 css
3 个 js
1 个图片文件夹
新建一个 css 文件,叫 css/main.css,内容为:
@import url(a.css);
@import url(b.css);
然后把 index.html 中的 2 个 <link> 标签改成一个
<link rel="stylesheet" href="css/main.css">
合并 js 文件的步骤略复杂些。首先也是新建一个 js 文件,叫 js/main.js:
document.write('<script src="js/a.js"></script>');
document.write('<script src="js/b.js"></script>');
document.write('<script src="js/c.js"></script>');
然后把 index.html 中的 3 个 <script> 标签改成一个
<script src="js/main.js"></script>
接下来就是配置打包工具的时间了。
skipModuleInsertion: true
modules: [{name: 'main', include: ['a', 'b', 'c']}]
这样打包出来的 main.js 是这样的:
// code from a.js
// code from b.js
// code from c.js
document.write('<script src="js/a.js"></script>');
document.write('<script src="js/b.js"></script>');
document.write('<script src="js/c.js"></script>');
onBuildRead: function (moduleName, path, contents) {
if (moduleName === 'main') {
contents = '/* empty code */';
}
return contents;
}
这样的话,打包工具就会把 document.write(...)
的代码去掉,得到干净的
// code from a.js
// code from b.js
// code from c.js
/* empty code */
运行 node r.js -o config.js
就可以得到一个打包成功的项目了,并且打包前后的代码都可以正常运行
附件是这个项目例子的源代码:project.zip
]]>我一共放了5个默认选项,使用情况排名依次是:
我一共放了5个默认选项,使用情况排名依次是:
为了避免自己孤陋寡闻,把其它主流编辑器遗漏,所以我保留了“其它”这个选项,从结果看,确实有疏漏,其中不乏Eclipse、Emacs、WebStorm、Aptana这样的神器,还看到了一个熟悉的身影Expression Web,那是我之前使用过的一款编辑器,微软出的,还不错:
]]>JSLint
? ​JSLint
是一个用来查找各种 JavaScript 程序中的问题的 JavaScript 程序。它是一个代码之类工具。
在早些年的 C 语言中,有些程序的常见错误是主流的编译器无法抓住的。所以出现了一个名叫 lint
的附带程序,可以通过搜索源文件寻找错误。
随着语言的成熟,其定义的健壮性足以消除一些不安因素,编译器也在问题警告方面越做越好,lint
也不再需要了。
JavaScript 是一个年轻的语言。它原本只是用在网页上完成一些无需劳驾 Java 的小任务。但 JavaScript 是一个强大得惊人的语言,现在它已经在大项目中派上用场了。当项目变得复杂之后,之前从易用角度出发的语言特性就带来了一些麻烦。这是一个为 JavaScript 而生的 lint
呼之欲出:它就是 JSLint
,一个检查 JavaScript 语法、判断 JavaScript 语法有效性的工具。
JSLint
会拿来一段 JavaScript 源代码并对其进行检索。一旦发现问题,它就会返回一则消息,用来描述这个问题以及源代码中的大概位置。发现的问题不一定是,但通常是语法上的错误。JSLint
通过一些代码规范来杜绝结构性的问题。这并不证明你的程序是正确的,只是提供另一种发现问题的眼光。
JSLint
定义了一个专业的 JavaScript 的子集,它比 ECMAScript 标准第三版的定义更严格,和 JavaScript 编码规范中的建议相对应。
JavaScript 是一个粗中有细的语言,它比你想象中的更好。JSLint
帮助你回避很多问题,在这个更好的语言中撰写程序。JSLint
会拒绝一些浏览器支持的程序,因为浏览器并不关心代码的质量。你应该接受 JSLint
的所有建议。
JSLint
在 JavaScript 源代码、HTML 源代码、CSS 源代码或 JSON 文本中都可以运行。
JSLint
? ​JSLint
是一个用来查找各种 JavaScript 程序中的问题的 JavaScript 程序。它是一个代码之类工具。
在早些年的 C 语言中,有些程序的常见错误是主流的编译器无法抓住的。所以出现了一个名叫 lint
的附带程序,可以通过搜索源文件寻找错误。
随着语言的成熟,其定义的健壮性足以消除一些不安因素,编译器也在问题警告方面越做越好,lint
也不再需要了。
JavaScript 是一个年轻的语言。它原本只是用在网页上完成一些无需劳驾 Java 的小任务。但 JavaScript 是一个强大得惊人的语言,现在它已经在大项目中派上用场了。当项目变得复杂之后,之前从易用角度出发的语言特性就带来了一些麻烦。这是一个为 JavaScript 而生的 lint
呼之欲出:它就是 JSLint
,一个检查 JavaScript 语法、判断 JavaScript 语法有效性的工具。
JSLint
会拿来一段 JavaScript 源代码并对其进行检索。一旦发现问题,它就会返回一则消息,用来描述这个问题以及源代码中的大概位置。发现的问题不一定是,但通常是语法上的错误。JSLint
通过一些代码规范来杜绝结构性的问题。这并不证明你的程序是正确的,只是提供另一种发现问题的眼光。
JSLint
定义了一个专业的 JavaScript 的子集,它比 ECMAScript 标准第三版的定义更严格,和 JavaScript 编码规范中的建议相对应。
JavaScript 是一个粗中有细的语言,它比你想象中的更好。JSLint
帮助你回避很多问题,在这个更好的语言中撰写程序。JSLint
会拒绝一些浏览器支持的程序,因为浏览器并不关心代码的质量。你应该接受 JSLint
的所有建议。
JSLint
在 JavaScript 源代码、HTML 源代码、CSS 源代码或 JSON 文本中都可以运行。
JavaScript 的最大问题就是其依赖的全局变量,特别隐含的全局变量。如果一个变量没有被显性的声明 (通常是通过 var
语句),则 JavaScript 会假定这个变量是全局变量。这会掩盖拼写错误等其它问题。
JSLint
希望所有的变量和函数都要在使用或调用之前被声明。这样我们就可以探测隐含着的全局变量。同时,这也让程序的可读性增强了。
有的时候一个文件会依赖于在别处定义好的全局变量或全局函数。这时你可以通过一个 var
语句让 JSLint
识别该程序依赖的这些全局函数和对象。
一个全局声明大概如下所示:
var getElementByAttribute, breakCycles, hanoi;
该声明应该出现在靠近文件最上方的位置,且必须出现在使用这些变量之前。
同时,我们有必要在一个变量被赋值之前通过 var
语句对其进行声明。
JSLint
同样可以识别一段 /*global*/
指令,该指令可以为 JSLint
注明在该文件中使用但在其它文件中定义好的变量。该指令可以包含一串变量名,用逗号隔开。每个名字可以跟随一个可选的冒号以及 true
或 false
,true
表示该变量可以被该文件赋值,而 false
则表示该变量不能被赋值 (这是默认行为)。在函数作用域也是如此。
有些全局变量可以被你预定义。选择假设为浏览器 (browser
) 选项可以预定义浏览器提供的标准的全局属性,比如 document
和 addEventListener
。它等同于:
/*global clearInterval: false, clearTimeout: false, document: false, event: false, frames: false, history: false, Image: false, location: false, name: false, navigator: false, Option: false, parent: false, screen: false, setInterval: false, setTimeout: false, window: false, XMLHttpRequest: false */
选择假设为 Node.js (node
) 选项可以预定义 Node.js 环境下的全局变量。它等同于:
/*global Buffer: false, clearInterval: false, clearTimeout: false, console: false, exports: false, global: false, module: false, process: false, querystring: false, require: false, setInterval: false, setTimeout: false, __filename: false, __dirname: false */
选择假设为 Rhino (rhino
) 选项可以预定义 Rhino 环境下的全局属性。它等同于:
/*global defineClass: false, deserialize: false, gc: false, help: false, load: false, loadClass: false, print: false, quit: false, readFile: false, readUrl: false, runCommand: false, seal: false, serialize: false, spawn: false, sync: false, toint32: false, version: false */
选择假设为 Windows (windows
) 选项可以预定义 Microsoft Windows 提供的全局属性。它等同于:
/*global ActiveXObject: false, CScript: false, Debug: false, Enumerator: false, System: false, VBArray: false, WScript: false, WSH: false */
JavaScript 使用近似于 C 语言的语法,它要求使用分号来分割确定的语句。JavaScript 试图通过一个自动插入分号的机制让这些分号变得可有可无。这是比较危险的,因为它可以掩盖你的错误。
和 C 一样,JavaScript 有 ++
和 --
和 (
操作符,这些操作符可以作为前缀或后缀。分号可以用来消除这里的二义性。
在 JavaScript 中,一个折行可以是一个空格,也可以当做分号使用,两者容易产生混淆。
JSLint
希望除了 for
、function
、if
、switch
、try
和while
,每个语句都以 ;
结尾。同时 JSLint
不希望看到没有必要的分号或空白语句。
我们可以用逗号操作符写出极度巧妙的表达式。同时也可以掩盖一些程序上的错误。
JSLint
希望逗号只用来当做分隔符,而不是操作符 (for
语句中的初始化部分和自增部分除外)。数组直接量里不希望有被省略的元素,多余的逗号不应该被使用,逗号不应该出现在数组直接量或对象直接量的最后一个元素的后面,因为一些浏览器无法正常识别。
在很多语言中,每个块都产生一个作用域,块内产生的变量在块外是看不到的。
在 JavaScript 中,块并不产生新的作用域,只有函数作用域。在一个函数中任意位置声明的变量都在整个函数中可见。JavaScript 的块混淆了有经验的程序员,并导致了错误的出现,因为用相似的语法做了一个错误的承诺。
JSLint
希望 function
、if
、switch
、while
、for
、do
和 try
语句之外不要产生其它块。
在有块级作用域的语言里,通常都推荐变量声明在第一次使用的地方。但是因为 JavaScript 没有块级作用域,所以明智的选择就是在函数的最顶端声明所有的函数变量。这里推荐每个函数用单一的一个 var
语句完成变量声明。这个要求可以通过 vars
选项取消掉。
JSLint
希望 if
、while
、do
和 for
语句都由块生成,{
也就是说,语句是被大括号包住的 }
。
JavaScript 允许一个 if
语句这样书写:
if (condition) statement;
这个格式是公认的:当很多程序员都基于相同的代码工作时,会给整个项目带来很多错误。这也是为什么 JSLint
希望如下使用块:
if (condition) { statement; }
经验告诉我们这个格式更保险一些。
我们希望一个表达式语句是一个赋值或一个函数/方法调用或 delete
。其它表达式语句都可能导致错误。
for
in
​for
in
语句允许在一个对象的所有属性名内进行循环。不幸的是,这样的循环也会覆盖到其原型链中所有被继承而来的属性。其副作用就是当我们只关心数据的时候,它连方法函数也会处理一遍。如果此时该程序没有任何预防措施,那么就会导致失败。
每个 for
in
语句的主体部分都应该包裹一个起过滤效果的 if
语句。该语句可以选择特殊类型或范围的值、或排除函数类型的属性、或排除原型的属性。比如:
for (name in object) { if (object.hasOwnProperty(name)) { .... } }
switch
​一个 switch
语句中常见的错误就是忘记在每个条件的结尾写上 break
语句,导致程序没有及时跳走。JSLint
希望语句的下一个 case
之前或 default
之前必须有下面三个当中的一个:break
、return
、throw
。
var
​JavaScript 允许 var
在一个函数内的任何位置完成定义。JSLint
的要求则会更严格一些。
JSLint
希望一个 var
只被声明一次,并且是在使用之前被声明。
JSLint
希望一个 function
在使用之前被声明。
JSLint
希望函数的参数不要被声明为变量。
JSLint
不希望 arguments
数组被声明为 var
。
JSLint
不希望一个变量被定义在块中。这是因为 JavaScript 的块并不具有块级作用域。这会造成意料之外的结果。把所有的变量都定义在函数的最顶部。
with
​with
语句的初衷是提供一个访问对象嵌套属性的简写方式。不幸的是,当我们设置新属性时这个行为非常糟糕。永远不要使用 with
语句。请用一个 var
替代之。
=
​JSLint
不希望看到 if
、for
、while
或 do
语句的条件判断中出现赋值语句。这是因为似乎:
if (a = b) { ... }
的本意是:
if (a == b) { ... }
当我们难以从常用语句中察觉出明显的错误时,是很难把程序写对的。
==
和 !=
​==
和 !=
操作会在比较之前做类型转换。这是不提倡的,因为它导致 ' \t\r\n' == 0
的结果是 true
。这会掩盖一些类型错误。JSLint
无法依赖 ==
进行判断,所以最好别再使用 ==
和 !=
了,以后都用更可靠的 ===
和 !==
操作替代之。
如果你只是在乎一个值是真的还是假的,那么可以使用简写方式,比如把:
(foo != 0)
替换为:
(foo)
而将:
(foo == 0)
替换为:
(!foo)
我们有一个 eqeq
选项,允许使用 ==
和 !=
。
JavaScript 允许任何语句拥有标签,标签拥有各自的命名空间。JSLint
的要求会更严格。
JSLint
希望标签只出现在响应 break
、swtich
、while
、do
和 for
的语句上。JSLint
希望标签可以同变量和参数区分开来。
JSLint
希望一个 return
、break
、continue
或 throw
语句后面都会跟着一个 }
或 case
或 default
。
JSLint
希望 +
不会紧跟着 +
或 ++
,而 -
不会紧跟着 -
或 --
。少写一个空格就会把 + +
变成 ++
,这种错误是很难被发现的。为了避免混乱,请善用括号。
++
和 --
​自增操作符 ++
和自减操作符 --
是公认的会鼓励过度使用技巧而导致糟糕的代码。它们是导致病毒和安全威胁的错误构建。同样的,乱用前自增和后自增会产生 off-by-one 的错误,排查这样的错误是极为困难的。我们有一个 plusplus
选项来允许使用这些操作符。
JavaScript 没有整数类型 (*),但是其具备位操作符。位操作符会先把操作对象由浮点数转换回整数,所以这样的位操作效率远不及 C 或其它语言。位操作符极少用在浏览器应用里。与逻辑操作符的相似性也会掩盖一些程序的错误。bitwise
选项允许使用 <<
、>>
、>>>
、~
、&
、|
这些操作符。
eval
是魔鬼 ​eval
函数 (及其相似的 Function
、setTimeout
和 setInterval
) 提供了 JavaScript 编译器的访问形式。这在一些情况下是有必要的,但是在大多数情况下它代表了相当糟糕的代码。eval
函数是 JavaScript 最不应该被使用的特性。
void
​在大多数近似于 C 的语言中,void
是一个类型。在 JavaScript 中,void
是一个前缀操作符,它总是返回 undefined
。JSLint
不希望看到 void
因为它具有迷惑性且没有实质的用处。
正则表达式写起来既简洁又神秘。JSLint
会检测一些可能导致可移植性问题的问题。同时会推荐把具有视觉混淆性质的字符全部转义。
JavaScript 语法中,正则表达式直接量会多写一对 /
字符。为了避免混淆,JSLint
希望一个正则表达式字面量的开头是一个 (
或 =
或 :
或 ,
字符。
new
​构造函数是设计为通过 new
前缀使用的函数。new
前缀基于函数的 prototype
创建一个新的对象,并把函数隐性提供的 this
参数绑定到那个对象。如果你忽略了 new
前缀的使用,则不会有新的对象被创建,而 this
则会绑定到全局对象上。这是非常严重的错误。
JSLint
强制规范构造函数的函数名大写开头。JSLint
不希望看到一个函数调用的函数名是大写开头但没有 new
前缀。JSLint
也不希望看到 new
前缀用在一个函数名不是大写开头的函数上。这一要求可以通过 newcap
选项关掉。
JSLint
不希望看到 new Number
、new String
、new Boolean
的包裹形式。
JSLint
不希望看到 new Object
,用 {}
替换之。
JSLint
不希望看到 new Array
,用 []
替换之。
因为 JavaScript 是个弱类型动态对象语言,所以不太可能在编译的时候判定一个属性名是否拼写正确。JSLint
在这方面提供了一些帮助。
在其报告的最底端,JSLint
显示了一个 /*properties*/
指令。它包含了一些名称和字符串直接量,它们是所有使用过的点标记、下标标记和对象直接量中用来命名对象属性的。你可以查阅这个列表来找到拼错的词。这是个比较简单的办法。
你还可以复制 /*properties*/
指令到你的脚本文件的顶端。JSLint
会根据这个列表检查所有的属性名。这样,你就可以使用 JSLint
检查拼写错误了。
比如:
/*properties charAt, slice */
有一些字符是不同的浏览器显示起来不一致的,在放入字符串之前必须先转义。
\u0000-\u001f \u007f-\u009f \u00ad \u0600-\u0604 \u070f \u17b4 \u17b5 \u200c-\u200f \u2028-\u202f \u2060-\u206f \ufeff \ufff0-\uffff
JSLint
不会做流程分析,也不会判断变量在使用之前是否已经被赋值。这是因为变量都有默认值 (undefined
),很多应用本身就是这样处理的。
JSLint
不会做任何类型的全局分析,不会尝试判断伴随 new
使用的函数是真正的构造函数 (只是遵循大小写规范),不会检查属性名拼写是否正确 (只是针对 /*properties*/
指令进行匹配)。
JSLint
可以处理 HTML 文本。它可以找到包含在 <script>
... </script>
标签中的 JavaScript 内容。也可以通过 JavaScript 找到 HTML 内容中公认的问题:
</p>
)。<
表示字面量 >
。JSLint
比 XHTML 的要求要低,但是比主流浏览器的要求要严格。
JSLint
也会检查 '</'
在字符串字面量的出现情况,你应该用 '<\/'
替换之。额外的反斜杠会被 JavaScript 编译器忽略掉,但 HTML 解析器不会。类似的技巧都不是必要的,但就是这样。
这里有一个 fragment
选项,用来检查一个合格的 HTML 片段。如果 adsafe
选项也选用,则片段必须是一个遵守 ADSafe widget 规则的 <div>
。
JSLint
可以检查 CSS 文件。它检查 CSS 文件的第一行是不是:
@charset "UTF-8";
这个特性是试验性的。请把任何问题或束缚报告出来。这里有一个 css
选项,可以容忍一些非标准的使用习惯。
JSLint
提供了很多选项,它们控制了其操作和敏感度。在网页版中,选项是可以通过复选框进行勾选的。
我们还提供了通过构造 /*jslint*/
指令和 /*properties*/
指令的方式进行辅助。
当 JSLint
被当做函数调用时,它接受一个 option
对象参数,这个参数允许你判定你可接受的 JavaScript 子集。网页版的 JSLint
在 https://www.JSLint.com,就是这样工作的。
选项还可以在 /*jslint*/
指令中被定义:
/*jslint nomen: true, debug: true, evil: false, vars: true*/
选项指令起始于 '/*jslint'。注意 j
前面没有空格。本规范包含了一系列的键值对,这些键是 JSLint
的选项,值是 true
或 false
。indent
选项可以取一个数字。一个 /*jslint*/
指令优先于 option
对象。指令遵照函数作用域。
(表格略,详见:https://www.jslint.com/lint.html#options)
如果 JSLint
可以完成检查,那么它会生成一个函数报告。其列出下面每个函数:
JSLint
会“猜”出这个名称。报告还会包含一个使用过的属性名列表。这里是JSLint
的消息列表。
世界上没有怯懦的高楼
没有细水长流的烟火
——1976《烟火》
看完春晚正准备要睡觉,看到祥子一条微博:八千块的烟火换来一小时的快乐,多少的代价可以换来一生的幸福。
于是我就这样陷入了沉思。
祥子我恨你……
此时此刻,估计整个城市,乃至全中国的“一小时的快乐”,都已经告一段落了。这对我来说,也意味着,去年的成绩、收获、经验教训,也停留在这里了;新年的奋斗史,从这一刻,就要开始书写了。
每到过年的时候,也是让我猛一抬头,想起自己和过去的老师同学又多一整年不见的时候,想起自己在傲游又多待了一年的时候,想起自己和一年前相比又度过了一年美好时光的时候。
来傲游第六年了,去年这个时候的五周年纪念的情形仿佛还历历在目。我也在此兑现自己一年前的承诺,给自己在傲游的第六年留个“快照”。
现在小学时期的稳定性记录已经被打破了,人生会不会从此变得没有追求了呢?呵呵
在我眼里,这一年,自己和公司都发生了很多变化。公司的办公区又一次扩张了,从“两居室小户型”演变成“两套大房子”了;高管走了一批、也来了一批;有些部门拆散了,有些部门合并了,有些部门洗牌了……无非都为的是个活字。我自己呢,团队角色和工作要求也发生了不小的变化,无非为的是能继续走下去。其实这些都不算什么伟大的志向。所以,尽管过程轰轰烈烈,结果其实很惨淡。
这和我刚来公司时的感受已经截然不同了。刚来傲游的时候,觉得像天堂一样,哪儿都好,各种优越感,每天无忧无虑,技术交流的时候、同学聚会的时候、校园招聘等各种时候,一说自己是傲游的,大家都好待见你;现如今,觉得公司哪儿都有问题,哪儿都做得不好,哪儿都有改进空间。上周一个好朋友的电脑重装系统了,跟我说他把Program Files/Maxthon
整个文件夹都备份了,问我怎么恢复收藏——聊到最后我们两个都很震惊:他震惊自己最信任的傲游浏览器把他的收藏搞丢了,我震惊他不知道我们有傲游账户和在线收藏。
烟火固然美丽,但也稍纵即逝。
去年年初的时候看到过一句话:“即使如日中天的Apple,也一定会有股票下跌的那一天” (如今它已经跌了)。一家公司再好,一份工作再适合你,也会有不如意的地方。其实问题总是会有的,如果每次面对问题的时候都选择逃避,这个问题不会自动消失,反而会永远缠着你。
我觉得去年一年,是我让自己从理想乐观回归现实的一年。从某种角度将,它让我看清傲游是一个有很多问题的公司,同时这些问题也是大家需要很大的勇气去承认、面对和解决的——这其实对于我,以及每一个傲游人来说,其实非常重要。它比我们去年赚了多少钱、发展了多少用户、打开了多大的市场这些成绩,都更重要。
所以,做事不能只看眼前成绩,不然什么也做不成。面对问题,不能逃避,只有勇敢面对,久而久之,养成习惯,就可以战胜一切!
过去的六年,对我来说,就当是一场烟火表演吧。璀璨、辉煌、这一切的一切,都无法奠定你未来的坦途。幸福的人生,需要的不是一场价值连城的烟火表演,也不是每年一度的烟火表演,而是一个持续的状态,一步一个脚印的存在感和成就感。
是时候面对久违的挑战了。
]]>不久之前刚在自己微博上晒了几张自己拍照片时的个人恶趣味,算娱乐一下吧。下面几张是我真的认真挑选过的,如果诸位摄影达人看到觉得拍得太烂,还求轻拍 ^_^
车水马龙
不久之前刚在自己微博上晒了几张自己拍照片时的个人恶趣味,算娱乐一下吧。下面几张是我真的认真挑选过的,如果诸位摄影达人看到觉得拍得太烂,还求轻拍 ^_^
车水马龙
多彩地砖
蓝天·高楼
店铺一角
西湖夜景
厦门夜景
厦门夜景
做个幸福的吃货!Yeah!
照照镜子,看看自己
乱七八糟的2012年就这样糊里糊涂的过去了。
今年发生了不少冠冕堂皇的大事儿,但好像没什么实质性的收获。可能此时此刻的我心思还是在事业上多一些,但无奈力不从心。作为一个有家有老婆的人,不能加班,不能熬夜,工作上几乎没了弹性,想冲冲不起来,有劲儿不能一口气使出来;另外我们的休闲娱乐需求也很旺盛,下馆子看电视电影到处旅游总是好的,可它们基本上填满了我的业余时间,没有充足的时间用心学习这种感觉比什么都糟糕。这些事儿其实也不是我改变不了,更多的是自己也不忍心改变吧。
我觉得这是我觉得2012糊里糊涂的主要原因。
第二点原因是我觉得自己变懒了也变邋遢了。搬了家这么久也结婚了这么久,很多早就想请客的人也一直没请到。每到周末就跟自己怕麻烦的心妥协了。洗碗洗衣服倒洗脚水擦地吃早餐之类的事情也越来越不积极了,我最近反思了一下,可能是太想把烂摊子都寄托给老婆来收拾了,家里的脏活累活总要有人做,己所不欲勿施于人,以后还是身先士卒吧。
在工作上,我比较欣慰的是自己有机会也有勇气接触更广泛的技术领域,让自己的知识面更广,更充实,但达到比较理想的状态是需要时间的。为了能够掌握更广泛的开发技能,我正在重读很多计算机的基本课程。在上学的时候它像是我毕业路上的敌人,我在乎的只是战胜那些题目;如今重读,它们更像是我的朋友,为我弥补一块块盲区,让我面对困难的时候更加自信。真的有点二次启蒙的感觉,我突然觉得这才是真正的编程。我希望在新的一年我可以处于一种低调踏实追求卓越的心态,不要像今年这样疯疯癫癫的,说得多做得少。
公司这一年的形势很微妙,我觉得全公司现在最纠结的地方在于,我们想做个典型的带有鲜明中国特色的公司,还是一个超凡脱俗的洋气公司。说白了就是一群屌丝和另一群高富帅白富美凑在一起想搞个团队活动,大家一起商量是去吃西餐呢还是吃大排档呢。正在犹豫之时,眼瞅着周围的人眼疾手快,把西餐厅的位子订光了,把大排档也挤了个水泄不通,顿时就都傻眼了,可还是没有相互妥协的迹象。咳,只说这么多了…
别的都还行。
2013不能比今年更烂了。
]]>设想一下HTML的世界最初只有div和span这两个标签,其实网页依然可以写得出来。更多标签的出现,其实是为了替代利用率高但不好书写的 <div class="{tagname}">
和 <span class="{tagname}">
来的。
想再接着多说一句的是,在HTML5里越来越多常见的div class组合或div id组合被直接命名为了新的标签。理由也是相同的,像header/footer/aside/nav/section/article都是我之前经常使用的class或id。我甚至觉得w3c创造新html标签的工作很简单,定期统计一下最常用的class和id,然后取前几名作为新的标签名就行了。
上周还有人在微博上感慨那个“史上最牛的HTML代码”:
<div class="mod">
<div class="hd"></div>
<div class="bd"></div>
</div>
再过几年它真的也许会消失的。
反过来思考也可以,尽量使用有语义的标签名,实在想不出来合适的标签名了,再用div然后起个class或id。这样的思路也不错。
和问题1同理,设想一下HTML的世界最初只有class没有id,其实网页依然可以写得出来,语义依然表达得出来。无非就是会出现很多特殊的class呗。因此,我们也很好理解,id的出现,就是可以和class一起协作,使某些语义即使没有现成的标签可以表示它,但依然可以把一般性和唯一性完美的结合在一起。
这里驳斥一个观点:“尽量都使用class,因为控制样式的时候class的优先级是同级的,id的优先级更高,它的出现会破坏样式优先级的平衡”。首先我觉得这是一个假命题,所谓的“平衡”是不存在的,也没有必要去刻意维护,通过id来表示的内容一定是相对特殊的,优先级自然高一些,这样的优先级设计是如此的自然。我能够接受的全部是class的适用范围仅是一些底层的css基础样式,如oocss里的基础样式,或很多网站都会有common.css文件或general.css文件,里面的东西尽量用class没问题。
另一个更重要的理由是,在HTML5里,除了id和class这两个特性可以控制样式之外,还可以通过特性选择器来定义样式,类似E[attr="..."]的写法。我们会发现可以控制样式的方式越来越灵活,选择越来越多。这是Web发展的必然趋势。当其他人已经在用id/class/data-*/tagname对样式展开多重维度攻势的时候,我们实在没有必要把自己还关在class的世界里。
比如两列布局的左右侧多半是正文和辅助信息的关系,那么就不建议用class="left"和class="right"而是倾向于class="main-content"和class="sidebar",或者直接用article和aside。
在自适应Web设计(responsive web design)如火如荼的今天,页面上的某个元素处在网页什么位置更像是个变量,所以通过位置来定义一个元素显然是会承受很多额外的维护成本和扩展成本。当改变发生的那一天,你发现自己的HTML代码变得文不对题。曾经的left跑到最上面去了,right变成了底部通栏,这都是很正常的变化。
实在没什么语义的,比如为了给IE加圆角而增设的标签,或清除浮动用的额外的标签,再或者是基础样式的,和具体内容无关的,再用div加表象的class来描述。
比如class="f14 red"。印象中网上有很多拙劣的例子,也有很多深刻批判这种用法的文章,我想说的是,如果你非要这样改样式,那不如直接写内联style来得直观。
互联网是一个快速发展的领域,它的快速发展甚至让人们忘却了很多传统领域的停滞不前。在这样的领域里工作,勇敢尝试,关注新技术,把握新趋势是如此的重要。不要拒绝新事物,不要被不思进取的人拖累,不要对大千世界失去好奇心和求知欲,方可永葆青春。
]]>傲游2006年的官网
这是今天看到的网页
这跟创新毫无关系,但和创新的一个反面话题有关——抄袭。虽然公司的设计和创意被别人照搬过去对我这种在傲游有些年头的人已经不是什么新鲜事了,但是在今年的新网站,还能够抄回2006年,好设计在国内死绝了吗?
我感觉在我们周围的互联网行业中,创新能力正在急剧群体退化。不论是设计、产品还是技术层面。1.封闭的互联网给山寨版的国外网站带来了__巨大需求__;2.再加上产权保护不给力,让抄袭变成了“0成本”。所以抄袭者顺风顺水,不甘心抄的人走起路来就变得相对困难。
另外,我不太认同微创新这个说法,原因很简单,__如果微小的不同之处也可以看做是创新,那什么又是真正的抄袭呢?__我们今天认为的抄袭,哪个会像做蜡像一样真的做个一模一样的东西出来呢?所以没什么争议,抄了就是抄了。如果微创新被鼓励,就等于抄袭被鼓励。
设计:“直接把傲游的官网样式抓下来用吧”
产品:“我们做的东西很简单,就做成跟xxxx一样”,“然后我们再加个xxx,就行了”
我担心在这样的游戏规则之下,人的创新意识和创新能力会在不知不觉中退化。前段时间参加了互联网大会,本来是去给我们CEO捧场的,只听了其中几个专场。直观的感觉是:大家都在讨论别人,上来就说Facebook今年怎么样,Apple今年怎么样,Google今年怎么样,说得不亦乐乎,好像Facebook、Apple、Google是他们自家的公司;一说到自己,就一两句话结束了。听到最后,觉得讲得最有水平的,同时也是中国互联网最有前途的行业,其实是搞数据分析的,因为他们的工作就是名正言顺的研究别人。
隔天凌晨,我又见证了苹果iPhone5等一些列产品的发布会。对比甚是明显:
我想这就是对国内互联网怪现象的最好总结了……
看过听过经历过后,我更坚定了的是,要拥有一颗强大的内心,面对抄袭,不能妥协,坚持创新的思考和行动。
]]>这次峰会上,诸多企业都在会场搭建了属于自己的展台:有浏览器,有应用平台、有技术图书出版社,这样不够,后台技术来了,电视来了,物联网来了,广告服务来了,这样还不够,更夸张的是眼镜和糕点也来了。其实仔细想想,中间这部分领域是早晚要和HTML5打交道的,而后面这部分呢,也几乎是每一个前端工程师生活的一部分(你看,干这行的视力都不太好,还经常以庆祝xxx为借口买蛋糕吃)。我们了解形形色色的行业和企业,体验工程师生活的每一部分。
当然说到企业展台,这也包括我的老东家傲游。今年公司启动了全平台的浏览器研发项目,除了已经有的PC、安卓版本,还陆续发布了Mac、iPad、iPhone版本。所以这次的展台内容很丰富,有些朋友甚至来到展台前把所有的设备都试用一遍,才意尤未尽的离开,这让我们这两天“辛苦站台”的同事们倍感欣慰。
我想说第二天的HTML5作品展真棒!一口气看58份优秀作品,结识58组前端精英,感受大家的创意和热情!这次给我印象深刻的,有一个是用Canvas写毛笔字的,可以根据书写的速度模拟出毛笔的力道来,非常神奇;还有一个作品用到了最新的摄像头媒介接口,做出了类似Kinect的游戏体验,他们的作者自豪的说:“我们的目标就是秒杀Kinect!”;另外我非常有幸结识到了HTML5图形库ichartjs的作者,我觉得他的作品像一阵及时雨,刚好填补了图表制作这个HTML5非常大的领域空白。除此之外的其它作品也都各有千秋,看得出他们呕心沥血的投入和付出。
我也有机会展示了自己参与的一个开源项目:就是H5Slides,我们专门为了这次的峰会搭建了一个供大家试用和体验的网站。不少开发者也对我们的作品产生了浓厚的兴趣。还有些人在网上看到过,现场认出我来了,哈哈好害羞滴说!
]]>这次峰会上,诸多企业都在会场搭建了属于自己的展台:有浏览器,有应用平台、有技术图书出版社,这样不够,后台技术来了,电视来了,物联网来了,广告服务来了,这样还不够,更夸张的是眼镜和糕点也来了。其实仔细想想,中间这部分领域是早晚要和HTML5打交道的,而后面这部分呢,也几乎是每一个前端工程师生活的一部分(你看,干这行的视力都不太好,还经常以庆祝xxx为借口买蛋糕吃)。我们了解形形色色的行业和企业,体验工程师生活的每一部分。
当然说到企业展台,这也包括我的老东家傲游。今年公司启动了全平台的浏览器研发项目,除了已经有的PC、安卓版本,还陆续发布了Mac、iPad、iPhone版本。所以这次的展台内容很丰富,有些朋友甚至来到展台前把所有的设备都试用一遍,才意尤未尽的离开,这让我们这两天“辛苦站台”的同事们倍感欣慰。
我想说第二天的HTML5作品展真棒!一口气看58份优秀作品,结识58组前端精英,感受大家的创意和热情!这次给我印象深刻的,有一个是用Canvas写毛笔字的,可以根据书写的速度模拟出毛笔的力道来,非常神奇;还有一个作品用到了最新的摄像头媒介接口,做出了类似Kinect的游戏体验,他们的作者自豪的说:“我们的目标就是秒杀Kinect!”;另外我非常有幸结识到了HTML5图形库ichartjs的作者,我觉得他的作品像一阵及时雨,刚好填补了图表制作这个HTML5非常大的领域空白。除此之外的其它作品也都各有千秋,看得出他们呕心沥血的投入和付出。
我也有机会展示了自己参与的一个开源项目:就是H5Slides,我们专门为了这次的峰会搭建了一个供大家试用和体验的网站。不少开发者也对我们的作品产生了浓厚的兴趣。还有些人在网上看到过,现场认出我来了,哈哈好害羞滴说!
周六下午的五大专场,安排的都满满当当,我主要参加了其中的两个:浏览器、前端开发。
浏览器这边对罗志宇和我们公司小红姐姐的分享印象深刻,一个是从底层实现的角度看前端性能,受到了很多启发,尤其是“CSS其实就是个和DOM多度多匹配的数据库”这点我一直没有悟出来,也没想到由此推衍出的性能话题,罗的话一语惊醒了我;另一个则是从应用角度看Web标准,嘿嘿,当然也是因为这是我们自己的声音嘛,小红在介绍我们内核研发工作的同时,也把我自己的工作内容描述的很优美很伟大,听完脸上有光:-)
前端开发这边看到了会前听 @adamlu 推荐给我的鸡尾酒(Cocktails)框架,听口音是一位来自台湾的工程师介绍的,讲得很精彩也很幽默。@苏震巍 的话题我赶上个尾巴,没听全,小可惜。回头等PPT全公开了我打算挨个儿下载看看。听说还有老外用中文讲技术,这是多么刺激的事情 XD
这次从TV专场的成功举办可以看出,智能电视已经在为HTML5跃跃欲试了。加上前阵子参加了一个三星的智能电视的沙龙,和现场的朋友们进行了很多的互动,总体的感觉是智能电视的厂商都算是比较传统的厂商,在与HTML5相遇的时候,产生了很多有趣奇妙的火花。投资人、HTML5开发者、软硬件厂商聚在一起的时候,话题总是那么精彩。看到移动互联网这么火热,大家都坐不住了。哈哈
还有就是大会开场时提到的HTML.next话题,我们应该庆幸自己处在一个Web技术日新月异,高速发展的时代,亲身参与和感受这样的变化。别以为HTML5就是终点了:“成功?我才刚上路呢”
这个太贴心了,会场居然可以办成一个技术与生活的大杂烩,我亲眼看到身为工程师的丈夫带着一家三口来参加周日的嘉年华的。老公在展台展示自己的成就,老婆孩子在玩棋牌,和灰太狼喜洋洋的公仔互动,渴了有乐可,饿了有奥利奥饼干和现做的蛋糕,其乐融融。我之前觉得对于成家的人来说,周末两天不休息来参会太残忍了,现做我就不觉得了,反而双手推荐!
工程师们也不闲着,除了技术交流,还有机会参加各种抽奖、砸蛋、作品投票、你说我猜之类的游戏。我也不免俗的去砸了一个金蛋,中了一套便利贴。
和太多神交已久的朋友们相见了啊!!!!神飞、李秉骏、苏震巍、IsNull、还有好久不见的winter、老赵、大城小胖、杨东杰、梦工场的几个城市的负责人…… 和大家聊在一起非常开心,同时觉得好多来自武汉、杭州、上海、深圳、广州的优秀作品和精英们的涌现,北京虽然能承办得到这么豪华的峰会,但有很多地方应该和全国各地的朋友虚心请教和学习。收获满满的两天!
我们还敢期待更精彩更大规模的HTML5峰会吗?@田爱娜
]]>当然,首先,阅读模式本身是一项非常棒的功能!他可以增强文章的阅读体验,统一不同页面的视觉风格。可以让人专注在文章内容中。等等。 而我要吐槽的点,是这个所谓的“智能判断文章内容”的算法。
阅读模式的核心任务之一,就是能够找出我们在各种凌乱广告、装饰物、页眉页脚之中的正文内容。在找内容这件事上,大家不约而同的使用了智能分析的路数。这里是早期开源的Readability脚本库的核心代码,我们可以看到其中包含着大量用来识别某dom结点是否是正文根结点的算法公示。而这套算法,又是从大量已存在的网页中归纳而来的。
既然是归纳的结果,而现如今已存在的网页已经数以亿计五花八门了,那不可避免的会存在判断误差。我们希望把内容识别的准确率提到最高,但会遇到一个问题:就是广告主、站长都有各自的小算盘,他们时刻准备着违背这些智能,降低判别的准确率,不让它正常工作,以谋取自己更高的收益或二次点击率。因此,这一智能的识别规则无法完全公开(Readability随后停止了开放源代码,我猜也有这方面的考量)。
可黑盒的智能规则又导致了另外一个问题:智能识别如果出现判断误差,对于那些希望支持阅读模式的网站,又无奈于找不到这些计算规则去适配。于是我们又会看到类似“how to enable safari reader”这样的问题满天飞。
最后阅读模式的识别规则、支持者、反对者扭打在了一起。
]]>当然,首先,阅读模式本身是一项非常棒的功能!他可以增强文章的阅读体验,统一不同页面的视觉风格。可以让人专注在文章内容中。等等。 而我要吐槽的点,是这个所谓的“智能判断文章内容”的算法。
阅读模式的核心任务之一,就是能够找出我们在各种凌乱广告、装饰物、页眉页脚之中的正文内容。在找内容这件事上,大家不约而同的使用了智能分析的路数。这里是早期开源的Readability脚本库的核心代码,我们可以看到其中包含着大量用来识别某dom结点是否是正文根结点的算法公示。而这套算法,又是从大量已存在的网页中归纳而来的。
既然是归纳的结果,而现如今已存在的网页已经数以亿计五花八门了,那不可避免的会存在判断误差。我们希望把内容识别的准确率提到最高,但会遇到一个问题:就是广告主、站长都有各自的小算盘,他们时刻准备着违背这些智能,降低判别的准确率,不让它正常工作,以谋取自己更高的收益或二次点击率。因此,这一智能的识别规则无法完全公开(Readability随后停止了开放源代码,我猜也有这方面的考量)。
可黑盒的智能规则又导致了另外一个问题:智能识别如果出现判断误差,对于那些希望支持阅读模式的网站,又无奈于找不到这些计算规则去适配。于是我们又会看到类似“how to enable safari reader”这样的问题满天飞。
最后阅读模式的识别规则、支持者、反对者扭打在了一起。
所以,归纳起来,智能识别的劣势有两个:一个是无法100%判断准确,这样无法完全满足用户需求;一个是面临多方面利益的冲突和挑战,这样会阻碍其快速发展和完善。
我们希望网页上的任何内容,只要是值得认真看一看的,都可以提供“阅读模式”的体验——就像iOS中屏幕上的任意区域被双击之后都可以智能缩放到合适大小一样。所以我们可以给用户另外一种“阅读模式”,它可以让用户主动选择想阅读的区域,然后把用户选中的区域设定为阅读器的正文。
有了这个东西,智能规则的压力会减小;支持者不必担心自己的内容无法很舒服的被用户阅读;挑战者也无计可施。一举三得!
随意阅读并无法取代现有的阅读模式,但会让阅读这件事变得更完整。同时,我们可以改进的地方有很多:
话说这次奥运会4对羽毛球选手被判消极比赛取消资格一事,绝对是国际羽联对中国长期以来忍无可忍的一次报复。毫无疑问中国现在在羽毛球这个项目上太过强大,尤其是女双,强大到对手连打败你的信心都没有了。这样的话选手的成绩在淘汰赛制中变得偶然性很大:第一轮就碰中国,名次肯定倒数,晚点碰中国,甚至可以拿奖牌。
我猜国际羽联把羽毛球由纯淘汰赛改成循环赛加淘汰赛,这也直接导致了这一出闹剧的出现。其实规则的改变就是给那些名次不好的人更多机会,给中国队更多危险。但我不认为这是一种良性的改变。
]]>话说这次奥运会4对羽毛球选手被判消极比赛取消资格一事,绝对是国际羽联对中国长期以来忍无可忍的一次报复。毫无疑问中国现在在羽毛球这个项目上太过强大,尤其是女双,强大到对手连打败你的信心都没有了。这样的话选手的成绩在淘汰赛制中变得偶然性很大:第一轮就碰中国,名次肯定倒数,晚点碰中国,甚至可以拿奖牌。
我猜国际羽联把羽毛球由纯淘汰赛改成循环赛加淘汰赛,这也直接导致了这一出闹剧的出现。其实规则的改变就是给那些名次不好的人更多机会,给中国队更多危险。但我不认为这是一种良性的改变。
我们希望在奥运会看到的是全人类的突破,把优秀的成绩变得更优秀,再优秀,不断突破自我,战胜自我。可国际羽联这样的改变只是单纯的为了“照顾弱者”而放慢优秀者的脚步,这同时也放慢了全人类的角度——你觉得人类不可能把羽毛球打得更好了吗?外国打不过中国,应该更多从他们自己身上找原因,想通过规则打败中国,只会让他们变得更加不堪一击。
比如,NBA历史上就多次因为限制个别“变态”球员而修改规则,感兴趣的还可以再查查张伯伦改变了多少NBA规则,离我们比较近的则是乒乓球总在改规则:把乒乓球变大变重,发球不能遮挡等等。
这两者差在哪里呢?改比赛内容的话,也算是让运动员挑战更高的极限,比如三分线变得更远了,就需要你有更精准的投篮才行;可修改赛制,对这项运动本身的发展有何帮助?
以前我们说规则是死的,人是活的。可今天连规则也开始变了,那么请允许我认为:在规则的“怂恿”下,中国、印尼、韩国的这几对选手被迫需要面对这样的尴尬。在这种规则下,给了谁,站在今天中国队的位置,都无法忽视规则与利益之间的矛盾冲突;而印尼和韩国呢,如果今天刚好是马来西亚选手和中国队交锋,大家觉得马来西亚球员会不会做出同样的事情?我想还是会,因为__在一个恶劣的体制下,每个人都会成为“共犯”中的一员__。
我再举个例子:自己遇到红灯却眼瞅着要迟到了,看马路上的人都闯红灯过去了,你心里会不会痒痒的?
就算不是每个人都会真正去做,至少这是一个正常人都会产生的尴尬。从这个角度讲,造成今天的局面,国际羽联难逃其咎。
我们的运动员真的是为了更高、更快、更强的奥运精神在参加奥运会吗?还是国家尊严、个人名利这些跟奥运精神比起来显得低俗下流的东西?索性很长一段时间这两件事对我们来说是完全重合的,所以不怎么深究。今天国际羽联就给了我们一把尺,在这把尺上,我们的丑陋被人家捉个正着。就算国际羽联是刻意的,我们也没有经得住“考验”。
而在金牌至上的长期思想下,即使个人有那么一点冲动和想法,也被大环境打磨掉了。
另外,当天确实假得太过分了。观众不买账了,事情才闹大的。这也是恶性循环的一部分。
这样的恶性循环还要在我们的身边上演多少次?!
]]>ZeroClipboard是在桌面电脑的浏览器上,通过flash技术实现“复制到剪切板”功能的一个程序。它的好处是可以兼容所有浏览器,完成剪切板的操作。
我们在使用的时候主要就用到两个文件:一个是js文件ZeroClipboard.js
,用来引用在网页中;另一个则是swf文件ZeroClipboard.swf
,它无需我们在代码里引用,而是被之前的那个ZeroClipboard.js
二次调用的。
ZeroClipboard的工作原理大概是,在网页的“复制”按钮上层遮罩一个透明的flash,这个flash在被点击之后,会调用其的剪切板处理功能,完成对特定文本的复制。这里有几件事需要我们来完成:
对于这几件事,ZeroClipboard分别提供了不同的api,来完成整个需求。
]]>ZeroClipboard是在桌面电脑的浏览器上,通过flash技术实现“复制到剪切板”功能的一个程序。它的好处是可以兼容所有浏览器,完成剪切板的操作。
我们在使用的时候主要就用到两个文件:一个是js文件ZeroClipboard.js
,用来引用在网页中;另一个则是swf文件ZeroClipboard.swf
,它无需我们在代码里引用,而是被之前的那个ZeroClipboard.js
二次调用的。
ZeroClipboard的工作原理大概是,在网页的“复制”按钮上层遮罩一个透明的flash,这个flash在被点击之后,会调用其的剪切板处理功能,完成对特定文本的复制。这里有几件事需要我们来完成:
对于这几件事,ZeroClipboard分别提供了不同的api,来完成整个需求。
创建的过程其实就是一个var clip = new ZeroClipboard.Client()
的过程,这时ZeroClipboard.swf
会被载入。值得注意的时,这里的swf文件默认需要放在和网页相同的目录下,且文件名固定。如果我们需要移动这个swf文件的位置或改名,则需要在创建swf文件之前运行:
ZeroClipboard.setMoviePath( 'http://YOURSERVER/path/ZeroClipboard.swf' );
或
ZeroClipboard.setMoviePath( './src/ZeroClipboard.swf' );
里面的参数可以是相对地址也可以是绝对地址。
这里有一个很有趣的英文单词:glue。我们可以通过下面这个api,将flash和按钮重叠,且浮在按钮之上:
clip.glue( 'clip-button-id' );
或
clip.glue( document.getElementById('clip-button-id' ));
即第一个参数为id或dom对象都可以。如果按钮在网页运行中位置发生了变化,flash是不会自动调整位置的,为此我们提供了另一个api可以手动更新flash的位置:
clip.reposition();
这里还提供了一种更巧妙的方式:如果按钮的上层有任何position:relative
的块状元素,比如div,而按钮和这个块状元素的位置又是相对固定的,那么可以在调用glue
函数时,将这个div的id作为第二个参数传进去,不过同时reposition
这个api就失效了。比如:
clip.glue( 'clip-button-id', 'clip-container-id' );
这一步很简单:
clip.setText('要复制的文本在这里');
通过addEventListener进行事件绑定,可以绑定的事件有以下几个:
onload
:flash文件加载成功onmousedown
:鼠标在flash上按下onmouseup
:鼠标在flash上释放onmouseover
:鼠标经过flashonmouseout
:鼠标移开flashoncomplete
:剪切板操作完成 (用鼠标点击该flash浮层的时候会触发事件复制到剪切板)clip.destroy();
]]>来北京的第6年,同时也是在傲游的第6年,一转眼小半年快过去了。
这半年过得算是波澜不惊吧,事情异常多,公司的,家里的,亲戚朋友的,各种活动的,还狗屎运出了一趟国。总会有些事情照顾不周,处理不妥,也在意料之中。我只想说:感谢经历,你就自己想办法挺过去吧。
在这不到6年的时间里,我一直没有停止做一件事,就是不断努力发现自己的问题和不足,并尝试改变它——相信很多人也都会这样。这期间有些改变是令我兴奋的;有些事情是立竿见影的,富有成就感的;还有很多事情,在潜移默化的发生着,你无法通过一两件事情看清楚,但经过长时间的积累,暮然回首,你会发现,它真的变了,有好的也有不好的。
]]>来北京的第6年,同时也是在傲游的第6年,一转眼小半年快过去了。
这半年过得算是波澜不惊吧,事情异常多,公司的,家里的,亲戚朋友的,各种活动的,还狗屎运出了一趟国。总会有些事情照顾不周,处理不妥,也在意料之中。我只想说:感谢经历,你就自己想办法挺过去吧。
在这不到6年的时间里,我一直没有停止做一件事,就是不断努力发现自己的问题和不足,并尝试改变它——相信很多人也都会这样。这期间有些改变是令我兴奋的;有些事情是立竿见影的,富有成就感的;还有很多事情,在潜移默化的发生着,你无法通过一两件事情看清楚,但经过长时间的积累,暮然回首,你会发现,它真的变了,有好的也有不好的。
前阵子看《锵锵》,老窦聊起减肥食谱,说有一次问医生道:
究竟有多少人能按这个健康食谱坚持下来呢?
答曰:
工作上能成事儿的,基本能坚持下来。
从这个角度讲,我发现今天自己比较满意或比较有成就感的事情,基本也都一直坚持着了。那些自己不满意的地方,平时也确实是三天打鱼两天晒网。这似乎也没什么好讲的。
于是乎又想起自己上大学的时候,跟已毕业多年的一位学长聊天,学长说道:
大学同学多年后聚会会有人问别的老同学:“以前在班上咱俩的差距无非也就是我考个85分,你考个90分,没啥感觉啊?怎么现在两个人的发展差距这么大呢?”。其实这说明了一个问题,你做每件事都用85分要求自己,这会形成习惯,而别人都用90分要求自己,一两次考试确实看不出啥差距,可时间长了,剪刀差的差距就明显了。
对于人生,我们的态度究竟是多少分呢?对我来说,通过6年的时间,我隐约看到了一些答案。再加上那些没有坚持下来的事情,我觉得自己的生活状态必须有所改变了——如果我还有梦想的话。
何为基础呢?如果学好A可以让你更快的掌握B,那大体上A应该算是B的基础吧。如果希望事半功倍的学习,有的时候似乎不能由着自己的性子,想起什么就去学什么,而是找到那个最大的A。对于现在的我而言,这个最大的A似乎是英语。正如我之前思考如何学习时想到的那样,英语太TM重要了!
除此之外,几个基本的计算机学科也是我接下来深入学习的对象。这些东西的价值,在多年后的今天、自己熟悉的领域,可能不太明显了,因为已有大量的经验可供参考。但是在处理更复杂或跟广泛的问题时,才发掘自己搞不定更多事情了,琢磨一个在别人看来入门级别的程序,都要搞很久,这种感觉真是糟透了……
也许对于一个前端开发来说,为不懂浏览器内核或后台服务器编程而忧伤算是一种强迫症吧。后来我仔细想了想,这些糟糕的感觉之所以出现,是因为给自己定了一个更高的目标吧,这不是什么坏事。每次想到这里,自己又重新振作了起来。
一方面是要把各方面的时间协调好吧,有意识管理自己的事务和行程;另一方面也希望自己可以对事情的轻重缓急始终有一个清醒的认识。我尤其觉得自己在电脑面前管不住自己 囧。这体现在自己不愿意起身做别的事情,有严重的拖延症;还有就是总是没法早睡早起(比如今天)、吃规律的早餐;还有就是总想着开TMD微博、人人网、网易体育和直播吧……
如果一个生活状态可以让自己变得更好,那么就去保持这个状态吧。否则,改变是刻不容缓的。
有句广告语是:“心情好,一切都美好!”。世界总会有纷乱,悲观的人会被击垮,乐观的人会重生。过往云烟,留下来的才是自己的。尽量把问题想的远一些,并坚定信念。
以上
]]>原本打算记我上周末成都之行的一篇日志,我觉得除了参加HTML5梦工厂成都站的技术交流活动之外,最大的收获,莫过于同杨东杰童鞋的畅谈。另外今天连续在微博上看了两个罗纳尔多和萨内蒂年轻时候的足球集锦,突然觉得这几年间,很多事情在发生着悄然的改变,比如曾经有个美少女组合叫S.H.E,昨天在地铁外看到Ella的一个活动海报,才发觉,这个团早已淡出了我们的视野。我们上大学的时候,有几个舍友特别喜欢挺S.H.E,导致我也比较熟悉他们的歌曲,后来让我对他们真正产生印象的是《Play》那张专辑,觉得它的概念蛮有意思的,整张唱片都是轻松欢快的气氛。于是《听袁惟仁弹吉他》这首歌一闪而过,于是就有了这个标题……
有点扯远了
]]>原本打算记我上周末成都之行的一篇日志,我觉得除了参加HTML5梦工厂成都站的技术交流活动之外,最大的收获,莫过于同杨东杰童鞋的畅谈。另外今天连续在微博上看了两个罗纳尔多和萨内蒂年轻时候的足球集锦,突然觉得这几年间,很多事情在发生着悄然的改变,比如曾经有个美少女组合叫S.H.E,昨天在地铁外看到Ella的一个活动海报,才发觉,这个团早已淡出了我们的视野。我们上大学的时候,有几个舍友特别喜欢挺S.H.E,导致我也比较熟悉他们的歌曲,后来让我对他们真正产生印象的是《Play》那张专辑,觉得它的概念蛮有意思的,整张唱片都是轻松欢快的气氛。于是《听袁惟仁弹吉他》这首歌一闪而过,于是就有了这个标题……
有点扯远了
杨东杰是前CSDN移动频道的主编,之前见过几次面,但没怎么细聊过。这次我去成都参加活动,他也刚好跑到成都出差了,我们就这样不期而遇。估计后面的东西我会写得有点像采访稿,呵呵,估计杨兄采访过那么多人,被采访的经历不太多吧,另外像这种通篇没什么他的观点都是我自己的观点的采访估计也几乎没有过 😃
说起成都,第一感觉是这里的生活压力会相对小一些,其次是成都的美食、美女、美景,然后就是戏称“离方舟比较近”。杨兄对于这三点有多兴奋,去看看他的微博就能感受到了。
然后是成都的天府软件园,我想说这个软件园足以构成一个独立的城市了。好大一块地,而且是全新的,和成都的其它区域有着明显的界限。关于成都的IT行业有两个特点:一个是很多大的互联网公司都已经开始在这里“圈地”了;另一个是有很多小的创业公司享受到了当地政府的扶持和优惠政策,拥有优越的创业环境。所以总体上感觉,成都的软件业还处在一个比较基础的阶段,未来的潜力和发展空间都是巨大的。
我想说杨兄在CSDN的视野非常开阔,也有很多独到的见解。这次交谈,我们聊到了很多互联网的话题,诸如国内微博运营、开放平台、移动应用趋势等,受益良多。其中一个话题印象深刻,是和起点中文网有关的,杨兄反复称赞这个网站不得了,通过起点中的文学作品,可以看到很多现状和未来,很多被认为是“高端”的人士,其实都在默默关注起点上的作品。所以这绝对是一个被低估的网站,而盛大也是一个被外界低估的企业。另外,说到这里,确实喜欢看书的中国人太少了,我自己也应该多看点书。
这里还想插播一则别的事情(越来越不像采访了 - -),这次在成都还有幸造访了中国电信的基地,他们的天翼空间也正在对HTML5跃跃欲试,跟这边的朋友也聊了很多,发现他们虽然主营业户是电信,但对HTML5非常感兴趣,也非常了解互联网这个圈子。现在真是越来越多的人,越来越多的领域加入到了移动互联网和HTML5当中来了,未来的互联网会更丰富、更精彩、更多元化。
还聊到个蛮有趣的话题,就是如何提高英语水平。刚好杨兄经常在WebAppTrend翻译英文的技术文章,我也刚刚出国开了一个W3C的会,都有各自的心得和困惑,聊到很多有趣的经历,不亦乐乎。还有就是如何看待创业这件事,杨兄说,在今天的中国,你去创业,是没有“失败”的——即使公司失败了,对个人而言,一定有很多收获,绝对不算失败。我确实觉得创业这种事不是一般人能做得来的,因为有太多事情要照顾到,相信这样的经历一定会让人快速的成长。所以,他也非常看好成都这个地方的创业者们,看好成都的发展。
再就是些生活习惯和生活态度的话题,最近“屌丝”这个词非常火,他也说了说自己的屌丝心态之痛,叛逆和对主流的质疑都不足矣导致不好的结果,但关键是要让事情带入一个良性的循环,走向更宽阔的道路。不然这个代价在时间的面前是非常惨痛和巨大的。这是一种“剪刀差”式的变化。
差不多就是这些了。最后,国王杯比赛快结束了,巴萨遥遥领先,比较无聊。略显无厘头的日志也要结束了。其实关于我们聊天的内容,这里都先点到为止,就不细说了。希望杨兄在未来有更好的发展。希望成都有更好的发展。这也算是我对这次的成都之行有个回顾吧。
完毕
]]>如何学习XXX?是一个在我周围经常听到和谈论的话题。
比如,如何学习JavaScript,如何学习HTML5,如何学习移动开发。我发现,在这个知识爆炸的年代,人们汲取知识的方式确实非常多元化,有人选择看书,有人选择上课,有人选择实践,有人选择逛微博,有人喜欢订阅RSS,有人喜欢跟牛人交流。。。
从形式上看,他们都各有优劣,我也确实觉得因人而异,因需求而定。同时,学习的内容非常关键,学习的态度也非常关键。
首先,我觉得最正统的学习,自然是边看书,边实践,理论与实际相结合的。书,最好是比较系统的,优质的,经得起推敲和实践考验的;实践,最好是简明的,直观的,循序渐进的。
但这样的学习,要想保证效果,势必需要一些整块的时间和精力,需要耐心和专注:这似乎在大学毕业之后,就比较难了。
我们在高节奏的IT生活中,似乎很难沉住气,于是我们见到了更多“入门级”或“速成”的学习内容,不求深度和广度,但求迅速上手,迈出第一步。再往后,或许生活节奏更快了,人们压力也更大,似乎“速成”都不够快了,人们把之前一本书的东西,浓缩成了一篇文章。。。然后,抽象成“十大要点”。。。最后,变成了一条微博。。。
请允许我以这样的方式归纳如今主流的学习方法吧。尽管并不完备。
我记得自己在Web标准化交流会,被裕波第一次建议分享的技术话题,就是《前端工程师如何学习JavaScript》,那会儿我的想法是,要因人而异,选择不同的内容和方式,大块的新东西,要“啃骨头”,沉住气,要不得半点马虎;已经有一定基础的情况下,可以选择“吃零食”,日积月累,汇流成河。今天看来,我依然是这么想的,但如果方法不对,会很痛苦,或事倍功半,或无法坚持。
而学习的另一个要素,则是内容。有的时候,跟同行朋友交流时,会遇到这类的讨论:我记得好像有人说过这样是不行的(不好的)、XXX(某名人或某本书)说这样是最好的方案。可有的时候,这些观点并不绝对,它可能是片面的,或是过时的,甚至是复述的人记错了理解错了,甚至它本来就是错的。当这样的讨论频繁出现时,我觉得出了问题:首先,他们在学习知识的时候,没有足够的基础,导致无法辨别上层知识的真伪;二来,对知识的学习缺乏深究的精神,知其然不知其所以然;第三,没有找到真正的起点。
导致这些问题的因素可能是多方面的,比如很多基础的材料是英文的,而语言障碍多多少少客观存在;比如我们的视野总是有限的,比如周围的人都是“那样”学来的。可真正的精髓,就这样被错过了,实在可惜。
作为比较普世的道理,我觉得有几件事值得我们参考:
首先,要追根溯源,我想得到的途径有三种,看官方文档,读源码,拜读技术发明人或创始人的书籍,这些都是原汁原味的,不经其他人转述,没有信息偏移和衰减的(说道这里,大家应该见人玩过那种“传声筒”的游戏吧,一句话经过五个人传到最后,变成了完全不同的另一个意思,就是这个信息偏移和信息衰减的道理)。
然后,开放自己的心态,多多参与更广泛交流,避免被狭隘和片面的观点所误导,关于这一点,国内有好多线上线下的技术交流活动,如果大家有心参与,应该足够了(不过,虽然不太严重,但值得指出的是,国内和国际的技术认知还是会有些许偏差,必要的情况下,要走出国门,做跨国交流)。
所以,还有第三点,英文太TM重要了!而且是基础中的基础!
有了基础,了解了本源,开阔了视野,然后再不断积累零散琐碎的知识,此乃成才成功之道。我们能有多大作为,一个重要方面,取决于我们做了多少准备,下了多少功夫。
再说得远一点,技术立本的公司的发展和生存,也需要紧跟趋势,深刻理解行业标准和规范,才有可能真正做到技术领先,甚至技术超前,引领行业趋势和行业标准。
以此作为2年前“如何学习JavaScript”话题的延伸和二次思考。
]]>这从没有花太多时间在编码上。我直接引入了三方的问卷调查工具:Wufoo。等于嵌入了一个iframe。我会定期更换这里的投票内容,有些投票可能是为了我更了解大家,更好的优化我的网站为大家服务的,我会根据大家的反馈默默做出改变;而方便公开的统计数据和分析结果,我也会公布在这里跟大家分享。
第一期的投票内容比较“肤浅” 囧
在添加投票功能的时候,我注意到Typecho的一个不足,就是在创建新的侧边栏面板的时候,不免需要修改主题模板的sidebar.php文件,因为Typecho本身并没有给侧边栏预留插件接口,所以不可以生成自定义的侧边栏面板。我觉得改进的方式可以是这样的:
这样主题模板和插件就进一步解耦,开发者就可以在不改动主题模板的情况下调整侧边栏的内容了。
一点个人心得,不知Typecho是否愿意接纳这个意见。
以上
]]>在这个过程中,我也尝试了更多CSS3的变换效果,让幻灯演示多一点精彩,所以最终我为每个幻灯演示分别使用了不同的幻灯片切换动画。有3d的、有2d的,也有WP7中的Metro风格动画等等。
另外说到幻灯演示,我正在发起一个这方面的开源项目(估计经常关注我的朋友都已经听腻了),本月之内会启动。
接下来我想给网站做一个投票系统,放在侧边栏。
]]>先说友情链接插件的事情吧,我在Typecho的插件站找到了一款友情链接的插件,名字就叫Links,非常方便实用。我现在随便放了3个链接,看看样子。大家希望跟我交换链接的,可暂时留言至此,回头我会另外做个交换链接的页面。
然后是侧边栏,我把最近文章和最近评论两个侧边栏的widget加入了名为large
的css class。这类widget会在条件允许的情况下占用更大的空间。一般情况下宽度是普通widget的两倍。在一些特殊的界面宽度下,widget的宽度是一样的,大家没有什么分别。
对于css3 media queries的利用,我这里按窗口宽度分了5档1400px+/1050px+/650px+/400px+/400px-,进行响应式设计。每一档的内容宽度、侧边栏宽度和布局都不太一样。以适应不同的终端。举其中一个例子(1050px~1400px之间):
@media all and (min-width: 1050px) and (max-width: 1400px) {
#wrapper {
position: relative;
padding-right: 280px;
}
#sidebar {
position: absolute;
width: 260px;
top: 160px;
right: 0;
}
#sidebar .widget,
#sidebar .large {
width: 98%;
}
}
最终效果如图:
这款皮肤我会稍后更新共享在这里。
另外我最近抽空研究过了SAE Storage,接下来的事情是做一些有趣的侧边栏小控件出来,比如投票、相册、之类的。
先记下这么多
]]>首先我们当然需要有一个百度统计的账号,然后通过这个账号生成我们要统计的统计代码(如下图):
其中有一串随机码,我们把这串随机码记下来。值得注意的是,随机码前面有一个转意后的问号(%3F
),不要把这几个字符算入随机码。
然后在插件设置中填入这串随机码(如下图),即可正常工作。
有需要的童鞋可以移步到这里下载
]]>我想说的是,作为一个长期游离于非核心业务和技术的网站开发者,能够在一家浏览器公司得到这个奖,无疑是对我工作莫大的认可。
首先,我要感谢公司的CEO——Jeff的赏识,为我提供了一份长期稳定的工作合同;
我还要感谢我刚来傲游时的Web组组长Richard和师兄乐丁,是你们带我走进了傲游的世界,也让我学到了很多知识;
我还要感谢我的前同事大黄,他让我了解了很多技术之外的领域、懂得很多做人的道理;
我还要感谢我的前同事宇捷,感谢你在我经济最困难的时候借钱给我;也要感谢另几位前同事:文燕、阿笨和Fish,在我初来北京无家可归的时候帮我找到了住处;
感谢SiC和Jay的栽培,你们也是指导我工作最多的人;
感谢崔凯的义气相挺,为我分担问题,排忧解难,实在一言难尽;
感谢部门的兄弟姐妹们一路陪伴,这个奖不仅是属于我的,更是属于你们每一个人的!
最后要感谢的是我的家人,虽然你们嘴上总是不待见我的这份工作,但还是一直默默支持着我,让我特别感动!
希望今天的得奖没有让大家失望。
更美好的明天仍在前方,让我们携起手来,继续前行!
(完)
相信这个小家伙会帮我更加了解我的网站的读者。
虽然就是嵌入了一段脚本,不过幕后的工作不止于此。我在此做个备忘:
/var/Helper.php
中。也弄清楚了Typecho中的Widget机制和命名规律。这让我决定通过插件的方式加入百度统计。footer.php
中直接加入百度统计,还有一层原因,就是为了保持主题的纯净,让主题可以被其他人也使用同时不被百度统计在内。接下来的计划是把这个插件分享出来,然后重新梳理并设计一下网站的路由规则。
以上
]]>如图的效果。标签有背景色,且左侧有一个三角形,三角形中间有个白色的圆圈。
你一定在想这个效果是背景图切出来的吧——答案是没有用到任何图片
那你会不会在想这个效果的html结构很复杂呢——答案是最简单的html结构
<p>
<a href="#">Tag1</a>
<a href="#">Tag2</a>
<a href="#">Tag3</a>
<a href="#">Tag4</a>
<a href="#">Tag5</a>
</p>
之所以可以达到这样的效果,是因为我们运用了一些比较巧妙的技术。接下来告诉你实现方式:
]]>如图的效果。标签有背景色,且左侧有一个三角形,三角形中间有个白色的圆圈。
你一定在想这个效果是背景图切出来的吧——答案是没有用到任何图片
那你会不会在想这个效果的html结构很复杂呢——答案是最简单的html结构
<p>
<a href="#">Tag1</a>
<a href="#">Tag2</a>
<a href="#">Tag3</a>
<a href="#">Tag4</a>
<a href="#">Tag5</a>
</p>
之所以可以达到这样的效果,是因为我们运用了一些比较巧妙的技术。接下来告诉你实现方式:
我们通过a:before
和a:after
这两个伪元素,通过绝对定位的方式,为<a>
标签做了扩展:首先把一个伪元素a:before
当做最左侧的三角形,然后再把另外一个伪元素a:after
作为中间的小圆点显示到界面中。
a {
display: inline-block;
position: relative;
}
a:before,
a:after {
position: absulote;
content: " ";
}
三角形的实现方式略带技巧性,其实就是把宽高都设为0,边框宽度设为文本高度的一半。然后将其右边框上色border-right-color
,其余三面边框颜色全部设为透明tranaparent
,就可以了。当然,在设定边框宽度之前,我们需要确定文本的高度,这里有一个非常合适的单位:em
。我们将链接的行高设置为1.5em
,然后将伪元素的边框设置为0.75em
即可。
a {
background: #ccc;
color: green;
line-height: 1.5;
}
a:before {
border: transparent 0.75em solid;
border-right-color: #ccc;
top: 0;
left: -1.5em;
height: 0;
width: 0;
}
这个小圆点同样需要用css3实现,相比之下,它的实现略简单,设置背景为白色、宽高均为0.5em
、上下边距均为0.5em
、圆角半径是0.25em
的矩形。这需要合理的坐标计算和尺寸计算。我们同样选择了通过em
这个单位来计算。
a:after {
background: white;
width: 0.5em;
height: 0.5em;
top: 0.5em;
left: -0.125em;
border-radius: 0.25em;
}
把这些内容凑在一起,会发现横向的距离会有些不合适,那我们再做一点微调:
a {
padding: 0px 10px;
margin-left: 1em;
}
这样看起来样子比较协调了。
最后,加入:hover
效果:
a:hover {
background: gray;
color: white;
}
a:hover:before {
border-right-color: gray;
}
这样,就大功告成了!
demo: https://jiongks.name/demos/css3-tag/
下面是三张截图,一个是工具栏,一个是预览区域,一个是语法提示区域
有需要的童鞋可以移步到这里下载,但__特别注意__:一定要放在/usr/plugins/Markdown
文件夹下,其它命名可能会导致500错误(感谢大家的反馈,我之前没有注意到这个细节)。
没错,就是前面提到过的字很大的皮肤,我给这款皮肤起了个非常土的名字叫:“我的字很大”
有几点说明:
style.css
文件里去掉#search {display: none;}
那一段就好有需要的童鞋可以移步到这里下载
]]>另外诸位对哪些html5新特性有科普需求的,不妨留言在下面,我会尽力而为 😃
目前这些内容的最近更新时间是2012年3月
]]>另外诸位对哪些html5新特性有科普需求的,不妨留言在下面,我会尽力而为 😃
目前这些内容的最近更新时间是2012年3月
XML Http Request Level 2
WebSocket
Server-Sent Event
Cross-Document Messaging
Channel Messaging
最后,上述内容还有一些是通过demo和ppt分享给大家的,它们会被汇总出现在all-demos和all-slides这两个页面中。
]]>随着网页时效性需求的增强和其所涉及范围的扩大,各种消息通信的运用越来越多。在html5中,有很一部分消息通信的新规范,旨在让这些工作更加方便实用。接下来会为大家介绍4个比较主要的通信方式:
* WebSocket
* Server-Sent Event
* Cross-Document Messaging
* Channel Messaging
WebSocket
这个技术已经被越来越多的人注意到了。长连接一直以来一直是web的一个盲区,而WebSocket让网页通过几个简单的js接口,就可以和服务器建立长连接;另外一个好处,也是长连接随之带来的,就是省掉了每次传统请求中的HTTP头、cookies等信息的冗余传输。
WebSocket的新知识分两部分:一部分是浏览器端的js接口定义,方便前端开发调用;另一部分是WebSocket协议,对浏览器相关js接口的底层实现和服务器端的配合提出了明确的要求,这部分主要是服务器开发和浏览器开发需要比较关注,对前端开发者影响不大。
WebSocket的大致js接口设计如下:
socket = new WebSocket(url[, protocols]);
socket.url;
socket.readyState; // CONNECTING|OPEN|CLOSING|CLOSED
socket.send(...); // string|arraybuffer|blob
socket.onmessage = function (e) {
e.data;
};
socket.onopen = function (e) {...};
socket.onerror = function (e) {...};
socket.onclose = function (e) {...};
var source = new EventSource('./sse.php');然后服务器端返回特定的格式:
source.addEventListener('message', function (e) {e.data;...}, false);
source.addEventListener('add', function (e) {e.data;...}, false);
source.addEventListener('customEventType', function (e) {e.data;...}, false);
echo "event:customEventType"; // 确定事件类型就可以配合工作了。
echo "data:content string here..."; // 返回数据内容
echo "retry:5000" // 5秒之后请求第二次;
// on top值得注意的是,这项技术可以解决跨域问题——当然,有个限制,就是被请求的页面通过head信息明确授权跨域访问。
iframe.postMessage('hello');
// on iframe
window.onmessage = function (e) {e.data;...}
var channel = new MessageChannel();Channel Messaging给了我们很大的想象空间,做一些有趣的技术实现。
var port1 = channel.port1;
var port2 = channel.port2;
port1.onmessage = function (e) {e.data;...};
port2.postMessage('Hello Port1!');
首先,选个现成的程序。这个已经选好了,就是Typecho了。
然后,给这个程序做个皮肤,经过了前后设计三个版本之后,我觉得现在这个看着靠谱一些。为了避免以后换了别的皮肤。在此截图留念
然后,把我之前分享过的一些demo、ppt等内容汇聚过来。现在基本都照原样拿过来了。不过我觉得还不够,因为之前的幻灯片都是针对webkit设计和开发的,另外url规则还是不够简单明了。回头要做的事情是:把url规则弄得更简单、兼容更多的浏览器。
然后,了解一些SEO和网站统计的知识,在这里做点尝试,也让自己更认识我的读者。(说实话我在启动这个网站的时候,并不太清楚我的读者是哪些人,喜欢什么,突然觉得这个重要)
然后,我希望尝试着放一些照片的展示。其实现在即将是视频时代了,拍照上传微博这种事情都快out了。不过我还是想跟风一把,试试看,放一些自己的想法在里面。
然后,我还希望尝试着做一些后台开发的学习和分享。这也是我转向sae的原因。最开始写博客的时候,我只是奢望一个能够直接写html代码的博客,给文章里做点样式和特效;到后来,我找到了github,通过gh-pages放静态页面;现在,我想通过sae写写php神马的。
以上是我近期的计划
]]>在越来越多的W3C规范中见到TypedArrays和arrayBuffer这两个名词了,由于实际开发中还用不到,每次读到,就跳过去了。
当然这是不对滴……
今天,我们就来讨论一下这个JavaScript typed arrays
简介
首先,这个东东可以看做是对字符串格式的扩展,也可以看做是比字符串更好的描述二进制内容的数据类型。我们都知道,平时用记事本打开一个二进制文件,往往会看到一些稀奇古怪的字符、间歇的空格空行等等。这是因为二进制文件中每一个“字符”单位的数据都是无意义的,这不想文本文件那样,每个字符单位的数据可以独立查看和理解。所以,我们在处理二进制数据的时候,用字符串是很困难的,能用到的恐怕就只有类似
for (var i = 0; i < str.length; i++) {
console.log(str.charCodeAt(i));
}
的操作,才有可能让我们进行二进制级别的数据操作。
我们今天要介绍的TypedArray比这样处理方便得多。
原理
TypedArray的大概原理就是以charCode数组的形式,把一个二进制文件进行分解和体现。而数字进制有8、16、32、64这么几种。
接口介绍
这里一共有2个独立的接口和1个系列的接口,独立的接口就是ArrayBuffer和ArrayBufferView,系列接口就是TypedArray系列,包括下面几种接口:
const BYTES_PER_ELEMENT // 取决于具体的单位字节数
readonly length; // 长度
getter get(index); // 数组取值
setter set(index, value); // 数组赋值
void set(array[, offset]); // 设置数据
TypedArray subarray(begin[, end]); // 截取其中一部分
var a = new XMLHttpRequest;这样a.response就会得到一个ArrayBuffer对象,对象的数据内容正代表了站点图标的二进制数据。然后我们继续运行
a.open('GET', 'http://www.baidu.com/favicon.ico');
a.responseType = 'arraybuffer';
a.send();
var b = new Int16Array(a.response);这样就得到了一份16进制格式的数据。我们将其在命令行输出,这时就会看到:
Int16Array
0: 0
1: 1
2: 1
3: 4112
4: 16
5: 1
6: 4
7: 296
8: 0
9: 22
10: 0
11: 40
12: 0
13: 16
14: 0
15: 32
16: 0
17: 1
18: 4
19: 0
20: 0
21: 128
22: 0
23: 0
24: 0
25: 0
26: 0
27: 0
28: 0
29: 0
30: 0
31: 0
32: 0
33: 0
34: 128
35: -32768
36: 0
37: -32768
38: 128
39: 128
40: 0
41: 128
42: 128
43: -32640
44: 0
45: -32640
46: 128
47: -16192
48: 192
49: 0
50: 255
51: -256
52: 0
53: -256
54: 255
55: 255
56: 0
57: 255
58: 255
59: -1
60: 0
61: -1
62: 255
63: -13108
64: -13108
65: -13108
66: -13108
67: -49
68: -1
69: -1
70: -769
71: -1841
72: -14388
73: -13188
74: -881
75: -817
76: -13108
77: -13108
78: -817
79: -817
80: -13108
81: -13108
82: -817
83: -1841
84: -13108
85: -13108
86: -881
87: -49
88: -13172
89: -14132
90: -769
91: -29489
92: -13176
93: -30516
94: -824
95: -13105
96: -29489
97: -824
98: -820
99: -13105
100: -49
101: -1793
102: -824
103: -29489
104: -14200
105: -14088
106: -769
107: -49
108: -13060
109: -13060
110: -769
111: -49
112: -13060
113: -13060
114: -769
115: -49
116: -14088
117: -28673
118: -769
119: -49
120: -1
121: -1
122: -769
123: -13108
124: -13108
125: -13108
126: -13108
127: 0
128: 0
129: 0
130: 0
131: 0
132: 0
133: 0
134: 0
135: 0
136: 0
137: 0
138: 0
139: 0
140: 0
141: 0
142: 0
143: 0
144: 0
145: 0
146: 0
147: 0
148: 0
149: 0
150: 0
151: 0
152: 0
153: 0
154: 0
155: 0
156: 0
157: 0
158: 0
buffer: ArrayBuffer
byteLength: 318
byteOffset: 0
length: 159
proto: Int16Array
╔囧╗╔囧╝╚囧╝╚囧╗
╔囧╗╔囧╝╚囧╝╚囧╗
╔囧╗╔囧╝╚囧╝╚囧╗
╔囧╗╔囧╝╚囧╝╚囧╗
]]>这个话题要从北京和山西之间的CBA半决赛大战开始。昨天山西主场赢了北京,把比赛拖入了第五场大决战,结果赛后发生了骚乱,两地球迷在网上也开始了骂站。说来也好笑,作为一个身在北京的山西人,我更关注山西队多一些,在网上参与讨论的身份却是“北京海淀网友”。你们想象得到,我在现如今,地域攻击如此严重的互联网,上去发表一下观点,多半会被自己的老乡误会,反之也一样。索性还是不参与了。后来又想买第五场大决战的球票去现场看看,不料网上开票7分钟之后就宣告售罄,自己没订到。此事也就作罢了。
都说文明看球,北京球迷指责地方球迷不能文明看球,结果北京球迷自己看球也不文明,我每次去工体看国安比赛,都能听到球迷“傻逼~傻逼~”,电视上也能听到,没完没了的。再加上山东球迷、广东球迷、上海球迷之间长期结下的“梁子”,结果大家就都扭打在了一起。
球场是个什么样子的地方呢?我在西安上大学的时候,就爱看陕西国力的足球比赛,想当年国力西北狼的威风可不是盖的。有一次做出租车,刚好司机师傅也是国力的球迷,师傅戏言,西安就是闲人多,懒汉多,爱凑热闹,所以球市特别好,只要有比赛,甭管足球还是篮球,甭管中国队还是外国队,老少爷们都一块儿上阵了。
其实这个世界上的任何地方都是如此。球赛,就是个热闹,有直播信号的热闹。球场内外发生的事情,包括常规的竞技,还有谩骂、争吵、不守秩序、扔水瓶、推搡、斗殴等等,都不是球赛这种环境所特有的,而是一个城市社会生活的写照;所谓的球场问题,其实是社会问题;所谓的球场文明,其实是社会文明。
举个反例,我甚至不敢想象,自己周围的这群球友,前一天见面还跟你一副大老粗的模样;结果第二天进了球场,就摇身一变,整得跟利物浦Kop球迷一样,脸上涂了各种颜色,带个高高的礼帽,动作整齐划一,随便一个人起个头,就跟着一起唱《You will never walk alone》?这比他冲客队球员骂脏话更震惊吧。所以,文明看球是个荒谬的提法,它似乎想把问题归咎于看球,其实你就是个凑热闹的。人在球场里的表现,都是平时生活中的习惯,看到周围人都叠纸飞机往下飞,你也就忍不住飞了。另外我没觉得在网上对骂的那些人比现场的球迷文明到哪里去了。
还有人讽刺现场的DJ“极端的”煽动情绪活跃气氛,这也是个很有趣的问题。任何群体活动都是有民意做潜在支撑的,不管是理性的还是极端的。DJ虽然是个人行为,但他喊的口号若没人搭理,自然会换别的花样或被其他DJ换掉,所以现在的DJ正是渐渐和球迷默契平衡到一起的结果。
所以我不认为他极端:这句话您第一次看的时候,请把重音放在“极端”上;第二次再看的时候,请把重音放在“他”上;如果再多看几次,那就请把重音放在“我”上。
我们不是从小就在利物浦长大的人,我们有自己凑热闹的习惯,有自己的表达方式,有自己的社会价值观。所以我们不能归咎于看球,也不能归咎于文明。这些文明的形成是自然而然的,只有比较,没有对错;或者说白了,文明是被一些社会因素和历史因素所形成的,对错不应该在老百姓这一边做评判。如果我们希望看到那个所谓的“文明”,不妨去尝试解决那些社会问题和历史遗留问题,而不要假装自己是从小在利物浦长大的球迷。
这里是介绍HTML5文件处理的第二部分,之前已经介绍过了基础的FileAPI,接下来是如何通过JS创造文件的部分。我们称之为FileWriterAPI。
需要提前解释的是,FileWriterAPI不是一个可以“独立”存在的规范,它强烈的依赖于FileAPI和即将介绍的FileSystemAPI。FileAPI是最基础的接口这毋庸置疑,而之所以先于FileSystemAPI介绍FileWriterAPI,是因为FileSystemAPI是一套更庞大的接口设计集合,FileWriterAPI相比之下相对简单,可以算作是FileSystemAPI也用得到的一部分基础,提前介绍给大家。
总览
这部分一共有三大接口:
* BlobBuilder
接口:创建Blob
* FileSaver
接口:提供一些列方法和事件监听方式,代表一个保存文件的过程
* FileWriter
接口:是从FileSaver
扩展来的,提供更丰富的输出选择
需要注意的是,FileSaver
和FileWriter
是不能通过接口指定要保存或要写入的文件的,它们都是对象创建时就已经确定的并且不可更改。同时FileSaver
更是不提供控制写入什么内容的接口,要写入的内容也是对象创建时就已经确定的并且不可更改的;而FileWriter
可以通过接口控制要写入的内容。
接口描述
同样的,这3个接口其实并不复杂,也很好理解(同样的,接口中的“#Foo
”表示任意Foo
类型的对象):
BlobBuilder
接口
#BlobBuilder.getBlob([contentType]) // 返回目前已放入的所有Blob数据构成的对象
#BlobBuilder.append(text[, contentType]) // 放入文本数据
#BlobBuilder.append(data) // 放入数据(Blob或ArrayBugger)
FileSaver
接口#FileSaver.abort() // 中断保存操作
#FileSaver.readyState // 保存工作的状态(DONE、INIT、WRITING)
#FileSaver.error // 最后一次出错的错误信息
#FileSaver.onwritestart // 写入操作开始时触发
#FileSaver.onwrite // 写入操作成功时触发
#FileSaver.onwriteend // 写入操作完成时触发(不管成功还是失败)
#FileSaver.onprogress // 写入操作过程中触发
#FileSaver.onabort // 写入操作被中断时触发
#FileSaver.onerror // 写入操作失败时触发
FileWriter
接口#FileWriter.position // 当前写入操作所处的位置
#FileWriter.length // 文件长度(或在无权读取文件信息的情况下返回已写入的长度)
#FileWriter.write(blob) // 在position处写入blob数据
#FileWriter.seek(offset) // 设置position属性为offset
#FileWriter.truncate(size) // 在size处截断文件
#FileWriter.abort() // 继承自FileSaver
#FileWriter.readyState // 继承自FileSaver
#FileWriter.error // 继承自FileSaver
#FileWriter.onwritestart // 继承自FileSaver
#FileWriter.onwrite // 继承自FileSaver
#FileWriter.onwriteend // 继承自FileSaver
#FileWriter.onprogress // 继承自FileSaver
#FileWriter.onabort // 继承自FileSaver
#FileWriter.onerror // 继承自FileSaver
Blob
对象var blobBuilder = new BlobBuilder(); // 创建BlobBuilder对象
blobBuilder.append("我今天只说三句话;"); // 连续放入文本
blobBuilder.append("包括这一句;");
blobBuilder.append("我的话完了。");
var url = window.URL.createObjectURL(blobBuilder.getBlob()); // 返回Blob对象并以此创建URL
window.open(url); // 通过URL打开这个Blob对象
FileWriter
fileEntry.createWriter(function (fileWriter) {
fileWriter.write(blobBuilder.getBlob()); // 返回Blob对象并通过fileWriter写入
fileWriter.onwriteend = function () {...}; // 绑定写入操作完成后的事件
});
BlobBuilder
和window.URL
依然存在着不同的前缀,比如webkit下的接口分别为WebKitBlobBuilder
和webkitURL
;fileEntry
和陌生的方法createWriter
,这正是接下来要介绍的一套比较复杂的API:FileSystemAPI的一部分内容。想弄清楚fileWriter
具体运行的环境,还得继续学习,不过抛开外部环境的形成过程,fileWriter
的用法应该可以体会到了;FileSaver
还没有用得到的地方,毕竟标准还没有最终形成,也许他将来某一天,借助其它规范的优势,摇身一变,就用在“另存为对话框”之类的地方了。在众多HTML5规范中,有一部分规范是跟文件处理有关的,在早期的浏览器技术中,处理小量字符串是js最擅长的处理之一。但文件处理,尤其是二进制文件处理,一直是个空白。在一些情况下,我们不得不通过Flash/ActiveX/NP插件或云端的服务器处理较为复杂或底层的数据。今天,HTML5的一系列新规范正在致力于让浏览器具备更强大的文件处理能力。
今天要介绍的FileAPI,就是为解决这类问题而生的。
总览
FileAPI是一些列文件处理规范的基础,包含最基础的文件操作的JavaScript接口设计。其中最主要的接口定义一共有4个:
* FileList
接口: 可以用来代表一组文件的JS对象,比如用户通过input[type="file"]
元素选中的本地文件列表
* Blob
接口: 用来代表一段二进制数据,并且允许我们通过JS对其数据以字节为单位进行“切割”
* File
接口: 用来代步一个文件,是从Blob
接口继承而来的,并在此基础上增加了诸如文件名、MIME类型之类的特性
* FileReader
接口: 提供读取文件的方法和事件
这里有两点细节需要注意:
1. 我们平时使用input[type="file"]
元素都是选中单个文件,其本身是允许同时选中多个文件的,所以会用到FileList
2. Blob
接口和File
接口可以返回数据的字节数等信息,也可以“切割”,但无法获取真正的内容,这也正是FileReader
存在的意义,而文件大小不一时,读取文件可能存在明显的时间花费,所以我们用异步的方式,通过触发另外的事件来返回读取到的文件内容
接口描述
这4个接口其实并不复杂,也很好理解(接口中的“#Foo
”表示任意Foo
类型的对象):
FileList
接口
#FileList[index] // 得到第index个文件
Blob
接口#Blob.size // 只读特性,数据的字节数
#Blob.type // 只读属性,数据的MIME类型
#Blob.slice(start, end) // 将当前文件切割并将结果返回
File
接口#File.size // 继承自Blob,意义同上
#File.slice(start, length) // 继承自Blob,意义同上
#File.name // 只读属性,文件名
#File.type // 只读属性,文件的MIME类型
#File.lastModifiedDate // 只读属性,文件的上次修改日期
FileReader
方法#FileReader.readAsArrayBuffer(blob/file) // 以ArrayBuffer格式读取文件内容
#FileReader.readAsBinaryString(blob/file) // 以二进制格式读取文件内容(该方法已不推荐使用,感谢 @超人与酱油瓶 指正)
#FileReader.readAsText(file, [encoding]) // 以文本(及字符串)格式读取文件内容,并且可以强制选择文件编码
#FileReader.readAsDataURL(file) // 以DataURL格式读取文件内容
#FileReader.abort() // 终止读取操作
FileReader
事件#FileReader.onloadstart // 读取操作开始时触发
#FileReader.onload // 读取操作成功时触发
#FileReader.onloadend // 读取操作完成时触发(不论成功还是失败)
#FileReader.onprogress // 读取操作过程中触发
#FileReader.onabort // 读取操作被中断时触发
#FileReader.onerror // 读取操作失败时触发
FileReader
属性#FileReader.result // 读取的结果(二进制、文本或DataURL格式)
#FileReader.readyState // 读取操作的状态(EMPTY、LOADING、DONE)
var input = document.querySelector('input[type="file"]'); // 找到第一个file控件
var firstFile = input.files[0]; // file控件的files特性其实就是一个FileList类型的对象
var secondFile = input.files[1]; // 当file控件的multiple特性为true时,我们可以同时选择多个文件,通过input.files[n]可以按序访问这些文件
var reader = new FileReader(); // 新建一个FileReader类型的对象
reader.readAsText(secondFile); // 按文本格式读取file控件中的第二个文件
reader.onloadend = function (e) { // 绑定读取操作完成的事件
console.log(reader.result); // 取得读取结果并输出
};
var size = file.size; // 先取得文件总字节数
var result = file.slice(3, size - 3); // 用slice方法去掉开头的3个字节
#Blob.slice
在webkit内核中加入了前缀,即#Blob.webkitSlice
;Blob URI
--------Blob
对象提供URI访问的方式和可能,内存中的Blob
对象无法很方便的传递给其它页面,于是我们设计了Blob URI
,使得这个Blob
对象可以在同源的任何网页中,通过向这个URI
发送Ajax请求等方式进行访问。window.URL.createObjectURL(blob[, options])前者可以为
window.URL.revokeObjectURL(url)
blob
创建一个URI,后者可以取消url
对应的blob关联。这里面还有一个options值得注意,目前options
中只有oneTimeOnly
特性有效,表示该url第一次被调用后自动revoke。File
继承自Blob
,所以我们也可以将某个File
对象转化成URI
供同源网页访问和使用。var url = window.URL.createObjectURL(imageFile);
...
img.src = url;
...
window.URL.revokeBlobURL();
我自己在家里就是独生子,从小就知道传说中的亲兄弟亲姐妹是什么意思——无非就是比我的堂哥堂姐表弟表妹“亲”一些呗;无非就是爸爸妈妈是一样的呗;无非就是除了放暑假放寒假的其它时候也可以一起玩呗。感觉都不是很有所谓的,甚至觉得“天呐那个表妹一放假就每天缠着我这要是亲妹妹可不得把我给烦死”。至于别的事情,就一直没什么特殊感觉了。
再有就是总听学校的老师讲,你们这些独生子女,长啦短啦……(就是现在俗称的巴拉巴拉……)。恩,衣来伸手饭来张口,没错我觉得我有点儿;恩,贪婪自私,没错我也觉得我有点儿;恩,被宠坏了,没错我也觉得我有点儿……
当时的我已经在很仔细的想了,如果我是上述这样的人,那该多恶心呢!我该多恨我自己呢!所以一直处处小心:有困难尽量不跟家里说,自己想办法克服;没钱花尽量不跟家里要,自己想办法省或借或周转;心情好的时候尽量让着别人(我现在觉得是我心情好的时候才会这么想),觉得自己没那么糟,人家也不容易之类的。
可现在这么长时间过去了,在这么多的环境里跟这么多人打过交道之后,令我最无助的是,每每想起一些过去做过的事情,真的觉得恶心,真的恨我自己。
我并没有打算归咎我的性格缺陷于独生子这件事,但我这段时间突然觉得我对那种有亲哥哥亲妹妹的生活的理解是很肤浅的,也找到了我的一些问题所在。
最近刚好看到一个台湾的谈话节目,是讲一个合唱团体里好姐妹的故事,在节目中无意中听到一句话:
因为我自己是独生女啊,所以一开始很不习惯集体生活啊,什么事情都要等啊,就很不耐烦啊。
这篇文章会接着介绍IndexedDB(以下简称IDB)。我们会介绍如何解决在webkit内核下、新旧版本规范的兼容问题。
目前支持IDB的webkit内核浏览器有chrome和傲游3,safari暂不支持IDB。
首先,由于内核不同,所以window.indexedDB
被改为了带有webkit
前缀的变量window.webkitIndexedDB。同时发生变化的还有两个对象IDBKeyRange
和IDBTransaction
。如果想兼容gecko和webkit内核,那么可以在程序的开头加入如下代码:
if ('webkitIndexedDB' in window) {
window.indexedDB = webkitIndexedDB;
window.IDBKeyRange = webkitIDBKeyRange;
window.IDBTransaction = window.webkitIDBTransaction;
}
else if ('mozIndexedDB' in window) {
window.indexedDB = mozIndexedDB;
}
var req = window.indexedDB.open(dbName); // 旧版在这里不需要写明dbVersion
req.onsuccess = function (e) {
var db = this.result;
if (db.version != '1.0') {
var subReq = db.setVersion('1.0'); // 通过setVersion修改版本号,而不是onupgradeneeded事件
subReq.onsuccess = function (e) {
// TODO: real success code
};
}
else {
// TODO: real success code
}
};
var req = window.indexedDB.open(dbName, dbVersion); // 对于旧版而言,会忽略第二个参数,因此这里可以兼容
req.onsuccess = function (e) {
var db = this.result;
if (db.version != dbVersion) { // 新版中两者绝对一致,否则只会触发onupgradeneeded事件,因此这里也可以兼容
// TODO: code of changing object stores for new version
var subReq = db.setVersion(dbVersion);
subReq.onsuccess = function (e) {
// TODO: real success code
};
}
else {
// TODO: real success code
}
};
req.onupgradeneeded = function (e) {
// TODO: code of changing object stores for new version
};
window.webkitIndexedDB
和window.mozIndexedDB
类似,IE10中对应的变量名为window.msIndexedDB
,所以,相兼容IE,把上面第一部分的代码改为:if ('webkitIndexedDB' in window) {即可。下面的“全兼容”的例子已经用到了这段代码。
window.indexedDB = webkitIndexedDB;
window.IDBKeyRange = webkitIDBKeyRange;
window.IDBTransaction = window.webkitIDBTransaction;
}
else if ('mozIndexedDB' in window) {
window.indexedDB = mozIndexedDB;
}
else if ('msIndexedDB' in window) {
window.indexedDB = msIndexedDB;
}
今天做一个IndexedDB(以下简称IDB)的demo,运行环境是Firefox 10。
DEMO演示链接 (firefox 10+ only)
我们做一个阅读列表的页面,可以让用户把任意网址存入这个阅读列表中,并为每一个网址起一个名字,也可以随时删除,且列表可以按网址自动去重。
正如上一篇文章介绍的步骤,我们先初始化数据库,然后建表,然后把添加/删除/读取网址的事件和数据库操作绑定在一起。
首先是html代码:
<body onload="<strong>init()</strong>">
_button onclick="<strong>clickAddBtn()</strong>">Add_/button>
_ul <strong>id="list"</strong>>_/ul>
</body>
为了演示方便,我们引入jQuery作界面处理,再声明一个全局变量db
,作为数据库连接的句柄;再声明一个全局变量list
,作为网页中列表元素的jQuery句柄。
var db;
var list = $('#list');
然后定义数据库初始化的行数init
function init() {
var req = window.<strong>mozIndexedDB</strong>.open('readinglist', '1.0');
req.onsuccess = function (e) {
<strong>db = this.result;</strong>
// TODO: 连接成功后展示列表
};
req.onupgradeneeded = function (e) {
<strong>db = this.result;</strong>
// TODO: 版本不同时创建一个新的object store
};
}
这段代码的作用是初始化数据库(readinglist
)连接,并在第一次连接数据库时创建表(links
)。我们把展示列表的函数定义为showList()
,把创建表的代码也补充完整,即:
function init() {
var req = window.mozIndexedDB.open('readinglist', '1.0');
req.onsuccess = function (e) {
db = this.result;
<strong>showList();</strong>
};
req.onupgradeneeded = function (e) {
db = this.result;
<strong>db.createObjectStore('links', {keyPath: 'url'});</strong>
};
}
然后我们定义添加/删除/展示链接的函数:add(title, url)
/remove(url)
/showList()
function add(<strong>title, url</strong>) {
var <strong>link</strong> = {
title: title,
url: url
}; // 创建要存储的对象
var transaction = db.transaction('links', IDBTransaction.READ_WRITE);
var store = transaction.objectStore('links');
<strong>var req = store.put(link);</strong> // put的作用是key存在时做更新处理,不存在是做添加处理
<strong>req.onsuccess = showList;</strong> // 添加成功后重新展示列表
}
function remove(<strong>url</strong>) {
var transaction = db.transaction('links', IDBTransaction.READ_WRITE);
var store = transaction.objectStore('links');
<strong>var req = store.delete(url);</strong> // 删除此链接
<strong>req.onsuccess = showList;</strong> // 删除成功后重新展示列表
}
function showList() {
// TODO: clear element: #list
var transaction = db.transaction('links');
var store = transaction.objectStore('links');
<strong>var range = IDBKeyRange.lowerBound(0);</strong> // 创建关键字范围描述
<strong>var req = store.openCursor(range);</strong> // 创建在上述范围内遍历的游标
req.onsuccess = function (e) {
var result = this.result;
if (result) {
var link = result.value;
// TODO: append this link to element: #list
<strong>result.continue();</strong>
}
};
}
注意这里的IDBKeyRange
和store.openCursor
是用来遍历列表的,前者确定遍历的范围,后者根据前者的范围逐条触发onsuccess
事件,这里定义的遍历范围是大于0,即所有非空的url,其实所有js类型的值都是可以在一起比大小的,如果想测试比较任意两个key的大小,可以运行函数window.mozIndexedDB.cmp(any first, any second)
。
最后,我们把最后两个TODO
的部分补充完整,再把界面上的事件绑定好。编码工作就完成了。
function showList() {
<strong>list.empty();</strong>
var transaction = db.transaction('links');
var store = transaction.objectStore('links');
var range = IDBKeyRange.lowerBound(0); // 创建关键字范围描述
var req = store.openCursor(range); // 创建在上述范围内遍历的游标
req.onsuccess = function (e) {
var result = this.result;
if (result) {
var link = result.value;
<strong>appendLink(link);</strong>
result.continue();
}
};
}
function appendLink(link) {
var url = link.url;
var title = link.title;
var li = $('_li>_a href="#" target="_blank">_/a> _button>X_/button>_/li>');
li.find('a').attr('title', title).attr('href', url).text(title);
li.find('button').click(function (e) {
<strong>remove(link.url);</strong>
});
list.append(li);
}
function clickAddBtn(e) {
var title = prompt('please input the title') || '[No title]';
var url = prompt('please input the url', 'http://');
if (title && url) {
<strong>add(title, url);</strong>
}
}
接下来介绍IndexedDB(以下简称IDB)的JS接口设计
如图所示,我们按照操作过程,把IDB的接口分成三部分来介绍:
1.初始化数据库连接
2.在数据库中建表
3.在表中存取数据
初始化数据库连接
var req = window.IndexedDB.open(dbName, dbVersion);这里有两个重要的参数,
req.onsuccess = function (e) {...}
req.onupgradeneeded = function (e) {...}
req.onerror = function (e) {...}
dbName
是数据库的名称,dbVersion
是数据库的“版本”。第2个参数“版本”可能不太好理解,IDB不允许数据库中的表在同一个版本中发生变化,所以当我们创建新表或删除旧表的时候,必须使用一个不一样的版本号。他的作用在于避免重复修改数据库的表结构。默认的版本是空字符串""
,我们在使用时,可以使用"1.0"
。如果请求中的版本号和当前数据库的版本号相同,则会触发onsuccess
事件,如果版本号不同,则会触发onupgradeneeded
事件,我们在这一事件中可以对数据库的表结构进行修改,然后再触发onsuccess
事件。req.onupgradeneeded = function (e) {之前提到了,当被访问的数据库版本号需要发生改变时,
var db = req.result;
vardb.createObjectStore(storeName, optionParameters);
};
onupgradeneeded
事件会被触发,我们就从这个事件继续说起。通过req.result
我们可以得到当前的数据库对象db
。db
有一个方法createObjectStore
,是专门用来创建表的。第一个参数是表的名称,第二个参数是可选的,它决定了我们要创建的表是内联关键字还是外部关键字,关键字是否需要自动生成,代表这两个设置的字段分别是keyPath
和autoIncrement
。比如,当第二个参数是{keyPath: 'profile.id', autoIncrement: false}时,说明这个表采用内联关键字,且
keyPath
是profile.id
,同时关键字不是自增的,需要每次插入数据时手动设定;当第二个参数是{autoIncrement: true}时,说明这个表采用外部关键字,并且关键字是自增的。
onupgradeneeded
事件中删除一个表,方法是db.deleteObjectStore(storeName)
。道理很简单,就不展开论述了。req.onsuccess = function (e) {对表中数据的存取通常是在onsuccess事件之后进行的。同样的,我们可以通过req.result获取数据库对象db,并随时通过db进行各种存取数据的操作。
var db = req.result;
var transaction = db.transaction(storeNames, mode);
var store = transaction.objectStore(storeName);
var subReq = store.add(value, key);
// var subReq = store.get(key);
// var subReq = store.delete(key);
// var subReq = store.clear();
subReq.onsuccess = function (e) {
console.log(subReq.result);
};
};
transaction
,两个参数分别是会涉及到的表的名字和读写模式,表的名字可以是数据也可以是字符串,如"users"
或["users", "articles"]
,读写模式可以是IDBTransaction.READ_ONLY
或IDBTransaction.READ_WRITE
。transaction
对象获取一个表,需要传入的参数是表的名称。store
这个对象进行的基本的表操作——添加数据、获取数据、删除数据、清空表。参数也都很好理解,有一个要注意的地方是,添加数据时,key
是可选项,如果我们已经在表里定义了keyPath
或表本身有自增关键字,则key
是不需要写的。subReq.onsuccess
事件中,通过访问subReq.result
获取操作结果。添加操作的操作结果是关键字,获取数据的操作结果是对象的值,删除操作和清空操作无需返回结果。IndexedDB 是HTML5中的一种数据存储方式。用来帮助网站,在浏览器本地,存储结构比较复杂的数据。它和HTML5中其它的数据存储方式有一些共性:
1.和我们熟知的cookies类似,IndexedDB是每个域名独立存储数据的。
对cookies不熟悉的童鞋,可以顺便学习一下cookies,不过这不影响大家理解IndexedDB。网上cookies的教材和文章非常多,这里不一一列举。
2.和localStorage相比,IndexedDB可以存储任意格式的json object,而localStorage则只能存string,我们在使用localStorage存储复杂数据的时候,常常会协同JSON.parse和JSON.stringify一起工作,而IndexedDB则可以直接存取对象,无需转换成字符串。
对localStorage不熟悉的童鞋,可以查阅w3c官方文档,这里有一份我参与翻译的中文版文档,这里还有一些localStorage的使用建议。
3.和web sql database类似,IndexedDB也分数据库,每个数据库可以建立多个不同配置的表,而且所有的操作都在事务(transaction)中完成,不同之处在于web sql database是通过SQL执行语句来完成操作的,而IndexedDB则直接通过JS API完成操作。
需要指出的是,web sql database规范已经被w3c抛弃,对此不熟的朋友,也不必学习了,如果有童鞋想尝试的,可以找一款webkit浏览器试试看(傲游3、chrome、safari)
IndexedDB的整体存储结构
见下图,IndexedDB(以下简称IDB)严格遵循w3c的同源策略,每个源都拥有独立的大存储空间;每个大存储空间内,又可以通过当前源下的页面脚本创建多个数据库;每个数据库可以包含多个表(ObjectStore);每个表都是一个json对象列表,可以存储多个json对象,比如{"name": "jinjiang", "age": 26}
。
ObjectStore中的key
不同的源、不同的数据库、不同的表、不同的对象,都是如何识别的呢?不同的源直接通过域名进行识别,比如weibo.com、maxthon.cn、renren.com;不同的数据库通过一个字符串(name)识别,比如"blog"、"bbs"、"wiki"等;不同的表也通过一个字符串(name)识别,比如"users"、"contacts"、"articles"等;上面这些识别方式都很好理解,不太好理解的,是如何在表(ObjectStore)中识别不同的json对象,即key。
IDB为ObjectStore提供了两种key:
1.键值对(out-of-line keys: key-value pair)
2.键路径(inline keys: keyPath)
第1种是比较好理解的,就像localStorage中的键值对类似,一个key对应一个value,不同的时,localStorage中的key和value都是字符串类型的,而IDB中的key和value都可以是其它json对象。比如
"a" => "b"第2种是通过value中的某个属性字段直接用作key。因为value都是json数据,所以我们可以这样做,假如我们想创建一个表,里面的数据是类似这种感觉的:
[3, 7] => 21
{profile: {id: 1, name: "葛优"}, girls: [...]}那么我们就可以把profile里的id作为key,方法是为这个表指定一个keyPath
{profile: {id: 7, name: "James Bond"}, girls: [...]}
{profile: {id: 8, name: "周星驰"}, girls: [...]}
keyPath => 'profile.id'这样ObjectStore就会自动按照每个value的
value.profile.id
进行识别和匹配。如题
现在正文大小已经调整到了20像素。之前是14像素。
同时文章标题的大小调整到了24像素。之前是16像素。
最大图片显示大小调整到了800像素,之前是500像素。
侧边栏基本都是些没有用的东西,所以与其占地方,干脆去掉得了。
希望此次改动可以尽量接近“阅读模式”
偶像这个词在我脑子里一直是个纠结的名词。有的时候我觉得崇拜偶像或模仿偶像很媚俗,另一方面在做事的时候又希望可以找到一些方法和经验。在一开始懵懵懂懂的时候,一定是完全照搬偶像的东西,至于自己的想法,都是到后段才开始融入的。
比如在刚开始做前端开发这一行的时候,当时没有老师或教科书告诉我前端要怎么做。于是我要做的事情就只能是上网搜一搜,看看雅虎的前端怎么做,看看其它知名的大公司的前端怎么做,看看知名的前端大神都怎么做。由于刚开始对对错几乎没有判断能力,有的时候把人家的缺点也学来了,却全然不知,反而长期的引以为荣。
随着工作上不断的积累,以及不断的结识到更多的前端同仁,我发现两个现象:一个是自己做过的事情,被别人无情鄙视,从而让自己清醒;另一个是自己做过的事情,别人也会学着做,但自己作为一个旁观者时,才发现问题所在,从而点醒自己。
其实还有更直接的东西可以让自己知道做过的事情存在问题,那就是事情本身的结果。自己非常自豪的运用了一项新技术,一种新工具,一套新理论,但实际效果并不理想,工作效率没有实质的改善;自己做了很多很多努力,感觉工作做得“有声有色”,但没有得到足够的认可和支持,老板也没有给你加薪,或加薪时没有给到你想象中的程度。
如果看到结果自己还是一脸的抱怨和不理解,那么我们还可以追溯回之前的讨论,从那两种现象中找到问题的答案。
我们很希望做每件事情都可以有一本教科书,规规矩矩有板有眼的告诉我事情的道理。在一个开放自由发展中的社会里,总会有一些教育的盲区,缺乏积累的行业,还会有更多的探索在等待着我们。我们今天的“教科书”,也是前人开拓出来的。所以,通过观察别人,找到并完善属于自己的世界,是一个很重要的学习方式,而模仿,则是大多数人的敲门砖。从这个角度讲,世界是需要偶像、明星和品牌的,需要更多的各行各业的偶像明星品牌。
接下来我们开始融入自己的想法的过程,其实也是一个把自己的偶像“平民化”的过程。我们需要更理性的思考和判断,而不是一味的崇拜和相信,不然会很可怕。到最后,偶像和神话将不复存在。大家都和你一样是个普通人。换句话说,我们一直觉得自己很普通,但有一天也变成了别人眼中的偶像。
以前看过一个电视剧,女主角喜欢看NBA,有一天她看到了姚明的照片,激动不已。男主角就在一旁嘀咕,说在别人身上找自己没有的优点有意义吗?这句话令我印象深刻。偶像、明星、品牌对于非从业人员来讲,又意味着另一层意义。我把其解读为对社会的综合实力的信心。也就是说,各行各业都在蓬勃发展,都能够造就偶像、明星和品牌,也让自己感到生活很快乐很有保障,可以让自己毫无顾虑的专注在自己的事情上。而偶像明星品牌的问题,在毁掉自己的同时,也将毁掉整个行业,引发诚信危机和信仰危机,人心惶惶。
比如中国篮球有了姚明,就带动了很多篮球周边的产业,国内很多运动装备的品牌也借此机会扬名国际。再比如家家户户都买豆浆机喝豆浆,也是牛奶总出问题导致的结果,久而久之,我们还会进而觉得外面卖的东西都是不可靠的……
再比如当年杨晨郝海东意气风发的时候,我们就觉得中国足球非常牛非常有希望。2001年国足出线的那天晚上,我还记得自己那时上高中,大街小巷都洋溢着幸福。国足出线第二天数学课上,当我们班公认的超级球迷郭老师走进课堂的那一霎那,全班同学报以雷鸣般的掌声和欢呼声,老郭也笑笑跟我们说,学数学也像踢足球,要快乐的学,要态度端正的学。我也很激动,那个学期我的数学考了96分。国足出线,对于我们老师和学生来说,都是个莫大的鼓舞。
不论怎样,一个足球明星可以让千千万万的孩子们喜欢上足球,开始踢足球,也可以让不踢足球的人看到自己的希望,给自己动力。总之都是找到自己。
今天晚上跟家人一起坐在电视机前看中国达人秀节目,有一个模仿的表演,几个演员分别饰演葛优、周润发、李小龙、李宗盛和梅艳芳,惟妙惟肖。评委周立波在给了这个表演“YES”的同时,跟他们说了一句话:
“模仿别人是为了找到自己。”顿时感慨万千。
Hi,
既然被你揪出来要求了,那终于还是到了要写关于我之前讲个人感情问题时省略的那1000字的时候了。
1000这个数字是我随口说的,以前高考的时候,要求作文需要写满800字,那在当时几乎是我写单篇文章的上限了,超过800字,我就语无伦次了。
现在在不老歌,如果你写的文章超过了1600个字符,那么在确认发表的时候,不老歌网站会提示你,你的文章超过了1600个字符,是否要单独为文章写个摘要?因为这样,博客首页就会只显示摘要,太长的正文就会被隐藏,点击链接进入文章的页面才会看到正文。也因为不老歌有了这个功能,我很清楚,自己越来越可以轻而易举的写出一篇“千字文”。我去年一共写了48篇博客,70%都被提示过写摘要了。这让我觉得勤动笔头还是有收获的,最起码收获了一些自信。期间,我还在外准备过3次100~200人规模的技术分享,当我想把这三次的分享内容呈现在博客上时,发现它们都超过了8000字符——这是不老歌的又一个“槛儿”,也就是一篇文章的最大长度。我不得已把前两个分享内容分成了上下两篇,第三个分享直接做成了在线PPT。上述的这些今年的“文学成就”,都是我读书时不敢想象的。但今天,我通过一些有意识的锻炼和学习,做得到了。
写上面这些,是想说,也许这是我为什么随口说出1000这个数字的原因,这应该是我能够有把握掌控,同时也是可以把话说清楚的字数,多一些少一些好像都不太对劲。
不过这里也有些区别,我以前写的东西都是自己即兴的心得和随想,一切都是很自然的,但还从来没有被如此安排写一篇“命题作文”,一下子不知从何说起了,顿时就又想到了高考……
还有一个区别是,我很少这样给身边的人写东西,感觉很特别。我以前跟你讲话,都是当面沟通,直来直去,就算是用电话、QQ、短信,也是有互动的,就会假想出你的模样,感觉你就在我对面,那种感觉是很美妙很亲切很甜蜜的,但坐在一台冷冰冰的电脑面前,更像是在跟电脑说话,跟程序说话,始终打不卡话匣子。也许我再多写点废话,会慢慢进入状态,能够在这个白色的文本框背后,看到你在对我笑,在听我跟你讲话;我是不是还可以凑过去抱一抱你,蹭一蹭你的小脸蛋,搂着你一起坐在大床上看康熙来了 :-)
每当我回忆自己的工作、学习、生活的时候,我都会把自己以前写过的博客、换过的签名档翻出来,傻傻的看一遍,然后就知道该怎么写了。但是我从来没有在任何地方上提起过你,所以接下来会提起的那些事情,我敢保证,绝对都是我内心深处的记号,不需要任何方式帮我提醒的。
1月,元旦,我们一起来太原见过了我家里人,奶奶说了,一看就是自家人,显得特别亲切,能被自己的家人也一样喜欢,这对我来说是何等的幸福。我同时也托你的福,和你一样,第一次去了一直想去没机会去的乔家大院和平遥古城。
2月,过年了,我们总是彼此感叹过年的时间分开的太久了,就像现在这样。情人节那天我给你准备了你最爱吃的草莓和巧克力,你给我讲了个兔子的故事。
3月,我跟爸妈一起去了趟香港和澳门,因为一些难言之隐,我们没有办法同行,不过我为你挑了很多礼物,而且我忍住没有去迪士尼哦!留着以后有机会和你一起去XD
4月,我们去了青岛和威海,看了满脑子的沙滩和大海,吃了一肚子的海鲜和酱汤,哈哈。你在海边的那张照片最美了!我拿它作桌面作了很久呢。
5月,我们在鸟巢看了滚石30周年的演唱会,一口气看到了好多大明星,从下午4点High到晚上10点半。我记得那么多明星里面,莫文蔚和刘若英最让你疯狂。我这个人来疯反而显得没有你投入,只管给你看包了。
6月,我被带去你家过粽子节了,那几天一直鸭梨很大,我还记得我最后一天喝多了……最不堪回首的一幕。
7月,8月是最热的两个月,好像没有发生太多的故事,但我在这段时间记住了一家名叫“雕刻时光”的餐厅,你说,我们不是在雕刻时光,就是在去雕刻时光的路上,令我印象深刻。还有就是我的房租提前到期了,那段时间挺不好过的,说真的我一度心脏都受不了了,谢谢你陪我帮我想各种办法度过这段日子。
9月,是在搬家中度过的。
10月,我们订婚了,预谋已久的。我跟你求婚的日子你应该不会忘记吧(对吧,真的不会吧,告诉我你不会忘记的对吧……)。地点就在你最爱的雕刻时光,我第一次给店员服了小费,把他们提前都“买通”了——天呐为了你我这都干得出来!另外,我们买了电压力锅,拿它蒸饭炖排骨神马的最给力了!“排骨就在锅里面自己炖自己”。
11月,是属于订婚综合症的一个月,过得飞快,每天都在讨论各种严肃的问题。我觉得我当时有些事情太坚持了,没啥意思。也许凡事不要太纠结,严肃的问题也可以变成轻松愉快的事情,这是我在11月学到的东西。光棍节那天我们去看了失恋33天,原来除了跟几个同事一起蹭Jeff的披萨、跟几个孤男寡女去欢乐谷之外,这个节日也能这么过。真幸福!
12月,你因为几个好姐妹忘了你的生日还哭了一鼻子。亲,就算全世界都把你忘了,我保证我一直都在。而且还会唱歌给你听。
还有几件事情,我也想特别谢谢你。
谢谢你总提醒我多喝水,多吃早餐,早点休息,有段时间我胃不舒服,多亏你照顾我。
谢谢你在你老爸老妈面前给我说了那么多好话,你把我说的那么好让我很惭愧,尤其是我其实不太“顾家”,是个工作狂,也不太会挑衣服买鞋,衣服鞋帽都是你陪我看的。
谢谢你给我做那么多好吃的饭菜,今年我也要多多学习厨艺了——先从我们家拿手的面食开始,今天我刚跟老爸学来了刀削面哦!另外我也会在每个周末继续骑车载你去早市买菜。
谢谢你包容我的个性,也让我开始学着控制情绪。
谢谢你支持我的事业,这让我更加珍惜和你在一起的时间。
谢谢你支持我看足球踢足球这件事,呵呵。
谢谢你给我这个机会把它们写出来。
谢谢你,fery!
我们的2012会更精彩!
I Love You.
大兔子
2011年,说过了技术和工作,最后是生活。
我觉得今年是我在北京生活比较习惯的一年了,以前我总结过自己的北京生活,是这三个词:槐花、柳絮、蟑螂。
也许人生就是这样,接受着蟑螂、烦恼着无孔不入的一阵阵柳絮、发掘着来无影去无踪的槐花香味。如今,大千世界一如既往的魅力十足,槐花香味依旧;而虽有风沙和“毒气”,有糟糕的餐饮服务,有黑心的房东中介,有拥堵的交通和下水管道,有与日俱增的物价,那都算是蟑螂的部分了;柳絮已全然不觉。我觉得能够接受或放下一些内心的理想化的纠结,也算是成熟了吧。
今天借由EaselJS这个脚本库,写一个简单的动画给大家当做为示范。
DEMO演示链接
这个demo的最终效果是游戏割绳子(Cut the Rope)中的小青蛙的动画表情。
首先简单介绍一下EaselJS这个脚本库,它是我最近发现的一个用来绘制Canvas动画的脚本库,比起Canvas的底层API,这个脚本库的接口设计更简单易用,所以做起动画来就易如反掌了。这里有详细的信息和文档
sprites.png
。_script src="easeljs.js">_/script>然后,创建canvas标签
_canvas id="canvas" width="100" height="100">_/canvas>
// create a new stage and point it at our canvas
var stage = new Stage(document.getElementById('canvas'));
BitmapAnimation
类创建出来的全局变量 icon
,这类对象可以在舞台中播放动画。而动画是由 sprites.png
中的图片循环播放生成的,这里我们需要通过另一个类创建对象:SpriteSheet
,整个小青蛙的创建方法如下:// create a SpriteSheet using "sprites.png" with a frame size of 100x100
var spriteSheet = new SpriteSheet({
images: ["sprites.png"],
frames: {width: 100, height: 100}
});
// create a BitmapAnimation to display frames from the sprite sheet
icon = new BitmapAnimation(spriteSheet);
// append into stage and start animations对上面的代码做一下解释:第一行代码是把小青蛙的对象添加到舞台,用到了舞台的
stage.addChild(icon);
icon.gotoAndPlay(0);
Ticker.addListener(stage);
Stage.prototype.addChild
接口;第二行代码是让动画从第1帧开始播放,用到了 BitmapAnimation.prototype.gotoAndPlay
接口;第三行代码则是开始播放动画,它的原理有点意思,Ticker
是EaselJS中的又一个类,用来作为控制动画播放的“节拍器”。当我们使用 Ticker.addListener
方法时,节拍器会有节奏的触发第一个参数的 tick
函数(默认是20帧/秒),而 stage
对象刚好有一个默认函数 Stage.prototype.tick
,会把舞台中的所有动画元素向前播放一帧。这样,小青蛙的动作就会连贯的播放出来了。// bind animation events这里给
icon.tick = function () {
if (icon.currentFrame == 18) {
icon.currentFrame = 0;
}
};
icon.tick
赋值一个函数,这个函数会在每一帧动画播放之后被触发。我们在这个函数中判断,如果播放到第19帧时,立即从第1帧继续播放。这样,小青蛙点头的动画就可以重复播放了。设置当前帧数的方法是修改 icon.currentFrame
的值。window.onkeydown = function (event) {刷新页面,这时,我们看到,小青蛙还是处在连续点头的状态,当我们按下回车键时,小青蛙立刻从第20帧开始继续播放,即撅嘴。但有个小问题,因为节拍器是循环播放的,所以当撅嘴的动画播放完毕之后,动画会自动跳到第1帧。即重新回到连续点头的状态。
if (event.keyCode == 13) {
icon.gotoAndPlay(19);
}
};
toggle
函数,通过flag
记录当前的状态是连续点头还是撅嘴:function toggle() {然后,绑定
if (flag) {
icon.gotoAndPlay(0);
}
else {
icon.gotoAndPlay(19);
}
flag = !flag;
}
window.onkeydown = function (event) {
if (event.keyCode == 13) {
toggle();
}
};
icon
的另一个事件 onAnimationEnd
icon.onAnimationEnd = function () {这个事件会在动画播放到最后一帧时触发,即第32帧时,我们在这个事件中要处理的就是让动画暂停下来,同时让动画停留在第32帧。
icon.paused = true;
icon.currentFrame = 31;
}
我不太确定是不是中国人都听人说过这句话:
吃了吧,不吃就浪费了。东西吃不完的时候,我们的第一反应是剩下多可惜啊!多浪费啊!尤其是稍微上点年纪,对物质不那么丰富的年代还有记忆的人,是绝对不忍心的。要么撑着肚子硬着头皮吃下去,要么打包或放冰箱第二顿热一热继续吃,实在不行才会倒掉。
吃了吧,不吃就浪费了。现在的我会思考另外两个问题:
说到一年来的体会,当然少不了公司这一块。这部分是最难以言表的,因为节奏很快,经历的事情很多。
首先要感谢的是公司很长一段时间对我工作的信任和鼓励,感谢同事们的支持和陪伴。这一年过得很开心。我今年有幸接触到了一些内核研发的边缘工作,接触到了移动端开发的知识,拥有了更棒的前端开发团队。我们部门也一起做了不少事情,起始页又上了几个重量级的新功能,哈哈站更哈了,官网变漂亮了,浏览器更HTML5了,海外市场也有我们的声音了,插件系统正式起步了……这些都是令人欣慰的事情!
今天不聊任何技术的东西,都是一些跟专业无关的心得体会。
心得之一是坚持技术交流与分享
好的东西是讨论出来的,我们对公司的贡献更大的价值,不光是埋头切页面和写代码,也可以通过学习提高技能和效率、通过回顾代码改进产品质量、通过思考产生新的创意、通过生活和休息调节自己的工作状态(所以让员工合理的休息也是在为公司创造财富)、当然也可以通过交流和分享帮助大家共同成长。我们这一年组织了各式各样的技术交流,线上线下、对内在外都有,工作再忙项目再紧张的时候,也没有放下。它在背后支撑了我们一整年的工作。交流与分享的一个额外的价值,就是工程师们普遍都不善言谈,表达能力有限,所以也算是为工程师们创造了难得的锻炼机会。
心得之二是做好上传与下达
我不太确定这句话大家爱不爱听……如果员工和老板的意愿产生了分歧?主管应该站在老板一边跟员工说一些冠冕堂皇的“鬼话”?还是站在员工一边“要挟”自己的老板呢?讨好老板的结果是被员工疏远,水能载舟亦能覆舟;讨好员工看似得人心,但潜意识中暗示员工们跟老板站在对立面上,最后是整个团队遭殃。估计这两种情况的下场都不会太好。我觉得要做好主管,上传和下达不能是单向的,也不能刻意站在哪一边。帮助两边化解分歧才是最重要的。我的心得是主动跟下属沟通比较容易,但“卸下心防”,跟自己的上司开诚布公的探讨问题是个不太容易的事,所以能够有勇气并自如的跟自己的主管交流,是员工在一个环境中成长和发展的重要前提。有些事情看似“自己无法改变”,这时不妨自问勇气或耐心,然后才是实际状况和沟通技巧。当“自己无法改变”变成一种习惯时,随之而来的就是频繁的挪窝,不相信稳定的工作。
心得之三是保持专注,少揽活儿,多用心
揽活儿意味着承诺和别人的信任。回顾工作中的各种经历,让我耿耿于怀的,都是那些扔出去的“空头支票”。我希望可以把每一件事情做满,认真的对待每一件事,所以也会谨慎的考虑自己要做的每一件事。一旦答应了,就是全力去做。不要拘泥于把事做完,还要把事做好,做好之后再问问自己,可不可以精益求精!
心得之四是用人不疑疑人不用,把握好“四个象限”的人才
都说“每一个悲催的设计师背后都有一群指点江山的神”,我非常不喜欢这样的事,所以要相信人的专业能力,要给足空间,别人才会帮你分担更多的工作。不然自己找人帮忙,但不放心,还指指点点,像是在自己调戏自己。另外我见过一个根据人的工作能力和工作意愿划分的“四个象限”用人原则:
低能力 | 高能力 | |
---|---|---|
高意愿 | 辅导 | 授权 |
低意愿 | 放弃 | 激励 |
人生就像巧克力,在你吃下去之前,你永远不知道他是什么口味。这首歌之前是送给Richard、光光、阿拉丁、Benjamin、fish、DJ、Grace们的。今天,我想把它送给更多曾经共事过的人们。
记得年初 @糖拌西红柿 他老人家写过一篇大作《Be Pro》。说的是Web标准的开放性与工程师的专业性,也给了我很大启发。
是啊,如果所有的浏览器都被严格要求网页写成XHTML格式,估计Web也不会这么流行——可以想象得到的是,如果你已经是一名经验丰富的前端工程师了,那么你在各种技术交流会上,给众Web初学者讲解繁琐的规范时,他们纷纷摆着一副“虽然不太懂但是感觉好厉害的样子啊”的表情,你会觉得没有成就感。但是门槛低了之后,作为平时“严格要求”自己的自己,混迹并淹没在大量的“页面仔”之中,内心的独白又是什么呢?
其实门槛的降低并没有削弱工程师本身的价值,首先它所带来的结果应该是更广阔的空间(市场)。以前做网站很辛苦,工程师不好找而且费时费力;现在div+css、table布局、标签不用闭合、属性不用加引号、标签没有语义也照样有效果,不懂xml也能做网页,就能找到好多人做好多事情。但除此之外,门槛的降低并没有削弱专业知识的价值,因为复杂的前端开发工作还是需要系统的思路和严谨的代码实现。我相信不管是被要求用HTML5还是XHTML2,写得出gmail的公司还是写得出gmail,写不出的还是写不出。为什么我们感觉自己的工作受到了威胁?其实还是我们自己不够专业,或是因门槛的降低而降低了对自己的专业要求。
换句话说,开放的标准给了我们的工作很大的“弹性”,对力求完美和偷工减料的工作结果都是包容的,但这仅仅体现在了当下的一些简单工作上。优秀的前端工程师是不必纠结眼前的成果,如果我们写的页面结构很清晰、语义很明确、可读性可扩展性可维护型都没的说,那么在日后需要改版或加入新功能,或被用户玩出bug来的时候,甚至在你交接工作的时候,绝对毫无压力!那些“不够专业”的童鞋估计就得埋头重新再做一遍了,而你则可以喝杯咖啡或做更多的事情。久而久之,专业与不专业的差别就体现出来了。
其实我们在周围的各行各业都能感受到门槛越来越低、同时专业性越来越鲜明的现象。比如拍照、比如登山、再比如创业。这也给我们提个醒:很多事情看到大家都在玩,感觉自己也行,其实只是门槛降低了,而不是自己多有本事。最后成功的还是那些成功人士,而且比以前更加成功,而你会输得更惨。
还有一件事情是门槛低所带来的,就是“只要努力用心就能成功”的错觉。我觉得理性的判断是专业的技能和职业的态度相结合,而不是极端的认为态度决定一切。因为我们也一定见过这种人:天生傲骨,藐视一切,特立独行,但取得了伟大的成就,是很多人心目中的神。还有句话我不记得出处了,叫“当你说出尽力两个字的时候,其实你已经输了”。所以做事不能光玩命,还得懂行。
有的时候我甚至在琢磨,专业二字带给我们的,是否就是那种“不管我用不用心怒不努力态度端不端正都能让你求我做事同时我还能把事情做好”的神奇的东西。
总之,我觉得不论如何,专业二字依然是非常神圣的。相信专业,追寻专业,绝对没错。
伴随着今天公司年会的结束,我才真正觉得2011年已经过去了。想为2011年的自己写一些东西,先从HTML5开始
我觉得自己今年在HTML5方面搭上了两辆顺风车:一个是参与HTML5研究小组、w3ctech等组织的技术交流,另外一个则是开通了微博,可以结识更多优秀的同仁,了解更丰富的知识。HTML5对于我来说,和大家一样,都是个新东西,都需要“现学现卖”,但有了这两辆顺风车的帮忙,让参与其中的人可以先行一步。进而把自己的心得和收获分享给更多的人,在教学相长的同时,也收获了更多的信心、机会和成就感。这些其实是我最核心的感受,论理论深度、造诣、成果,我其实都没什么,仅仅是先行一步罢了。可能是由于HTML5研究小组主要成员这一身份的关系,现在逐渐会有周围的朋友联系我做讲座、翻译、写书什么的,我非常感激,但没有太多底气去做,尤其越是有偿的,我反而觉得无功不受禄。
还有一个感受,就是HTML5有两个好朋友:移动和游戏。我几乎没有见到过有人在关注没有移动和游戏元素的HTML5——其实除去这两块,也就剩下HTML5的桌面实用工具了。而这恰恰是我最感兴趣的领域。在今年接触的众多HTML5话题中,唯一一个属于桌面应用这个范畴的,就是 @尼奥_ 的团队在 code jam 第二期中做了一个时间管理工具。我觉得那个很棒,真正觉得HTML5在给人们的生活带来便利。
其实HTML5在移动和游戏方面的如火如荼也是很好理解的,尤其是手机游戏。移动是因为巨大的市场,游戏则是展示新技术的最直观舞台,而且游戏开发确实很复杂。不论是对于前端开发者还是对于一个商人来讲,没有什么比移动和游戏更令人振奋的了!但如果是做大众产品,个人感觉还是相对概念化了一些。其实像各种浏览器的HTML5扩展、微博和人人网右下角通过桌面提醒功能实现的私信提示、包括李开复博士在HTML5研究小组的年会上提到的谷歌圣诞节彩蛋,也许才是老百姓对HTML5真正的第一印象。HTML5在移动和游戏方面开花,最后在大众应用中结果。
另外我觉得HTML5需要更多模式化和工程化的思维,而不仅仅是技术上实现某个效果的可能性。记得在2010年接触到的HTML5,都是一些零散的概念,我曾经感慨说HTML5说来说去就是这么多东西了,唯有更多更丰富的上层建筑,才会让HTML5真正发挥威力。果不其然,今年各种基于HTML5的工具、函数库层出不穷,尤其在Canvas、WebGL、CSS3、SVG等图形处理方面非常众多,这些上层建筑的出现会令HTML5的思路逐步变得清晰和明确。而工具和函数库的在上层,就是更多的框架、引擎和理念了。我觉得这将会是HTML5走向成熟的下一个重要标志,最后,优秀的规范、工具、函数库、平台、引擎、理念,才会催生真正优秀的HTML5作品甚至是HTML5产业。
还有一件事我也感触很深,就是浏览器真的要给力。我想说的是支持HTML5不只是浏览器内核层面的事情,还包括很多安全性、隐私策略、基础体验等等,更不仅仅是“跑高分”。如果认真读过w3c文档的话,在感叹标准的制定非常严谨的同时,我们也会发现,那些可以用分数来衡量的HTML5特性,在整个HTML5规范中,只占很小的一部分。“分数”是直观的跑给那些不懂技术的人看的,做技术的人不应该满足于一个“高分低能”的浏览器。刚好今天我们公司年会上jeff也表态了,新的一年里,浏览器要在跑出347分的高分之后,更要持续加强对HTML5的多方面支持,真的令我振奋!
自己在2011年的困惑之一,是如何分配自己的时间和精力。单说HTML5这一块,花多少时间看文档、多少时间看新闻、多少时间学习新的工具和库、多少时间写自己的代码、多少时间和大家一起交流讨论,都是非常令人纠结的问题。可能明年上半年我会专注在看文档这件事情上多一些,从最基本的知识层面开始做起。另外我希望可以继续专注在桌面应用这个看起来很土的方向上。我觉得未来的天下是移动和游戏的,它们就好比海阔天空;而桌面应用则是大地母亲,所有知识、灵感和创意的源泉。当然这也多少跟我的工作有更多的契合。
以上是我写给HTML5的2011年
上个月末的w3ctech上,有同行提到了LocalStorage这个话题,我觉得在HTML5的众多新特性中,LocalStorage算是比较实际同时浏览器也比较好实现的特性。
LocalStorage的规范描述在这里:http://dev.w3.org/html5/webstorage/
首先一个细节,LocalStorage只能存储键值对(key-value pair)形式的数据,并且key和value都只能存储为字符串类型。之所以这样说,因为JS是动态语言,我们可以在setItem
时传入int型数据(比如localStorage.setItem("a", 1)
),但是它会转换成字符串之后再进行存储和准备随时调用,当我们用getItem
访问"a"
时(localStorage.getItem("a")
),得到的是字符串"1"
而非数字1
。
第二,虽然localStorage[key] = value
的写法主流的浏览器都是支持的,但不推荐这样书写代码(更正一下,标准里有getter/setter的明确规定,上书写法也是标准的一部分,感谢Kenny提醒)。而且很显而易见的问题是:对length
、setItem
、getItem
、clear
这样的key进行读写是会产生问题的。假如我们执行:
localStorage.setItem = null;那么整个LocalStorage的接口完备性将会遭到破坏。
localStoarge.removeItem = null;
localStorage.clear = null;
JSON.parse
/JSON.stringify
配合使用。这样我们可以方便复杂数据结构和字符串之间的转换,获取数据的时候使用JSON.parse(localStorage.getItem("a"))
,写入数据的时候使用localStorage.setItem("a", JSON.stringify(obj))
。setItem
时如果超过容量上限,会触发QuotaExceededError
异常。我的经验是,如果你是存文本的,一般碰不到这根线,可以无视;如果用DataURI方式存二进制文件,就需要特别注意了,视频的话,基本没有5MB以下的,所以不会考虑LocalStorage的,也不用特别注意;但如果是图片,很容易几百K的图片多存几张就够5MB了,所以有必要提个醒。当然有些浏览器也会通过提醒用户确认来允许网站使用更多的容量,那个是另一说了。window.onstorage
事件。其实这也不算多高级,只是用的地方比较少罢了。假如我们同时打开了同域下的多个页面,这时我在一个页面里操作localStorage.setItem
、localStorage.removeItem
或localStorage.clear
,其它同域的页面就会触发这个事件。事件附带的参数是这样的:window.onstorage = function (event) {
var key = event.key // 被修改的键名
var oldValue = event.oldValue // 旧的值
var newValue = event.newValue // 新的值
var url = event.url // 触发改变的网页的url
var storage = event.storageArea // 当前localStorage的引用(当sessionStorage改变时,这里就是当前sessionStorage的引用,好吧扯远了,看不懂可以先无视)
}
排名不分先后:
欧冠决赛:巴塞罗那 3比1 曼联
女足世界杯决赛:日本 120分钟2比2 点球击败 美国
女足世界杯:美国 120分钟3比2 巴西
欧冠1/4决赛:拜仁慕尼黑 2比3 国际米兰
西班牙国王杯决赛:皇马 120分钟1比0 巴塞罗那
我在我今年所有看过全场直播的比赛中,选出了5场比赛,以此作为纪念。
作为一个中国的巴塞罗那球迷,我每年都看巴塞罗那的比赛最多,其次是每天下班晚饭时间的中超,其他比赛挑着看,这种状态持续了三五年了。今年花在工作和家庭上的时间逐渐多了,巴萨的比赛还是看很多,但其他比赛相对看得少了。
今年尤其对两场女足世界杯上反败为胜的比赛印象深刻,一次是美国在被罚下一人的情况下在加时赛最后一分钟超级逆转巴西,其中玛塔的两粒经常进球、整场观众高喊“USA”的场景以及瓦姆巴赫在进球后的疯狂庆祝都让我印象深刻;另一场则是日本用精湛的技术和过硬的心理素质顽强得连续两次追平,最后令美国队在点球大战中精神崩溃。看得都是荡气回肠。
我今年尤其觉得女足比赛越来越精彩了,甚至比男足比赛更有观赏性。就连第二天的《锵锵三人行》节目里,梁文道和徐子东都忍不住在节目中大声说出了“女足真好看”这几个字!
另外就是众人皆知的欧冠决赛。“我萨”踢得气势恢宏啊!呵呵。记得赛前张路分析说:曼联的战术就是“我跑死你!”,巴萨的战术就是“我遛死你!”,比赛中果然如他所料。巴萨的三粒进球个个精彩,朴智星虚脱的身影、弗格森微微颤抖着的手以及阿比达尔的带队举杯,都是值得回味的片段。这场比赛也应该算是巴萨真正脱变成为“宇宙队”的里程碑吧。
其余的两场比赛,真的看过直播会觉得异常精彩,张力十足!精彩程度绝对不是进球就能代表的,而且很明显的感受到相互对垒的两队球员都陷入了疯狂!我这两场比赛都是站在输家的立场上看的,但看过之后完全没有沮丧的感觉,因为比赛实在太精彩太刺激了。
不知道其他人有没有2011年让你印象深刻的足球赛呢?
如今我们说餐饮业是个“服务业”,应该不会有太多争议了吧。其实我对餐饮并没有什么追求和兴趣,但说到服务,就有不少感触了。
做互联网也需要很多服务的心态,服务二字通俗的讲就是不管我们费了多大劲,只关心顾客要的东西有没有做到,那个才是最重要的。
软件工程师在旁人眼里是一群思维古怪、作息古怪、言谈古怪、性格古怪的人,能把工程师们拉拢在一起高效的工作,是很有挑战的事情;网民众多、千奇百怪并且力大无穷,能把一款大众互联网产品的服务做好,也是很难的。作为一名从业者,我每天都在思考有关服务的话题——包括吃饭的时候。每每看到那些餐厅服务员的行为,真是感慨万千。
回到开饭店被顾客吃出“异物”的话题。今年我在饭店吃出“异物”的情形,令我印象深刻、不吐不快的有两次:
一次是春天的时候在永和大王,我点了一杯冰豆浆,结果喝着有股酸味,就跟服务员说豆浆味道好像不太对……结果我还没说完,服务员就打断我说:“哦,我马上给您换一杯啊~”。就立刻给我换了一杯豆浆,这次换给我的豆浆是好的了;
还有一次是在味千拉面吃炒饭,到快吃完的时候,吃出一个塑料的小东西,也不太清楚是什么,就跟服务员说这是从炒饭里吃出来的,并质问是怎么回事,服务员一开始很扭捏,紧接着是答非所问:“不然给您重做一份吧”,然后迅速把我们的炒饭收走倒掉了。
你们刚开始也许期待我“不吐不快”的是遇到了吃出异物还狡辩不给重做的吧?呵呵,这种情况在北京确实很少了,是个进步。在我的记忆中,服务这个概念不是从小就有的:以前老板跟顾客结账基本都在算“我做了一盘什么,你该给我多少钱”,后来变成“你点了多少东西,该给我多少钱”。我觉得非常欣慰,但我今天不是吐槽这个的,因为我觉得真正的“服务”可以做得更好。
我首先想声讨的是如今的服务员都被洗脑成“东西坏了就立刻重做一个”的机器。拿豆浆的例子说,服务员根本不关心那杯豆浆怎么了,更不会去深究这中间是什么环节出现了问题,其他顾客的豆浆会不会有类似的问题,如何在将来的服务中避免……而且我跟服务员说豆浆味道不对,服务员二话不说把豆浆扔到一边给我换一杯新的,顾客真的会满意吗?觉得这样做很爽快吗?实话说我都是抱着“试试看会不会又喝着味道不对”的心态去喝那第一口的,用餐结束后带着反胃的感觉离开的。
其次,“重做一个”的道理在哪里?为什么不能退款?这是真正“服务顾客”的态度吗?我们发现并提出饭菜质量问题都是吃到一半的时候,这时顾客也许已经吃个半饱了,你再蛮横的端上一大盘来给我,是想撑死我吗?我觉得,这更像是通过自残来博得顾客的同情,故意做给顾客看的。而且这样做从顾客的立场浪费时间,从饭店的立场还浪费劳动成本,站在社会的立场更是浪费粮食啊!
这里是我当时在新浪微博上的感慨:
今天在永和大王点了一杯冰豆浆,结果喝到一多半的时候喝出一些酸味的东西。我找服务员寻问,被不加过问和思索的直接换了一杯新满的。其实我不想再喝了,只是想让他们反思一下这个异味怎么来的,怕更多的豆浆出问题。他们却没有人思考,反而觉得我像没喝饱图便宜…哎
http://www.weibo.com/1712131295/zF4mVgq0KV
这周末好忙,前后参加了两个前端交流活动,一个是关于前端MVC和Node.JS的,一个是HTML5的。其实说到这里,我不得不重新审视一下我称呼这两个活动为前端交流活动是否合适。
在周六的前端MVC技术交流过程中,有位多次参加这一交流活动的同行,若有所思的谈到了这样一个话题:感觉这些东西已经不是前端了。说罢,又补充道,不是前端,而是JavaScript。随后大家的话匣子被这句话彻底打卡了,开始了热烈的讨论,不过这个话题没有讨论太多,很快焦点就转移到了MVC的好处在哪里、按照MVC分工有什么优劣、分工好还是不分工好之类的……不过我后来一直在惦记着这个问题,也非常理解这位同行的想法:前端不就是展示页面么?以前大家都把焦点放在了界面上,放在了特效上,放在了浏览器兼容性上,现在哪里来的这么多数据通信、逻辑处理、对象模型?这还是“前端”么?
MVC也就罢了,紧接着Node.JS来了。主讲人石头津津有味、慢条斯理的介绍着如何通过Node.JS,用前端工程师熟悉的JavaScript搭建一个博客后台……我又深深的陷入了思考。如果说MVC还有前端的影子的话,那么说Node.JS也是前端,会不会太扯了?我们身为一个前端工程师,到底应该去做什么?创造哪些价值?
还没把这个问题想清楚,又参加了另一个活动:HTML5 Code Jam。看到了五彩斑斓的HTML5世界:Canvas、WebGL、Audio、Notifications、LBS、WebSocket……LBS和WebSocket已经是完全跟前端没有关系的东西了,Canvas、WebGL虽然是界面展现层的东西,可这些都是纯编程出来的,都是枯燥的物理碰撞、矩阵变换、坐标计算,这跟Win32绘图、OpenGL渲染有什么差别呢?HTML5带给我们的,远不止是前端的世界,更是应用软件开发的世界。
我突然感觉,作为一名有计算机相关专业背景的前端工程师,我已经在这种模糊的定位中生活了很久。也许相比起传统的Win32开发、网站后台开发、新兴的Objective-C开发来说,前端开发都算是“档次”最低的。在这种长期的潜意识刺激下,当我们遇到了琳琅满目的JavaScript APIs的时候,一种找平衡的心态油然而生。我觉得HTML5在引领技术革命的同时,也抓住了这群每天被以iOS开发暴发户为首的一群传统开发者鄙视的前端开发者的“用户需求”:借HTML5抬高自己的身价,跟Native开发平起平坐,同时跟其他前端划清界限。
但是事实上,要想真的跟Native开发平起平坐,你需要的不是熟练的JavaScript,而是传统开发的眼界和思路。而对视图和样式是否敏感似乎又变得不太重要了。一个写得来Node.JS、WebSocket、LBS的人,搞不定传统前端中基本的三列布局,是很有可能的事情。
那你们说,作为一名前端,还要不要去学HTML5了?
我觉得,如果你想专注在真正的前端领域,看一看新的HTML标签跟属性,还有CSS3,还是很有必要的,其它的东西简单了解一下就够了。与其花时间深究数据结构和算法、网络通信与分布式计算、数据库管理等这些高深的编程知识,我们不妨多看看用户体验与界面设计,了解一些图像处理软件的“切图”技巧,再多多品味生活,寻找一些前端的灵感和技巧。
如果你是以程序员自居的,那么你真应该认认真真的积累一些传统应用程序的开发经验,深入的学习一些软件工程的理论和数学理论,以及产品本身所涉及的相关专业的理论,把这份工作当做一次纯粹的编程。
两者兼顾当然也是可以的,总之,要清楚这是两件事就对了。
希望我们今后在讨论HTML5的时候,也不要忽视真正的前端,正确看待前端的本职工作,正确看待自己的身份。比如在前不久刚刚结束的WebRebuild.ORG年会上,当我们看到久违的已经略显生疏的“网页重构”四个字时,再想想最近一年我们周围的变化,很有趣。
最起码我今天觉得前端和HTML5是两件事了
在 Mac 下,原生按钮的样子灰常口爱,起码我觉得比 Windows 下的口爱一些。虽然好多自定义按钮的样式也非常棒,但我还是固执的喜欢原版的。
不过我发现一个有趣的现象,当我们改变按钮的font-size属性时,会有这么几种情况发生:
* input[type="button"]、input[type="submit"]这两类按钮的字号大小不会发生任何改变
* button标签生成的按钮,字号会随之改变,但是按钮的样式发生了改变……直观的说,就是变丑了:圆角没了、阴影不一样了、背景填充也不一样了,很奇怪
* input[type="file"]里的那个“浏览”按钮一直很淡定,从来不受任何影响
百般纠结之后,我通过查询webkit的默认css样式表,找到了一些解决问题的线索,那就是通过-webkit-appearance: push-button;
这一属性设置使得button标签的按钮保留原生按钮的样式,但同时font-size属性值也跟input[type="..."]的按钮一样,失效了。
至今没有找到一个完美的方案,mac也有难搞的一面啊 (当然,如果你打算自定义按钮的样式,就没有这些烦恼了)
在线幻灯片地址:
http://jinjiang.github.com/slides/think-about-webapp-layout/
说明:如果您使用的是文艺浏览器,那么您使用的是普通浏览器或XX浏览器,则幻灯片的内容会以普通网页的方式呈现。
下面是内容摘要:
不老歌的默认字体一直是 "Courier New" 对于经常写代码的人来说,这种等宽字体非常亲切,看着也很舒服,不觉得费眼。
后来,我发现比较高版本的Windows里,有另外一个等宽字体,感觉比较漂亮,那就是Consolas,于是我长期把我的博客默认字体设为:
body, td, input, button, select, textarea {
font-family: Consolas, "Courier New";
}
body, td, input, button, select, text area {
font-family: Optima, Consolas, "Courier New";
}
code, pre {
font-family: Monaco, Consolas, "Courier New";
}
Monaco
http://bulaoge.net/?g3g4
个人感觉比较“可爱”的一款等宽字体
Gill Sans
http://bulaoge.net/?g3g4
比较有现代感——事实上,这款字体正是被归类为“现代”类字体,网上见到不少Keynote文档在使用这个字体,不过它被加粗的时候,就不太好看了……
Futura
http://bulaoge.net/?g3g4
这款字体跟上一个Gill Sans感觉差不多,加粗后的效果顺眼一些
Optima
http://bulaoge.net/?g3g4
就是现在我们在Mac下看到的字体,当然可能以后我的博客的字体还会变就是了,上述三个字体都是“现代”类的字体
Lucida Grande
http://bulaoge.net/?g3g4
最后一个推荐的字体,它虽然不在任何子分类里,却非常好看非常常见——因为它是Mac OS的默认字体
今天为了实验一个file api的效果,不得不放弃mac下自带的safari浏览器,装上了chrome——在这之前我觉得safari应该足够了,因为从开发者的角度看,我一直觉得chrome在内核方面的改造步伐过于激进,而safari相对稳妥一些,不过file api,这个东东safari确实没有支持,很纠结。在深思熟虑之后,还是决定把chrome装上了。
结果初次试用 chrome for mac 非常沮丧,再一次以无尽的崩溃为代价在尝试着新技术。具体问题是这样的:
我想实现的效果,是拖拽一个图片到页面上指定的矩形区域,然后把这个图片作为背景显示出来。这分为了两步:第一步,用ondrop捕获外部图片文件拖入的事件;第二步,可以从该事件的event.target.result中获取图片的base64编码,然后把这段编码赋值给background-image属性,格式为url(...)即可。
网上有一个现成的例子:http://html5demos.com/file-api,其实我做的效果跟这个大同小异。
这件事情chrome for mac完成的很好,但接下来,我想把这个程序稍作改进,就遇到了问题
由于被拖拽进去的文件不见得是图片文件,这导致有些情况下,文件拖进去了,却无法正常显示成图片,我想加入一个智能判断的环节,如果图片生成失败,就进行提示。其实现成的方案是存在的,就是先把base64编码赋值给一个img标签的src属性,然后分别通过捕获这个img标签的onload和onerror事件来判断图片是否可以正常显示。
这样我的实现方案就变成了3个步骤,4段代码:
* 第一步,用ondrop捕获外部图片文件拖入的事件,和刚开始一样;
* 第二步,可以从该事件的event.target.result中获取图片的base64编码,然后把这段编码赋值给一个img标签的src属性,监听onload和onerror事件;
* 第三步,如果onload事件触发,则再把这段base64编码赋值给background-image属性;而如果on error事件触发,则提示图片生成失败。
把代码写好以后,刷新网页,把图片文件再次拖入定义好的矩形区域,这时chrome for mac就崩溃了……
问题基本定位在了img标签在onload中再次处理这段base64编码的时候,更具体的原因就不得而知了。无奈之下,我只好把这个新加入的功能又去掉。
这件事情就算这样过去了,但是我更觉得chrome越来越不靠谱了,除了这次的问题,我之前还遇到过或亲眼目睹周围的开发者遭遇web socket协议更改导致之前写好的程序不能工作、Audio内存管理不善导致播放大量音频时内存不能释放直至崩溃等等——更何况我这次安装的不是beta版也不是dev版,而是面向大众的正式版。如果未来的chrome一直是一个“千疮百孔”的产品,恐怕会给前端开发者带来又一轮的灾难。
前阵子都是随便找个技术一顿瞎扯,今天扯点别的吧
是的,我又搬家了
为什么要说又呢?作为“北漂一族”的我,不妨借这个机会跟你介绍一下北京租房的那点儿事儿
作为一个外地人在北京混,租房子度日还是比较正常的,每当房租合同到期的时候,你会有这么几种遭遇:第一种是和房东和平续约,这是人品最好的情况,也再正常不过;第二种就是房东跟你说他要涨价了,这个时候你就需要纠结一下了,要继续住么?还是选个别的地方继续住——其实都挺闹心的;第三种就是房东不打算租给你了,勒令你搬走,最被动的莫过于这种了,毫无选择的离开;最后还有一种,就是老子(老娘)觉得自己有点小钱了,住不惯这里了,换个好点的大房子租/住了,这是最拉风的一种。
当然,这里面也有混合型,比如房东不想租给你了,你也不打算继续住在这里了,双方互不相欠,情投意合。
我在北京的这几年,基本没有遇到过第一种(要么说这种情况的人品好呢),第二种、第三种也比较少,以第四种居多。其实我是一个随遇而安的人,住房子一直也不分什么好坏,多简陋多扯淡的情况,我都能将就,但每次房租到期的时候,又很想借这个机会换个环境,看看能否租到更好的房子,所以我一般都是等这个时候主动提出来不打算续租的。另外房子总换(基本都住不了超过1年),由此我形成了两个习惯,一个是我的家当非常少,屋子一直收拾得比较简单整洁,因为这样可以很方便的搬来搬去;二个是我每次再看新房子的时候,都看淡了很多问题,比如家里的公共卫生(卫生间、厨房、走廊)怎么样、有没有暖气有没有网(这些也可以没有,很厉害吧!)、是不是中介的房子等等,我都有办法习惯和应对。
现在回过头来看,这几年的漂泊对我来说有着非常重要的意义。它帮助我学会和各种人相处,解决各种“家务事”,沉着面对各种突发问题;也懂得了很多做人的道理、明白了如何礼貌待人,怎样做是会打扰到舍友的、需要注意的,怎样化解邻里之间的误会和矛盾等等这些细小的东西。当然这些东西我很难说自己已经做得很不错了,起码我已经学到了很多。
我以前租到的房子大多喜忧参半,所以没啥特殊感慨,不过这次我租到的房子相当不错,有点小兴奋,所以想分享给大家
房子在一个新盖好的小区里,地铁站边上,上班路上的时间不超过20分钟。房子是两居室,由于在顶楼,所以是复式结构(二楼还有一间),房间通透明亮,干净整洁。而且没有什么隔间之类的东西,算我在内只住三户人,有共用的阳台、厨房、餐厅、2个卫生间、还包括一个大客厅。室友们也都很好相处,更令我不淡定的是,我们仨都爱看球赛,只要时间合适,都一起看球聊天,其乐融融
哈~哈~哈~哈~
房租嘛,比起以往的价位稍微贵了一些,但住起来真的很爽,我很痛快的租下来了!
搬进来的第一天晚上,躺在大床上,一个人傻笑得合不拢嘴,突然觉得,自己在北京没白混啊,有种苦尽甘来的感觉。
后来我又跟老爸视频来着,我端起自己电脑的摄像头,在屋子里环游了一大圈,老爸也很满意——他上次来北京看我的时候,我住的地方只有这里的1/3大,而且厨卫都很小,没有客厅没有阳台。
如果不出意外的话,我想在这里多住一段时间。
住在这间房子里面的感觉,是只有我初来北京的那年五一长假,漫步在槐花香味的大街上时,才会有的感觉。翻开这篇之前有感于槐花香味的博客,我还发现,当年的我有着与今天相似的感慨:
You have changed a lot. ——这是我一个同学对我近期状况的评价,我猜这里的changed是改善的意思。确实过去的几个月是我这辈子过得最爽的几个月,爽得有点忘乎所以。我现在真的觉着我是幸运的,能够自由自在的漫步在北京的街头,享受着槐花的芳香,而且不必为一日三餐发愁,这对于现在的我来说已经足够了,相比于很多仍在为自己前途命运发愁和困惑的同龄人来说,我真的很幸运,不管我们这一代人是不是幸运的,我已经知足了。不过这不代表我会安逸于现状,在转变身份的同时,我自己也多了一份稳重和成长,也感到了更多的责任感和压力。相比起现在的自由和快乐,大千世界的精彩以及自己的鸿鹄之志更加吸引我,更让我热血沸腾!!我相信,前方还有更美丽的风景,那里应该不只有槐花的香味……
发现最近自己的CSS编码能力有生疏的迹象……
今天犯了一个非常低级的错误
在此还原一下状况:
设置一个盒子的样式,包括背景颜色、背景图片、图片位置、还有边框,在:hover时还有一些变化,比如边框会变亮变粗。
一开始写好的代码,简化一下大概是这样的:
li { background: #999; border: 1px #333 solid; padding: 0.5em; -*-box-sizing: border-box; } li:hover { background: #ccc; border-width: 3px; } li.classA { background-image: url(images/classA-bg.png); }
一切正常,跟预想的效果是一样的
后来我注意到一个细节,当:hover之后边框变粗了,背景图片也相应的向右下方错位了2个像素。为了不想让背景图片错位,我想到了一个办法:就是利用CSS3中的background-origin属性对背景图片的起始坐标改为边框外顶点,这样边框的宽度就不会对背景图片的位置构成影响了。
于是代码变成了这个样子:
li { background: #999; border: 1px #333 solid; padding: 0.5em; -*-box-sizing: border-box; -*-background-origin: border-box; }
但是发现这一改动没有“生效”,背景图片依然会错位。
经过简单的调试,我很快发现了自己的低级失误,就是在设置:hover的背景颜色样式时,没有使用background-color而是background这个缩写:
li:hover { background: #ccc; border-width: 3px; }
它的出现使得li中的background-origin被覆盖为了默认值(即padding-box)。当这一属性不被修改时,只有background-image被覆盖,但li.classA刚好再次覆盖了背景图片的值,所以这一问题在当时被掩盖了,可只要稍微修改一下background-repeat、background-position、background-clip、background-origin等属性,问题就立刻被暴露了出来。
所以,在处理伪类等动态改变的样式时,如果涉及属性值的覆盖,尽量不要使用简写的属性值进行覆盖,因为很容易“伤及无辜”。
之前在处理background这类CSS属性的时候,无法就是颜色和图片,所以能否使用简写很好判断,一不小心写错了也很好辨别并更正。随着CSS属性越来越灵活,功能越来越多,现在一不小心写错代码,就需要判断更多的环节去排查。所以CSS属性的简写写法也不是在所有的地方都推荐使用的。
最后总结一下吧:在动态修饰样式时,需要慎用简写,避免“伤及无辜”,同时注意CSS3带来的更多考量。
附:background-origin和background-clip这两个CSS3属性的简介
俗话说得好:
十年树木,百年树人
国足的这些“年轻小伙子们”(以前看球都说大哥哥,现在自己有点年纪了,该叫他们年轻人了),在西班牙籍主帅卡马乔上任之后,看到了一些可喜的变化,我当时觉得,中国队“把现有的资源充分利用,脚踏实地,把路一步一步走踏实,就已经够厉害了”。直到今天晚上,看到伊拉克队在大名鼎鼎的济科教练的调教下,踢出了更有创造力和侵略性的足球之后,发现这还是不太够……
当然,我仍深信不疑,卡马乔执掌中国队之后,中国队就此踢上了现代的足球。尤其是他通过强调业务细节改变了团队的战斗力和精神面貌。其实大道理谁都懂,专业的都在细节里——这对于一个公司和一个部门或一个项目组来说,都是一样的。
但如果这还不够,那该怎么办?
我觉得这就该我们卧薪尝胆一下了。说白了我们今天这样慌慌张张的找补锅将,其实是因为我们之前的路没有走好:长期不重视人才战略,没有高质量的联赛,没有健康的文化和榜样。再深入想一想,我们的下一届国家队在哪里?他们在奥运会预选赛第一轮就被阿曼淘汰了,前两天在太原新落成的“红灯笼”体育场又0比2输给了帕尔梅拉斯青年队。4年之后,这支连CCTV解说员刘建宏都戏称为“史上最烂的国奥队”,肩负起冲击2018俄罗斯世界杯光荣使命的时候,就算足协能把弗格森、穆里尼奥、瓜迪奥拉都请来,又能怎样呢?
如果再想想未来的冲击2022年卡塔尔世界杯的人在哪里,他们正在身体发育、养成踢球好习惯的关键时刻。可中国现在有几个像样的搞青训的呢?怎么办?怎么办?到时候就算给亚洲10个出线名额,打进10强赛就能去卡塔尔,你做得到吗?怎么办?!
长期规划这四个字似乎从来都和急功近利的中国社会不太搭调,但10年之后的杯具,其实从今天开始就在上演了。在我们互联网这个圈子里,你说投资个东西,10年以后才成气,不管能成多大的气,准没人理你;相反你说今年能赚个几十万几百万,但明年就不好说了,大家准都抢着上。
那么2年的规划能不能打动人呢?呵呵,我还真想打算试试
我希望中国足球,可以给全天下的球迷许一个承诺。从现在开始,好好的把那些失去的东西找回来,不要再一味盯着眼前成绩了,青训、文化、产业,都认真反思一下,拿出个实实在在的10年规划出来。12年以后,也就是在我们筹备2026之前,拿出一个“成品”和“正品”来——不要半成品!不要残次品!
其实10年对于足球来说不算特别长期的规划,日本足球一规划就是50年,说50年之内能拿世界杯,一开始好多好事的中国球迷都等着看笑话,结果今年女足世界杯日本就已经兑现了这一诺言。这就是站得高看得远,科学发展观。之前嘲笑日本足球的人,现在只能自己找个地方钻起来了……
国足,10年之内,我不再指望什么了;10年之后,我再来看你
如题,就是这个活动:http://www.mhtml5.com/events/code-jam
另外也秀一下我们的研发成果——网页版的大富翁:http://jinjiang.github.com/rich-game
源码:https://github.com/jinjiang/rich-game
2天时间做不了太多东西,所有很简单,只能两个人玩,而且只能在特定的浏览器下运行(傲游3浏览器、谷歌浏览器、Safari for Mac/Safari for iPad等webkit内核浏览器)
这是我第一次接触比较正式的游戏开发,也是第一次玩code jam,而且我们的团队同心协力,最终拿到了最佳团队和技术两个奖项,感觉很美妙。
回顾这次活动,有一些感触,也有一些“成功秘诀”愿意分享给大家
前两天Windows8的发布会真是气势恢宏,发布当天,Win8的新闻立刻占据了几乎所有科技新闻的头版头条。那天的发布会我也通过网络视频的方式看了现场直播。这里简单记录了一些观摩Windows8发布会之后的感想。
工作嘛,只要有钱赚,开不开心不重要
产品好不好用,老板喜不喜欢,大家都在想的
我们从来都不考虑5毛的感受
都还光了,迟早得出来混的……
我饿了,你还不给我煮碗面吃?!
周末回老家参加小强童鞋的婚礼,顺便探亲。
不免跟家里人聊聊在北京这个大都市的生活状况,老爸老妈问我,最近跟同学们联系的多么?——他几乎每次都问我这个问题,可能觉得我太一心创业了,也可能觉得身边的朋友对我来说是一笔多么伟大的财富等等。
我也每次都利用老爸老妈问我这个问题的机会,审视一下我自己的社交状态。
“还行吧,偶尔约出来吃顿饭、踢踢足球什么的”
“XXX最近联系得多么?”老爸又问起一个以前关系很好的朋友的名字
“哦……没怎么联系,也没他啥消息,应该在XXXX工作……”
“XXX呢?以前不是总听你提起过么,我现在还记得这个名字呢。”老妈又问
“应该还在XX念书吧……”,我又想了想,“平时就是跟总在网上的几个同学联系比较多,平时不上网的就不怎么联系了……”
说完这句话,我突然惊讶的发现,过去自己身边的“朋友”,被我用互联网这个狗屁玩意儿划分成了两类:一类是常联系的,一类是不联系的。
因为互联网很方便,也因为自己是做这行的,每天都在网上泡着。我顿悟:好像感觉自己平时都在网上,周围的好朋友就都也会(甚至“理应”)每天挂QQ、上人人网、织微博或参与到其它社交网络之中。
——这种想法真是愚蠢至极
仔细想想,自己身边确实有这样的朋友或老同学,他们有的人习惯定期用电话、短信的方式跟周围的朋友取得联系,问寒问暖;有的人则喜欢给异地的老友相互写信,并把信件都珍藏起来;还有的人喜欢没事儿就约人吃饭、或直接串门打牌唠嗑之类的;当然,还有人乐于每天逛人人网,订阅同行同事的博客、关注朋友的微博——现在的我,就是最后这种。
小强平时就不怎么在网上出现,要不是他给我打电话,我多半会错过这位挚友的大喜事。而且在他婚礼上碰到的老同学,也都是在网上不常见面,过着很真实生活的人。真的很久没跟他们联系了,有的甚至是毕业以后就再没联系上的人。大家欢聚一堂,相谈甚欢,很开心,很温暖。而且大家越热情,我就越觉得自己惭愧……
我休假这两天一直在思考这个问题,我们的社会和科技都处在一个飞速发展的时期,是不是发现一种新的方式、新的思路、新的工具,就应该理所当然的把老办法、老观念、老技术都遗忘或抛弃呢?比如我们是不是真的可以因为QQ的存在而丢掉手机呢?是不是有电脑就可以接受提笔忘字这件事呢?是不是因为有网络就把自己锁在家里隔人与千里之外呢?
所以,我还是应该时常放下手中的电脑,多找北京的朋友玩一玩,多给异地的老同学打打电话,保持联系,多回家看看家人——也当是活动一下颈椎,预防一下脂肪肝,缓解一下视疲劳了。传统的东西还是经得起考验的、应该保持和尊重的;现代化的东西还是趋势的、值得关注的;后现代的东西……还是应该谨慎的。
说完自己的情况,我感觉自己生活的大环境也都是后现代的。
所谓的现代化信息社会,如今已经变了味,并没有带给这个社会更多的沉淀,而是自私、浮躁和急功近利;所有的东西都被贴上了“不靠谱”和“不确定”的标签;更可悲的是,我们的传统文化在渐渐的流逝。就像中国电子大厦门前的人行道一样,翻新了多少次,换了多少种砖,也没觉得好到哪里去,雨天走着一样打滑,还把沿街原本就经不起风雨的小树苗折腾得够呛……
做软件做网站也是一样(此处省略200字)
写到这里,感觉这个题目说得有点大了,我又“后现代”了一回
上周末的HappyElements(乐元素)工程师大会之行也是我和游戏公司的第一次亲密接触,收获颇多。
这间事情的由来还要从HTML5研究小组说起,我受研究小组田爱娜的引荐,希望为乐元素分享一些HTML5的技术话题。我原本有个自己的项目在赶工,当时是抱着“完成任务”的心态答应的,后来想想,机会难得,不认真准备,一定会后悔的。就卯足了劲构思和选题,我找了一个跟游戏沾点边,同时可以有很多演示实例的题材,就是界面展现中的新技术:Canvas/CSS3/SVG。
后来我又想了想,要想让分享有价值有效果,了解听众的习惯和口味是很重要的。所以,嘿嘿,我在出发之前偷偷跑去乐元素的官网瞅了瞅,并顺着那些社区游戏的介绍点进了人人网的游戏页面,看看游戏效果。……好吧,我平时不玩游戏,也很难接触到他们的产品,不过当我打开人人网的时候,我惊奇的发现,周围的很多好友都在玩他们的游戏!真了不起,对乐元素的崇敬之情进一步加深了。
参加其他公司的内部会议是一种很奇妙的感觉,尤其是当乐元素的老大和众骨干们谈及公司的发展、愿景、理想的时候,一方面会觉得自己有罪恶感,像是混在一群素不相识的人当中,还若无其事的偷听他们的私密故事,但有的时候也会误以为自己就是他们的一员了,会为他们的成功感到高兴和自豪,会被老板慷慨激昂的发言所鼓舞,充满斗志想要干点什么!
乐元素真是一个发展迅猛的公司,而正如乐元素的CEO所说,今天的成功,和他们的价值观、文化、心态、经营理念都有很大的关系。我从中学到了不少,并很认真的记了下来。我觉得相比起我从乐元素上学到的东西,自己准备的主题分享真是太不值一提了:
前端开发的最爱!
期待重生!
Canvas | CSS 3 | SVG | |
---|---|---|---|
优势 | 灵活、跨平台、像素级处理 | 事件交互性强、小巧方便、学习成本最低 | 兼顾灵活性和前端开发习惯、学习难度低 |
劣势 | 事件判定、平台的视觉体系无法利用、全部都是编程 | 效果有限 | 比较陌生,平台差异、代码健壮性 |
适用产品 | 大型或复杂的界面 | 文档类、功能型界面 | 规模不大,但对设计要求较高的界面 |
适用人群 | 游戏开发者 | 前端设计师、前端开发者 | 矢量图设计师、艺术家 |
Thanks & Q-n-A
]]>在此贴上本人受邀在 Happy Elements 的工程师年会的主题分享
在线PPT:http://jinjiang.github.com/slides/new-generated-html5-ui/
PPT源码:https://github.com/Jinjiang/slides/tree/gh-pages/new-generated-html5-ui
Web Page → Web App
所以我们从游戏开始谈起……
贪婪的人类啊!
大量应用横空出世!
Web Game 首当其冲!
Canvas/CSS3/SVG
游戏开发者的最爱!
接上一篇git和github简介(上)
$ git init
初始化仓库$ git status
查看当前仓库状态
# On branch master nothing ot commit (...)
$ git add . $ git add index.htm style.css ...添加文件到临时区域,前者为添加全部
$ git commit --all --message "xxx" $ git commit -am "xxx"提交修改记录。两者效果相同
$ git log
查看修改记录.gitignore
文件config.php
$ git mv ... ...
移动$ git rm ... ...
删除git mv/rm
和mv/rm
$ git branch
列出所有分支
* master xxx yyy
$ git branch xxx
创建名为xxx的分支$ git checkout xxx
切换至名为xxx的分支$ git merge xxx
合并xxx分支的修改$ git tag
列出所有标签
v3.1 v3.0 v2.5 v2.0 v1.0
$ git tag v2.0
生成一个新标签$ git show v2.0
tag v2.0 Tagger: Jinks Zhao ... Date: Sat Jul 3 10:06:16 2010 -0400查看该标签的相信情况
half-way to release 1
commit 08eaf7c6b... Merge: be96dbe 39a1b50 Author: Jinks Zhao ... Date: Sat Jul 3 07:28:16 2010 -0400
Merge branch 'xxx'
$ git describe
v2.0-2-g8ee0f4a显示以上一个标签为基础的提交情况
$ git add -i
交互式添加$ git reset --hard HEAD $ git reset --soft xxx $ git checkout -- <file.ext>恢复版本或文件
$ git diff $ git diff <file.ext> $ git diff <commit> $ git diff <commit> >file.ext> $ git diff <branch1>..>branch2> $ git diff <branch1>...>branch2>比较文件或版本之间的差异,有多种参数用法
$ git stash $ git stash apply如果你在完成一个功能特性的时候发现之前代码里有一个bug,但是这个bug又和你正在开发的功能特性无关,你想在完成整个功能特性开发之前就修复这个bug。但是现在功能特性已经开发一半了,这时候该如何处理呢?
发布、管理、派生
$ git clone git://github.com/nickname/projectname.git [localprojectname]克隆一个远程的仓库
$ git remote add <remoteName> <remoteURL>添加远程跟踪的分支
$ git fetch origin master获取远程最新的代码
$ git merge origin/master合并远程最新的代码
$ git pull origin master获取并合并远程最新的代码,前两者的快捷组合命令
$ git push origin master将本地最新的代码推送至远程仓库
$ cd existing_git_repo $ git remote add origin git@github.com:nickname/projectname.git $ git push origin master
$ git pull origin master
$ git push origin master
$ git push origin gh-pages
方便后续学习、实践
欢迎提问
]]>在此贴上本人在Web标准化交流会6月25日北京站的主题分享
在线PPT:http://jinjiang.github.com/slides/learning-git/
PPT源码:https://github.com/Jinjiang/slides/tree/gh-pages/learning-git
git 简介
下载、发布、派生……
在 github.com 上寻找并下载
自己想要的程序
从基本的命令行开始
whoami
- 命令行里的"hello
world"ls(dir)/cd/mkdir/rmdir
查看/进入/创建/删除目录cp(copy)/mv(move)/rm(del)
移动/复制/删除文件<cmd-name> -x --xxxx (/xx)
执行命令<cmd-name> -h --help (/?)
帮助信息注:括号内为windows中的写法
sudo apt-get git ...
git config --global user.name "..." git config --global user.email ...
配置github账户:
http://github.com/guides/providing-your-ssh-key
wa band (wa = web app) 的表演
已经
结束了
哇咔咔
---------- 温习一下梨花体 ----------
呵呵,就知道你们很挑剔的
现场去了超过600人,高朋满座
想要通过我们并不熟悉的音乐领域,在一个月当中的零碎时间里,准备一个小节目,能包容这么多的人的,包括为德高望重的李开复博士的致辞预热——事实上李开复博士全场只会看到我们一个节目——因为我们是开场表演,表演之后,李开复博士将致辞,致辞结束之后他就先撤了。。。
这些都是我准备节目的第一天起就知道或预想到的事情
田爱娜着实给我出了个不小的难题
斯坦尼斯拉夫斯基 Konstantin Stanislavski (1863 – 1938)
是一位著名的俄国表演艺术家(百度百科上有他的详细介绍),今天把这位老爷子搬出来,是因为最近看到了他表演理论中的一句话:
A character's actions will lead to his / her emotions.
动作引起真情
A character is sitting at a dinner table. All of a sudden the character quickly stands up and throws the plate at the wall, thus causing more anger in the character. Rather than just trying to be mad, the character made an angry motion, throwing a plate, that made the anger greater.
让一个演员拾起盘子使劲儿摔到墙上,把它摔碎,你会发现他产生莫名的很愤怒。这种激烈的行为会令人冲动,而且愈演愈烈。
图片来源:卓越亚马逊
今天想给大家推荐羽泉这张组合久违的新专辑。周末看到他们在《鲁豫有约》的一期节目,节目中回顾了羽泉一路走来的很多感动的事情,也谈到了他们现在的工作和生活:
《鲁豫有约》 之 羽泉一路感恩再出发(上)(下)
因为这个时代羽凡还说,他很欣慰在这么多年之后,仍然找得到创业般的感觉和激情,并享受其中,觉得自己仍站在一条跑道的起点上,要更努力。作为一个“成功人士”能保持如此有斗志的一颗心,非常令人钦佩。
我们每个人都好像特别忙
都比过去更忙 压力更大
有更多的竞争
想得到很多别人得到过的东西
然后于是就好多的烦恼 不快乐
有些时候可能我们@好几百人
看他们说什么 他们发生了什么
他们精彩不精彩的生活
高效的 八卦的
就是少了关心自己
所以《@自己》就是这个主题 关爱自己
你要拒绝别人给你贴标签如今的人们似乎习惯了生活在别人的世界里,而且面对不同的人,扮演着各种各样不同的角色;也习惯了东张西望,习惯了用相对于自己的双重标准,对别人指指点点,说三道四,当面说完背地里还会再议论,什么事情都要搞得乌烟瘴气;却不曾想过自己真正想要什么?是不是一直坚持着自己的梦想?有没有偷偷降低对自己的要求?
不管别人认为你是什么
往你身上贴什么标签
好的也好 坏的也好
你呀 还是要做你自己
在此贴上本人在HTML5研究小组5月14日技术分享沙龙的主题分享。和当天分享的内容略有出入,“出入部分”属个人言论,不代表HTML5研究小组立场
在线PPT:http://jinjiang.github.com/html5-slides-20110512
源代码:https://github.com/Jinjiang/html5-slides-20110512
赵锦江
HTML 5 === Web Application
注:部分动画和缩放特效暂只针对 Webkit v533.9 内核进行开发
欢迎提问
]]>上周为老东家写了一篇科普文章,也被几位热心的同事和朋友转发转帖了。有些读者批评我说这些问题太不值一提了,还拿出来说事儿。我虚心接受大家的批评。
另外想再做个补充:
首先这里的问题确实都是“科普”问题。相信觉得这些问题不值一提的应该都是前端的老鸟了,或者也对傲游比较熟悉了。真的不必对科普内容太过纠结。
第二,我举出的这些问题都是在北京、上海前端开发者的各大小技术交流会上最常问我的问题。现在很多前端开发者都不清楚我上述的引擎选用的逻辑,不清楚我们的浏览器还有Web Inspector开发工具——其中不乏国内知名顶尖的前端开发者。
我想举最近的一个例子:上周末和我 HTML5研究小组 的 @秀野堂主 一起聊到了傲游3,他之前对傲游3并不了解,听过我介绍傲游3已经在使用webkit内核并拥有web inspector开发工具之后,觉得不错,并给予了我们更多的关注。这些老鸟们眼中的“常识”就是这样通过我们耐心的解答和宣传一点一点被大家知道和熟悉的。
也希望今后,看过或了解这些傲游的“科普小知识”的人,周遭有人问到这些问题,不必觉得他们“不着调”。如果您还能耐着性子帮忙解答,傲游对您感激不尽!
最后,虽然有人会说问题很简单,但我很清楚不少前端开发工作需要这些问题的答案,还是会继续写。就像全行业都在大力推广html5一样,每次开会、宣讲,都是那一份ppt,但还是会天天讲,月月讲,确实有人看腻了,但有些人还不了解,这就是当今前端的现状。这样做就是因为还有很多人需要了解这些知识,我们也希望拉上更多的人跟我们一起参与进来。
以上
下面的问题是我遇到的周围人问的频度较高的几个问题
之所以在标题中把“拥抱html5”前后加井号,是因为这次参会第一次用微博和大家一起充分互动了一下,井号已经逐渐当strong标签用了。
这次参会只有两个突出印象:第一,html5为webapp而生;第二,游戏作为最热门的webapp,绝对是html5未来的主力军。
视频:http://www.w3ctech.com/2011/html5/video
讲师ppt:http://www.everbox.com/f/vBsCh9XnDvmhIOFixRFk4uoJak
今天办公室的私人数据库出了点小问题,对mysql进行了重装,但由于疏忽了部分配置信息,导致旧数据没有办法继续使用了,MySQL数据库服务无法启动。险些及时找到了问题所在,虚惊一场。事后觉得这类问题还是很容易遇到的,所以打算写个简单的备忘。
我的mysql是在windows下,用msi安装包直接安装的,我们经常会自定义或配置的信息有这么几个:
1. 程序目录
2. Server Datafiles 目录
3. InnoDB Tablespace 目录
4. 默认编码
5. 管理员密码
6. 各种封闭性选项(多为三选一或二选一)
对于6,估计相同的人安装会选择相同的选项,出错的几率应该不大
对于5,是比较有特征的信息且是一定需要设置的,出错的几率也不大
对于1,这个目标太过明显了,估计大家也不会装错,就算装错应该很快反映的过来吧……
那就剩下2、3和4了
我个人习惯,会把4设为utf-8(默认值不是这个,貌似是latin1)
另外2和3那两个目录很重要,一定要设置得和你的数据所在的目录相吻合,才可以正常工作。两者的设置时机不尽相同,前者在设置安装程序目录时一并设置,后者在安装完成后的初始化配置向导中进行设置。
这次虚惊一场,就是因为重装MySQL之后Server Datafiles目录忘记设置了,结果默认的安装目录里的数据是空白的,和InnoDB Tablespace目录信息无法匹配。导致1076错误,同时MySQL无法启动。
最后,提供一个最可靠的办法,就是寻找my.ini文件。不同版本的MySQL,它的my.ini文件可能在程序目录中,也可能在个人文件夹中或windows文件夹中,如果找不到,可以在本地硬盘里搜索。上述所有的设置项基本都可以在这个ini文件中找到。可以通过这个文件确认修改结果。
上个月末和家人一起随团玩了个港澳五日游。
这次是公司慷慨解囊,承担了我们一家三口全程的费用。
首先公司安排的这个团吃住都不错,早上有吃到饱的早茶,午餐有吃不完的乳鸽餐,晚上住在五星级的酒店里,考虑得都很周到。
然后是这几天的行程安排得很不错,当地导游的讲解也非常精彩,而且留给我们充足的自由活动时间,这样不仅熟悉了这里的状况,也玩得比较自由随性。
在香港和澳门这几天,有很多有趣的见闻,也有不少感触。
W3C 官方文档在此:http://www.w3.org/TR/css3-transitions/
p { transition-property: width, background-color; transition-duration: 2s; }在上述代码的基础上,每次对div的横坐标和背景色进行修改,其属性值都不会立刻切换生效,而是渐变生效,历时2秒钟。
Name: transition-property Value: none | all | [ 〈IDENT〉 ] [ ‘,’ 〈IDENT〉 ]* Initial: all Applies to: all elements, :before and :after pseudo elements Inherited: no Percentages: N/A Media: visual Computed value :Same as specified value.属性值 none 表示没有任何动画效果;
Name: transition-duration Value: 〈time〉 [, 〈time〉]* Initial: 0 Applies to: all elements, :before and :after pseudo elements Inherited: no Percentages: N/A Media: interactive Computed value: Same as specified value.写个代表时间的数字即可,比如 2s。也可以写多个,用逗号隔开。默认值是 0,即无动画效果。
Name: transition-timing-function Value: ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier(〈number〉, 〈number〉, 〈number〉, 〈number〉) [, ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier(〈number〉, 〈number〉, 〈number〉, 〈number〉)]* Initial: ease Applies to: all elements, :before and :after pseudo elements Inherited: no Percentages: N/A Media: interactive Computed value: Same as specified value.个人觉得大家理解 ease/linear 就行了,稍微进阶点可以再看看 ease-in/ease-out/ease-in-out,至于那个 cubic-bezier 神马的,感兴趣的自己慢慢琢磨吧。推荐一篇文章:http://www.cnblogs.com/cloudgamer/archive/2009/01/06/Tween.html (准确的讲不是推荐文章本身,而是里面的那个 transition-timing-function 预览功能)
Name: transition-delay Value: 〈time〉 [, 〈time〉]* Initial: 0 Applies to: all elements, :before and :after pseudo elements Inherited: no Percentages: N/A Media: interactive Computed value: Same as specified value.动画执行的延迟时间,同样是写时间值,属性值写法和 transifion-duration 类似,这里不做过多介绍。
Name: transition Value: [〈‘transition-property’〉 || 〈‘transition-duration’〉 || 〈‘transition-timing-function’〉 || 〈‘transition-delay’〉 [, [〈‘transition-property’〉 || 〈‘transition-duration’〉 || 〈‘transition-timing-function’〉 || 〈‘transition-delay’〉]]* Initial: see individual properties Applies to: all elements, :before and :after pseudo elements Inherited: no Percentages: N/A Media: interactive Computed value: Same as specified value.依次是属性名、时间间隔、渐变公式、延迟时间,不同的属性名可以写多个这样的组合,用逗号隔开。
p { transition: color 2s ease 1s, width 0.5s; }
background-color color background-image only gradients background-position percentage, length border-bottom-color color border-bottom-width length border-color color border-left-color color border-left-width length border-right-color color border-right-width length border-spacing length border-top-color color border-top-width length border-width length bottom length, percentage color color crop rectangle font-size length, percentage font-weight number grid-* various height length, percentage left length, percentage letter-spacing length line-height number, length, percentage margin-bottom length margin-left length margin-right length margin-top length max-height length, percentage max-width length, percentage min-height length, percentage min-width length, percentage opacity number outline-color color outline-offset integer outline-width length padding-bottom length padding-left length padding-right length padding-top length right length, percentage text-indent length, percentage text-shadow shadow top length, percentage vertical-align keywords, length, percentage visibility visibility width length, percentage word-spacing length, percentage z-index integer zoom number]]>
问题是这样的:
window.onload = function () {
var jsonStr = '{"name": "w", "sex": "male"}';
//method 1
var json = (new Function("return " + jsonStr))();
//method 2
function strToJson(str) {
return str;
};
var json = new strToJson(jsonStr);
}
问 method 2 为什么不能达到 method 1 的效果?
大家觉得对于这样的问题该如何解答呢?
我是这么分析的。
var funcName = new Function('return 1;');相当于:
function funcName() {return 1;}如果这两句 js 等价可以理解的话,那么 method 1 就等价于:
var json = (function () {return {"name": "w", "sex": "male"}})()
function funcName() {return 1;} var a = funcName();相当于:
var a = (function funcName() {return 1;})();进而:
var a = (function () {return 1;})();所以 method 1 又可以进一步等价于
function funcName() {return {"name": "w", "sex": "male"}} var json = funcName();
(function () {var a = ...; ...})()如果把函数里的代码直接写在外面:
var a = ...; ...这样的话,命名空间会被函数定义中的局部变量所污染。而上一种写法可以使你尽情的使用局部变量而不会有命名空间被污染的后顾之忧。
堵车堵得越来越严重了有木有!
地铁里面人越来越拥挤了有木有!!
有木有!!!有木有!!!
所以,
这些交通工具在北京都已经不靠谱了。
所以,
我决定做出些改变。
所以,
我把家搬到公司附近了,近到可以走着去上班。
而且,这个地方离地铁站购近,以备不时之需。
另外,我还把马哥的自行车给搞来了!很拉风吧有木有!
以后会以走路为主,骑自行车为辅,必要的时候再坐地铁。
---------------- 马哥在此的分割线 ----------------
说到马哥,这位仁兄应该也在交大待够了吧,我们同年上大学,我去西安上学了,他就在交大;我中间转专业学软件了,他还在交大;我大学毕业来北京了,他还在交大;后来他离开北京深造了1年,回来以后还在交大;我们在交大的老同学张鹏毕业了,他还在交大;我们以前在交大的老同学慧姐找到工作了,他还在交大;我们在交大的老同学刘磊毕业了,他还在交大;我来北京中间搬过4次家了,他还在交大;我们公司搬家了,他还在交大……
今天,马哥终于要和交大说拜拜了,连自行车也送我了。看来这次是真的要从交大毕业了,以后我就没法去交大找马哥踢球了啊有木有!以后交大就少了马哥这个镇校之宝了有木有!!
交大你伤不起啊!
真相在此:http://www.w3.org/TR/2003/CR-css3-color-20030514/#colorunits
----------------------------------------------
首先,css 的 color
属性值,可以是一个关键字定义,也可以通过数值来定义。且两者都有多种定义的形式。
body {color: black; background: white }
h1 { color: maroon }
h2 { color: olive }
em { color: #f00 } /* #rgb */
em { color: #ff0000 } /* #rrggbb */
em { color: rgb(255,0,0) } /* integer range 0 - 255 */
em { color: rgb(100%, 0%, 0%) } /* float range 0.0% - 100.0% */
em { color: rgb(255,0,0) } /* integer range 0 - 255 */
em { color: rgb(300,0,0) } /* clipped to rgb(255,0,0) */
em { color: rgb(255,-10,0) } /* clipped to rgb(255,0,0) */
em { color: rgb(110%, 0%, 0%) } /* clipped to rgb(100%,0%,0%) */
em { color: rgb(255,0,0) } /* integer range 0 - 255 */
em { color: rgba(255,0,0,1) /* the same, with explicit opacity of 1 */
rgba(0,0,0,0)
* { color: hsl(0, 100%, 50%) } /* red */
* { color: hsl(120, 100%, 50%) } /* green */
* { color: hsl(120, 100%, 25%) } /* light green */
* { color: hsl(120, 100%, 75%) } /* dark green */
* { color: hsl(120, 50%, 50%) } /* pastel green, and so on */
em { color: hsl(120, 100%, 50%) } /* green */
em { color: hsla(120, 100%, 50%, 1) } /* the same, with explicit opacity of 1 */
.tooltip {color: InfoText; background: InfoBackground}
写点跟上个月Web标准化交流会有关的东西吧。
第一次去百度的地盘,也领略了百度有啊的前端技术实力。
很佩服他们搭建了属于自己的前端框架和平台,也很佩服他们的团队可以把如此严谨而富有创造力的类库、规范、工作方法都执行的很好。在这些方面,我们还没有做得很好,刚刚上路而已。
提到前后端开发协作,百度有啊的童鞋的观点简单明确:让前端的工作深入到后端去,铲除不同技术的隔阂和界限。我相信百度的同行们都拥有着“超强的个人能力”,在前端的html/css/js老三样基础上掌握更多的语言和外延技能并不是难事,所以也收到了很好的效果。
当然交流会上也有一些不同的观点和看法,有人提到了在架构和解耦上多下点功夫,问题就会少很多。我也非常认同。同时协作开发中很多问题都是因为沟通不畅导致的,当然也可以通过沟通来解决。
如果把这些观点综合起来,从各个环节进行改进和注意,前后端的开发协作可以做得更好。而且我认为这其中一定存在着某种最佳实践,可以在更普遍的范围内为人们所借鉴和使用。今天会上讨论的几个方式我觉得还是稍显随意或局限于具体的环境和人。
在去年年底的D2前端论坛上了,杜欢的演讲主题《可复制的前后端分离开发模式》(在线ppt、ppt下载、现场视频)也提出了类似的观念和实践方式。同样让我受益匪浅。也许今年前后端分离会逐渐成为大家茶余饭后共同关注的话题——这可比html5实际得多。
在包括Photoshop钢笔工具在内的很多绘图软件里,大家会发现一般的曲线都可以通过对几个结点的拖拽完成修改,曲线的两端有两个端点,拖拽端点,就会改变曲线的起始点和结束点;两个端点周围又各自延伸出一个控制点,通过一条辅助的直线段和端点连接起来。拖拽两个控制点,曲线的弯曲度就会发生改变。总体感觉有点像织毛衣:一条弯曲的毛线,毛线两端有两根毛线针,呵呵。
这可能是大家对曲线直观的感觉吧,但其实这四个点构成的曲线就是一条三维贝塞尔曲线。
百科中对它的介绍比较抽象,不过我喜欢用数据和公式来说话:
首先n维贝塞尔曲线的公式为:
n
x = ∑ C(n,i) * t^i * (1-t)^(n-i) * xi
0<i<1
n
y = ∑ C(n,i) * t^i * (1-t)^(n-i) * yi
0<i<1
如果到我们这里的三维贝赛尔曲线,那就是:
x = C(3,0) * (1-t)^3 * x0 +
C(3,1) * t * (1-t)^2 * x1 +
C(3,2) * t^2 * (1-t) * x2 +
C(3,3) * t^3 * x3
= (1-t)^3 * x0 +
3t(1-t)^2 * x1 +
3t^2(1-t) * x2 +
t^3 * x3
= x0 (1-t)^3 + 3 x1 t(1-t)^2 + 3 x2 t^2(1-t) + x3 t^3
y = y0 (1-t)^3 + 3 y1 t(1-t)^2 + 3 y2 t^2(1-t) + y3 t^3
举个例子,如果起始点为(0,0),结束点为(2,0),两个控制点分别为(0,1)和(2,1),那么x0~x3分别为0,0,2,2,y0~y3分别为0,1,1,0。上面的公式就变为:
x = 0 + 0 + 6 t^2(1-t) + 2 t^3
y = 0 + 3 t(1-t)^2 + 3 t^2(1-t) + 0
这就是通过4个结点得到的贝塞尔曲线的二元参数方程式。
在实际应用中,我个人接触到的方程示意比较少,基本都是大家通过可视化的作图工具将曲线调整出理想的结果,然后再记录或推断出端点、控制点的坐标的。这里仅供了解理论知识。
首先,权威说明在此:http://www.w3.org/TR/css3-2d-transforms/
今天休假外出,在火车上研习了一本有关SVG的书SVG开发实践。这本书写得蛮不错,虽然理论知识略显凌乱,不过作为一本开发实践的书,已经极大满足了我对SVG的求知欲!
其实技术都是想通的,书中提到了几个很眼熟的东西,让我在看过之余也对其它领域的知识有了更深刻的认识和了解。比如今天我想分享的CSS3属性:transform
在此先做个提示,目前只能在最新版本的傲游3、谷歌、沙发里(不是春天里)、火狐、歌剧浏览器中使用,并且属性名要加-webkit-、-moz-、-o-的前缀。
Name: transform-origin Value: [ [ <percentage> | <length> | left | center | right ] [ <percentage> | <length> | top | center | bottom ]? ] | [ [ left | center | right ] || [ top | center | bottom ] ] Initial: 50% 50% Applies to: block-level and inline-level elements Inherited: no Percentages: refer to the size of the element's box Media: visual Computed value: For <length> the absolute value, otherwise a percentage
Name: transform Value: none | <transform-function> [ <transform-function> ]* Initial: none Applies to: block-level and inline-level elements Inherited: no Percentages: refer to the size of the element's box Media: visual Computed value: Same as specified value.
<div style="transform:translate(-10px,-20px) scale(2) rotate(45deg) translate(5px,10px)"/>
<div style="transform:translate(-10px,-20px)"> <div style="transform:scale(2)"> <div style="transform:rotate(45deg)"> <div style="transform:translate(5px,10px)"> </div> </div> </div> </div>
transform: matrix(a, b, c, d, e, f);
|a c e| |b d f| |0 0 1|
|x| |a c e| |x'| |y| = |b d f| . |y'| |1| |0 0 1| |1 |
摘自W3C的官方文档:
Name: border-top-right-radius, border-bottom-right-radius,
border-bottom-left-radius, border-top-left-radius</strong>
Value: [ <length> | <percentage> ] [ <length> | <percentage> ]?
Initial: 0
Applies to: all elements (but see prose)
Inherited: no
Percentages: Refer to corresponding dimension of the border box.
Media: visual
Computed value: two absolute <length> or percentages
<strong>Name: border-radius</strong>
Value: [ <length> | <percentage> ]{1,4} [ / [ <length> | <percentage> ]{1,4} ]?
Initial: 0
Applies to: all elements, except table element when ‘border-collapse’ is ‘collapse’
Inherited: no
Percentages: Refer to corresponding dimension of the border box.
Media: visual
Computed value: see individual properties</pre>
解读:
border-top-right-radius: 20px 10px;
border-bottom-left-radius: 50% 20%;
border-bottom-right-radius: 30px;
border-radius: 20px / 10px; /* 椭圆 */
border-radius: 20px 10px; /* 一个“扭曲”的圆角,不解释 */
border-radius: 30px; /* 最常见的用法 */
注意前两个意义完全不同哦
以上是圆角边框的书写规则,至于你这么写了它会显示出神马样子,在一些极端情况下各个浏览器的反应还是不尽相同。最标准的应该是下面这个截图的效果了
图片来源:http://www.w3.org/TR/css3-background/transition-region.png
这一点上不得不佩服 IE 9,表现相当完美。每一个细节都很完美。
DEMO
以前每次过年过生日,都会给自己许各种各样的愿望,盼着自己长大,让自己变得更好,同时憧憬新的一年会更美好。
今年过年是我第一次有老了一岁的感觉。
从角色上,自己多了很多身份,逐渐觉得自己不只是为自己而活着了,背包旧的很好看,让我走的好缓慢;从心态上,我希望自己的工作和生活都可以趋于稳定,在稳中求进步;从大环境上,兄弟姐妹、同学同事们都开始抢着结婚了,顿时觉得自己也到了该谈婚论嫁的年纪。
与此同时,还有一些东西在随着时光悄然变化着,那就是人的观念——对人生的观念,对价值的观念,对世界的观念。
过马路等红灯是件再小不过的事情,但在北京的大街上,你会发现很多人做不到,梁文道曾说“中国人普遍觉得被监督的规则才叫规则”,我有一天夜里12点走在高梁桥斜街,看到老外在空无一车的人行横道旁等行人的绿灯亮起,才过马路,在对其肃然起敬的同时我立刻就想起了这句话。
现在的我,不再那么容易冲动,渐渐学会忍耐,渐渐转为理解和释怀;不再那么敢于大胆尝试,也会因为更多的冷静而丧失灵感;不再总拿道德二字要求别人,而是默默约束自己;但对于自己曾被认为刚正不阿的处事原则,虽然还在坚持,但那条界限已经由清晰变得模糊;有些事情明明知道死也不该做,但是看似遇到一些难处,再挤出一些勉强站得住脚的理由,也默许了自己……
曾经希望自己可以做纯粹的软件,创造纯粹的社会价值,对那些不正当竞争嗤之以鼻,对捆绑安装和垃圾推广嗤之以鼻,也不在于什么孔方园把戏;等老板真正跟你谈到上市,谈到股票,你那种发自内心的兴奋迅速充斥了大脑,顿时觉得公司应该怎么样上市快就怎么来,大家怎么样能投机我们也能。透过这面镜子,我才发现,虽然看似个理想主义者,但在诱惑面前,自己也TM奔钱去了。
我以前最不爱听的话,包括“习惯成自然”,“没办法大家都这么干”之类的,现在我也不知不觉成了他们其中的一员。
如果以前有人问我,你是否在为自己的理想而奋斗?我的回答都是肯定的,今天我有了一丝犹豫;如果再问是否应该去奋斗?我又多了一丝犹豫。
追逐理想毕竟是有代价的
谁在为我的理想埋单?
我觉得也许是自己的青春吧。
现在仔细想想,可能还要再搭上朋友的青春,长辈的年纪,而且不是从今天开始的——已经一段时间了;另外自己的青春眼瞅着快挥霍完了,代价还是挺大的。
今天听到Fusion乐团的《成人世界》,回味了一下自己四年的工作,看看周遭朋友接踵而至的结婚请柬,想想自己的未来,想想我身边的人,再看看现在的自己。五味俱全……
希望今天过后,我还能感觉自己是年轻的,这比觉得自己年轻过要强很多
如果只是觉得自己年轻过了……那总比觉得自己没年轻过强一点点吧
---------------------------
《成人世界》]]>
词:陶步升
曲:陶步升
怀念着过往的感动 就像轻柔的风
静静的吹走 成长带来的承重
有时候也会冲动 试后才知道痛
才会往前走 一步一步的挪动
经历些许感动 克制了的冲动
琢磨不定的我学会包容
总是不停遇见 熟悉陌生的脸
在忙碌之中 寻找幸福原点
有时疲倦 还略带埋怨
试着让自己学会适应一些
可太多人改变 带着面具的脸
倔强微笑着不愿多说一些
到头来才发现 这是成人世界
恍然发现自己已踏入好几年
我也渐渐改变 戴着面具的脸
倔强微笑着不愿多说一些
到头来才发现 这是成人世界
恍然发现自己已踏入好几年
即公司的五星级厕所开张和60周年国庆阅兵+晚会之后,春晚我也是不会放过的。春晚我来啦!!
1. 舞台效果非常棒,不光是背景板,连地板的图案都是可以随节目的主题变换的,身临其境。
2. 主持人张泽群、朱迅宣布场外互动活动规则的时候,屏幕中间有一个奖品转动的特效——特效太小了啊,谁能看见呢 囧
3. 还是张泽群和朱迅,播报什么大使馆、企业新春贺词的时候,大字报明明就是电脑做出来的动画效果,还假装用手把大字报拖拽过来——那个动作有点二。
4. 网络词汇再次被僵硬的插进了艺术家们的嘴里,就好像台湾人说话说着说着突然蹦出来个“丫”字。
5. 小品依旧变本加厉的拿诚信开涮,只是它越开涮我越不安。
6. 志玲姐姐亮了。亮点有三:第一,董卿两次想把她招呼过来未果,林志玲急中生智还让人家表演人员站在中间演示那个空坐着弹琵琶的道具,全国人民都看出来了,当然董卿第三次伸出她的魔爪时林志玲还是没能招架住;第二,她紧张颤抖着的声音反复说了两遍:“这是一个空~~~箱子”;第三,连续换了三套服装,追平了当年小品演员李文启在《有事儿您说话》中创下的记录(主持人除外)。
7. 本山大叔的小品如期而至,导致我们楼上楼下街坊邻居开始集体放鞭炮捣乱以示抗议,本山大叔也很拾取,前后省略了一百来字就表演完了。
8. 汪峰没上春晚,旭日阳刚却借着他的歌火了,上春晚了,汪童鞋泪流满面。
9. 周杰伦用歌曲《兰亭序》提醒他的粉丝,自己已经一年多没出唱片了。
10. 一直接受不了那种歌曲串烧的形式,每一首歌都只唱一段,还意犹未尽的时候就被阉割了,直接换下一首。听音乐本来是个挺轻松愉悦的事儿,结果听这些串烧搞得自己很紧张很纠结。
11. 前年春晚12点以后觉得春晚已经奄奄一息了,就准备关电视,差点儿就错过了纵贯线;吃一堑长一智,今年果然等到宝贝了,李健和萧敬腾、方大同站在一起范儿有点不太正,不过节目还是“可圈可点”,两位港台的喷药估计也想着出来跟大陆的70、80后等中老年潜在消费者混个脸熟,没想到12点以后叔叔阿姨们都洗洗睡了,萧大侠还杯具的感冒唱破音鸟,你注定成不了刘谦小沈阳啊。
12. 我又发现一个规律,哪一家互联网公司去年被“整”了,到了年底必定会砸钱做广告上春晚,这个就不细说了,你们懂的:)
今天看完春晚的第一个小品《美好时代》之后,突然发现一个规律,就在某SNS留言到:
我仔细观察过了,连续好几年,春晚的小品都包含了一个字,我想这应该可以反向论证某种社会主旋律的存在。这个字是:骗
过年回家没带自己的笔记本回去,用了家里的XP台式机上网。
机器性能很一般,再加上为家人装了一套金山安全软件,就更慢了。
这两种情况交织在一起,迫使我在上网聊天的时候产生了一个念头,也唤起了我的一些美好记忆,那就是:
Tencent Messenger 2007——也就是我们俗称的TM
TM2007有多神奇,建议诸位有XP系统的从网上下一个试试看就好了。干净简洁的界面,实用的功能,还有小巧的体积和超高的性能。从它的问世之日起就深深的吸引着我。但很可惜的是,TM2007至今不兼容Windowns Vista以上版本的操作系统,而TM2007自从beta1之后,就停止了更新,他们的团队转而开发TM2009去了。
可TM2009及其更高的版本,实在让人大失所望,他们丢掉了原有的TM的精神,开始变得臃肿,变得花俏,变得华而不实……哎,想起来就伤感,不多说了。
有趣的是,微软的MSN也走了相同的路线,XP自带的Windows Messenger也相当简洁实用,结果现在发展成了越发臃肿难看的Windows Live Messenger——甚至还附带了一堆套件。
今天再次启动TM2007的时候,忽然发现,今天的软件对于好多功能和体验的追求,其实以前早就做到了,现在技术发达了,选择更多了,却迷失了,不知道何去何从了。
我回头非常愿意多夸一夸,深入的夸一夸,狠狠的夸一夸,TM2007有多TM的好!!
大概有这么些:
首先,常规菜单
第二,联系人“instance”搜索
第三,联系人头像
第四,只显示在线联系人的时候
第五,最常联系的梯度列表
第六,移动硬盘
第七,我的小秘书
第八,商务风格的、简洁的、在此基础上还可定制的界面
今天时候不早了,先欠着,回头图文并茂写给大家
现在大家做软件,总想与众不同,我不晓得是不是在为了创新而创新,但就是把一些很简洁的东西搞复杂了,搞晕了,搞退步了。
拿Web里的表单控件来说,系统原生的、现成的input/select/button/textarea/form放着不用,就因为“自定义空间有限”,硬要拿各种div模拟——尤其是select,最后搞了一堆恶心的float,勉强模拟出样式了,跟其它文字对齐了;点出列表来还要弹出个层,再模拟点击效果;然后接下来是没有tab聚焦功能,再绑定键盘事件;然后更恶心的来了,聚焦以后回车、上下键也要模拟,你又写了一堆js;好不容易都弄完了,扔给测试测一下,除了可能出现的各种诡异的bug之外,他们/她们会不断挑战你的想象力:文字为空的时候行高不够了,文字过长折行或看不见了或者样式乱了,选择框在页面最下面的时候列表层应该从上面弹出来——你一开始写的时候可能都没法考虑得这么周到,从此你陷入了无尽的维护任务中……别急,这还没完,有些事情会更让你绝望:弹出的列表需要是“模态”的(你可能会疯掉,MD神马是模态?!)、网页特别小或者列表特别长的时候列表本身需要弹出网页边框之外等等等等……
等你回过头来看自己做得这档子事儿,忙活了半天,要死要活,结果做出来的东西还是不完美,还让产品和设计嫌弃和鄙视,毫无质感,自己也憋屈,可其实就为了解决了一个所谓的“自定义空间”问题。
更可笑的是,这里面95%的设计和开发,select自己已经做到完美了。
这时候你把自己开发出来的“控件”换成select标签,通过简单的jsapi或option标签加上数据,体验一下,什么感觉?
除了TM2007和select这两个例子,我建议大家有条件的,把暴风影音1、pplive1、mini迅雷1、搜狗拼音输入法1.0都找出来用用,体验一下,什么感觉?
杯具的是,我突然发现Maxthon 2.x和MyIE相比,也是如此……
今天老爸还跟我说,你们这个傲游浏览器有些地方还不如IE方便呢,我一开始听了不敢相信,后来他在电脑面前一五一十指给我看,说了说他的理解,句句在理!我顿时石化了,觉得自己一个从业者,在大众网民面前,一无是处,像个白痴一样……
最后再多说两句。WEB本身是一个很简洁的世界,它的简洁美只有前端开发自己最了解。即使没有HTML5的帮忙,WEB界面几乎可以快速实现你想要的任何功能需求——其优雅之处正在于此;我们可以在看此简单到不能再简单的iOS系统里做各种各样的事情,可以在mac os里通过数得着的几款软件完成所有的日常办公、休闲、娱乐、创作,也是如此优雅自如,这一点,相信苹果产品的用户也感同身受。
今天的软件和网站,搞了这么多的花样,做了这么多功能和特效,换了一套又一套界面库,风格改了一版又一版,有几个越做越简洁,越做越实用的呢?
呼唤简洁!
前几个月基本处于闭门造车的阶段,傲游3的RSS阅读器功能终于有个雏形了,目前正在通过傲游浏览器3测试版本非常谨慎的对外发布。时间紧,任务重,忙得一塌糊涂,快要忘记自己是谁了,跟周围的同行也基本没怎么交流。
有关RSS阅读器的开发心得有很多,可以慢慢总结分享,现在想探讨的也是今天跟几个同行聊起的一个话题。在讨论某站点运用到的CSS 3 transition特效的时候,winter大神曰:
“如果PM出需求 美工设计 前端切图 后端接代码 请问谁会设计这种动画?”此话一出,我立刻追问winter自己对这个问题是否有自己的答案,于是大讨论就这样开始了……
这不是HTML5真是一语中的。
这只是HTML4 with transition
HTML4 with storage
这是我从杭州D2前端论坛归来,结合最近工作内容后的感受。
记得我刚加入傲游的时候,我的职位名称还不带“前端”这么冠冕堂皇的字眼——也就是个“网页开发”人员。那时每天最主要的工作,就是和页面布局、表单、事件打交道,随着ajax技术被越来越多的使用,我的工作范围扩大到了xml解析和较为复杂的字符串分割和拼接,但工作核心还是界面。那会儿的工作,翻来覆去好像也就是这些,如果xml或正则不太熟,简单问问边上后端或客户端的程序员就搞定了,核心竞争力就是dom/css还有firebug。
随着Web技术的丰富,“前端”这个概念的到来,以及人们对“前端”技术的高期望值,这种状况逐渐在转变……
我们在工作中接触的“传统模式”的网页越来越少,相比之下,网站的样子做得和应用程序越来越接近,现成的表单、css样式、html元素已经无法满足产品要求了,同时数据处理和逻辑控制变得越来越复杂,过去以界面-事件为驱动的开发模式逐渐变得不适用了,而以数据-模块为驱动的模式被更多的认同和运用。
再看看最近几次Web标准化交流会、WebRebuild大会、D2前端论坛、Google Fast Dev大会等等前端技术活动,探讨的很多内容都是非常抽象化、结构化的,不然就是算法优化和性能优化。对于每天只接触div+css重构网页,再利用jQuery绑几个提交按钮、表单验证、弹出几个用div模拟的对话框的前端工程师来说,可能体会不到那些新思想、新技术带来的欣喜,无法汲取真正的营养。但对前端抱有极大期待、拥有极大野心的童鞋们来说,无异于久旱逢甘露的感觉。而如何处理好前端世界里的各种数据结构与算法,就在其中扮演了非常关键的角色。所以未来前端的驱动力必然会在数据这个层面。
说“驱动”这个词可能比较晦涩不易理解,如果再解释一下,那就是我们在打算完成这项工作之前,最先要考虑的事情是什么,然后顺着这个方面起步,完成所有的工作。
如果想做好更为复杂的网站或Web应用,上来就先写html/css代码然后在静态效果上绑定各种事件进而完成js的开发模式会让你很容易就陷入了数据和逻辑的苦海——这其实就是界面、事件驱动的做法。而数据、模块为驱动的模式则是在一开始抽象出整个网站或应用的数据结构和数据逻辑,然后把界面以模块为单位进行拆分(粒度并非在事件这么小的级别),这样程序基础非常牢固,可以应付负责的业务逻辑和数据结构,也可以应付各种后期维护和调整。在这个时候,再去完善界面,绑好事件,就变得顺理成章,这部分的开发也不会因此变得复杂或费力。
想做好这件事,就需要深厚的程序员功底了。所以前端开发,在朝界面-事件驱动转型为数据-模块驱动的过程中,需要更多对数据敏感的工程师的加入。
前端开发,任重道远
最近做RSS相关内容的原因,特意去opml.org学习了一下大名鼎鼎的OPML(Outline Processor Markup Language)文件格式。
OPML格式是基于xml的,首先,在根结点下有一个head结点和一个body结点:
<?xml version="1.0" encoding="UTF-8"?> <opml> <head> <title>xxx</title> ... </head> <body> <outline ... /> ... </body> </opml>
head结点中的内容和具体的数据关系不大,大概有下面这么几个(最显眼的莫过于title了)
<head> <title>xxx</title> <dateCreated>xxx</dateCreated> <dateModified>xxx</dateModified> <ownerName>xxx</ownerName> <ownerEmail>xxx</ownerEmail> <ownerId>xxx</ownerId> <docs>xxx</docs> <expansionState>xxx</expansionState> <vertScrollState>xxx</vertScrollState> <windowTop>xxx</windowTop> <windowBottom>xxx</windowBottom> <windowLeft>xxx</windowLeft> <windowRight>xxx</windowRight> </head>
body结点中全部都是outline结点,表示了RSS列表的内容和目录结构,比如:
<body> <outline text="Folder 1" ...> <outline type="rss" text="Title 1" xmlUrl="http://xxx.rss" ... /> </outline> </body>
这里就是opml最主要的部分了,格式对于html/xml开发者来讲应该相当熟悉了。值得一提的是,几个关键的属性text、title、xmlUrl、htmlUrl、type、description中,title/description/htmlUrl都是可选的,如果text/title同时存在,会以text为准,而不是更常见的title,另外xmlUrl和htmlUrl分别表示这个outline的RSS地址和网站地址,而不是所有的RSS都有htmlUrl的。另外RSS的type必须是"rss"。
其实OPML可以做很多事情——远不止导入导出RSS这种,感兴趣的童鞋可以移步到opml.org。
这次回家最大的感触就是,电视闭路线越来越鸡肋了。
更深刻的感触是,家里的电视用掉的带宽远比用掉的闭路信号多得多。
以前我每次回家都有个习惯,就是把自己的移动硬盘带回家,里面拷满了各种各样的像电视剧、电影之类的视频文件,带回家以后用家里的液晶电视体验一把“家庭影院”的感觉。一家人围在一起看大片,其乐融融。
这次回家比较匆忙,硬盘里也没带太多东西,原本觉得挺负罪的,不过回家之后发现老爸老妈已经习惯了使用在线视频网站观看“高清”甚至“超清”的视频了。我体验了一下,除了大概每20分钟卡一小下,非常清晰!非常流畅!甚至比我用硬盘带回来的视频还清楚还方便……突然觉得自己好土=。=
我又想起我的一个高中同学,他现在还在学校念书,用的是校园网,平时访问校外网络是要按流量计费的,所以他们平时就习惯去校内的bt网下东西,而校内的bt种子永远只有最新的东东才找得到,所以必须下载下来,找个硬盘保存好,以备不时之需,上个月,他刚刚又新买了一个500G的移动硬盘,至今仍然沉浸在幸福和喜悦之中。
看到家里的超/高清网络电视,再想想这位还没“解放”的老同学。不禁感慨,互联网的发展真快,对生活的改变真大。也许很快,移动硬盘这种东西就会逐渐被各种在线服务和移动终端所取代吧。
再看看网购的那些事儿,我记得第一次见到活人网购,是我大四毕业之前,通过舍友帮我充手机费的机会发现的;直到我第一次网购;又直到我在网上给家里订购了一台液晶电视,老爸老妈第一次坐在家里,等着工作人员送货上门,然后非常体贴的把电视装好,然后礼貌的告辞;直到家人自己学会了网购,直到刚上大一的老弟告诉我,他们宿舍所有人都所有日用品、服装鞋帽的消费几乎都是通过互联网的时候……
哇!
互联网诶!!
我的工作诶!!!
如图:
有幸参加了John的北京见面会,故合影留念。
照片中John右侧的两位分别是我过去的战友和未来的战友。
最近比较忙,也有机会参与了很多前端项目的架构工作。
之前,作为一个“干体力活”的前端工程师,我一直觉得架构是个特别性感的活,感叹优秀的架构师是如何在各种复杂的项目中都能够考虑到方方面面的。
后来,随着工作的深入,我拉近了和架构师之间的沟通距离,在和架构师们交流的过程中,我逐渐发现,原来架构的工作是有很多原则和套路可循的,像庖丁解牛一样性感而潇洒。很多开放新的问题,可以通过简单的思维方式,瞬间变成一个很简单的选择题。
然后,我有幸参与了一些简单的架构工作。在亲自“操刀”的过程中,我发现,其实架构,最基本的思路,就是把产品设计师的构想从中文翻译成英文(我的意思是编程语言基本都是基于英文的),所以优秀的技术和优秀的设计往往是不谋而合的。一个技术架构的过程,其实就是将技术和艺术巧妙结合的过程。做架构不光是个性感的活,还是一门艺术。
如今,我有很多前端架构要做了,在性感和艺术之间,我又逐渐发现……
其实架构还是“体力活” - -
——这也是我前阵子QQ签名档的由来
当然了,这个结论丝毫没有让我失去对架构工作的兴趣和热情——就像我当年第一次玩JavaScript一样,虽然被认为是“体力活”,但因为有性感和艺术的相伴,所以还是乐在其中:)
funny everyday with front-end architecture
本月末的Web标准化交流会将要讨论的两个话题,其实都是在说“前端开发帮浏览器开发擦屁股”的问题:第一个话题是是否应该擦,第二个问题是该怎么擦,擦的怎么样。
很多人说起internet explorer 6,气就不的一处来,显得很冲动。觉得每天调试各种浏览器之间的兼容问题是浪费时间,劳民伤财。所以才喊出了“IE6 must die”的口号。
但我觉得这个问题问得很有趣,口号喊得更有趣(如果问题和口号是他们的因果关系的话)。
首先,兼容ie6,是在创造价值,而不是浪费时间
就我观察到的现状和国情,很多前端开发正是因为ie6的存在才活得很滋润。如何解决ie6的外边距双倍的bug,如何写条件注释,如何避免ie6下的内存泄露,都是他们每天茶余饭后,津津乐道的话题。其实道理很简单,最大众的浏览器还是ie6,他是近70%老百姓的上网工具。我们让这么多人可以正常的浏览网页,享受Web技术带来的方便和快乐。这是一个创造价值的过程,而不是浪费时间。我猜如此嫉恨ie6、嫉恨兼容问题的前端开发,是不可能为用户做出好产品的。
而大家抛开ie6之后会怎样呢?抛开在浏览器兼容性上的经验和研究,专门在Firefox或Webkit上开发Web,大家能搞出多少花来?有多少前端团队在搞模块化开发?又有多少人专注于js库上层的架构?多少人专注于前端图形/图像处理、前端数据存储机制、前端网络通信机制?多少团队做得出Gmail这样的应用?大家的前端价值都体现在了什么地方呢?
其次,“IE6 must die”是喊给谁听的?
我觉得这句口号最开始应该是Firefox/Opera/Safari/Chrome(或他们的五毛党,在黑社会里真正出手的一般都不是老大)喊给前端开发者的口号。目的是忽悠大家灭掉微软,而不是为了用户。
那么我们究竟在喊给谁听呢?
喊给前端开发同行吗?让他们停止为70%的老百姓服务?
喊给用户吗?难不成也让用户“二选一”?更何况是向用户喊出如此不礼貌的口号
喊给浏览器们吗?这是他们自己喊出来的口号,怎么又喊回来了?
所以,这句口号自己意会就好了,在不认为这句口号自私之前,我是永远不会跟任何人喊这个口号的。
最后,我不代表前端开发界,我只代表我自己。所以我很感兴趣大家会在这个月底的交流会上说些什么
我们交流会见
今天在人人网上看到收起/展开面板的按钮做成了大概下面这个样子
【→】展开的目录
【↓】收起的目录
觉得有点奇怪,好像感觉写成
【→】收起的目录
【↓】展开的目录
更合理一些。但是这种东西也没有找到什么明确的规定,所以也说不上对错……
可起码从个人角度感觉,下面的图示更直观一些。
因为箭头的方向表示了目录的“状态”,说的深入一点,是“引导用户往哪个方向看”的问题。如果目录是收起的,那么只有右边的标题可以看,所以用【→】;如果目录是展开的,则引导用户顺着思路看下面展开的正文或详细内容,所以用【↓】。
那么收起/展开的按钮一定时表示“状态”的么?其实也不一定,比如:
【+】可以展开的目录
【-】可以收起的目录
这时就变成对“动作”的表述了,再说的深入一点,是“预告用户点了这个按钮会怎样”的问题。
但是不管是“状态论”还是“动作论”,两种思路都是和它的图形表现紧密联系的,不然就会像人人网的界面那样——引起误会到不至于,毕竟大家智商都够用了,但产品是做不完的,对体验这东西的精益求精应该是永无止境的。
前端开发也是如此
学英语也是如此
先贴我为上周六Web标准化交流会准备的ppt《HTML 5 实例演示》:
http://www.slideshare.net/jinjiang/html-5demo20101030/
以及示例的代码:
http://github.com/Jinjiang/HTML-5-Demo-20101030/
以及会后的合影(转自崔凯先生的博客):
顺便说一句,这是我第一次使用github(恩,有点意思,以后应该多玩玩)。
同时,在会上了解到很多html 5的特性已经在移动端或浏览器扩展这类的环境中发挥优势了——非常令人振奋的消息!
不过我也发现一些“有趣”的现象:很多团队都会通过 qt + webkit 建立一个属于自己的网页环境并打包成app发布,这样有一个明显的弊端:就是再小的app,也需要内嵌qt和webkit的代码,所以大小几乎逃不掉5兆的。
想真正把web技术广泛运用,路还很长……
我想 html 5 的概念今后应该不用再讲了,接下来我们应该多多探索其中的内涵,完善它们的上层建筑——基于 html 5 代码库和代码框架,多多了解甚至参与相关的开源项目,多多学习“跨界”的技术知识——比如数据库操作、图形学编程、甚至是月影兄在会上提到的高中几何;再做更多的demo进行亲身体会。相信大家会为 html 5 和前端开发“春天”的到来做好更充分的准备!!
最近在W3C的网站闲逛,囧然发现《Web SQL Database》草案被无情的归为“Obsolete”一类。这意味着web数据库的一个重要js api就此倒下。
更具讽刺意味的是,与此同时,有一个被归为“Completed Work”的文档《Offline Web Applications》中还介绍大家通过web sql database的相关方法进行离线存储……
W3C,我穿越了吗?
好吧,在我搞清楚这件事情的来龙去脉之前,大家可以在这里对web sql database有一个简单的了解。其实我对web sql database被淘汰出局早有预感,因为它的问题和优势同样明显:不同的数据库程序,sql的写法都不尽相同,没有非常统一的规范。在没有统一规范的语法基础上制定上层规范显然是不明智的,因为web开发者面对这样的规范,依然需要为各类数据库底层程序做兼容处理——相信很多php等后端语言的程序员对sql语法及其兼容层的运用应该有非常深刻的感触。
同时,另一个W3C草案逐渐被大家所接受和推崇,那就是《Indexed Database API》,它抛弃了主流但却无法统一的sql语法,与之对应的是一套全新的语法——就像后端程序里的数据库操作接口封装一样。它保证了不同数据库、不同浏览器下的接口一致性。相信会更令web开发者感到满意!
不禁感叹,优胜劣汰的残酷。
1年前还在用webkit玩web sql database,觉得好酷,开发者工具的配合也很好,但转眼之间,随着indexed db的推出和完善,web sql database立刻就被抛弃了。
为web sql database默哀的同时,也为新技术鼓掌!!
上周参加了Google互联网开发技术交流会。
好吧,我最近总是跑来跑去的
大家见我每次更新博客,都会提到近期参加了某某活动,有感而发……
html5能做什么?大家看看这个html5幻灯演示基本就已经修炼成功了。
----------------------------------------
没错,html5其实没有什么特别高深的东东,我还参加过好多html5题材的讲座和分享,他们甚至连原创slide都懒得做,直接把上面那个演示拿来稍微改一改就敢讲——因为真没什么新东西了。再看看lifesinger的会后点评,真是一针见血!
我个人的建议也是类似的,新标准只是形式上的共识和统一,但是形式后面的理论,诸如图形学编程、分布式计算、网络通信、数据库设计与管理等等,对于传统的程序员是很平常的,对于前端开发领域却都是全新和陌生的,这些传统程序领域的知识缺失才是真正阻碍html5实践的最大困难。
----------------------------------------
还有一个问题:国内的老百姓都在用ie6上网,而ie6是不支持html5新特性的,我们做出各种酷炫的html5应用又能怎样呢?
在现在的国内互联网公司,进行html5实践其实是一件很奢侈的事情——因为ie6居高不下的市场份额决定了它的投入产出比会很低。
很多人会想到一个折中的办法:通过所谓的“优雅降级”,让支持html5的浏览器有更好的界面效果和用户体验,同时兼容ie6等主流浏览器。
听起来不错,但兼容这个词对于前端开发来说简直是个噩梦,好多公司的现状是兼容现有的html标准还来不及呢,谁会迁就那个没人用的新标准呢?我猜最多是作为市场推广和宣传的噱头吧——况且从利益的角度讲,就算是“优雅降级”,也应该让用户数量最多的ie6最“优雅”吧?你自己想想这堆屁事儿不觉得纠结么?
另外现在搞得定html5的人毕竟还是少数,并且分散在各自的战壕里。只有大家的力量汇聚在一起,才会发生我们想要的那种大事件。
还有更好的solution么?
当然,我们还有另外一个选择——重新洗牌:给前端开发一个纯粹的html5世界,这个世界里全部都是崇尚html5的同道前端工程师,大家在同一个世界里工作,交流,奋斗;这个世界不需要再考虑兼容问题了——反正兼容了也没什么意义,何必劳民伤财;这个世界彻底摆脱了旧事物的束缚,会更加敏捷,完成更有冲击力的新产品,产生更广泛有效的社会影响力!
----------------------------------------
这样的环境真的会有么?
想要就会有!你懂的。
让兼容两个字成为历史吧!让大E就等于上网的时代成为历史吧!
<!DOCTYPE /> <html> <head> <style type="text/css"> body { color: blue !important; } </style> </head> <body style="color: red;"> hello world </body> </html>
今天在某技术群里的小发现 看上面的代码,hello world最终的颜色是什么? 答案是蓝色 比内联的 inline css 的优先级还高!
以前一直没注意过这个细节,今天学起来了
]]>接着上周的Web标准化交流会说起
老规矩,先上图:
我嘞个去,这货不是我,这货不是我
会场的样子
大家的合影
更多照片请移步到此:http://www.douban.com/photos/album/31291526/
资料来源:http://www.slideshare.net/josephj/webrebuild
这里有真相!
这对于我这种完全不懂linux/apache的人来说,真是花了不少功夫……
1.首先要在linux上面安装apache/php,我使用了Ubuntu自己提供的安装包和直接安装的方式。
2.然后认真学习了apache的配置文件,了解了Ubuntu里apache的默认配置信息,也看懂了蒋定宇的配置文件。
3.把apache的配置文件也写好了,重启apache,发现网站还是没有跑起来——系统提示apc_fetch()函数执行失败
4.又去php.net查阅了一翻,发现它是属于PECL的一个包,默认是没有被安装的。于是开始想尽一切办法把APC包装好
5.一开始安装了PEAR PECL,然后把网上的最新版本APC包下载下来,可怎么也安装不上,似乎文件包和安装命令不太匹配的样子,这个问题纠结了我很久,直到刚才……
6.昨天我请教了我们公司后端开发的同事,他说,我按照Ubuntu自己提供的安装包安装,这个是一个关键:即安装目录是被拆开放到不同的文件夹中,这使得安装第三方扩展包的时候无从下手。刚才我又想到了这句话,心想如果APC也通过Ubuntu自己提供的安装包安装,应该就100%没有问题了。于是打开“新立得”软件包管理者,搜索,果然搜到了一个叫做“php5-apc”的模块,安装完成。
7.于是欣喜得去刷新页面——还是没打开
8.其实这离成功非常近了,重启apache,或,重启Ubuntu,东西就都出来了(就是上图的样子)
太TM兴奋啦!!
这周抽空研习了一下蒋同胞的modev,突然发现,这么好的营养自己却吸收困难……
首先是对YUI(3)还有js框架的概念太陌生了,一直有些感性认识,但没有实际碰过。赶紧借这个机会找来大把的资料来翻阅,除了YUI3的主站也看到了玉伯先生的blog和克军先生的ppt。恩,一块大骨头!坚决啃之……
然后是linux/apache/git…… 真是悔恨平时开发太依赖windows/iis和图形化管理工具了,git可能还好搞定,毕竟对svn指令有一些接触,rapidsvn也用了那么久,看过下面的console代码跑(囧)。apache/类unix 就得多看书多实践了,我得给我硬盘里的Ubuntu扫扫灰了,另外周末再去跑一趟书店XD
再有就是jslint/jsdoc/phpdoc/cssdoc这些东西了(哎我越写越惭愧,连这个都一直没学起来……),最近周围讨论编码规范和习惯的比较多,得把现成的工具用起来!
最后就是英语啊 英语啊 英语啊
前阵子跟美国的同事讨论议题 好囧 讲到一半没词儿了 还得我们老大给翻译 -.-
总之 要加油了
明天早起参加公司的“粉丝同乐会”去,希望跟我们的老用户深入交流,擦出些火花来~
webrebuild.org几个小时前刚刚结束
参加今年的大会,首先是找会场找了好久 囧
然后,又见到了好多老面孔,认识的,寒暄一下,不认识的,围观一下
当然,重要的是,今天蛮多收获
说点跟工作无关的吧。本次世界杯对我来说,有几个特殊的感觉
1.周围是个人都在看世界杯
这是我今年感触特别深刻的,以前总觉得这种比赛,如果不是球迷,不会真感兴趣的,最多装个样子,陪周围朋友看两眼甚至聊个两句就了事的,而且坚决跟球迷划清界限——看来我一直低估世界杯的社会影响力和魅力了,因为我发现周围的人,男女老少,都在为世界杯而疯狂,连以前班上、现在公司里的女生,都在cry for agentina了,而且为能够被男生称为球迷而自豪——甚至我们叫她们“伪球迷”她们都很自豪!以前从不聊足球的哥们儿,见面就是满嘴的哈维、穆勒、克洛泽、弗兰。我突然觉得我以前每个周末深夜都不是自己一个人在战斗啊
2.预言帝、阴谋论频繁出现,足球的魅力正在褪色
前阵子看了个谈话节目,说足球和电视剧的区别是什么,回答就是足球只能看live,如果比赛不“新鲜”了,你很可能是在看到赛果新闻的情况下看球,什么悬念、什么刺激就都没有了,而电视剧随时可以按下暂停或直接看重播,没有什么新闻会来烦你,你可以永远保持那份悬念和刺激。可是,如果未来真的是可以预见的,或者球赛的结果是被操纵的,足球比赛还会有人愿意看么?我不太相信时光机这种东西的,但我逐渐接受操纵比赛这种可能性了
3.国际足球需要差异化竞争
感觉32强的打法越来越相似,越来越功利,比赛也越来越焦灼,回首64场比赛,强弱分明或令人荡气回肠的比赛都屈指可数了——顺便跑个题,在看世界杯官方演唱会的时候CCTV的主持人提到了一个词叫“世界音乐”,意思是把所有地域音乐的元素揉和在一起的音乐风格。我觉得各个国家队的足球风格也有点走向“世界足球风格”的意思,这对绝大多数国家来说可能都是好事,但从国际足球的角度看,其实并不令人推崇。我们希望看到个性鲜明的足球的激情碰撞,而不是PES 20XX的反复重播——仅仅是球衣和国旗不一样罢了。
4.我萨让我很有面子!
喜欢巴塞罗那的足球有一阵子了,前阵子球队不管是赢切尔西,还是输国米,看着都挺不得劲儿的。现在西班牙夺冠了,对中有7名主力来自巴塞罗那,有一种释放和解脱的感觉——球迷的心态总是很夸张的。
5.每天晚上终于可以约到人下场踢球了
耶!
上个月的Web标准化交流会中,看到了有关“Javascript”设计模式的探讨。交流会的探讨虽然结束了,但还是有点意犹未尽的感觉。
“设计模式”这个词最早就是从Java那里听来的,相信很多工程师的私人书架上都有这本书。由于交流会话题的关系,突然对这本书颇有兴趣。所以连夜把Java设计模式一书温习了一番。
看过之后,再次证明了一点:一本好书,每次读它,都会有新的收获。
这次再看这本书的时候,脑袋里已经不再是那些Java代码和UML框线图了,而更多的是Javascript、Ajax角度的思考,从而归纳出语言无关、面向对象无关的抽象理解。这应该才是真正的“模式”吧。
所以我突然觉得,“模式”这种东西,是一种极其确定的,高度一致的,可以保证解决确定问题的流程和思维,不是能够通过一门语言或一个实例甚至几行代码就能够阐述的。而且“模式”与“模式”之间并不是并行的,它们在横向分类或纵向过程中产生联系,形成了丰富而又清晰的方案。我们如果对其进行合理的识别和运用,就可以通过有限个“模式”解决开发中大部分的问题。
-------------------------------------------
首先,“遍历迭代器”就是个最简单也是最常用的“模式”,也就是说我们需要对一个集合中的每一个元素进行相同的操作。像:
for (var i in obj) { var member = obj[i]; ... }
for (var i = 0; i < arr.length; i++) { var item = arr[i]; ... }
都是遍历迭代器模式,如果遇到其它的数据结构(比如dom链式表),就需要不同的方法,但模式是不会变的:
var node = root.firstChild; while (node) { ... node = node.nextSibling;}
它们有共同的“模式”,就是我们需要一个角色,通过一个确定的规则,逐个访问到集合中的元素,最后结束,期间要保证不能重复访问元素。
以上就是“遍历迭代器”模式的含义。今后遇到类似的技术需求,就可以大胆、机械的直接运用“遍历迭代器”模式了,不用费时费力胡思乱想了——这是模式希望带给我们的。
-------------------------------------------
“模板方法”模式提醒我们的是,遇到数据相同,表现不同的东西,可以创建不同的表现“模板”,让数据和表现分离的同时,方便数据丢到不同的“模板”中,快速展现出不同的视觉效果。比如WordPress博客站令郎满目,但其实数据库结构和业务逻辑都是一样的,不一样的地方,就是“模板”了。以后想自己建个博客,不用多想了,搞来个WordPress或Typecho换换模板就行了。WordPress专门把和“原材料”无关的界面表现独立开来,正是运用了这种“模板方法”模式。
-------------------------------------------
“工厂方法”模式是“模板”模式的一个典型应用——这里可以看出“模板”之间的关系可能是各种各样的,这里他们两个就是包含关系。有的时候加工出一种对象比修改、使用、删除它要复杂的多,性质也相对独立,就可以把这类任务交给这个类的一个特别设计的类或函数。正如“工厂”的字面意思所言,把外部依赖性不强却又复杂的工作拿给“工厂”里,狭义意义里的程序都是不知疲倦的,所以这个工厂比现实中的血汗工厂还要无情而又保证产出质量。是每一个准求利益的人的福音。
-------------------------------------------
有的时候我们会单纯的认为,只有抽象出来的东西,才可以做各种各样的事情。真实存在的东西,都只能完成特定的任务。其实并非那么绝对,首先“单例”模式告诉我们,抽象出来的东西也可以只用来完成特定的任务;而“原型”模式、“生成器”模式则告诉我们,具体的实例也有能力生产出更丰富的内容,完成丰富的任务。
-------------------------------------------
设计模式还告诉我们,程序的世界里,“分久必合、合久必分”。
“组成”模式和“装饰者”模式,分别阐述的思想是:将内容和容器的形式统一起来,最易懂的例子就是文件和文件夹组成的文件系统了,文件可以放入文件夹,但文件夹本身和文件一样都是文件系统的最小单位;对数据内容的创建和包装都看作是生成新内容,从这个角度讲,对数据的创建和修改可以统一。此所谓“分久必合”。
“访问者”模式和“职责链”模式,则是从另外一些角度,帮助程序员将大问题分离开来:“访问者”是提供“到此一游”的方法,将数据本身和数据操作分离;“职责链”则是把状态本身和状态操作分离。此也所谓“分久必合”。
“桥接”模式和“策略”模式,从名字听起来很空,实际想表达的意思就是:先把综合问题区分为几个独立问题,然后把各自解决的结论组合起来;或者根据上下文不同,选择不同的处理策略,走不一样的发展道路。此所谓“合久必分”。
-------------------------------------------
“享元”模式是另外一个我认为需要强调的设计模式,它的思想是尽可能的把程序结构分得很细,并将常用的东西作为程序的元来供大家重用。我们在编写各种底层js框架的时候,就需要大量这方面的思考。但即使不是底层的实现,我们在设计上层建筑的时候同样要时刻注意,是否可以把某些程序段变成“元”,并补充到类库或框架或全局变量中以供大家重用。
-------------------------------------------
最后介绍两个更为抽象的模式:“命令”模式和“解释器”模式。“命令”模式旨在把任何层面的程序或操作规约,并转换为统一格式的行为表述——我们的各种log日志就是在做这件事情;而“解释器”则旨在创建一种语法规则,通过这个规则,对相同数据类型的不同数据实体解读出不同含义的内容,各种协议(比如HTTP协议、FTP协议)就是在做这件事情。
-------------------------------------------
至此,还有若干个设计模式没有提及,不过上述内容基本覆盖了70%我们在程序设计和编写中需要考虑的各种问题了。这些模式是从思路层面为大家提供一种“最佳实践”,让我们更高效准确的完成程序的设计或算法的设计。同时也指导我们创造出更多的“模式”和“最佳实践”。如果诸位通过上述讲解有所领悟,那将是我莫大的荣幸。
以上
在个人应用软件的战场,打得最热闹的恐怕就数这三块了。
有竞争,才会有进化,当然也会有优胜劣汰。
比如词典,他们始终懒得翻译那些Webkit里的文字,直到最近有道词典新版的出现;
比如输入法,他们始终懒得做个多平台的输入法——当然了,Web输入法勉强算是做到了……
那些“懒惰”的软件,最终会被市场无情的淘汰。
所以我们不应该仅仅拿“创新”二字来衡量成败得失。
注意——不是肠子!(一个冷笑话)
最方便、省心的方式,就是和各种衣服一齐放入洗衣机……
当洗完衣服之后,你会在洗衣机里发现一个崭新的钱包~ 钞票会干干净净的躺在里面,银行卡、公交卡、饭卡也会干干净净的!当然了,钢镚儿你得仔细找找……
Wowww~ It's COOL!
Wait! That's not over.
接下来,把湿漉漉钱和各种磁卡贴在你们家柜子上。这下你可以放心休息了!明天一大早起来,你会发现地上掉了好多钱和卡——这是一件多么令人兴奋的事情啊!!
相信我。我介绍的方法真的不错。
而且我今天刚刚试过。
尽管我做这些都不是故意的。
IE 9 Preview 发布有一阵子了,给人两个突出的感觉:软件平台化和对硬件的解放和充分利用。
首先是软件平台化——这当然不只是从 IE 9 的全名上看得出来的(Internet Explorer Platform)。对 W3C 标准的支持已经很彻底了,性能也优化了,“功耗”大大降低了,看得出微软在这方面的决心和信心;除此之外对 SVG 的支持很不错,而且可以通过 js 控制其动态显示效果,也许我们会在不久的将来看到很多基于SVG的酷炫富应用;同时还有很多诸如Web数据库、LocalStorage之类东东的支持。当图形处理和各种数据通信与存储的方式都得以实现之后,出现在我们面前的,俨然一个平台。
另一个方面则是,通过对GPU的充分利用而解放CPU,从而在保证流畅的显示效果的同时,用能力做出更复杂高效的逻辑运算和数据处理。从一些微软提供的浏览器端图像处理的Demo来看,GPU加速的效果是相当明显的!我相信GPU加速是浏览器业界的一个“好开始”,同时也会孕育更多的Web应用甚至Web系统。
相比之下,其他浏览器也在紧张的忙碌着,只是方向和策略有所不同。新一季的浏览器大战愈演愈烈了!(每每想到这里,脑海中就会浮现红警3的激昂旋律)
如果你是一名前端开发者,赶紧学习HTML5、CSS3、DOM3、SVG、Canvas、Web Socket、Web Worker、Web Databaes、Local Storage……等等这些新东西吧。或许它们会给互联网带来更多的惊喜!
So does your life.
刚通过朋友提醒,今天是著名的CSS裸奔节~
废话不多说了,来裸奔了
大家也快快加入到裸奔的行列中来吧
不老歌童鞋们如果想CSS裸奔的话,代码如下:
var list = document.head.getElementsByTagName('link');
for (var i = 0; i < list.length; i++) {
if (list[i].rel == 'stylesheet') {
document.head.removeChild(list[i]);
}
}
var tempList = document.head.getElementsByTagName('style');
for (var i = 0; i < list.length; i++) {
document.head.removeChild(list[i]);
}
昨天有幸参与了一个前端研发流程及其高效协作的相关讨论。
照片来源:崔凯先生的博客
会上的《Web开发中的角色与协作》一讲讲得非常好:
当今的前端开发,俨然用户需求、视觉设计以及后台程序的中心。却又势单力薄,缺乏三方的“保护”。所以在一个团队中,前端与上述三者之间的“距离”往往会是很致命的。
有句话叫:不以物喜,不以己悲。凡事保持一颗平常心,是一种高超的思想境界。
从字面上看,这句话隐藏了另外两层意思:
不以己喜,不以物悲。最近有感而发,想说一说。
中国队终于击败了韩国队,而且是3比0的大比分。
对于我这种看了十二三年足球的人来说,真是无比激动!
我始终相信中国足球的未来会美好的,看过我之前blog的童鞋应该明白我想说的是什么:首先足球本身是很有正面力量的运动,任何负面新闻都改变不了这个事实;第二足球是职业化高度发展的运动,中国市场这么大,没有搞不好的理由;第三,也许很多人不关注我们的青少年成长环境,徐根宝的“中国曼联”正在孕育着一批优秀球员,并已经为国家队贡献出了张琳芃这样的国脚,郝海东也投资了自己的足球俱乐部,以百年俱乐部为目标,高瞻远瞩,为我们勾画出了一幅足球的宏伟蓝图;最后也是最重要的一点,这是我们自己的球队,我们没有理由不支持!
另外有人猜疑这次大胜韩国会不会有假球的嫌疑,我的观点是一贯的,那就是我们可以保证中国队的每一位球员都是全力以赴的!香港裁判对中国队有没有偏袒呢?我看了比赛觉得裁判很职业,没有偏袒的迹象。那韩国队的发挥有没有问题呢?这个问题我们就不用替他们发愁了吧……如果有,那也完全是他们的损失和耻辱。不能以此否定中国队的成绩。
哎,讲得有点严肃了
现在中国队终于迈过了一道坎,可喜可贺,可歌可泣。未来还有很多里程碑有待完成。我愿意继续关注足球,继续期待。
有句话讲得好:“足球小世界,社会大球场”
我们每天的工作和生活又何尝不是类似的情形呢?不断面临着挑战,面对着各种外界的诱惑和困惑……我们只有知难而上,坚持不懈,才会继续前进,才会有出头之日!
过年了说点励志的吧 哈哈哈
之前写过一篇Javascript的学习笔记,貌似过去很久了……
今天是第二篇。
-----------------------------------------------------------------
Javascript中的非函数式语言特性
1. Javascript中提供的变量作用域共有三种:表达式(直接量)、语句、函数(局部变量)和全局(全局变量)
2. typeof null == 'Object';
3. 对象只有“构造于某个原型”,并不存在“持有某个原型”,所以构造器(实质为一个函数)的原型是一个实例(对象),而实例(实质为一个对象)的构造器是一个函数(构造器)
4. 函数只有在需要引用到原型时,才具有构造器的特性。而且函数的原型总是一个Object()构造器的实例。不过该实例创建后,constructor属性总先被赋值为当前函数,即:MyObj.prototype.constructor == MyObj
5. 多个原型和多个构造器之间交替相联系,形成了一条原型链
6. 万物皆为prototype链 + props链,空对象{}可理解为其prototype链为Object.prototype,props链为空
7. 一般Javascript的对象(构造器)组成为:内置对象(Number/String/.../Math/Global/Arguments)、引擎扩展对象(ActiveXObject/COM/XML/...)、宿主对象(window/navigator/image/...)
写得有点粗略了,有些东西还是挺晦涩的,没有写出具体的例子……
暂总结如下,下一次是函数式语言特性的部分
其实是去年12月19号是事情了。
阿里巴巴园区外
会场的坐席
第一次参加这样的前端活动,收获不少,而且回京之后把自己的收获分享给了部门同事,进行了话题的二次探讨和交流。当然也借此机会见到了几个老同学,品尝到了正宗的西湖醋鱼和东坡肉,欣赏到了杭州西湖的秀美风光。
这理想谈一谈我对D2中几个话题的想法和感受:
首先是刘彦先生的开场致辞,正如致辞中所说,前端技术从探究浏览器兼容性、到追求性能、再到优化用户体验,经历了好几个阶段,而未来前端关注的,是更自然的交互行为。
模板语言与大前端:
大前端的概念我非常认同!前端和后端本身就没有很严格的界限,更不能认为其中某一端不重要,其中的模板和控制器是前后端协作的重中之重;模板语言是之前接触比较少的东西,听大为兄这么一介绍,发现这东东离我们已经很近了!会后又研究了一下大为兄推荐的名为lite的项目,感觉很神奇,还在研习中。另外大为兄在主题中提到了几句话我觉得很有道理:
不能看见显示,就对显示编程(显示只可以看做是数据的拷贝)
足够复杂了,就应该简化
工具是人主动使用的,平台是人置身其中的
YUI3与前端演变:
YUI3是个很有趣的东西,用“自助餐”和“点菜”来比喻不同时期的前端脚本开发,非常形象。克军的语录:
好用的库 = 一堆工具 + 一本说明书
新时期的多人协作,强调对语言的管理、对结构的管理和对实现的管理
惯例 > 配置
理念 > 技术
SilverlightQQ:这个主题听睡着了……
我比较关心的是对于需要Silverlight这样的新技术支持的工程,团队是如何建设和运作的,设计师和开发者又是如何协作的——因为我们的公司也面临着类似的问题。至今仍然没有很清晰的答案,还在摸索中。
前端安全:
我司1年前已经做过专题探讨了,老内容 + 新概念而已。主题中总结出来的一些防范措施是很有价值的内容,比如过滤输入转移输出、控制Cookie域和GET/POST方法、综合运用Cookie/Referer/Session/Token/IP等等。另外Flash/Flex/Silverlight/HTML5等方面的安全话题这里并未涉及……应该也是有些环节值得关注的。
前端性能:
这也是我们平时很常见、很头疼的问题,主题中提到了好多优化的点和响应的自动化模式——这些方面正是我司不足的地方,所以要赶紧学回来!
另外想称赞一下这个小家伙~ 现场鲜活、实时的交流——以前都在馋老外玩这个,现在我们也做到了,很棒!我会后还去注册了一个人间网的账户,在里面又认识了不少童鞋。
最后是自由讨论发言时间了,我原本以为电视上的技术PK只是个传说……这次亲眼看到了两位正则达人的正面对话!会场的技术氛围非常浓厚,两位大侠也是惺惺相惜——这才是我们想要的论坛!
本次D2之行还有一些很突出的感受,首先这里是一个很纯粹的技术天地,没有任何杂质,一群志同道合的兄弟朋友聚在一起,很亲切;其次是很多互联网公司都给予了前段技术很大的关注和投入,我隐约感觉,我们在这方面已经被其他公司拉开差距了,一定要抓紧,一定要抓紧。
最后贴几张杭州的留影:
西湖景-1
西湖景-2
我在阿里巴巴园区
西湖边-1
西湖边-2
西湖边-3
最近在协助某神秘的“有关部门”调试一个神秘的 B/S 系统,发现其中有一段代码是对 XML 数据进行解析的,XML 数据来源可能是字符串,也可能是一个 XML 文件。在原来的系统中,这一部分的实现方式是 IE only 的,即使用 ActiveX 对象,实现 load/loadXML 的方法。所以现在要解决的问题之一,就是使这一段代码可以在IE以外的浏览器环境中正常运行。
后来发现了 DOMParser 这个东东,可以在 IE 以外的环境中对 XML 字符串进行解析处理,问题解决了一半:
function loadXml(xml) { var doc, parser; if (typeof ActiveXObject != 'undefined') { parser = new ActiveXObject ("MSXML2.DOMDocument"); parser.async = false; var flag = parser.loadXML(xml); doc = parser.documentElement; } else { parser = new DOMParser(); doc = parser.parseFromString(xml, "text/xml"); doc = trim(doc); } return doc; }
对于读取 XML 文件,有查到一个document.implementation.createDocument('', '', null)
方法,但只能在 Firefox 下使用,放到 Webkit 核心的浏览器中还是不能正常工作。所以这里直接用到了 Ajax 请求的同步处理方式:
function load(url) { var doc, loader; if (window.ActiveXObject) { try { loader = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { loader = new ActiveXObject("Microsoft.XMLHTTP"); } } else if (window.XMLHttpRequest) { loader = new XMLHttpRequest(); } if (loader != null) { loader.open('GET', url, false); loader.send(null); doc = loader.responseXML; } return trim(doc); }
这两大段函数体中都出现了一个共同的函数:
function trim(doc) { if (doc && doc.childNodes) { var firstChild = doc.childNodes[0]; if (firstChild && firstChild.nodeType == 7) { doc.removeChild(firstChild); } } return doc.firstChild; }
这个函数是为了统一各种不同的 XML 解析结果的根结点(有些解析结果会附带编码说明,或直接取编码说明后的兄弟结点)。
理论阐述如此,而且已经在自己的本子上试过了。明天去有关部门实践一下~
这几天在家休假,不过也没有忘了看书学习。摘一些给大家:
----------------------------------------
http://www.gracecode.com/archives/2987/
网上看到的php5.3的必包的文章,有点看得云里雾里的,大家也来看看
http://www.gracecode.com/archives/2997/
有关SQL注入的讨论,顺便给大家推荐这个博客,可以订阅来读读
http://css-tricks.com/new-poll-formatting-css/
http://css-tricks.com/different-ways-to-format-css/
有关 CSS 书写格式的介绍和讨论
http://blog.csdn.net/java060515/archive/2007/08/09/1733396.aspx
Javascript 中最常用的55个经典技巧
http://www.qianduan.net/
再推荐一个博客《前端观察》
http://www.showeb20.com/?p=2431
IxEdit: 所见即所得的jQuery设计工具
http://paranimage.com/9-tabs-based-javascript/
9个基于js的tabs实现
http://readitlaterlist.com/
这个应用 我个人很有需求 就是临时存一下用的更轻便一些的在线收藏 不过他们做得不够方便 我司做这个应该有先天优势吧
----------------------------------------
就这么多了,好久不写东西了,技术先行。
恩恩
I download Ubuntu 910 Operating System at the first time.
Just as the news said, U910 updates the loading screen, and shows mail icon, im icon, bluetooth icon, network icon and sound volumn icon etc in a smart place. That's very convenient for me.
And then, I installed Firebug in Firefox, QQ for Linux & Google Chrome for Linux. All of them are my indispensable applications. I'm writing this post in Google Chrome for Linux.
Pity that I havn't install any Chinese input method yet. So just an English post here.
从工作和人员安排上看(尤其是Web):
1.切图和设计工作统一安排比较合适还是和开发统一安排?
2.产品和需求工作统一安排比较合适还是和设计统一安排?
3.维护和开发工作统一安排比较合适还是和部署统一安排?
在我看来,切图和开发在一起完成更合适、把产品和设计统一考量更合适、开发和维护分开比较合适。
不知道大家的观点如何
最近看到一个聊天室的例子:http://css-tricks.com/jquery-php-chat/
与其说是一个聊天室的demo,不如说是借这个demo构建了一个聊天室的开发原型。
一个聊天室要有通信机制,要有身份识别机制,要有数据存储机制,要有表现机制。这差不多就可以简要概括一个聊天室的构建:
通信机制
需要Web的Ajax前台和Php后台协同工作,这里分为获取已有数据(这里是定时循环获取的)以及发送新数据(留言)这两点,额外还有一个页面初始化的操作(确定数据的起始点)。
以上三部分分别对应源代码中的:
updateChat()
sendChat(message, nickname)
getStateOfChat()
分分合合,都有讲法,都有政绩。
但它终究是个圈,原地踏步。
总说弯路是一定会走的,发展一定是曲折的……
在我看来,更像是推卸责任
而且总拿这种理论说事,把人搞得一点信心和耐心都没有了
把自己搞得很忙,觉得自己比以前进步了,都只是对得起自己罢了
要对得起大家,更需要智慧,更需要绝对能力
不然就都是穷折腾
非常浅 浅浅的……
看到国庆阅兵和晚会,不免会想到一年前的奥运会开闭幕式,分享一些我自己注意到的细节
1. 主席阅兵时站的车换大灯泡了,蛮“口爱”的
2. 朱尔镕尔基戴着墨镜,头发全白了……
3. 广场两边装了大屏幕,这个体验非常好!
4. 直升机航拍的效果太玄了,阅兵的时候,“看到地图上整齐的小坦克,好像用鼠标划住编队啊!Ctrl+1,Ctrl+2...”;到了晚上再看,我自己在北京呆了这么久都没像那天晚上觉得北京的夜景如此绚烂!
5. 导播切了几个以往被认为是“状况外”的镜头:一个是战机从机场起飞的镜头,一个是海军舰艇的镜头,很好很意外
6. 探头摄影机越用越有心得了,知道从近处标兵的简章拉到远景了——貌似这个早该会了 囧
7. 说说解说员吧,首先,我个人已经习惯了罗京先生的解说……在此缅怀一下罗哥
8. 阅兵快结束的时候,也就是小少先队员们想他们10年前的先先锋队一样放飞气球的时候,二位解说员还没来得及“对本场比赛进行总结”,随后出现的一幕是:“出席本次阅兵的有……”一直念到先锋队收队以后才念完,画面很混乱
9. 晚会的中央翻板跟历届的阅兵相比终于没有墨守陈规,但跟奥运会相比嘛……好吧,不能说没新意,算个加强版吧。So does the fireworks.
10. 《大中国》这首歌唱得我很激动,尤其是唱到副歌:“中国~~祝福你~~~”
11. 广场两边装了大屏幕——这个之前有了——我现在想说的是我们终于可以在国内玩“在大屏幕上找自己”的游戏了!唱看NBA、足球转播的朋友们应该对这个游戏不陌生吧,这个很好!以前就算有大屏幕,都在显示比分、字母什么的,包括意大利超级杯的时候……无聊死了
12. 大神们与民同乐!
13. 中秋晚会的舞台太炫了!而且是第一次把主会场搬到北京以外(不如说是国庆直播任务太重,直接承包给了江西卫视……)表演者的阵容也很强大,可惜假唱……有点小遗憾
暂时想到这么多,零零碎碎的,大家有什么新发现可以留言补充
今天在网易体育看了一则专访欧洲金靴弗兰的新闻。摘弗兰回答的几句话送给大家:
关于乌拉圭和阿根廷的世界杯生死战:
我记忆最深刻的进球不是为哪一家俱乐部的,而是为乌拉圭进的。在祖国的旗帜下,我除了竭尽全力战胜对手没有第二选择。我知道,阿根廷很危险,与乌拉圭的比赛很可能决定一切,但是,乌拉圭队也想去世界杯,我们一定会拼到底。如果赢了阿根廷,我只能对阿奎罗说一声,对不起,对马克西道声抱歉。(注:弗兰是乌拉圭人,而阿奎罗和马克西是弗兰在俱乐部的两位阿根廷籍队友)
中国足球就应该学习维拉里尔这样的俱乐部,一个5万人的城市经常亮相欧洲赛场,因为那里人人热爱足球。
有些俱乐部有8,给你3,有的俱乐部给你2,因为他只有3,后者更能打动我。]]>
WPF 的语法对于一名 Ajax 选手来讲,是非常有诱惑力的!
今天把 DwmExtendFrameIntoClientArea 这个方法应用到了 WPF 下面。
之前那个例子是在 Forms Application 下实现的——由于之前不太清楚 WPF 中如何找到 DwmExtendFrameIntoClientArea 的第一个 hWnd 类参数,今天查了下:
IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle;
HwndSource mainWindowSrc = HwndSource.FromHwnd(mainWindowPtr);
mainWindowSrc.CompositionTarget.BackgroundColor = System.Windows.Media.Color.FromArgb(0, 0, 0, 0);
DwmExtendFrameIntoClientArea(mainWindowSrc.Handle, new MARGINS(-1, -1, -1, -1));
传说中的 Aero 这个词,是指 Windows 窗体的一种半透明的显示效果。
在 Vista+ 的操作系统里,窗体是自动有 Aero 边框的,但有些程序不仅仅是边框才有 Aero 特效。比如 IE / Media Player 等等。(这两个例子都快被人举烂了……)
前阵子看到了 Firefox 4.0 的未来设计图,整个窗体都是 Aero 特效的,看过一直流口水。经过一些无聊的探索,我发现了将 Aero 特效从边框扩展到其它区域的实现方法。
以下是 C# 代码
在 C# 代码出现之前
好吧
表问我为什么 Ajax 程序员会写 C#
表问我为什么会写 C# 却去做低贱的 Ajax
表问我为什么决定了要做 Ajax,还回头去看 C# ……
internal class DwmApi { [DllImport("dwmapi.dll", PreserveSig = false)] public static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, MARGINS pMargins); [DllImport("dwmapi.dll", PreserveSig = false)] public static extern bool DwmIsCompositionEnabled(); [StructLayout(LayoutKind.Sequential)] public class MARGINS { public int cxLeftWidth, cxRightWidth, cyTopHeight, cyBottomHeight; public MARGINS(int left, int top, int right, int bottom) { cxLeftWidth = left; cyTopHeight = top; cxRightWidth = right; cyBottomHeight = bottom; } } }这段代码引入了:
private void Form1_Load(object sender, EventArgs e) { if (DwmApi.DwmIsCompositionEnabled()) { DwmApi.DwmExtendFrameIntoClientArea(this.Handle, new DwmApi.MARGINS(-1, -1, -1, -1)); } } private void OnPaint(object sender, PaintEventArgs e) { if (DwmApi.DwmIsCompositionEnabled()) { e.Graphics.FillRectangle(Brushes.Black, this.ClientRectangle); } } protected override void WndProc(ref Message m) { base.WndProc(ref m); const int WM_DWMCOMPOSITIONCHANGED = 0x031E; switch (m.Msg) { case WM_DWMCOMPOSITIONCHANGED: if (DwmApi.DwmIsCompositionEnabled()) { DwmApi.DwmExtendFrameIntoClientArea(this.Handle, new DwmApi.MARGINS(-1, -1, -1, -1)); } break; } }这三个函数分别做的是:
照片憋了很久才放给大家看,是因为一直没空整理。
说起半个月前的事情,现在还有很多感触。虽然我更喜欢的国际米兰没有获胜,不过能够有幸享受这个美好的足球之夜,90分钟混在蓝黑球迷阵营里扮演着山寨梅阿查声浪的一份子,我已经很知足了。下面一起看照片吧。
---------------- 分割线 ----------------
最先给看两个最有效果的!
鸟巢全景
鸟巢全景2 (开赛前)
强插广告:有一点遗憾的是这里不允许上传更高画质的图片,诸位没有眼福看大图了(其实这两句话是广告情境设计),想看大图请通过屏幕下方的联系方式请与我联系,只需支付$5.99。
上周末跟几个同事去爬了一下中央电视塔。
爬上塔后我感叹道:全北京都在这里了!
图1:我在中央电视塔上
图2:我们在中央电视塔上
在电视塔,不光可以鸟瞰京城,还有一些趣味项目可以参与和体验。比如:
图3:CCTV-5 奥运赛事直播节目
主持人:刚才那场 110 栏比赛真是太精彩了!
嘉宾:是啊!头一次见人往休息室那个方向冲刺的…… 囧rz
Nokia 1202
虽然型号很低,但拥有好多先进智能手机没有的优点:
1. 单色白屏,节能省电,待机时间超长;
2. 短小精干,操作简单,低辐射;
3. 铃声够大,字体够大,可以用到老年…… 囧rz;
4. 价钱便宜。
再搭配上我的小i~ 无敌鸟!
有谁想体验一下由 Nokia 1202 的 mic 传出去的声音的,可以给我打个电话感受一下~
每体验一次,您将为中国电信捐出一份爱心
明天上午9点,日全食,长江流域众多地区可以看到。好多人想去看的样子,蠢蠢欲动。其实没什么稀罕的。
浅浅的 淡淡的
最近公司所在楼层的厕所进行了改装,传说中已经算是5星级的了(也不知道是怎么算出来5星的)。但真的比以前的厕所强么?我个人可不这么认为,如下:
1. 本公共厕所加装了一个扬声器,循环播放古典或传统民族音乐,这个问题不大;
2. 本公共厕所似乎采用了先进的声控(或红外控或正太控或萝莉控)系统,只要“控不到人”,就关灯关音乐关换气扇关空调,抛开控得准确不准确不说,那个换气扇和空调能不能多开一会儿?
3. 更何况“控”得还不准。有的时候人明明在,灯就关了,过一会儿立刻又好了,有人没人的技术判定很恶心……
4. 不管是坑还是斗还是池,都改红外控放水了(这些可以确定是红外控的),但是坑和斗都提供手动按钮了,池没有,有的时候天热想在池边洗把脸真是一件痛苦的事情……
5. 既然坑、斗、池都有红外控了,难道不能跟灯控、音乐控、出入气控结合一下么,不然怎么会在人家大便的时候就把灯给关了……
6. 地板劳驾不要铺那种太高级的,我的意思是,不要铺太反光的地板砖,不然隔壁的隐私会透过挡板与地板之间的缝隙……
7. 也是最后一点:
为什么周末还他妈的锁门?!丫算什么5星级厕所到底像不像让人家上啊还不如我们老家的茅坑呢24小时营业是骡子是马都可以随便去还环保……(终于语无伦次崩溃中)
用惯了 javascript 中的 json,格式很简单,很灵活。发现 php 中也有这个东西可以用。
通过 json_encode(obj) 和 json_decode(str, [bool=false]) 可以在 php 中实现 json code 与 json object/array 之间的转换。
比如:
var_dump(json_decode('{"title": "Blog", "catogery": "php"}'));
会输出下列内容:
object(stdClass)#1 (2) { ["title"]=> string(4) "Blog" ["catogery"]=> string(3) "php" }
而第二个参数设为 true 后:
var_dump(json_decode('{"title": "Blog", "catogery": "php"}', true));
会输出下列内容:
array(2) { ["title"]=> string(4) "Blog" ["catogery"]=> string(3) "php" }
最后有一点需要注意的是,json code 中的 key 必需要求使用双引号标注起来,而 javascript 中没有这个硬性要求,只要是符合变量名规则的 key 都不需要加双引号。
]]>[HTML DOM Table].cells[]
[HTML DOM Table].rows[]
[HTML DOM Table].insertRow(index)
[HTML DOM Table].deleteRow(index)
[HTML DOM TableRow].cells[]
[HTML DOM TableRow].rowIndex
[HTML DOM TableRow].insertCell(index)
[HTML DOM TableRow].deleteCell(index)
[HTML DOM TableCell].cellIndex
-----------------------------------
在部分“现代浏览器”中,table 的 innerHTML 是会引起错误的,而且再加上 tbody/thead/tfoot 这种幽灵般的标签的存在,个人认为上述的方法和属性无疑是控制 table 内容的更好选择。
最后是一则本人的最近引起小骚动的签名档:
一个厕所三个坑——W3C
勾三股四
本名赵锦江
英文名Jinks
Blog: http://bulaoge.net/?g3g4
QQ: 110 698 041
Email: zhaojinjiang爱他yahoo刀com刀cn (万恶的网络爬虫 - -)
SlideShare: http://slideshare.net/jinjiang/
GitHub: http://github.com/jinjiang/
酷爱编程
喜欢足球、音乐
目前在傲游(Maxthon)上班
写代码是我的专业
前端开发是我的老本行
崇尚简单生活而不失激情
信科学 信W3C
大概的人生规划是:
20岁~30岁写代码
30岁~40岁玩音乐
40岁~50岁当足球教练
听说程序员的平均寿命是47.5岁……
如果我还能活到50岁,就给自己写一篇个人自传——只写给我自己
如果我还能活到60岁,就把50岁写的书出版
总之我就是这么爱幻想……
我已回到现在
另外,每个Blog都是有过去的……
2010-08-21 更新——调戏凡客:
Yeah!
大家好我是卢广仲!
Yeah!
我爱看 YouTube !
Yeah!
我现在终于知道怎么穿墙看 YouTube 了!
Yeah!
绑 hosts
Yeah!
C:\WINDOWS\system32\drivers\etc\hosts
203.208.39.104 www.youtube.com
就可以啦!
Yeah!
最后记得每天吃早餐!
Yeah!
页面被 iframe 内脚本“打散”的情况越来越常见。比如,在百度的搜索结果页面中有这样的脚本:
if (top.location != self.location) { top.location=self.location; }这时,如果我们把这个页面作为 iframe 放到另一个页面里,则外层页面会被直接跳转至搜索页面。这个代码对页面本身做了保护——即不会被其它页面引用,也给我们在需要引用这些页面时带来了一些困扰。下面介绍两种解决方法:
if(document.all){ var isIE = true; var location = ""; var domain = document.domain; }再对 top.location 赋值就不会将页面“打散”了。
window.onbeforeunload = function(evt) { return '(请用户选择留在此页面的提示)'; };这样,在页面跳转之前,会弹出一个确认对话框提示用户要不要继续执行页面跳转(即“打散”)的操作。如果用户选择了“否”,则页面会终止跳转操作, iframe 里的脚本也就不会“打散”整个页面。
最近在读一本有关 javascript 的书。
这本书很有趣,虽然内容是枯燥的,但偏偏又是我感兴趣的——我总是看着它不知不觉的犯困,每次睡醒之后,又立刻想把它继续看下去。
它虽然是讲 javascript,可基本不讲浏览器环境,统统是在推敲和雕琢语言本身的细致入微的环节。透过这本书,我看到了很多 js 被 ie/ff/chrome/op 以及 web 设计师、架构师们打磨过后,被埋没的内容——而这些内容又是这么让人豁然开朗。我才发现,原来自己之前对这门语言的了解,是很片面的。同时,这本书还打破了我的一些迷信思想:
1. 不是只有老外的书才写的好——我们国人也写得出有深度有内容的好书;
2. 不是只有传说中的神仙才写得出艺术品一般的代码和产品——我们每个人都有这个潜力;
3. 不是只有起步早才有机会的——javascript 的历史也不过十余年,从作者的人生经历来看,想成大器,只要心已决,永远都不晚。
摘一些自己通过这本书认识到的知识盲点和知识体系:
1. 'abc' 和 new String('abc') 的区别在于前者传递的是值,后者是引用;
2. string 的值可以使用下面的方法书写:var str = 'abcdefg\
反斜杠代表书写时要折行,字符本身并不属于字符串,它的值会是一个没有换行符的 26 个字符的字符串,但有一点要注意,反斜杠后不能跟注释;
hijklmn\
opqrst\
uvwxyz';
3. == 和 === 的比较规律和原则如下:
运算符 | 两个直接量比较 | 直接量和引用比较 | 两个引用比较 |
---|---|---|---|
== | 比较两者的值是否相同 | 先将引用转化为值,再与另一个值进行比较 | 比较引用是否相同 |
=== | 同时比较类型和数值 | 肯定不相同 | 比较引用是否相同 |
大于、小于等 | 直接比较两者的“序列”大小 | 先将引用转化为值,再与另一个值进行比较 | 无法比较,直接返回 false |
(\d)\.\1
表示两个相同的数字用点连接起来的情况(如"12345.12345");((function foo(a,b) {...})(x,y));
6. eval 语句中应该是一个完整的语句,而非表达式,此函数共执行三件事:解析语句 - 执行语句 - 返回语句的返回值;while (...);
if (...);
else {...}
8. 标签的写法以及 break 的一个不常用的用法:my_label: {
...
break my_label;
...
}
continue 也有类似的用法;new constructor();
在没有参数时可以简写成new constructor;
这里 constructor() 并不能认为是函数调用,因为不能将其写成下面的样子:new (constructor());
10. 对象的隐形属性(如一些 native code)被重写后在有些 js 引擎中会变为显性属性(即出现在 for ... in 循环中);if (property in object) {...}
这个礼拜应该算是北京夏天真正到来的一刻。
上周下雨,很凉快,之前也没有热得很离谱。最近几天嘛,哎呦~不一样了~
四季都有分明的特点,有人喜欢夏天,有人喜欢冬天,有人喜欢春秋,有人喜欢战国。。。
我暂时抱着一种抗拒的心理来迎接夏天,因为很热,连晚上都是,整夜整夜睡不好觉,吃饭也吃得不香,蚊子也很多,每天晚上都被咬很多口,第二天浑身痒痒,生不如死。
昨天有人跟我说还蛮喜欢夏天的。
你们真的喜欢夏天么?反正我看不出夏天哪点好。。。
最近在看一本跟 PHP/MySQL 相关的书籍,使我更深入的了解和认识了这些技术。这里会不定期更新一些自己的学习心得。以备将来复习用,同时也分享给大家:
1. 调用函数的时候,在函数名前加 @ 符号会避免由于函数执行出错而输出的错误信息。
2. 形如 mysql_xxxx 的很多函数都有一个可选参数,即代表某个数据库连接的变量,这个参数通常在我们见到的 PHP 程序例子中都被省略了,当这个参数被省略时,执行该函数的数据库连接即为最近一次建立的数据库连接。
3. mysql_fetch_array 有一个可选参数,决定返回的数据库数据结构是顺序列表还是散列表还是两者兼有,默认是两者兼有,即$row[0]、$row[1]...和$row["id"]、$row["nickname"]...两种方式都可以顺利访问数据库数据。
4. 多选的 select 控件在提交表单时,可以将其 name 设置为数组名,如:
《select multiple name="province[]"》...《/select》这样选中的项会存到名为 $_GET["province"] 或 $POST["province"] 的数组当中,如果把其 name 值设为 province 而不是 province[],则提交表单后只会解析出最后一个选中的项的值。 ]]>
总而言之,言而总之,我们总是把信仰认为是最可靠、最值得信赖的东西,当有一天,我们发现连自己的信仰都不靠谱的时候,那种失去信仰的感觉,是很可怕的。
如今,人们的信仰多种多样,千姿百态,有宗教信仰,有文化信仰,还有一种信仰我个人将其称作“崇拜信仰”,即把自己崇拜的事物(如老爸、明星、球队或产品等等)奉为信仰。只要是老爸的话,就一定要听;只要是偶像做的事情,就会去模仿和谈论;只要是苹果出品的东西,就会买来用或大加赞赏;只要是中国队的比赛,就一定会赢(这个有点夸张了)。我们有想象过那个自己心中的“偶像”、“苹果”或是“中国队”被人扒得一干二净,最后发现其本质是如此猥琐时的感觉么——那种诺大世界中找不到可以信赖的东西的无助感觉……
记得曾听我一个朋友评价电影《阿甘正传》轰动一时的原因和意义。其中就提到了当时的美国社会和那一代美国人多处于丧失信仰、自我迷失的阶段,这时需要一种正面力量的刺激,让大家重新找到各自的信仰,也需要“造神”,通过个人英雄主义来带动周围的人。
我觉得“造神”这个词用在这里很准确,这个世界上没有常胜将军,人总是不完美的,但越接近神的人,越能够给周围的人正面的影响。有时觉得那些“造”出来的神很无聊,甚至很鄙视他们,不过我逐渐感受到了大家在“造神”其中的良苦用心和其深远意义。
也希望这些“无限接近神的人”,应该了解和清楚,自己的一言一行,背负着什么样的社会责任。
所以,
尤文图斯的庸者一定经历了一段失去信仰的过程,
黄健翔的忠实听众一定也反问过自己是不是神志不清了,
五月天的迷妹们也肯定在贴吧喷完口水后悄悄地问自己每天在听的音乐到底算不算真摇滚……
再所以,
科比比姚明更接近神,
湖人队也是一定会晋级的,
巴萨的球迷此时此刻一定是迷失自我的,
我对我身边的大神,也真的很失望……
我们应该把每一位参与者,都看作是与我们并肩奋战的人。即使他笨手笨脚的,没有帮到什么忙,甚至帮了倒忙,我们还是应该肯定他的贡献和态度。
有的时候自己会被一些不好的结果影响了对过程的认识。也许是因为我总是很着急想把事情一步做好。
写篇日志提醒一下自己,如果自己再遇到这种状况,但愿可以想起这则日志,跟他们说声谢谢。
这个世界好小,以至于我们在任何时候、任何场合、做任何事情,都要顾及“所有的人”——哪怕不是我们当时所处圈子里的人。
我们每个人都有自己各式各样的圈子。圈子与圈子之间相对独立,如果工作圈里遇到了困惑,可以找生活圈的人来分担,如果在生活圈里找到了快乐,也可以在学生圈里跟大家分享;可这又是藕断丝连的,因为各自圈子里的人也是可以通过其它途径相互认识和熟悉的。尤其是困惑这种东西,通常我们都不想将其公开,尽量低调解决——甚至是更为私密的内容,这时我们往往希望可以有一个相对可信、封闭、保密的圈子来帮助你。
但,这些我们平时觉得值得信赖的“圈”,真的就是可信?封闭?保密的么?
我逐渐觉得,在当今信息化的社会,这样的“圈”似乎是不存在的
我总觉得自己是一个存不住话的人,有什么想法,都一定要说出来才痛快,只是我可以选择说给谁听罢了。
或许,我将来也应该学会,在必要的时候,把想法默默吞下去。
有一种动弹不得的感觉……
燃烧!
趁我们还有满腔热情
趁我们还可以过青年节
趁我们还有理想
趁我们还有抱负
趁我们还有体力
趁我们还没有伤痕累累
都无怨无悔
只是
希望在我们都化为灰烬的同时
还可以照亮什么
------------------------------
我们都还年轻,不是么……
if (window.addEventListener) { domInput.addEventListener('input', handler, false); } else { domInput.onpropertychange = handler; }如题,以上方案,可以兼容目前的在所有主流操作系统上,所有主流浏览器里,用所有主流输入法输入文本(包括粘贴)的事件。
刚刚看过今晨的欧冠录像,切尔西和利物浦战成4比4,红军的激情和蓝军的顽强都在这里展现的淋漓尽致!
通过摄像机的镜头,我又看到了熟悉的斯坦福桥球场,想起了4年前切尔西和巴塞罗那在这里的一番激战。当时的比分是4比2,切尔西和巴塞罗那这两支当时欧洲最优秀的队伍,为了一个欧冠8强席位都拼得你死我活,出线权也随着双方一个接一个的进球4度易主,也缔造了罗纳尔迪尼奥那一脚惊世骇俗的年度最佳进球。
那个时候的我,还是个大学生,大学的时候宿舍没有电视,又想看球,就约了几个球友晚上出去租一间带电视的小破屋子一起看,斯坦福桥的那场比赛是我学生时代看得最过瘾的一场,而且印象如此深刻。
今天再次看到斯坦福桥的又一场经典战役诞生,不得不感叹切尔西金元的能量和职业足球的魅力!
也希望自己在未来的某一天可以有机会亲身经历这一切。
以此为我的博客新分类“今天的业余爱好也许就是明天的职业”作序。
找了好久,精挑细选,我选择了在不老歌开始新的网志生活。
前阵子被人说自己是火星人,只用QQ聊天,只懂得专研代码,就知道踢球;不爱看小说,不爱看电影,不会做饭,不爱逛街,也不怎么用电话。总之一点都不像正常人
我想了想,好像勉强属实。但又一想,以前的自己似乎不完全是这样的——最起码,从前的我,还总跟人讲电话发短信,人缘不错也爱凑热闹,每个礼拜都给家里的老爸老妈通电话问寒暖报平安的。
或许以前的自己真的太累了,想休息了。我逐渐喜欢比较清静的环境,喜欢一个人在夜深人静的时候默默思考,写点东西。
当世界的纷乱在我们的眼中还可以用缤纷来形容时,一切都是那么会让人蠢蠢欲动。
在校内网崩坏的那一刻,
在之前的烂摊子逐渐冷却的那一刻,
在脚踝在篮球场上扭伤的那一刻,
在夜深人静诚实分析自己的那一刻,我突然觉得这里好惬意。
你们也喜欢这里吗?