typescript变量声明

前一章我们写了一个相加程序,可以完成数字和数字的相加,也可以完成字符串和字符串的相加,根据第一个参数,提示第二个参数的类型。如果把一个数字和字符串相加,编译器就会报错,是不是很智能呢?细心的会发现声明了一个类型:type operator = string | number;

type是声明一个类型的关键字,不会新建一个类型,它创建了一个新名字来引用那个类型。

这是我们完成类型检测的关键。下面我们来系统地学习typescript类型的奥秘,其中插入ES6语法的拓展。

声明变量let、var、const

var导致的声明提升

在ES6之前的变量声明,var的使用,看看下面的道理:

1
2
3
4
console.log(a);// undefined
var a = 1;
console.log(a);// a = 1;
console.log(b);//Uncaught ReferenceError: b is not defined,引用错误b没有被声明

a和b打印出来的值不一样,为何?在javascript运行的过程中先会执行一遍预解析,使得变量a会被录入,但是没有执行赋值,故第一次打印a的值为undefined,赋值后打印a的值为1,变量b无声明,得到b is not defined。

经典闭包问题的解决

在JavaScript的面试中经常会碰到闭包问题,你能回答出下面的结果吗?

1
2
3
4
5
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}

答案是5个5,思考一下。

typescript介绍

TypeScript 简单来说是一门编程语言,与你熟知的 JavaScript、Java 和 C++ 之类的编程语言一样。既然有这么多的语言可供选择,为什么又要新创建一门语言?先看看官网对 TypeScript 的描述,TypeScript 是 JavaScript 类型的超集,它可以编译成纯JavaScript。那么为什么需要使用 TypeScript 而不是 JavaScript 呢?下面我将说服你为什么要使用 TypeScript。

历史

javaScript是一门好语言吗

对于这个问题,不同的人有不同的想法。JavaScript 作为最多人使用的编程语言,也是在网页端取得绝对地位的语言,常常因为其动态类型、隐式转换而受到诟病。下面简要说明,一个完整的 JavaScript 包括三个不同的部分组成,核心(ESMAScript语言,简称ES),文档对象模型(DOM)和浏览器对象模型(BOM),ESMAScript中有5种简单数据类型:undefined,null,boolean,number和string。

1
2
var result1 = ("55" == 55); //true,因为字符串“55”被转换数值55后相等
var result2 = ("55" === 55); //false, 因为不同的类型不相等

这看起来令人困惑,明明差不多的操作,得到的结果却截然相反,如果由于疏忽,少写了一个等号,会得到令人检测不到的 Bug。当然,你可能精通JavaScript,不会犯这种低级错误,但是如果和你合作的某个人,你能真正地保证他不会犯这种错误吗?

CoffeeScript的出现

为了减少犯这种错误的可能性,出现了CoffeeScript。CoffeeScript是一套javaScript的转译语言,于2009 年 12 月 24 日, 提交版本 0.0.1 ,创建者 Jeremy Ashkenas 戏称它是-JavaScript 的不那么铺张的小兄弟。因为 CoffeeScript 会将类似 Ruby 语法的代码编译成 JavaScript,而且大部分结构都相似,但不同的是 CoffeeScript 拥有更严格的语法。
在coffeeScript中,由于操作符==常常带来不准确的约束, 不容易达到效果, 而且跟其他语言当中意思不一致, CoffeeScript 会把 == 编译为 ===, 把 != 变异为 !==. 此外, is 编译为 ===, 而 isnt 编译为 !==,这样就避免了问题。
随着项目的日渐拓展,代码量越来越多,有了这样的代码:

koa装饰器

装饰器是一种特殊类型的声明,可以用来“装饰”三种类型的对象:类的属性/方法、访问器、类本身。装饰器使用@expression这种形式,expression求值后必须为一个函数,称为装饰器工厂函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数
注意装饰器的调用顺序:

  1. 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。
  2. 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。
  3. 参数装饰器应用到构造函数。
  4. 类装饰器应用到类。

参数装饰器会在方法装饰器之前调用,对参数进行注解,外部装饰器可以拿到注解。

解析现代koa装饰器

