2016年9月25日星期日

微信小程序开发入门篇

前言

直接开始,简单理解就是微信作为原生层,我们的应用作为网页,是一种 hybird 的开发方式,唯一不同的是,在这个平台上必须遵守微信的一些设计规范、运营规范等东西。

官方资料

https://mp.weixin.qq.com/debug/wxadoc/dev/?t=1474644088899

开发工具

官方已出正版,可直接使用无 appid 进行体验

下载地址

使用的开发元素

  1. JavaScript (交互、数据等脚本)
  2. wxml (页面结构、组件)
  3. wxss(页面样式)

wxmlwxss 是新的文件格式,不用理解,就映射成 htmlcss 即可,但是不同的是,有一套自己的标签和支持的范围(比如 css 部分支持度有限),详情可以看上面的官方资料。

项目的结构

下载到 DEMO 过后,就会有一个基本的项目结构,相当简单。

├── app.js
├── app.json
├── app.wxss
├── pages
│   ├── index
│   └── logs
└── utils
    └── util.js

其他就不看了,直接看 app.json 这一个文件即可,这是一个全局 app 的配置文件,具体详情:文档

有很多配置,具体看文档,这里主要说一下三个配置:

  • 导航、标题部分 使用的是原生 header, 只能改变: 导航栏的颜色,不能修改其中的内容。导航,需要开发者自己控制(微信规定,页面路径只能是五层)。
  • pages 部分 默认加载第一个页面,其余靠跳转
  • 底部导航(tabBar) 只能配置最少 2 个、最多 5 个 tab。

其余配置就不需要怎么了解了,根据后面的小栗子来

hello world

  • app.json
{
  "pages":[
    "pages/index/index"
  ],
  "window":{
    "backgroundTextStyle":"light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "WeChat",
    "navigationBarTextStyle":"black"
  }
}
  • app.js
App({}) // 暂时先什么都不管
  • pages/index/index.js
Page({
  data: {
    hello: 'Hello World',
  }
});
  • pages/index/index.wxml
<view class="container">
    <text>{{hello}}</text>
</view>

输出就不用截图了,就是替换 {{hello}} => 'Hello World'

可以看出,app.js 是启动的入口文件,然后 pages 目录下面都有一个 Page 对象来做页面封装,然后再渲染到页面中,启动结构: App => Pages 的模式,典型的类 App 开发( manager => activity)。

使用过 angularjs 或者 vue 等框架的直接就可以上手了,MVVM 模式,但是唯一不一样的是提供手动触发界面渲染,使用 this.setData() 方法,具体查看 API 或者跟我一起看后面的栗子。

一个简单的静态表单

在微信提供的这套框架中,提供了很多组件,这里用一个静态表单的代码来感性认识一下,具体的还得你看 API 了哦。

注:栗子都是修改 app.json 中的 pages 配置,直接默认预览,因为不想受 navigate 的跳转影响,那个很简单,看 API 传参数即可。

要做的效果是:

效果图

  • pages/form.wxml
<view class="page">
  <form class="form" catchsubmit="formSubmit">
    <view class="section">
      <view class="label">姓名</view>
      <input name="username" placeholder="请输入姓名" />
    </view>
    <view class="section">
      <view class="label">性别</view>
      <radio-group name="sex" >
        <label wx:for-items="{{sex}}"><radio value="{{item.key}}" />{{item.value}}</label>
      </radio-group>
    </view>
    <view class="section">
      <view class="label">爱好</view>
      <checkbox-group name="love">
        <label wx:for-items="{{love}}"><checkbox value="{{item.key}}" />{{item.value}}</label>
      </checkbox-group>
    </view>

    <view class="btn-area">
        <button type="primary" formType="submit">Submit</button>
    </view>
  </form>
</view>
  • pages/form.js
var pageObject = {
  data: {
    sex: [],
    love: []
  },
  // 注意事件部分,提供常见的事件
  formSubmit: function(e) {
    console.log('表单数据为:', e.detail.value)
  }
};

// 生成数据
var radios = [{
  key: '1',
  value: '男'
}, {
  key: '2',
  value: '女'
}];

var checkboxs = [{
  key: '1',
  value: '篮球'
}, {
  key: '2',
  value: '乒乓球'
}, {
  key: '3',
  value: '足球'
}];

var len = radios.length;
for(var i = 0; i < len; ++i) {
  (function (_i) {
    pageObject.data['sex'].push(radios[_i]);
  })(i);
}

