使用Typescript来写javascript
使用Typescript来写javascript
昨天偶然发现idea居然支持typescript了,于是打算尝试一下typescript,目前的感觉还不错,相比haxejs,它与angularjs之间的配合要流畅得多。
与coffeescript的比较
Typescript与Coffeescript都是对javascript的改进,但两者走的是不同路线。Coffeescript是从语法的角度,通过提供类似于python/ruby的语法,让代码写起来更加简洁,可读性更好。并且它提供的一些控制结构,可以避开Javascript中的问题,比如for ... in ...
,使用coffeescript可以让多层嵌套看起来不那么痛苦:
self.validate json, (err, json) ->
if err then cb(err)
else self.mapFiles json, (err, json) ->
if err then cb(err)
else self.addFields json, (err, json) ->
if err then cb(err)
else self.store.create json, cb
回调的参数放在右边,看起来就像是把前面函数的返回值放在了右边供调用,读起来比较轻松。习惯于python/ruby的开发者可能会比较喜欢coffeescript,我也觉得它在这方面很好。
而Typescript走的是另一条路,通过增加静态类型,提高程序的可靠性,并没有从语法层面进行大的改进。
我觉得它们两者是互补的,如果能把两者结合起来,既提供静态类型,又增强语法就太好了,不过这可能在很久以后才有可能出现。
编辑器支持
Idea并没有声明已经支持typescript,没有看到相关插件,在建项目时也没有任何提示,只有在创建一个以.ts结尾的文件时,它可以识别出来,说明这还是一个实验性的功能,没有完全完成。不过经过我的试用,基本功能都有了:
- 代码高亮、格式化
- 代码提示
- 错误检查
其中编译为js文件的功能似乎有点问题,另外module()函数的检验有时候也不太对,不过这并不影响我们的使用。尽管还是一个开发中的功能,但已经跟idea对haxe的支持不相上下了。
另外,你还可以使用VS2012+typescript插件,这是官方推荐的。另外喜欢用sublime text2的同学,可到这里下载:https://github.com/raph-amiard/sublime-typescript。另外vim/emacs也有相关的插件也有,eclipse这里似乎还没有动静。
据说typescript提供了一些服务性质的api,可以让IDE实现功能(如代码提示等)更加容易。
下载地址
官方地址:http://www.typescriptlang.org
先到nodejs网站下载并安装nodejs,然后运行以下命令:
npm install -g typescript
其中npm是由nodejs提供的包管理工具,安装nodejs后就直接可用了。
相关资料
Typescript相关的资料不多,目前官网上仅有一些示例和简单的文档,我收集的有以下这些链接
- 官网:http://www.typescriptlang.org/[示例][在线尝试][官方例子源代码]
- 语言规范:http://www.typescriptlang.org/Content/TypeScript%20Language%20Specification.pdf,可用来参考,很难看懂
语言特性
Typescript官方的文档很简单,只给出了一个简单的例子,没有详细的文档来讲解各功能,所以对于初学者入门还是稍有难度,我花了不少时间才基本会用,比我预想的时间多了很多。
难度不是在语言特性上,而是在代码的组织和示例上,实在太少。比如如何调用声明文件,如何按module来组织代码等,对于初次接触typescript的人还需要一些时间理解。
我对Typescript的印象,可总结为以下几点:
- Typescript的思路还是Javascript,只是对它进行了增强
- Typescript可以看作是Javascript的超集,所有javascript代码可以直接写在typescript中,这一点对于Javascript程序员来说很方便
- Typescript中增加了静态系统,提供了class/interface/method的支持。类型系统可以让我们在编译期验证代码是否有笔误,也可以获得编辑器的提示
- Typescript以module方式来组织代码,既支持commonjs的方式,又支持amd的方式
- Typescript中的文件可分为实现文件和声明文件,有点像c++中的.cpp和.h的关系。
- Typescript即可用来写后台代码,也可以用来写前台代码,初步感觉似乎更偏后台一些
Class/Interface/Method
Typescript中提供了class、interface关键字,可将代码以类的方式组织在一起。构造函数为constructor,静态方法前要加static。在实例方法中调用静态方法时,必须在前面加上类名。这块看起来比较普通,很好理解。
class User {
constructor(name:string, age:number) {
}
hello() {
alert("hell, " + name);
User.test(age);
}
static test(n:number) {
console.log("this is a static method, argument: " + n);
}
}
module
这块对于非Javascript程序员来说,有点不太好理解。我之前虽然学习了一段时间的nodejs,但现在已经快忘光了,这几天拾起来的时候,还是很痛苦的,资料很少。现在只能算是有所了解,讲的可能不全对。
Javascript在语言层面没有提供模块机制,当代码多了以后,很难组织起来。比如在服务器端,将不同的功能分在不同的文件中以复用,或者在浏览器端,想把一个大文件分成多个,按需下载。但文件之间依赖关系、如何向外暴露对象供使用等,都没有规定。所以人们做了很多尝试,制定了一些规范来解决这个问题,其中比较有名的有两个名词,一个commonjs,一个是amd。
关于它们的介绍和相互关系,可以看这篇文章,说得很好:http://www.udpwork.com/item/3978.html
简单地说,commonjs是一套规范,它定义了如何将代码组织为模块,如何向外暴露对象,如果依赖。在导入模块时,又可以分为同步和异步,一般可认为commonjs代表同步,AMD代表异步。服务器端代码更需要同步,浏览器那边更需要异步,在typescript的编译器中同时支持这两种方式。但commonjs是默认的,所以我前面说感觉它更偏向后端。
当我们在代码中使用了module()函数时,Typescript可以把它们编译为commonjs或amd方式的调用。比如:
/// <reference path="./libs/underscore.d.ts"/>
import _ = module("underscore");
当我们这样编译时:
tsc test.ts
它会生成这样的js代码:
var _ = require("underscore");
当我们指定为amd时:
tsc --module amd test.ts
它会生成这样的代码:
define(["require", "exports", "underscore"], function(require, exports, _____) {
var _ = _____;
});
在服务器端我们一般用前者,在浏览器端一般使用后者(或者完全不用module)。
不使用Module
如果我们在typescript使用了module函数,则生成的代码在浏览器端执行时,需要有一些script loader的支持。对于浏览器端代码,我们一般生成amd风格的代码,所以需要找一个支持amd的库放在前端。这样的库有很多,比如:
- RequireJS
- Nodules
- JSLocalnet
- curl.js
可根据自己的需要使用。我尝试了RequireJS,不喜欢它的网站风格,写了那么多,但总是没说重点。比如关于它的配置,我们需要面对的第一个问题,可是它就是没给一个示例出来,让我在网上到处找别人写的例子。要想用好它,可能得先好好读它的文档,再到网上找别人的代码看。
所以最后我还是按照传统的方式来组织代码,在typescript中完全不使用module函数,而用了全局声明的方式:
/// <reference path="../../libs/underscore.browser.d.ts"/>
declare var _:UnderscoreStatic;
而不是
/// <reference path="../../libs/underscore.browser.d.ts"/>
import _ = module("underscore")
当使用declare var
来声明某变量时,即假设它已经在全局中可用,后面的UnderscoreStatic
则是在underscore.browser.d.ts
这个文件中定义的,关于underscore提供的所有方法的接口描述。
使用这种方式时,我们保证这段js在浏览器端运行时,已经导入了underscore.js。我们可以按照传统的方式来引用js文件:
<script src="http://freewind.me/blog/20130128/.../jquery.js"></script>
<script src="http://freewind.me/blog/20130128/.../underscore.js"></script>
<script src="http://freewind.me/blog/20130128/.../myapp.js"></script>
Headjs
如果你既不想用requirejs等库,又想异步下载js文件,可以考虑这个库:http://headjs.com/
headjs不支持commonjs/amd,它有自己的异步下载的api,十分简洁好用。配合我上面的declare var方式,很好用。
这里给出一个简单的例子(headjs+jquery+underscore+angularjs+本站js):
<script src="http://freewind.me/public/javascripts/head-0.99.min.js"></script>
<script type="text/javascript">
head.js('/public/libs/jquery-ui-1.8.24/js/jquery-1.8.2.min.js',
'/public/libs/jquery-ui-1.8.24/js/jquery-ui-1.8.24.custom.min.js',
'/public/libs/underscore/1.4.3/underscore.min.js',
'/public/libs/bootstrap-2.1.1/js/bootstrap.min.js',
'/public/libs/angular-1.0.2/angular.min.js',
'/public/libs/angular-ui-0.3.2/angular-ui.min.js',
'/public/libs/marked.js',
'/public/libs/slickswitch/js/jquery.slickswitch.js',
'/public/libs/html5.js',
'/public/libs/flot/0.7/jquery.flot.js',
'/jsRoutes.js',
'/public/libs/moment/1.7.2/moment.min.js',
'/public/javascripts/angular-config.js',
'/public/javascripts/app.js');
head.ready(function () {
app.value("AdminCommonData", {
"menuTree": [],
});
});
</script>
在head.js()
方法中,可传入多个js路径,它们并行下载,但按照声明顺序依次执行。可以把一个head.js()分开写成多个。head.ready()
方法将会在所有js下载完成后执行里面的回调函数。
headjs有一个非常好用的特性,即可以把head.js()放在head.ready()的后面:
head.js(".../a.js");
head.ready(function() { console.log('I'm ready')});
head.js(".../b.js");
其中的head.ready()函数,虽然写在"b.js"前面,但还是会等到b.js下载并执行完后,才会执行。
如果我们的模板引擎使用了继承关系,则该特性很有用。比如在play中有一个layout.html文件和内页main.html,我们可以这样组织代码。
layout.html
<html>
<head>
<script src="http://freewind.me/public/javascripts/head-0.99.min.js"></script>
<script type="text/javascript">
head.js('/public/libs/jquery-ui-1.8.24/js/jquery-1.8.2.min.js'
// 所有全局通用的js放在这里);
</script>
</head>
<body>
#{doLayout /}
</body>
</html>
<script>
head.ready(function() {
// 最后执行的启动代码放在最后
});
</script>
main.html
#{extends "layout.html" /}
<script>
head.js(".../main.js",
// 仅在本页中使用的js文件)
</script>
<div>
...
</div>
<script>
head.ready(function() {
// 可以在这里为layout.html最后的函数调用准备数据
});
</script>
在这里要补充一句,如果你使用angularjs+headjs的话,不能在<html>上声明ng-app="xxx",而应该在layout.html中最后的函数中调用:
<script type="text/javascript">
head.ready(function () {
angular.bootstrap(document, ["MyModule", "MyAnother"]);
});
</script>
注意一定要把<html>上的ng-app去掉。我之前尝试把它们两个结合使用总是失败,这次终于找到原因。
声明文件
如果你仔细看了前面的例子,会发现有一些以三个斜杠开头的代码,如:
/// <reference path="../../libs/underscore.browser.d.ts"/>
它是一种注释,告诉typescript编译器,当前文件使用了哪些声明文件,以帮助编辑器提示信息,及编译器检查类型。这种注释很重要,如果后面的路径不对,则编译会失败。
引用的文件以.d.ts
结尾,它们是一种声明文件,就像是c语言中的header文件,只包含了类或函数的签名,而没有实际内容,用于编辑器提示和编译器验证。它们的内容形如:
declare interface UnderscoreVoidListIterator {
(element : any, index : number, list : any[]) : void;
}
declare interface UnderscoreMemoListIterator {
(memo : any, element : any, index : number, list : any[]) : any;
}
declare interface UnderscoreListIterator {
(element : any, index : number, list : any[]) : any;
}
这种文件很重要,因为我们要想使用第三方的js库,一般都需要手动做出这样的声明文件,才能在typesafe的环境中使用它们。只需要以注释的方式写上即可,不需要在实际代码中声明或引用什么。
现在在idea中,这些引用还得手动去写,不太方便,我想tsc或者编辑器应该会对它进行增强。
下载第三方js库的声明文件
现在有很多优秀的第三方js库,我们要在typescript中使用它们,难道要一一手动创建这些文件吗?这个工作量可不小。
好在已经有人这么做并把成果开源出来了,我们可以直接下载它们,放在自己的项目中,再加上引用注释即可。
包含几乎全部的:https://github.com/borisyankov/DefinitelyTyped,你应该把它clone到本地。
使用方法:把它们clone到本地,放在某个地方(比如工具目录中),然后在我们自己的typescript中添加以一个引用即可:
/// <reference path="../../libs/AngularTS.module.d.ts"/>
/// <reference path="../../libs/underscore.browser.d.ts"/>
创建自己的声明文件
如果我们用typescript写了一些模块,想让别人调用,除了把整个代码复制给他以外,还可以生成一个声明文件(.d.ts
),让他使用该声明文件即可。tsc提供了选项让我们生成.d.ts:
tsc --declaration my.ts
如果一切正常,将会在当前目录下产生一个my.d.ts
文件,里面包含了my.ts中定义的代码的接口声明。
类型
Typescript在javascript的基础上提供了类型系统,但它同时也支持动态类型。如果我们把一个变量或者返回值声明为any
,则它表示“动态类型”,编译器不会检查它的类型信息,但我们也得不到编辑器的提示信息。
any
function(obj: any) {
obj.non_exist_method();
}
如代码中所示,obj被声明为any,虽然内部调用了一个不存在的方法,编译器也不会提示有误,只有在运行期才知道。
推断
虽然typescript支持静态类型,但我们并不需要像java那样,在每个地方都要声明类型,因为typescript可以推断:
var name = "Freewind";
则name会被认为是string类型.
function myname(name:string) {
return name;
}
则myname函数的返回值被认为是string类型。
但我们在声明的地方,最好还是加上类型,以后看起来会比较清楚。
常用类型
any | 可表示动态类型 |
string | 字符串 |
number | 数字 |
bool | true或false |
null | null |
undefined | undefined |
void | void |
string[] | 字符串数组 |
{a;b;} | 等于{a:any; b:any;} |
{ a:string, b: number; } | |
{ a:string, ()=>number; } | 后面那个是函数 |
() => void | 表示形如 function() {} 这样的函数 |
(string) => number | 表示形如 function(name:string) { return 10; } 这样的函数 |
{ [string]: number; } | 表示一个object,它的key为string,值为数学,形如:
{ "aaa": 111, "bbb": 222} |
语法糖
this.filter((todo: Todo) => todo.get('done'));
使用grunt自动编译typescript
如果你使用的编辑器还不支持自动编译typescript,可以使用grunt来编译,还可以进行更多的任务,如对产生的js进和合并、压缩等工作,十分方便。
具体可参看这篇文章:在Java项目中拥抱Nodejs — 使用gruntjs编译typescript,并将生成的js合并、压缩
Typescript待实现的特性
仅以目前的typescript来说,虽然在类型方面做的不错,但是语言本身还有很多可以改进的地方。比如简化语法,提供更好用的控制结构,对异步进行更好的支持等。如果可以在语言层面改进javascript那些容易出错、不方便的地方,我想会有更多人采用typescript。
从这里的issue列表中,可以看到呼声较高的特性有:
- 支持泛型
- 实现await关键字:可以让异步调用变得简单
- for … of:js内置的for(x in y)对于数组来说,容易产生问题,但我们又需要经常遍历数组,所以提供for of代替
- 内置压缩js的功能
- protected关键字
- 生成xml格式的文档
- extension methods:可以让我们的函数调用更加流畅,比如underscore的那些方法 ,就可以写成 array.filter(function(item) {return ..})
- allow module.exports
- string interpolatio and block strings
- support for abstract classes
- macros:利用macro,可以扩展语言本身
其中我对2,3,6,10,11这几项很感兴趣,如果它们实现了,则typescript的吸引力会大大增强。
TypeScript展望
对于Typescript的未来,我还是比较看好的,因为对于服务器端的编程,类型系统是很重要的,可以让我们的代码质量变得更高,让不同版本之间的库的兼容性也更好。
我之前使用nodejs感觉很郁闷的一点是,某一个库升级了(如改变了api接口),则相关的库都出错了,而想要找出问题很难,只能通过单元测试找到问题,再查看文档解决。而这样的问题在java中出现的就比较少,因为有类型系统的保证,如果接口改变了,直接编译都会出错。
使用typescript后,让nodejs代码也具有的这样的能力,对于社区的前进是很有帮助的。而且对于java/c#程序员来说,这是很有吸引力的。
随着以后typescript相关编辑器、工具的成熟,可以预见它将和coffeescript一样,成为javascript开发人员的标准备选,也许会有一些库和工具直接支持typescript,那样的话就会有更多人来使用typescript了。
如果你对typescript也感兴趣,欢迎加入QQ群:Typescript热情交流群(250299804)
相关工具还不够成熟
经过几天的试用,发现现在使用它也还是不够方便。主要有几下几点:
- Idea的编辑器支持typescript的高亮、格式化及代码提示,但还不能像java那样,实时显示错误,所以只能借助第三方工具(比如gruntjs)来编译并显示错误。这种方式对于一个静态类型的语言来说,不是很方便,因为类型方面的错误很容易犯,如果分成两步走,来回切换工具会让定位很麻烦。
- 我使用的是gruntjs,它基于nodejs,有typescript插件可以编译typescript文件。但是由于nodejs在windows平台上的有一个bug导致无法全面取得错误信息,当ts文件有错误时,只能取到第一行(错误行号),而拿不到具体信息。有时候怎么看那一行都不知道错在哪儿,只好又另开一个cmd,调用tsc命令去编译,很麻烦。
- 更麻烦的事情来了。在gruntjs中编译提示有误,但我手动调用tsc编译又没错,调试很久都不找不到原因。gruntjs就没法用了。
- Typescript不支持将多个ts文件合并生成一个js并压缩,所以我只能借助gruntjs工具实现,但gruntjs又有点问题。
- Typescript语言本身还存在一些问题。比如array明明有length属性,但不能被识别,导致明明与它匹配的接口会编译出错。另外,一个any[]类型的数组,应该可以包含各种对象,但放入一个string和一个函数后,提示有误,必须在string前加一个<any>(可能是因为Typescript目前还不支持泛型,导致推断有误)
综上所述,目前使用typescript进行前端开发,问题还比较多,各种小问题都会影响开发效率。所以看样子,只能老老实实地使用javascript(或者coffeescript?),等到typescript成熟一些后再用它。
使用Typescript来写javascript的更多相关文章
- CoffeeScript?TypeScript?还是JavaScript
请注意本文只是我的偏见,我努力地理解借助CoffeeScript或TypeScript之类的编译器写JavaScript代码的理由.静态编译.强类型语言和框架,我有着这些流行的.丰富的背景.我的上一份 ...
- eval5: TypeScript编写的JavaScript解释器
eval5是基于TypeScript编写的JavaScript解释器,100%支持ES5语法. 项目地址:https://github.com/bplok20010/eval5 使用场景 浏览器环境中 ...
- 使用TypeScript如何提升JavaScript编程效果?
TypeScript是个什么鬼?和JavaScript有什么关系? TypeScript是由微软开发的一种可快速入门的开源的编程语言,是JavaScript的一个超集,且向这个语言添加了可选的静态类型 ...
- 像C++一样写JavaScript
像C++一样写JavaScript C/C++/Java的include或import可以引用第3方文件和包. 这个功能在Html/Js里没有默认的实现. 假设我们有这样一个HTML文件index.h ...
- CoffeeScript飞一样的写javascript
之前看到同事在使用coffeescript写js,当我看到那简介的coffee文件,就深深的被coffescript吸引了,简洁的语法,熟练之后会大大提升javascript的开发速度,写脚本也能像飞 ...
- 代码规范(RL-TOC)用更合理的方式写 JavaScript
代码可以改变世界 不规范代码可以毁掉世界 只有先学会写规范的代码,才可以走的更远 编程语言之间有很多编程规范都是通用: 命名 不要用语言不明的缩写,不用担心名字过长,名字一定要让别人知道确切的意思; ...
- 如何写javascript代码隐藏和显示这个div
如何写javascript代码隐藏和显示这个div 浏览次数:82次悬赏分:10 | 解决时间:2011-4-21 14:41 | 提问者:hade_girl <div id="div ...
- 分享:使用 TypeScript 编写的 JavaScript 游戏代码
<上篇博客>我写出了我一直期望的 JavaScript 大型程序的开发模式,以及 TS(TypeScript) 的一些优势.博客完成之后,我又花了一天时间试用 TS,用它来重构之前编写的一 ...
- 在TypeScript中扩展JavaScript基础对象的功能
最近工作中用到,记录一下:假设我们需要一个功能,把一个数字比如10000输出为下面的字符串格式“10,000”,一般是写一个方法,那么我希望更方便一点,直接向Number类型添加一个格式化方法,比如叫 ...
随机推荐
- 生成R文件
aapt package -f -m -J H:/workspaces/java_android/Test2/gen -S H:/workspaces/java_android/Test2/res - ...
- Runtime简介以及常见的使用场景(此内容非原创,为转载内容)
Runtime简称运行时,是一套比较底层的纯C语言的API, 作为OC的核心,运行时是一种面向对象的编程语言的运行环境,其中最主要的是消息机制,Objective-C 就是基于运行时的. 所谓运行时, ...
- (简单) ZOJ 3209 Treasure Map , DLX+精确覆盖。
Description Your boss once had got many copies of a treasure map. Unfortunately, all the copies are ...
- opencv图像特征检测之斑点检测
前面说过,图像特征点检测包括角点和斑点,今天来说说斑点,斑点是指二维图像中和周围颜色有颜色差异和灰度差异的区域,因为斑点代表的是一个区域,所以其相对于单纯的角点,具有更好的稳定性和更好的抗干扰能力. ...
- Struts2实现异步调用机制详细剖析(XML和JSON)
一.使用XML传递 1.页面展示getXML.jsp <%@ page language="java" import="java.util.*" page ...
- MongoDB 3.0 WiredTiger Compression and Performance
MongoDB3.0中的压缩选项 在MongoDB 3.0中,WiredTiger为集合提供三个压缩选项: 无压缩 Snappy(默认启用) – 很不错的压缩,有效利用资源 zlib(类似gzip) ...
- 【python】matplotlib在windows下安装
昨晚装了好久的这玩意,终于在凌晨成功搞定,然后跑起了一个人人网抓取好友关系的脚本~开心. 以下是我参考的最给力的文档,全部安装一遍,就可以啦~ 但是!在安装前一定要先确认自己的python版本!本人自 ...
- 12、手把手教你Extjs5(十二)执行菜单命令在tabPanel中显示模块
上面设计好了一个模块的主界面,下面通过菜单命令的执行来把这个模块加入到主界面当中.在MainModule.js中有一个函数,生成了当前的菜单数据: // 根据data.systemMenu生成菜单条和 ...
- iOS技术框架构和更新版本的技术特性
Core OS层 Sytem 系统层包括内核环境,驱动及操作系统层unix接口.内核以mach为基础,它 负责操作系统的各个方面,包括管理系统的虚拟内存,线程,文件系统,网络以及进程间通讯.这一层包含 ...
- UVA 1386 Cellular Automaton
矩阵快速幂. 样例是这样构造矩阵的: 矩阵很好构造,但是500*500*500*logk的复杂度显然是无法通过这题的. 其实本题构造出来的矩阵是一个循环矩阵,只需直到第一行或者第一列,即可直到整个矩阵 ...