下面来一段装饰器写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// CatController.ts
import { Path, Get, Post, Param, Query, Body } from 'koa-decorate';
@Path('/api/cat')
class CatController {
@Get
@Path('/info/:type')
getCatInfo (
@Param('type') type: string,
@Query('info') info: string) {
return { type, info }
}
@Post
@Path('/info/:type')
CreateCat (
@Param('type') type: string,
@Body('requestBody') requestBody: any) {
return {
status: 200,
data: Object.assign(requestBody, { type }),
message: 'Created successfully...'
}
}
}
export { CatController };

这是一段TypeScript上 koa 路由类的写法,注意到在其中,使用了@Paht @Get的写法, 并且在入参中也有@PathParam('id') id: number这样的写法。这就是装饰器。其中 @Path(‘/api’)中的API是这个装饰器的入参,在这里是注解,因为这个框架通过Reflect.defineMetadata将这个入参写入到了该方法中。

Reflect.defineMetadata是一种反射的写法,保存一个对象到缓存中,通过Reflect.getMetadata来访问缓存,无需做过多操作

思考一下该如何实现。

上面我们一共引入了六个装饰器Path, Get, Post, Param, Query, Body, 其中path既是类装饰器又是方法装饰器,get和post是方法装饰器,且装饰在path方法装饰器之上,Param, Query, Body 是属性装饰器。考虑到装饰器的执行顺序,先是属性装饰器,再是方法装饰器,然后是类装饰器。

koa-decorate 参数装饰器

三个参数装饰器 Param, Query, Body 很简单,定义一个独一无二的Symbol类型的键,保存被修饰的参数的值和索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const paramSymbolKey = Symbol.for("param");
const querySymbolKey = Symbol.for("query");
const bodySymbolKey = Symbol.for("body");
// 参数装饰器,定义一个target.propertyKey[Param]对象,并设置对象的argName = argIndex
const Param = argName => {
return (
target: Object,
propertyKey: string | symbol,
argIndex: number
) => {
// 获取保存在target.propertyKey[param]的值,如果不存在,就设置一个空对象
const args =
Reflect.getMetadata(paramSymbolKey, target, propertyKey) || {};
// 设置对象的argName = argIndex,键为被修饰的值,值为参数的索引
args[argName] = argIndex;
// 设置新的target.propertyKey[param]的值
Reflect.defineMetadata(paramSymbolKey, args, target, propertyKey);
};
};

query 和 body 参数装饰器与 param 装饰器的内容大同小异,只不过是保存的不同的键的对象。

typescript接口

在计算机中,接口是计算机系统中两个独立的部件进行信息交换的共享边界。这种交换可以发生在计算机软、硬件,外部设备或进行操作的人之间,也可以是它们的结合。
Typescript通过类型检查的方式减少了程序犯错误的机率,但是不能约束代码的产出方式,模块暴露出的API变更之后会产生一系列的麻烦。在TypeScript里,接口的作用就是规范代码,为需要约束的类型命名和为你的代码或第三方代码定义产出方式。因为typescript认为约束条件一样时是属于同一类,所以它有时被称做“鸭式辨型法”或“结构性子类型化”。

具名接口

通过interface关键词为约束条件添加名字,主要为对象和函数添加约束,利用typescript中提到的类型:

  1. string
  2. number
  3. boolean
  4. symbol
  5. array
  6. function
  7. object
  8. null
  9. undefined
  10. any
  11. void
  12. tuple
  13. unknown
  14. never
  15. 字面量类型

对象的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface User{
name: string;
id: number;
}
interface Car{
name: string;
id: number;
}
let user: User = {
name: "lily",
id: 3
}
let car: Car = {
name: "tick",
id: 1
}
function getUserName(user: User) {
return user.name;
}
getUserName(car);

函数getUserName限制传入参数为User类,可是我们传入了一个Car类,并且没有报错,这是为什么呢?User类的约束条件为function test(obj) { return obj === {} && obj.name !== undefined && obj.id !== undefined;},同时Car类的约束条件也是一样的,所以函数可以传入Car类,所以typescript有时被称做“鸭式辨型法”或“结构性子类型化”。

函数的接口

