[Dart 入门教程] HelloWorld - 一个 HTML 小程序

很多朋友都习惯以 Hello World 开始学习一门语言 —— 在这里我们就反其道而行,假设浏览此文的都是编程达人,因此我们要直接上手写代码。或许这个做法有点冒失,每个行业每一门语言总需要几个敢于尝试的人,为什么我们就不能是先行者之一呢(貌似有点自恋,哈哈)?

在前文中我们有提到,Dart 是一门类似 JavaScript 的用于开发 Web 程序的脚本语言。那么相信大家可以理解,在这里我们需要一个 HTML 文档作为载体,用以承载我们的 Web 程序。

helloworld.html


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta author="MurphyL">
    <meta url="http://cijian.us">
    <title>HelloWorld</title>
    <link rel="stylesheet" href="helloworld.css">
  </head>
  <body>
    <!-- h1{A dart demo}+#sample_container_id>#sample_text_id{Click me!} -->    
    <h1>A dart demo!</h1>
    <div id="sample_container_id">
      <p id="sample_text_id">Click me!</p>
    </div>
    <!-- script[type='application/dart'][src='helloworld.dart'] -->
    <script type="application/dart" src="helloworld.dart"></script>
  </body>
</html>

helloworld.css

body {
  background-color: #F8F8F8;
  font-family: 'Open Sans', sans-serif;
  font-size: 14px;
  font-weight: normal;
  line-height: 1.2em;
  margin: 15px;
}

p {
  color: #333;
}

#sample_container_id {
  width: 100%;
  height: 400px;
  position: relative;
  border: 1px solid #ccc;
  background-color: #fff;
}

#sample_text_id {
  font-size: 24pt;
  text-align: center;
  margin-top: 160px;
}

对于大家而言,这个 Web 页面的大部分内容都很好理解 —— 简单的 HTML、CSS 以及一份用 Dart 编写的脚本。这里的 HTML 以及 CSS 就不细说了,直接把目光投到 Dart 的脚本上。和 JavaScript 不同的是,<script> 的 type 属性 application/dart 用来描述这里引入的是一份用 Dart 编写的脚本代码。

helloworld.dart

import 'dart:html';

void main() {
  querySelector("#sample_text_id").onClick.listen(reverseText);
}

void reverseText(MouseEvent event) {
  var text = querySelector("#sample_text_id").text;
  var buffer = new StringBuffer();
  for (int i = text.length - 1; i >= 0; i--) {
    buffer.write(text[i]);
  }
  querySelector("#sample_text_id").text = buffer.toString();
}

我们可以看到,在 helloworld.dart 文件中,最前面是一个让很多前端同学摸不着头脑的 import 语句,正如我们将在后文看到的,它的功能很多。所以 MurphyL 暂且不解释其具体的作用,现在我们只需要简单的认为它只是为我们导入了 Dart 核心行为之外的其他功能。

“所有的 Dart 应用程序都是以 main 方法作为程序的执行的入口点” —— 先给对 Dart 还不明所以的同学来一榔头 —— 意思是说程序代码的第二行是一段程式化的内容,必不可少。程序的第二行代码的作用是声明一个 main 方法(也就是 JavaScript 的 function),有过 C 或者 Java 开发经验的朋友都知道,它就是我们程序的入口了 —— 理解有障碍的朋友姑且将其当作 JavaScript 的自执行函数。简而言之,程序从这里开始执行了。[注1]

在 Dart 中,以 main 方法作为程序的入口点不只是约定,它是这一门语言强制的最佳实践。它是这样声明的:

void main(){
    // do something
}

首先我们看看 void main(),这一行只有两个单词,不知道是什么意思的朋友可以记下:这是要声明一个方法(或者说是 function),第一个单词的意思是要声明的这个方法的类型为 void(无返回值);第二个单词的意思是这个方法的名称是 main;最后的一对括号的意思很好理解了,但是他给我们透露了一个很重要的信息就是 —— 这个方法没有参数。[注2]简单的总结一下:

  1. Dart 程序都需要一个 main 方法;
  2. Dart 程序的 main 方法没有入参;

注意:和 JavaScript 不同,Dart 语言中分号是不可选的。