len = checkboxs.length;
for(var i = 0; i < len; ++i) {
  (function (_i) {
    pageObject.data['love'].push(checkboxs[_i]);
  })(i);
}

Page(pageObject);

静态的很简单,这里主要就是构建页面使用它的组件和风格,还是特别快的

CSS 部分

.form {
  display: block;
  padding: 0.6em;
  margin-top: -15px;
}

.section {
  margin-top: 15px;
}
.section .label {
  font-size: 14px;
  color: #333;
  line-height: 28px;
}

.btn-area {
  margin-top: 15px;
}

input {
  font-size: 14px;
  height: 28px;
  padding-left: 6px;
  border: 1px solid #D9D9D9;
  background: #fff;
}

input:focus {
  outline: 0 none;
}

label {
  margin-left: 10px;
  font-size: 14px;
  vertical-align: middle;
}

radio-group, checkbox-group {
  margin-left: -10px;
}

radio, checkbox {
  vertical-align: middle;
  margin-right: 6px;
}

稍微复杂交互的例子

有拖动(拖动效果先不管)直接样式更新以及简单的数据驱动的交互。

效果图

  • pages/simple.wxml
<view class="main">
    <button class="btn"
      style="top:{{btnTop}};left:{{btnLeft}}"
      type="primary"
      data-name="我是 button."
      bindtap="tapClick"
      bindtouchstart="touchStart"
      bindtouchmove="touchMove">拖动我</button>

    <text data-id="{{item.id}}" class="text {{item.active ? 'on' : ''}}" bindtap="tapText" wx:for-items="{{values}}">{{item.id + ' - ' + item.value}}</text>

    <view class="move">
      <button type="primary" bindtap="onPrev">上</button>
      <button type="primary" bindtap="onNext">下</button>
    </view>
</view>
  • pages/simple.js
var pageObject = {
  data: {
    btnTop: 0,
    btnLeft: 0,
    move: 0,
    values: [{
      id: '0',
      value: '小撸',
      active: false
    }, {
      id: '1',
      value: '菜胖娃',
      active: false
    }, {
      id: '2',
      value: '骚龙龙',
      active: false
    }]
  },
  tapClick: function (e) {
  },
  touchStart: function (e) {
  },
  touchMove: function (e) {
    var touches = e.touches;
    var touch = touches[0];
    var pageY = touch.pageY + 'px';
    this.setData({
      btnTop: pageY
    });
  },
  onPrev: function () {
    var move = this.data.move;
    if(move <= 0) {
      return false;
    }

    var values = this.data.values;

    var curValue = values[move];
    var prevValue = values[move - 1];

    values.splice(move - 1, 2, curValue, prevValue);

    this.setData({
      move: move - 1,
      values: values
    });
  },
  onNext: function () {
    var move = this.data.move;
    var values = this.data.values;
    if(move >= values.length - 1) {
      return false;
    }

    var curValue = values[move];
    var nextValue = values[move + 1];

    values.splice(move, 2, nextValue, curValue);

    this.setData({
      move: move + 1,
      values: values
    });
  },
  tapText: function (e) {
    var dataset = e.target.dataset || {};
    var id = e.target.dataset.id;

    if(id) {
      var index = -1;
      var newValues = this.data.values.map(function (d, i) {
        if(d.id === id) {
          d.active = !d.active;
          index = i;
        } else {
          d.active = false;
        }
        return d;
      });

      this.setData({
        move: index,
        values: newValues
      });
    }
  }
};

Page(pageObject);
  • pages/simple.wxss
view {
  display: block;
  position: relative;
}

.main {
  padding: 0 20px;
}

text {
  display: block;
  margin-top: 20px;
}

.text {
  padding: 10px;
  padding-left: 6px;
}

.text.on {
  color: #fff;
  background: #009CD5;
}

.btn {
  position: absolute;
  left: 0;
  height: 40px;
}

.move {
  margin-top: 20px;
}

.move button {
  margin-top: 10px;
}

这个例子主要是一些事件的熟悉、dataset 的定义等。

服务端的数据

那么我们怎么和服务端交互呢?其实很简单,微信这边相当于一个客户端,我们建立两个项目,一个客户端、一个服务端提供服务。

首先我们启动一个服务端项目:wx_server
其中就一个文件,users.json 直接提供 JSON 数据,使用 serve 或者 http-server 启动一个服务。

  • users.json json [{ "name": "小撸" },{ "name": "骚龙龙" },{ "name": "菜胖娃" }]

然后其实剩下来都是客户端的事情了,先看下目录结构,我觉得 MVC 结构挺不错。