为函数添加约束条件,约束函数的传入参数和返回值,与第四章添加函数的类型检查时一样。

1
2
3
4
5
6
7
interface isArray{
(array: []): boolean;
}
const isArray : isArray = function(array: []) {
return Object.prototype.toString.call(array) === '[object Array]';
}

属性的约束

检查对象的属性分为必须、可选、只读,函数的参数有必须、可选之分

可选属性

有时候不确定某个属性是否存在时,在声明的属性后面加入一个?,就可以使属性变得可选。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface User{
name: string;
id?: number;
}
let user: User = {
name: "boy",
}
let user1:User = {
name: "jack",
id: 4
}
function createUser(name: string, id?: number): User {
return {
name: name,
}
}

检查条件为function test(obj) { return (obj === {} && obj.name !== undefined && obj.id !== undefined;) || obj === {} && obj.id !== undefined;}

数据结构之排序

排序就是将一组对象按照某种逻辑重新排列的过程。

初级排序算法

选择排序

不断重复找到最小的元素,移到相应位置。{0-N} = {0, 1, 2,..N}, {n} = swap( nums[n], min{ nums[n~nums.length] } )

1
2
3
4
5
6
7
let nums = [1,3,5,2,4];
for(let i =0; i< nums.length; i++) {
for(let j = i; j < nums.length; j++) {
minNum = min(minNum, nums[j]);
}
swap(minNum, nums[i]);
}

有 N 次交换和N+(N-1)+..+2+1~N^2/2次比较

插入排序

每次将一个数插入有序数组的适当位置。{0-N} = {0, 1, 2,..N},{n} = { nums[0~n]}.swap( less(nums[n], nums[n-1]) )

1
2
3
4
5
6
7
for(let i=0;i<nums.length; i++) {
for(let j = i; j >0; j--){
if(num[j] < nums[j-1]) {
swap(nums[j], nums[j-1]);
}
}
}

插入排序所需的时间与输入的元素的初始顺序有关,最坏情况下需要N^2/2次比较和N^2/2次交换,最好需要N-1次比较和0次交换

web服务器框架原理

原理

通过一个数组收集中间件,然后通过算法让其依次执行,平常所写的业务代码被收集起来,交给框架去最优化运行

命名空间

1
2
3
4
5
6
7
8
9
let app = {
stacks: [];
};
//监听http请求
app.listen = function (...args) {
const server = http.createServer(app.callback);
return server.listen(...args);
};
module.exports = app;

实例收集

通过app.use收集要使用的中间件

1
2
3
4
5
6
app.use = function(fn) {
if(typeof fn === "function"){
throw('middleware must be a function')
}
app.stacks.push(fn);
}

算法与运行时

数组的链式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
app.callback = function(req, res) {
dispatch(req, res, app.stacks);
}
//每当有请求传来,就调用中间件数组
function dispatch(req, res, stack) {
let next = function (err) {
if (err) {
return handleError(err, req, res, stack);
}
let middleware = stack.shift();
if (middleware) {
try {
//next传递给下一个中间件,形成尾调用
middleware(req, res, next);
} catch (err) {
next(err);
}
}
};
next();
};

中间件

中间件是一个函数,function(req, res, next),req是IncomingMessage对象,res是ServerResponse对象,next函数调用下一个中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const query = function query(options) {
var opts = merge({}, options);
var queryparse = qs.parse;
if (typeof options === "function") {
queryparse = options;
opts = undefined;
}
return function query(req, res, next) {
if (!req.query) {
var val = url.parse(req.url).query;
req.query = queryparse(val, opts);
}
next();
};
};
app.use(query());

计算机系统构造第二期

模块化、对象和状态

我们关于世界的常规观点之一,就是将它看作聚集在一起的许多独立对象,每个对象都有自己随着时间变化的状态。所谓一个对象有状态,也就是说它的行为受到它的历史的影响

赋值与局部状态

假定开始账户里有100元钱,在不断使用withdraw的过程中可以得到以下响应序列:

1
2
3
withdraw(25) // 75
withdraw(25) // 50
withdraw(60) //余额不足