那么我们来看 main 方法体里的内容,相信大部分朋友在我没解释之前已经能猜到这一行代码的意思了。虽然这里只有一行代码,但是它做了很多事情(暗暗的感慨一下 Dart 语言的强大)。和阅读 JavaScript 代码一样,我们以点号(.)来拆分这行代码,然后逐段阅读:

  • querySelector("#sample_text_id"):找到一个 ID 为 sample_text_id 的 DOM 节点(意外的惊喜是 Dart 采用了和 jQuery 一致的选择器,如果我们能用上 Dart ,jQuery 好像不再必须了,哈哈。);
  • onClick:这一部分其实不需要解释 —— 获取已经找到的 ID 为 sample_text_id 的 DOM 节点的 onClick 方法(细致的说来,语法其实和 JavaScript 像极了有木有?);
  • listen(reverseText):给已经找到的 ID 为 sample_text_id 的 DOM 节点的 onClick 方法重新注册监听方法,若是当作 JavaScript 来说的话就是给稳重的这个节点的 onclick 事件重新绑定一个响应函数 reverseText。

说完 main 方法,也引出了 reverseText。那么我们也话不多说, reverseText 方法也是一个 void (无返回值)的方法,但是它和 main 方法不同的是需要一个传入一个 MouseEvent 作为入参,从字面上理解这是一个鼠标事件,那么这里的入参 event 很容易让我们联想到上文的 onClick。reverseText 的字面意思已经很明确:将一段文本 (text) 倒置 (reverse),那么我们看一下 Dart 是如何实现这个经典的作业题的。

这里就不再像描述 main 方法一样来解读 reverseText 方法的程序代码 —— 我们看一个比较信心的东西 —— reverseText 方法的第三行中的 StringBuffer —— 可变长字符串,会 Java 会觉得欣喜。若是要想不懂得朋友说明白其功用还真的不容易,所以已经干脆不解释了,我们就把它当作是个字符串(String)吧。不要说它的方法、属性和 String 不同,解释起来费劲。

for 循环很招摇很晃眼,和 JavaScript 很像,依旧不负责任不解释。整个循环体读下来意思是:倒叙遍历 ID 为 sample_text_id 的 DOM 节点的文本值,将这个节点的文本值中的每一个字符写入已声明号的 StringBuffer 中。额,瞅着个意思字符串已经有了一个倒置过来的实例哈。那么程序的最后一行解释起来就简单了 —— 将 ID 为 sample_text_id 的 DOM 节点的文本值设置为 StringBuffer buffer 的 toString (虽然说是将 StringBuffer 理解为 String,可是本质而言,它并不是字符串,因此需要 toString)之后的值。

貌似到这里,所谓的 Hello world 已经说完了。虽然偷了很多懒,但是终究是完了。那么纵观全文,基本上可以理解这个程序的作用:Web 页面上有一个 ID 为 sample_text_id 的 DOM 节点。当我们点击这个 DOM 节点的时候,这个节点的文本值的内容倒置过来。

PS. 目前大部分浏览器尚不支持 Dart ,因此无法再次给出原生的预览效果,点此查看使用 JavaScript 的实现版本。

注释

  1. 在一个 Web 页面中,所有的嵌入在 HTML 代码中的 JavaScipt 语句或者利用 script引入的脚本文件,一经解析便立即执行。但我们往往需要一个执行的入口点,一般是在 Web 页面准备完成时执行一个 function,比如 document.onload 或者 jQuery(document).ready(handel)
  2. Dart 的 main 方法并非一定要声明为 void 类型,你也可以和 C 一样声明为 int。

jQuery 插件开发解析(一)——从定义开始...

写在前面

jQuery 凭借其简洁的API,对 DOM 强大的操控性,易扩展性已经被广泛使用,越来越受到 web 开发人员的喜爱。虽然我是一个 JavaEE 开发者,但是在工作生活之中接触 jQuery 的的经历也不算少,所以开发经验也还算有那么一些。生活工作之中时常被一些朋友问及技巧或者解决方案,因此索性写这么一篇文章,算是给各位朋友做个备份,同时借助这个平台转增给各位 jQuery 爱好者,希望能达到抛砖引玉的效果吧!

jQuery插件开发的几种方式:

网上流传了很多 jQuery 插件的开发方法,MurphyL 没有理由去评论这些做法是否正确,因此在本文中,MurphyL 会从最正统(官方)的方式开始介绍。当然,存在即合理,MurphyL 不时也会夹杂一些其他的比较简单的民间开发方法。
那么首先我们来简单的看一下最正统的 jQuery 插件定义方式:

(function($){
    $.fn.插件名= function(settings){
        //默认参数
        var defaultSettings = {
        }
        /* 合并默认参数和用户自定义参数 */
        settings = $.extend(defaultSettings,settings);
        return this.each(function(){
            //代码
        });
    }
})(jQuery);

先来看模板中的第一行代码(当然我们要把这一行代码的后半部分给揪出来一起看,不然第一行就完全无意义了):

(function($){

})(jQuery);

这行代码其实是用于创建一个匿名函数。如果你对匿名函数和闭包不了解,将会对这种代码非常疑惑,那么 MurphyL 强烈建议您阅读JavaScript中的匿名函数及函数的闭包这篇文章。

模板中匿名函数的作用

保护“$”这个变量,避免“$”这个变量与你页面中的全局变量冲突。这点非常重要,“$”这个变量在编程语言中使用率非常高,你无法保证你所引入的其他js都是用“$”来代表“jQuery”。

$ 与 jQuery 各自的含义

jQuery 是 jquery 库定义的一个全局变量,而 $ 这个变量相当于 jQuery 的简写,$ 的冲突率是非常高的,不同的 JavaScript 框架$有不同的含义,但如果都使用 jQuery,那是非常繁琐的一件事,这就是第一行这行代码的用处,这个匿名函数创建了闭包,意味着在这个闭包内,你可以任意的使用 $ 这个变量,不用担心冲突的问题。

匿名函数的函数体形式

你可以试运行下面的代码,再对照下模板中的第一行代码,也许你就会大致明白其形式。

(function( x , y){  
    alert( x + y);    
})(2 ,3 );

定义一个带有个名为“$”参数的匿名函数。将jQuery这个全局变量传入匿名函数,并执行匿名函数。

接下来说一说 $.fn$.fn 或者 jQuery.fn 本质上可以等于 jQuery.prototype。prototype (原型)出现了,prototype 在 js 中极其重要,是在 JavaScript 实现面向对象编程的关键,真的展开说,估计 MurphyL 可以写好几天,这里 MurphyL 推荐二本书《JavaScript 高级编程》和《JavaScript 设计模式》都有对 prototype 进行详解,你也可以看网上的教程。这里以 MurphyL 编写的 tip 插件为例:

$.fn.tip = function(settings){

}

实际上你就给jQuery扩展了一个名为 tip 的方法,接下来你可以如下调用执行该方法:

(function($){
   $('a').tip();
});

那么,在$.fn.tip 中 this 上下文就会指向 $('a') 这个对象。

jQuery 的继承方法 $.extend

$.extend 在jQuery 插件开发中有个很重要的作用,就是用于合并参数。仍旧以 tip 为例:

$.fn.tip = function(settings){
        var defaultSettings = {
            //颜色
            color        : 'yellow',
            //延迟
            timeout      : 200
        }

        /* 合并默认参数和用户自定义参数 */
        settings = $.extend(defaultSettings,settings);
        alert(settings.input);
    }

插件调用代码如下:

$('a').tip({color:'blue'});

如果你运行以上代码,就会发现弹出的值为 blue,而不再是默认的 yellow。$.extend(defaultSettings,settings); 的含义是,使用 settings 来覆盖 defaultSettings(同名键值)。实际上 extend 不止接受二个参数,相对于模板上的写法,MurphyL 更喜欢下面的写法:

settings = $.extend({},defaultSettings,settings);

即不去覆盖defaultSettings(默认参数),而是合并到一个空的 Object。

return this.each(function(){
    //代码
});

要读懂这行代码,有三个问题需要解决:

  1. this指代什么?
  2. 为什么使用return?
  3. 为什么要使用each?

其中第一个分体已经在前文解答,不再赘述;

对于第二个问题,基本上大部分的 jQuery 插件教程都没有说到原因,MurphyL 觉得主要原因还是方便链式调用:this.each() 执行完返回的是 this,这时候再 return this.each() ,返回的依旧是 this ,而这个 this 上下文又是指代前文中的 $('a') ,意味着你可以在 tip () 后继续加其他方法,比如:

$('a').tip({color:'blue'}).size();

最后一点很容易理解:$('a') 很明显是一个对象集合,我们希望所有的带有 title 属性的容器都能出现提示框,所以就需要遍历 $('a')