├── app.js
├── app.json
├── app.wxss
├── model
├── page
│   └── simple
├── service
└── util
    ├── ajax.js
    ├── assign.js
    ├── handler.js
    └── util.js

这里我建立 model 来做模型处理,service 做服务封装。然后 util 目录提供一些工具类。
直接上代码吧,这里的一个坑就是文件默认 require 模块不是 .js 结尾,必须写全,不知道正式版后会不会默认,所以最开始还是写严谨点好。

  • assign 这个不贴了,就是 Object.assign 的一个封装。
  • util/handler.js 这里提取项目所有的接口,这里我只是提取了一下服务 ROOT 前缀。
  • util/ajax.js 主要是请求的简单封装,官方提供的是 wx.request,然后正式项目准备提取成 promise 的对象方式,这里先这样。

  • handler.js

module.exports = {
    common: 'http://localhost:3000/'
};
  • ajax.js
var handler = require('./handler.js');
var assign = require('./assign.js');

var defaultConfig = {
    method: 'GET',
    fail: function () { }
};

function ajax(config) {
    if(!config.url) {
        throw "必须传入请求的接口 url!";
    }
    if(!config.root) {
        config.url = handler.common + config.url;
    }

    var _config = assign(defaultConfig, config);
    console.log(_config);
    wx.request(_config);
}

module.exports = ajax;
  • app.js
var ajax = require('./util/ajax.js');
var handler = require('./util/handler.js');

App({
  onLaunch: function () {
  },
  onShow: function () {
  },
  onHide: function () {
  },
  // 这里是关键的全局数据导出
  globalData: {
    ajax: ajax,
    handler: handler
  }
});
  • pages/simple.wxml
<view class="page">
    <text class="page-text" wx:for="{{users}}">{{item.name}}</text>
</view>
  • pages/simple.js
var app = getApp();
var globalData = app.globalData;
var ajax = globalData.ajax;
var handler = globalData.handler;

var pageObject = {
    data: {
        users: []
    },
    onLoad: function () {
        var self = this;
        ajax({
            url: 'users.json',
            success: function (res) {
                var data = res.data || [];
                self.setData({
                    users: data
                });
            }
        });
    }
};

Page(pageObject);
  • pages/simple.wxss
.page-text {
  display: block;
  line-height: 32px;
  font-size: 16px;
  color: #000;
  border-bottom: 1px solid #b2b2b2;
}

其实很简单,就是服务端提供接口即可,因为 APP 小程序这边可能有大小限制(目前可能是 1MB)所以应该是要分离的吧。

剩下还有一些生命周期,那个就看文档就应该 OK 了,匆匆写一篇入门文章 = =~ 睡觉了,不然媳妇要弄我了。

2016年9月20日星期二

使用 TypeScript + TDD 快速开发的配置

1. 你需要一个好的工具来写测试

对于大多数开发者来说,都不太喜欢编写测试。当你在编写测试的周期的时候,能有一个快速的效应时间,那么这件事将变得更容易。结合一个好的测试框架你会有一个更好的开始。

测试驱动开发(TDD, Test-driven development)是一种你编写单元测试和应用程序代码的软件开发方法。典型的就是写一个空的根方法,创建一个失败的测试来验证不存在的程序逻辑,直到全部的程序逻辑测试都变成绿色。最后,在你 push 你的提交前重构你的代码。

让我们创建一个基于 TypeScript 的 TDD 配置。

2. 立即运行,并且只显示失败的结果

大量的 IDE 都支持单元测试和测试驱动开发,但是大多数开发者使用文本编辑器编写 JavaScript。我们需要一个命令行的方式去创建一个快速的 TDD 配置。

它应该是:
1. 改变的时候立即运行测试,因此我们只需要在保存文件的时候启动测试;
2. 它应该只显示失败测试结果,因为我们的屏幕不是特别大,不能显示 100 个成功的测试(虚词)。

还有一个需要我们也关心的地方,如果在运行测试之前没有通过,这时候又开始一个新的测试,我们就需要停止前一个测试。这是 grunt + mocha 不能处理的。

3. 使用 TypeScript 建立快速的 TDD

我们需要的配置如下:
1. 它需要工作在命令行;
2. 能够 watch 文件的改变;
3. 编译 TypeScript 和 运行测试用例;
4. 前一个没通过的测试将被停止;
5. 只显示失败的测试结果。

使用一个 gaze-run-interrupt 的 npm 包来 watch 文件的改变,并且出发一系列命令,也能 kill 前面的序列。还将使用 Mocha 和它的 min-reporter 功能来显示失败的测试。