可以看到每次执行withdraw的结果不同,我们用一个变量balance去表示账户里的现金余额

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let balance = 100;
function withdraw(amount) {
if(amount > balance){
console.log("余额不足");
} else {
balance -= amount;
console.log(balance);
}
}
withdraw(50);// 50
withdraw(20);// 30
withdraw(20);// 10
----------------------------------
function new_withdraw(amount) {
let balance = 100;
if (amount > balance) {
console.log("余额不足");
} else {
balance -= amount;
console.log(balance);
}
return balance;
};
w1 = new_withdraw(50);// 50
w2 = new_withdraw(20);// 80
w3 = new_withdraw(20);// 80

可以看到w1、w2、w3是各自独立的,互不影响,与所有状态都必须显式地操作和传递额外参数的方式相比,通过引进赋值和将状态隐藏在局部变量中的技术,我们能以一种更模块化的方式构造系统。

计算机系统构造浅解第一期

程序设计的基本元素

  1. 基本表达式,用于表示语言关心的最简单个体。
  2. 组合的方法,通过它们可以从简单的东西出发构造出符合的元素。
  3. 抽象的方法,通过它们可以为复合对象命名,并将它们当做单元去操作。

在程序设计中,我们需要处理两类要素:过程和数据。数据是一种我们希望去操作的“东西”,而过程就是有关操作这些数据的规则的描述。

抽象过程

数据参数化

如果我们要计算2的平方,你可能会这样写:

1
result = 2*2

现在我们要表达一个”平方”的概念,则有如下公式:

1
2
3
4
function square(x) {
return x * x;
}
result = square(2);

我们提出了一个复合过程平方,从只能计算2的平方到任意数的平方,差别在于给了过程一个参数,把数据参数化,可以提高抽象性和适用性,下面用平方去定义平方和的概念;

1
2
3
function squareSum(x, y) {
return square(x) + square(y);
}

精读前后端渲染之争

本文转载自Here’s why Client-side Rendering Won,同时中文翻译地址,如果想获得更好的阅读体验,请阅读原文

1 引言

我为什么要选这篇文章呢?

ca1b6618-2209-11e7-99ae-c2a75ad3dabb.png

十年前,几乎所有网站都使用 ASP、Java、PHP 这类做后端渲染,但后来随着 jQuery、Angular、React、Vue 等 JS 框架的崛起,开始转向了前端渲染。从 2014 年起又开始流行了同构渲染,号称是未来,集成了前后端渲染的优点,但转眼间三年过去了,很多当时壮心满满的框架(rendr、Lazo)从先驱变成了先烈。同构到底是不是未来?自己的项目该如何选型?我想不应该只停留在追求热门和拘泥于固定模式上,忽略了前后端渲染之“争”的“核心点”,关注如何提升“用户体验”。

原文分析了前端渲染的优势,并没有进行深入探讨。我想以它为切入口来深入探讨一下。

明确三个概念:「后端渲染」指传统的 ASP、Java 或 PHP 的渲染机制;「前端渲染」指使用 JS 来渲染页面大部分内容,代表是现在流行的 SPA 单页面应用;「同构渲染」指前后端共用 JS,首次渲染时使用 Node.js 来直出 HTML。一般来说同构渲染是介于前后端中的共有部分。

使用G2重构日历

G2是什么

G2是一套基于可视化编码的图形语法,以数据驱动,具有高度的易用性和扩展性,用户无需关注各种繁琐的实现细节,一条语句即可构建出各种各样的可交互的统计图表。

简单来说G2就是一个可视化JavaScript工具库,把传进去的数据用图表表现出来,但是不同于其他图形库,G2是基于图形语法工作的

图形语法

图形语法描述了我们从数据到图表的映射

从数据到图形的过程
  1. 从数据创建变量 data -> variables[set]
  2. 对变量集进行运算,相对比与集合的运算,找到感兴趣的研究变量(cross, blend, nest)
  3. 对要研究的变量进行归一化,定义变量的性质(cat, time)
  4. 对复杂的变量进行统计,并不是所有的变量都要进行统计
  5. 创建几何图形 variable -> graph
  6. 将建立的图形放到坐标系
  7. 对数据进行美学优化(color, point, label)
  8. 得到相应图形