配置步骤

  1. 在你的 package.json 文件中添加 gaze-run-interrupt 开发依赖(devDependency
  2. 在你的项目结构中,加入一个 tdd 文件,并且给它执行权限。

#!/usr/bin/env node

var gaze_run_interrupt = require('gaze-run-interrupt');

gaze_run_interrupt('{src,test}/**/*.ts', [{
  command: 'node_modules/.bin/tsc'
}, {
  command: '../../node_modules/.bin/mocha',
  args: ['--reporter', 'min'],
  cwd: 'dist/js'
}]);

  1. 执行 TDD
node tdd

最后执行效果:

TDD 效果

原文出自:http://bytearcher.com/articles/fast-tdd-setup-for-typescript/

注:原文的代码需要输入邮箱,我自己手敲了一下,放在 github tdd_demo 还有就是原文中的配置可以根据 vscode 工具进行优化,还有脚本的方式我修改为了 node 脚本执行的方式,其实有其它更好的方式,但是这篇文章主要是说明一种快速 TDD 软件开发的方式。

2016年9月19日星期一

JavaScript 的内部字符编码是 UCS-2 还是 UTF-16

对于 JavaScript 使用的是 UCS-2 还是 UTF-16 这个问题,我找了很久,没有发现一个权威的回答,我决定自己研究一下它。这个回答来自于你对 JavaScript 引擎或者对 JavaScript 语言的理解。

一、著名的 BMP (Basic Multilingual Plane)

Unicode 标识符通过一个明确的名字和一个整数来作为它的码位(code point).比如,“©️” 字符的码位可以用“版权标志”和U+00A9(0xA9,也可以写作十进制 169)来表示。

Unicode 字符分为 17 组平面,每个平面拥有 216 (65,536)个码位.有一些码位没有分配字符,也有一些码位被保留,成为私有的,也有一些码位是永远被保留的,作为无字符的标志。每一个码位都可以用 16 进制 xy0000xyFFFF 来表示,这里的 xy 是表示一个 16 进制的值,从 0010

这第一个位置(当 xy00 的时候)被称为 BMP (基本多文种平面, Basic Multilingual Plane)。它包含了最常用的码位从 U+0000 到 U+FFFF。

这里需要补充一点额外的平面知识,以及术语的表格。

平面 始末字符值 中文名称 英文名称
0号平面 U+0000 - U+FFFF 基本多文种平面 BMP
1号平面 U+10000 - U+1FFFF 多文种补充平面 SMP
2号平面 U+20000 - U+2FFFF 表意文字补充平面 SIP
3号平面 U+30000 - U+3FFFF 表意文字第三平面 TIP
4~13号平面 U+40000 - U+DFFFF (尚未使用)
14号平面 U+E0000 - U+EFFFF 特别用途补充平面 SSP
15号平面 U+F0000 - U+FFFFF 保留作为私人使用区(A区) PUA-A
16号平面 U+100000 - U+10FFFF 保留作为私人使用区(B区) PUA-B

引用自:wikipedia

其余 16号平面(U+100000 到 U+10FFFF)称为补充的平面。这里我将不讨论它;只需要记住两个概念:BMP 字符和非 BMP 字符,后者也被称为补充字符。

二、UCS-2 和 UTF-16 之间的不同

UCS-2 和 UTF-16 都是 Unicode 的字符编码方式。

UCS-2(2个字节的通用字符集)是一种固定长度的编码格式,只需要使用编码为 16 字节编码单元来表示码位。这样的表示结果将和 UTF-16 在 00xFFFF (BMP)范围内大多数的结果一样。

UTF-16(16 位 Unicode 转换格式)是对 UCS-2 的一个扩展,它允许表示比 BMP 范围内更多的字符。它是一种可变长度格式,它的每个码位能够使用 1 个或者 2 个 16 位长的码元来表示。这种方式能够编码的码位在 00x10FFFF 之间。

比如,在 UCS-2 和 UTF-16 中,对于 BMP 字符 U+00A9 版权标志(©️)都能被编码为:0x00A9

这里补充一下 UCS-2、UCS-4、BMP

CPU 处理多字节数的方式分为:“大尾”(big endian)和“小尾”(little endian),简单的理解就是一个 Unicode 编码,比如 6C49,写到文件里面 6C 49 或者 49 6C,两种方式,前者就叫“大尾”,后者就叫“小尾”。
UCS 可以分为两种格式:UCS-2 和 UCS-4。UCS-2 使用两个字节编码,UCS-4 使用4个字节(实际只有 31 位,最高位必须是 0)编码。
转换关系:UCS-4 中高两个字节为 0 的码位称为 BMP;UCS-4 的 BMP 去掉前面两个零字节就得到 UCS-2;UCS-2 加上两个零字节就得到 UCS-4 中的 BMP。

三、代理对(Surrogate pairs)

对于 BMP 之外的字符,比如 U+1D306 四条线居中(其实不好翻译:tetragram for centre,𝌆),只能使用 UTF-16 中两个 16 字节来编码:0xD834 0XDF06。这种就被称为代理对。值得注意的是一个代理对只代表一个单字符。

补充一下代理对的概念

实际上就是指上面的一个 UTF-16 编码,使用 2 个 16 字节来编码。
那是因为一个 UTF-16 编码不够,然后就应该使用 2 个 UTF-16 编码来表示更多的字符。然后这样就会出现:之前 2 个字节的空间表示一个字符,就会变成 4 个字节的空间。所以就规定只有一定范围内使用 2 个 UTF-16 编码来表示一个字符,这样的方式就叫代理对,其余的依然使用 2 个字节来表示。

代理对中的第一个字符单元总是在 0xD8000xDBFF 之间,称为高位代理或者顶部代理(high surrogate or lead surrogate,暂时这样,查到专业术语再翻译)。第二个字符单元总是处于 0xDC000xDFFF 之间,称为低位代理或者尾部代理(low surrogate or trail surrogate)。

UCS-2 是缺乏对代理对的支持的,所以要表示之前的字符需要使用 2 个分隔的字符。

四、码位(code points)和代理对(surrogate pairs)之间的转换

Section 3.7 of The Unicode Standard 3.0(pdf) 中定义了一个转换算法。

假设:一个码位 C 大于 0xFFFF 的编码使用代理对 来表示的公式为:

H = Math.floor((C - 0x10000) / 0x400) + 0xD800
L = (C - 0x10000) % 0x400 + 0xDC00

转换公式变换后,比如从代理对 <H, L> 转换成一个 Unicode 码位 C,可以使用公式:

C = (H - 0xD800) * 0x400 + L - 0xDC00 + 0x10000

五、Ok, 那么关于 JavaScript 的编码问题呢?

在 ECMAScript 中定义来怎样解释字符的问题.

在 level 3 或者更高等级的实现中,遵循国际标准,与 Unicode 3.0 标准或者更新的标准,以及 ISO/IEC 10646-1,和 UCS-2 或者 UTF-16 作为编码格式。如果采用的 ISO/IEC 10646-1 自己未被指定,它被认定为 BMP 的自己,集合 300(这里没懂)。如果没有采用其它的编码格式,那么将按照 UTF-16 进行编码。

换句话说,JavaScript 引擎是允许使用 UCS-2 或者 UTF-16 进行编码的。

然后按照 specific parts 规定,认为引擎里面的编码需要一些 UTF-16 的知识。

当然,内部引擎对于大多数 JavaScript 开发者来说没有实际影响。对于更多有趣的发现JavaScript 是如何考虑字符的 中,有一段:

尽管在本文档的其它部分中,表示字符单元和文字字符将使用 16 位的无符号值,用来表示单个 16 位文本单元。Unicode 字符将使用抽象的语言或印刷单元(可超过16位,因此可以由多个代码单元)来表示。码位可以用 Unicode 标准值来表示。一个组合字符序列的成分可以有个别“Unicode 字符”,即使一个用户可能会认为整个序列是单个字符。

可能需要重新翻译,原文

Throughout the rest of this document, the phrase code unit and the word character will be used to refer to a 16-bit unsigned value used to represent a single 16-bit unit of text.
The phrase Unicode character will be used to refer to the abstract linguistic or typographical unit represented by a single Unicode scalar value (which may be longer than 16 bits and thus may be represented by more than one code unit).
The phrase code point refers to such a Unicode scalar value.
Unicode character only refers to entities represented by single Unicode scalar values: the components of a combining character sequence are still individual “Unicode characters”, even though a user might think of the whole sequence as a single character.

JavaScript 使用单独字符来处理字符单元,然后人们通常认为是一组 Unicode 字符。当使用 BMP 范围外 Unicode 字符的时候,这样会有一些不好的结果。比如代理对使用 2 个字符单元组成:'𝌆'.length == 2,即使这里是只有一个 Unicode 字符。如果是字符,代理对将暴露一部分:'𝌆' == '\uD834\uDF06'

在这里你想到了什么呢?对于这种方式,至少是 UCS-2 的替代方式(不同的地方是,UCS-2 不允许有代理字符,然后 JavaScript 字符串是这样做的)。

你可以认为它像 UTF-16 一样在工作,特别是分成两部分的方式是被允许的,代理的这种错误排序是被允许的,代理被暴露成了分离的字符。我认为你将更容易的理解成这种行为叫“UCS-2 的代理方式”(UCS-2 with surrogates,不好翻译,也可以理解成伴随着代理的 UCS-2)。

类似 UCS-2 的行为对整个语言更有影响,比如 补充字符范围的正则表达式 比那些支持 UTF-16 的语言要更难写。

代理对只是为了显示在浏览器中(layout 的时候),对单个 Unicode 字符的重新组合。这发生在 JavaScript 引擎的影响范围之外。为了证明这个,你能在 document.write() 的时候分开写一个高位代理和地位代理字符.

document.write('\uD834');
document.write('\uDF06');

在结束后也将被渲染成一个图案:𝌆

六、结论

JavaScript 引擎内部是自由的使用 UCS-2 或者 UTF-16。我所知道的大多数引擎使用的是 UTF-16,无论它们使用什么方式实现,它只是一个具体的实现,这不将影响到语言的特性。

然后对于 ECMAScript/JavaScript 语言本身,实现的效果是通过 UCS-2,而非 UTF-16。

如果你在任何时候需要 编码一个 Unicode 字符, 在必要的时候能够替换成分离的代理,也可以免费试用我的 JavaScript escaper 工具。

如果你想在一个 JavaScript 字符串中获取 Unicode 字符的长度,或者创建一个基于 non-BMP Unicode 码位的字符串,你能使用 Punycode.js 的工具方法,将 UCS-2 字符串转换成 UTF-16 码位。

// `String.length` 只是统计所以 Unicode 字符
punycode.ucs2.decode('𝌆').length; // 1
// `String.fromCharCode` 能够让你直接使用非分离的代理
punycode.ucs2.encode([0x1D306]); // '𝌆'
punycode.ucs2.encode([119558]); // '𝌆'

ECMAScript 6 在字符串中将支持一些新的编码序列(现在看来已经 ok 了,可以查看一下资料简单了解下),名为 Unicode code point escapes 比如:\u{1D306}。另外,它将定义 String.fromCodePointString#codePointAt,这两个方法都接受码位(code points) 而不是字符单元(code units)

感谢:Jonas ‘nlogax’ Westerlund, Andrew ‘bobince’ Clover 以及 Tab Atkins Jr.。他们给了我调查的灵感和帮助我。

提示:如果你喜欢阅读关于 JavaScript 的内部字符编码,可以 check out JavaScript has a Unicode problem,这里更详细解释了实际的问题,以及提供了解决方法。

翻译原文:https://mathiasbynens.be/notes/javascript-encoding

2016年9月14日星期三

在响应式网站中,提升加载webfonts的性能(一)

前言

因为 Arial, Verdana, Garamond or Times New Roman 这些字体几乎在所有电脑上都有安装,所以以前每个网站都使用他们来渲染文本。至今,虽然webfonts已经在互联网中得到普遍的使用,但是我们依然不知道如何高效的加载他们。

为了提供更好的用户体验,我写了一些怎么做才能高效加载webfonts的简单解决方法,这个方案的实施并不需要昂贵的硬件支持。

0. 目录

主要从以下方案进行介绍:

(1)使用woff字体格式(PS:可能是EOT和TTF格式默认情况下不会进行压缩,然而WOFF格式具有内建压缩,而且WOFF格式的支持很广泛的原因。);

(2)对于不支持webfonts的浏览器使用“web安全”字体(PS:使用前言中提到的一些几乎所有电脑都安装了的字体作为后备字体,保证用户能够正常浏览网页);

(3)下载字体的“二进制”格式,并且优化它;

(4)使用你自己的字体库;

(5)将字体进行base64编码后,存放于CSS文件中;

(6)如果用户本地没有网页请求的字体,那么就异步加载该字体,并且将该字体存储在localStorage中,以便第二次请求字体直接从localStorage中进行读取,避免字体加载带来的性能问题;

Hello Node.js

一、前言

很久之前就想系统的学习nodejs技术了,但是由于很多事情,忙不太过来,今天晚上下定决心要入门这门技术,所以一口气看完了《Node.js开发指南》和《Node.js中文文档》,总算是真正入门了,接下来就总结一部分,剩下的只有明天再总结了。

二、Node.js简介

一句话简单说明一下node.js是什么东西。

Node.js 是一个让 JavaScript 运行在服务端的开发平台。

三、Node.js的安装

学习了Node.js,觉得如果在window下学习这门技术的话还太成熟,第三方的支持不太好,所以只介绍linux或者mac上面的安装。node.js在window上面的安装直接下载安装包,一直下一步就可以装好,环境变量也会自动配置好,npm(node package manage)在新版本也会相应的装上。

对于mac环境安装也比较简单,介绍两种安装方式。

Javascript模块化开发-轻巧自制

一、前言

现在javascript的流行,前端的代码越来越复杂,所以我们需要软件工程的思想来开发前端。模块化是必不可少的,这样不仅能够提高代码的可维护性、可扩展性以及鲁棒性,更大的好处就是能够提升整个团队的开发效率,也能够让新进的程序员更快的接手工作。今天晚上根据前辈们的经验,写啦一个简单的模块定义的封装组件,当是练手吧。不过感觉还是蛮好用的。

二、学习模块化前我们应该先了解点什么呢?

其实突然就学习模块化的javascript开发,那还是比较丈二和尚,摸不着头脑的。不过如果是做过后台开发的程序员们,可能对于模块化的开发思想并不陌生,因为后台的这方面技术已经很熟悉了。那么这里我就分享一下前端javascript模块化开发的学习。

1. 了解模块化开发思想

如果有软件工程背景,那么这一思想就是你自身就应该掌握的。模块(module)就是可以组合、分解以及更换的单元,其实也满足组合大于继承等这些带来的好处吧。如果看成一个系统的话,我们可以从软件体系结构来理解,模块是较大系统中的独立部件,功能、状态与接口反映外部特性,逻辑反映的是内部特性。

Node.js npm 详解(1)

一、npm简介

安装npm请阅读我之前的文章Hello Node中npm安装那一部分,不过只介绍了linux平台,如果是其它平台,有前辈写了更加详细的介绍。

npm的全称:Node Package Manager.

(1)通俗的理解

其实从字面意思就可以理解这个产品有什么作用翻译为“Node包管理器”。对,就是Node的包的一个管理工具,目前我尝试的有

  1. 下载并安装包(npm install [pkg])
  2. 升级安装包(npm update [pkg])
  3. 卸载安装包(npm uninstall/rm [pkg]),可以指定卸载包的版本号 ...

其实这些命令很简单,常用的必须记住,不常用的查询即可,这才是比较好的学习知识方式。

Node.js npm 详解(2)

安装Node和npm前半部分的配置可以参考之前我的两篇文章:

  1. Hello Node
  2. Node npm 详解(1)

四、本地模式和全局模式

如果你了解环境变量里面的,用户变量和系统变量。可以做一个类比进行理解。当然,windows上面的环境变量概念比较好理解。

强大的Makefile

一、make 规则

Target: Dependencies
    <TAB>Commands

例子:

main.o: main.c common.c common.h
    gcc -c main.c

二、建立 Makefile (这里不带后缀名,并且根据习惯首字母大写)

输入以下内容:

clean: 
    @echo hello
    echo hello

ps:
在终端输入命令:make clean 执行(但是需要在Makefile的同级目录)
如果是在windows环境下,你也可以使用nmake进行测试,但是我相信在windows就不用这种方式了。
1. clean: 定义执行的make的命令。
2. @echo: 直接输出“hello”字符串,但是不打印代码。
3. echo: 输出“hello”字符串,并且打印代码。

执行结果如下:

像积木一样搭建网页

前言

我们在浏览网页的时候,经常会看到很相似的几种内容。比如下面这两种样子:

视图1

视图2

看上去是不是比较像呢?其实就是图片的位置不一样,大致上可以认为是一样的吧,那么针对这样子的相似度较高的网页,我们应该怎么样开发呢?

其实这就得谈谈“页面重用机制”了,下面所写的积木式网页构建方式就能够很好的处理页面重用这个问题。

接下来将从以下4个方面对积木式网页开发进行描述:

  1. 分析页面结构
  2. 构建网页的结构
  3. 编写网页基本的样式
  4. 编写不同表现的样式

从触摸开始(一)

1. 前言

最近看了一本《移动Web手册》的书,奇舞团翻译的,非常不错。个人觉得在进入移动端的时候最先应该了解移动端新的交互模式:触摸。为什么这样说呢?在PC端,我们大多数的交互都是通过鼠标来实现,在开发过程中,对鼠标事件的处理也非常多,通过之前在移动端的开发经验,在移动端的交互也避免不了经常与触摸打交道,所以我们有必要单独学习一下它。

2. 触摸、鼠标交互模式

(1) 连续性

触摸事件是不连续的,鼠标事件是连续的。

连续性

操作顺序:A -> B -> C

在鼠标上:鼠标点击A -> 划过B -> 点击C

在触摸上:手指触摸A -> 跨过B -> 触摸C

React 组件之间如何交流

前言

今天群里面有很多都在问关于 React 组件之间是如何通信的问题,之前自己写的时候也遇到过这类问题。下面是我看到的一篇不错英文版的翻译,看过我博客的人都知道,我翻译可能不会按部就班,会尽可能用中文的意思,来将作者要讲述的技术描述清楚。英文能力有限,如果有不对的地方请跟我留言,一定修改……_^

原著序

处理 React 组件之间的交流方式,主要取决于组件之间的关系,然而这些关系的约定人就是你。

我不会讲太多关于 data-stores、data-adapters 或者 data-helpers 之类的话题。我下面只专注于 React 组件本身的交流方式的讲解。

React 组件之间交流的方式,可以分为以下 3 种:

  • 【父组件】向【子组件】传值;
  • 【子组件】向【父组件】传值;
  • 没有任何嵌套关系的组件之间传值(PS:比如:兄弟组件之间传值)

PostCSS是个什么鬼东西?

前言

最近大漠前辈在群里发关于PostCSS的系列文章,但是耗子姐姐又说看了有点云里雾里的感觉,所以这篇文章将按一个思考的角度来理解一下 PostCSS 到底是一个什么东西。

配图

一、提出不懂的地方

很多时候第一次在网上查询 PostCSS 概念的时候,大家都解释成一个后处理器的概念,其实个人觉得这些概念不重要,更为重要的有以下几点:

  1. 它本质上是一个什么东西?
  2. 它能解决我们什么问题?
  3. 它是通过什么方式来解决我们的问题?
  4. 它解决我们的问题是为什么?
  5. 怎么实现与 SASSLESSStylus 相同的功能(因为它们被经常拿来比较)
  6. 它由哪些东西组成?
  7. 既然是程序可以用的,那么它的API呢?

Redux 能做什么?

一、我先给一个 Redux 的定义

Redux 被大家知道应该都是来自于 React 的出现,但是这篇文章会通过介绍一个 React 之外的例子,来让大家理解 Redux 是什么东西,到底还能够做点撒。

我自己的理解是:

Redux 是一个改变状态(state)的模型,这个模型通过一个单向操作的方式来改变状态,用数学符号的方式来理解我认为应该是:y = f(x),嗯,就是一个最简单的函数模型。

不过这样说,有些人还会不理解,毕竟以前撸代码不是这个样子的,那么我们先反向的来获取自己的理解。

首先,我们需要知道 Redux 有的以下几个东西:

  • createStore
  • reducer
  • dispatch

我觉得需要介绍清楚 Redux ,知道这三个就行了,至于还有一些函数我觉得都是调味料,这里先不介绍。

React.js 模式

前言

我想找一个好的前端前端框架,找了很久。这个框架将能够帮助我写出具有可扩展性、可维护性 UI 的代码。通过对 React.js 优势的理解,我认为“我找到了它”。在我大量的使用过程中,我发现了一些模式性的东西。这些技术被一次又一次的用于编程开发之中。此时,我将它写下来、讨论和分享这些我发现的模式。

这些所有的代码都是可用的,能够在 https://github.com/krasimir/react-in-patterns 中下载。我可能不会更新我的博客,但是我将一直在 GitHub 中发布一些东西。我也将鼓励你在 GitHub 中讨论这些模式,通过 issue 或者直接 pull request 的方式。

Mac OS xDebug PhpStorm 快速配置

前言

在网上找了一些相关配置的例子,觉得很多比较复杂,并且配置项过于多了,有很多冗余并不是最开始就需要的配置,所以写一下记录,也方便以后自己查阅。

一、brew 安装 php

在 Mac OS 这边不需要手动去下载编译打包,直接使用 brew 进行快捷安装会比较方便管理,对于最原始的打包安装方式也可以,不过需要自己去创建一些 link 所以这里只记载 brew 的方式。

  1. 确定 brew 环境的干净。
  2. 确定 php 版本,这里是用 php54,如果有多版本切换的需求可以安装 php-versionbrew-php-select,前者是用 brew 可以安装,后者依赖 npm
  3. 安装 php54-xdebug,因为 xdebug 会有一个映射版本。