Category Archives: Hello World

Hello World 业界杂谈 微信开发

微信公众平台的新功能

在犹抱琵琶半遮面了N久以后,小伙伴们期待已久的新版微信公众平台后台终于上线了,这次上线的,不仅有页面的改版,更带来很多实用的新功能

比如统计功能,可以统计用户、消息的各种数据
再比如商户功能里支付测试支持,以及对浏览次数和成交量的统计

当然啦,对于开发者来说,最吸引我们的还是伴随而来的新开发的各种接口以及姗姗来迟的调试功能

“微信公众平台接口调试工具”是一个很好用的功能,可以让我们在不使用服务器的情况下就可以调用各种接口,估计以后会很常用

下面,我们来盘点一下微信公众平台对普通公众号开发的接口们

  • 1. 基础接口
    1. a. 服务器配置的验证
      这里,是你在设置服务器url时,微信会验证这个服务器的正确性,具体的,包括代码回复格式的正确性,以及对应的微信号是否正确
      b. access_token的获取
      “access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token”,有效期两个小时
      特别注意,是“唯一”,比如对于同一个工作号,一个项目里先申请了一个access_token,另一个项目里又申请了一个,则在第一个项目里的access_token就已经失效了(即便没有超过两个小时的时限)
  • 2. 消息相关
    1. a. 接受消息
      在微信开发平台后台配置了“服务器配置”的url后,再有用户给公众号发送信息,就需要你的服务器来回复信息了
      接受消息包括文本、图片、语音、视频、地理位置,以及链接
      在这里,服务器收到的数据是xml,我们回复的,也需要是xml
      b. 语音识别结果
      “开通语音识别功能”,可以将语音信息对应的文本信息一起发送给服务器,可以那它语言搜索内的功能,如果再和上面的“上报地理位置”结合,就更有想象空间了~~
      c. 主动向用户发送消息
      之前,要主动向特定用户发送消息,只能使用微信的模板消息,使用起来相当复杂
      有了这个主动消息接口,问题就简单多了,“当用户主动发消息给公众号的时候”,开发则既可以在24小时内,给该用户发送消息,包括文本、图片、音视频、图文
      从这里开始,消息的数据格式变成了json
      d. 上传下载多媒体文件
      如果你要给用户回复、发送语音、图片、视频等内容,这个接口是非常必要的
      接口返回media_id,在3天之内,便可以随便使用这个media_id来使用你的多媒体信息
  • 3. 自定义菜单相关
    1. a. 自定义菜单的增删改查
      微信终于全面开发了自定义菜单的功能,有了自定义菜单,才有可能以微信为平台构建我们的“轻App”
      b. 自定义菜单事件
      微信的自定义菜单,可以是一个链接,也可以是发送给服务器的一个事件消息,然后服务器回复对应的消息,回复的格式同上面第2部的“接受消息”
  • 4. js api
    1. 当用户在微信内置浏览器访问我们的页面时,我们可以通过jsapi做下面的事情
      a. 获取用户网络状态,包括wifi、2G、3G
      b. 隐藏右上角分享按钮、隐藏网页底部导航栏,让你的页面更像原生应用
  • 5. 地理位置相关
    1. a. 上报地理位置
      “开通了上报地理位置接口的公众号,用户在关注后进入公众号会话时,会弹框让用户确认是否允许公众号使用其地理位置”,弹框只在关注后出现一次
      “进入时上报地理位置”,”每5秒上报一次地理位置”,这又是一个看起来很牛x的功能,我们可以围绕它做出类似导航、购物搜索的功能
  • 6. 用户信息
    1. a. 分组管理
      通过服务器增删改查分组信息,比如,可以将关注用户划分成主动向公众好发送过信息、发送过地理位置信息、回复过xx信息等等

      b. 获取用户基本消息
      “关注者与公众号产生消息交互后”,公众号既可以获得该用户的基本信息,包括昵称、头像、城市、性别等

      c. OAuth2.0
      当用户在微信内置浏览器访问我们的页面时(不需要必须关注公众号),我们就可以获得访问者的openId,以及用户的其他基本信息
      如果你执行获得访问者openId,通过几次redirect,和普通的OAuth过程是一样的
      如果要获得用户基本信息,微信就会弹出授权界面,并且每次重新再试图获取用户信息是,微信都会弹出授权界面

      d. 获取关注者列表
      终于有了同步所有关注用户列表的接口了!

      e. 扫描带参数二维码
      这是一个相当强大的功能,可以让用户扫码后自动关注你的公众号(需要用户点一下“确定”),同时你还可以给用户回复一条欢迎信息

上面只说了微信开发接口的功能,具体的接口地址、参数什么的,同志们可以自己到公众平台的开发者文档里去查询

———
参考:微信公众平台开发者文档

Hello World 他山石

[转]php字符串首字母转换大小写

首字母变大写:ucwords()

$foo = 'hello world!';
$foo = ucwords($foo); // Hello World!
$bar = 'HELLO WORLD!';
$bar = ucwords($bar); // HELLO WORLD!
$bar = ucwords(strtolower($bar)); // Hello World!

第一个词首字母变大写:ucfirst()

$foo = 'hello world!';
$foo = ucfirst($foo); // Hello world!
$bar = 'HELLO WORLD!';
$bar = ucfirst($bar); // HELLO WORLD!
$bar = ucfirst(strtolower($bar)); // Hello world!

第一个词首字母小写lcfirst()

$foo = 'HelloWorld';
$foo = lcfirst($foo); // helloWorld
 
$bar = 'HELLO WORLD!';
$bar = lcfirst($bar); // hELLO WORLD!
$bar = lcfirst(strtoupper($bar)); // hELLO WORLD!

字母变大写:strtoupper()

字母变小写:strtolower()

转自:http://www.jiangkl.com/?p=1454&preview=true

Hello World

go语言入门[3]—-并发&线程通讯

go语言的的多线程是通过“goroutine”来实现的,通过“go”关键字新建一个goroutine
比如:

    go hello()

其实,goroutine比线程更小,“十几个goroutine可能体现在底层也就五六个线程”,且是内存共享的

不同的goroutine之间,通过channel来通讯,比如下面的的程序,主线程是一个http sever,收到http请求后,将url塞入channel,然后下面的“see”函数在来处理这个channel

package main
 
import (
	"fmt"
    "net/http"
)
//定义一个channel
var cc chan string
 
func main() {
    fmt.Println("--ubox-event--start--")
 
    //新建一个channel	
    cc = make(chan string)
    //使用一个goroutine启动see函数,编号为1
    go see("1")
    //启动httpServe
    http.HandleFunc("/*", httpServe)
    http.ListenAndServe(":8008", nil)
}
 
func httpServe(w http.ResponseWriter, req *http.Request) {
    url := req.URL.String()
    w.Write([]byte("Hello"))
    //将url塞入channel
    cc <- url
}
 
func see(n string) {
    fmt.Println("--see--" + n + "--start--")
    //通过range参数取出channel,每当上面httpServe被塞入一次,这里即执行一次
    for s := range cc {
        fmt.Println("--see--url-" + n + "-" + s)
    }
}

go run 启动上面的程序,然后访问localhost:8008/xxxxx,便会看到 “–see–url-1-/xxxxx”

有意思的是 ,如果你在上面再启动两个goroutine,即go see(“2”),go see(“3”)

然后不断访问localhost:8008/xxxxx,打印出来的并不一直是 “url-1”,而是 url-1、url-2、url-3轮流出现,且每次访问只出现一次,也就是说channel并不会一直分配给第一个range

Hello World

go语言入门[1]—-发邮件

这里说的发邮件,用的是“net/smtp”,不多说,看代码

package main
 
import (
	"net/smtp"
	"fmt"
	"strings"
)
 
//对发送接口的简单封装
func SendMail(to, subject, body, mailType string) error{
	//发件服务器相关配置,这里以gmail为例
	u := "xxxxx@gmail.com"
	p := "xxxx"
	host := "smtp.gmail.com"
	port := "25" 
	//Auth
	auth := smtp.PlainAuth("", u, p, host)
	var content_type string
	if mailType == "html" {
		content_type = "Content-Type: text/html; charset=UTF-8"
	}else{
		content_type = "Content-Type: text/plain; charset=UTF-8"
	}
	msg := []byte("To:" + to + "\r\nForm: 老大<" + u + ">\r\nSubject: " 
		+ subject + "\r\n" + content_type + "\r\n\r\n" + body)
	send_to := strings.Split(to, ";")
	//发信
	err := smtp.SendMail((host + ":" + port), auth, u, send_to, msg)
	return err
}
 
func main() {
	to := "xxxx@163.com;xxxx@gmail.com"
	subject := "test-go-smtpxxx"
	body := "<html><body><hr><h1>test-smtpxxx</h1><hr></body></html>"
 
	fmt.Println("send email")
	err :=  SendMail(to, subject, body, "html")
	if err != nil {
		fmt.Println("mail error: %v", err)
	}else{
		fmt.Println("mail ok!")
	}
}
Hello World 业界杂谈

nodejs-express初体验

上次文章提到,正在用nodejs做一个小项目,现在总算是做完了

学习/搞着玩是一回事,虽然两年前就开始接触nodejs,做实际要用到项目里是另一回事,经过两周的折腾,我这里不想夸,也不想踩,只想说一下我真实的感受,给准备用nodejs的朋友一点参考

整体来讲,nodejs+express的开发模式,感觉还可以,既不像前端大牛们说的一样好,也不像另一些人说的那样一无是处,下面针对几个关键点分别说一下

1. express

早就听人说过,express是个“优秀”的web框架,我这次的使用体验,却让我没有这种感觉。大概是用惯了cakephp那种规约编程/傻瓜化的web框架的缘故,我觉得一个框架最应该做的,就是让使用者不要过多感觉到框架的存在,以最简单的方式获取请求参数/调取model/lib/以及向view层传递显示,可以让人将所有的精力都投入到业务逻辑中~~要不我还用你框架干嘛?

对express,最别扭的一点,就是它的routes配置~~居然每一个请求的url都要去做配置,让我一下想到了java/spring mvc的那个xml~~懒婆娘的裹脚布也不过如此

另一个sb的地方,是它的logger~~本来不想重写日志类的,特别是access日志,服务器能自动记录最好,找了好久,才找到如何设置express 日志的格式,可是设置好以后,发现日志时间记录的不对,格式也不好看,一查原码才发现,logger里用的是date.toUTCString~~UTC啊,尼玛我看日志的时候,还得想着和实际时间差8小时。。。

最关键的一点是,express官方的api极其“简约”,很多东西不得不去翻框架原码才能发现一些“隐藏”的功能

最后,作为一个web框架,居然没有filter,虽然app.use可以部分实现filter的功能,但用起来还是感觉很不爽

2. npm

npm真TMD乱,很多好名字都被占了,占着茅坑不拉屎

比如thrift,装上以后发现根本不能用,版本太老,仔细一看,居然2年没维护了

再比如email,居然没看见在哪儿设置邮件服务器~~太TM扯淡了

3. 模板引擎

express默认的jade就不提了,稍微复杂点的页面,就会搞得一团糟

我用的是ejs,不好不坏,只能说“可用”。我比较喜欢view层代码,后端程序和前端html/js可以一目了然,所以我使用<%作为格式符,然后用jsp编辑器打开ejs文件~~哈哈,一目了然

ejs比较不好用的一点是对于变量的判断过于严格:如果试图输出没往view层传递的变量,页面居然会直接抛500错误~~太较真了吧,你当空串处理不久行了吗?

4. nodejs

不好意思,上面说了3点,大部分都是在踩,现在该说点好的了

js&node,本身还是一款很不错的组合,虽然异步编程需要一些时间来适应,但却给高并发和快速影响带了了天生支持;另外,js语法简单自由,也可以带来很高的开发效率

对于所谓的“前后端共用代码”,我倒是觉得不太有必要~~毕竟,前后端执行环境差异巨大,写这种“公用代码”的维护成本,还不如直接copy一份给前端用

最后,给自己留一个问题:下次你还会选nodejs吗?

以前写过一个简单的前端mvc框架,jLeaf,基本思路是模仿cake和spring来做的,node出来后,曾经想顺势再写一个后端框架,名字就叫jRoot,可惜因为工作忙,也因为人懒,没能实际做出来,看到express这么难用,真想现在就把jRoot做出来,可惜现在更忙了~~~希望后面能有时间把它搞出来,也希望node能有其它更好用的web框架出来

 

Hello World 倒塌集

不靠谱的东西

这周碰到了两个不靠谱的东西,一个是微信自动回复里的图片,另一个是魅族手机

微信的公众平台API却是非常强大,可以依托它做出各种精彩的应用,然而,这周一却碰到了一个美中不足的问题不,这个问题不大不小却让人很不爽:自动回复里的图片,不管你给什么尺寸,在不同的设备里,总是会或左右,或上下的被截去一段,比如下图公众号“IT程序猿”发的内容:

可以看到,中间的图片,左侧二维码只显示了一般,右侧文字也没遮住了一部分

我测试了各种尺寸的图片,发现都不可以,最后的解决方案是:用大约1:2的图片,但是图片的上下、左右都不放重要内容,这样就随它截去了~~

这周碰上的另一个东西是魅族手机的系统浏览器。

魅族版的android,界面确实很漂亮,甚至可以说很华丽,但它的自带浏览器,确实也很屎。

昨天测试发现一个bug,在这个浏览器内,页面里里面有几个输入框,当输入时,整个页面都会随着上下不停的抖动。貌似,是因为有输入动作时,浏览器会试图将输入框放到页面的顶端,但每次定位都不成功,下次又试图再次定位,于是就出现了这种怪异的现象~~解决方案是。。。目前还没有

 

Hello World 业界杂谈 点点滴滴

php ci快速入门

久闻php ci的大名,一直没有机会使用,上周做个小项目,用到了这个框架,整体感觉很不错,这里跟大家分享一下ci的快速入门

ci文档入口:http://codeigniter.org.cn/user_guide/toc.html(中文!)

ci,即codeigniter,我用的版本是当前的最新版本2.1.3

使用ci,只要将apache或者nginx的发布目录指到codeigniter的顶级目录下就可以

下面,就按MVC的顺序来所说ci的使用

url

比如要访问/news/list的action,默认情况下,url需要这么来写:xxx.xxx.xxx/index.php/news/list~~多个index.php,很别扭

如果要去掉这个index.php,需要加上“.htaccess”来实现urlRewrite:

1
2
3
RewriteEngine on
RewriteCond $1 !^(index\.php|images|robots\.txt)
RewriteRule ^(.*)$ /index.php/$1 [L]

C

ci的controller位于application/controllers目录下,文件名和ation方法名需要遵循规约变成的规则

不多说了,还是直接写代码吧:

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
class News extends CI_Controller{
	function __construct(){
		parent::__construct();
	}
	// http://localhost/news/lists,如果要使用其他的url,需要到“application/config/routes.php”去配置路由
	public function lists($keyWords){
		//使用model,model()方法的第一个参数“news”对应application/models/news.php
		//第二个参数“News”对应下面如何引用这个model:$this->News,如果不设置这个参数,这是$this->news,及model的名字
		$this->load->model('news', 'News');
		$data['list1'] = $this->News->list_all();
		//使用library核心类
		$this->load->library('news_manager');
		$data['list2'] = $this->news_manager->search_news($keyWords);
		//使用view,向浏览器输出内容
		$this->load->view('header');
		$this->load->view('news/list', $data);
		$this->load->view('footer');
	}
	//
	public function view_news($id){
		$this->load->view('view_news', array(
			'title' => news->title,
			'content' => $news->content
		));
	}
}

如果要加载非默认数据源,需要到config/database.php里配置不同的数据源,比如现在除了default,又添加了一个叫做abc的数据源,我们如何调用哪个它哪?

1
2
3
4
5
6
7
8
9
//abc
$abc = $this->load->database('abc', true);
$this->load->model('abc_table1');
$thisi->abc_table1->db = $abc;
//default
$defaultdb = $this->load->database('default', true);
$this->load->model('news');
$this->news->db = $defaultdb;
//即通过“$this->load->database”建立数据源,然后通过$thisi->model->db=xxx为当前某个model的引用设置数据源

另一个要说的功能是:可以将controller文件放在子文件夹中,比如“application/controllers/shop/products.php”,调用它就需要“/index.php/shop/products/xxx”

M

上面的代码已经用到了模型的调用,这里所说模型的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class news extends CI_Model{
	function __construct(){
		parent::__construct();
	}
 
	function cretae($title, $content){
		$data = array(
			'title' => $title,
			'content' => $content,
			'create_time' => new time()
		);
		//INSERT INTO news (title, content, create_time) VALUES ('xxxx', 'xxxx', 'xxxx')
		$this->db->insert('news', $data); 
	}
}

1
2
3
4
5
//DELETE FROM mytable WHERE id = $id
$this->db->delete('news', array('id' => $id)); 
//等效
$this->db->where('id', $id);
$this->db->delete('news');

1
2
3
4
5
6
7
$data = array(
	'title' => $title,
	'content' => $content,
);
$this->db->where('id', $id);
//UPDATE news SET title = xxxx, content = xxx where id = $id
$this->db->update('news', $data);

1
2
3
4
5
6
7
8
9
//SELECT * FROM news
$query = $this->db->get('news');
//select * from news where id = $id limit $offset, $limit
$query = $this->db->get_where('news', array('id' => $id), $limit, $offset);
//直接取结果集
$list = $query->result();
//取结果集的数量
$count = $query->num_rows();
//此外,ci的查询还有丰富的方法,支持各种查询,诸如:where/or_where/where_in/or_where_in/like等等

打印sql

1
echo $this->db->last_query();

V

ci的view位于application/views/目录下,比如view_news.php,可以直接使用php代码书写

1
2
3
4
5
6
7
8
<html>
<head>
	<title><?php echo $title;?></title>
</head>
<body>
	<h1><?php echo $content;?></h1>
</body>
</html>

上面的title、content参数,可以通过下面的方式在controller中传递过来:

1
2
3
4
$this->load->view('view_news', array(
	'title' => $news->title,
	'content' => $news->content
));

———
参考:http://codeigniter.org.cn/user_guide/toc.html


———
蒋评:
先不论框架的易用性、性能等因素,相对于cakephp和thinkphp,ci最大的又是就是它的中文资料丰富,有完整的中文文档,甚至官网还提供中文视频教程:http://codeigniter.org.cn/tutorials,这些都大大降低了新手的入门难度[赞]

Hello World 幻界杂谈 点点滴滴

jQuery()方法的第二个参数

jquery框架的神奇之处在于,即便是对于我这种以“精通jquery”自居的人(嘿嘿),每次在看文档时,仍然能有新的发现,比如今天,就发现$(xxx)方法,其实还可以有第二个参数,并且随着第一个参数的不同,第二个参数还有不同的含义:

1.  $(selector, [context])

这种用法,相当于 $(context).find(selector) 或者 context.find(selector)

2. jQuery(html, [ownerDocument])

文档对ownerDocument的解释是:“创建DOM元素所在的文档

也就是说,如果你要编写挎document的脚本,比如iframe或者用window.open开一个新窗口,可能会用得着它

3. jQuery(html, props)

这个比较简单,直接把文档里的例子贴出来了:

$("<input>", {
type: "text",
val: "Test",
focusin: function() {
$(this).addClass("active");
},
focusout: function() {
$(this).removeClass("active");
}
}).appendTo("form")

也就是说,props内的属性会像.attr()方法一样,被设置到新创建的标签内

注:IE总不能通过.attr()设置input的type属性
----------
参考:http://www.w3school.com.cn/jquery/core_jquery.asp#syntax1

Hello World 点点滴滴

setTimeout的精度

前几天刚发了一篇利用setTimeout提高ui响应速度的文章,本来感觉自己对setTimeout的理解已经够深入了,但是昨天偶然听说setTimeout其实是有“bug”的:在setTimeout 0时,并不能精确定时,还说IE会有16ms的延时
虽然早就想到js这种东西定时肯定不会很精确的,而且这个时延对“提高ui响应速度”影响也不大,但是如果是做动画的话,影响可能就比较大了,还说抽空写了段小程序试了试

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
//定时长度
var OUT_TIME = 0;
//循环次数
var LOOP_COUNT = 100;
$(function(){
  $('a#start').click(function(){
    var t1 = mst();
    loopOut(LOOP_COUNT, t1);
  });
});
//递归循环
function loopOut(i, t1){
  if(--i > 0){
	setTimeout(function(){
	  loopOut(i, t1);
	}, OUT_TIME);
  }else{
    var t2 = mst();
    alert(t2 - t1);
  }
}
//返回当前时间点的毫秒数
function mst(){
  return new Date().getTime();
}

上面的程序,当点击“start”按钮后,首先会取当前时间t1,然后调用loopOut,当循环参数i大于0时,会通过setTimeout定时0ms递归调用loopOut,同时i会减1,形成循环终止条件,放循环满100次时,再次取当前时间,与t1相减,也就是100此setTimeout循环的用时
测试结果发现,“IE会有16ms的延时”,这话并不算错,IE7、8,最后的返回值大概是1530~1550,算起来,一次setTimeout大概要延迟DT=15.4ms
而且chrome/safari/firefox,返回值大概是420~430,即DT=4.2ms

当增大定时长度OUT_TIME时,只有增大到各浏览器的DT时,实际的定时长度才会有效,也就是DT可以看成“最小定时长度”
但是即便定时长度大于DT定时也是不精确的,非IE大概是0.2ms,IE就有点离谱了,比如OUT_TIME=50时,平均每次的定时长度大概是60ms,差了10ms(鄙视)

综上所述,虽然setTimeout传入的时间值是毫秒,但是是在并不精确,如果对精度要求较高,最好好好计算各浏览器的误差

下面讨论另外两种情况

1. 既然setTimeout有这种问题,那么与它类似的setInterval有问题吗?
将上面的“loopOut”方法稍加改进,即可setInterval:

1
2
3
4
5
6
7
8
9
function loopOut(i, t1){
  var si = setInterval(function(){
    if(--i == OUT_TIME){
      var t2 = mst();
      alert(t2 - t1);
      clearInterval(si);
    }
  }, OUT_TIME);
}

这是,IE再次表型了它的卓尔不群:当OUT_TIME=0的时候,setInterval的回调方法只会执行一次,只有OUT_TIME>0,这段脚本在IE里才能正常执行
除此之外,setInterval和前面setTimeout的表现基本相同

2. node.js的setTimeout精确吗?
将上面click部分的代码去掉,稍加修改,既可以让代码再node里执行,但是为了去除代码初始化的影响,这里用了另外一个setTimeout,让其在程序初始化100ms以后在执行

1
2
3
4
setTimeout(function(){
  var t1 = mst();
  loopOut(LOOP_COUNT, t1);
}, 100);

测试结果发现,服务器端脚本相对浏览器端的效率果然高出不少,在循环次数为100的时候,node.js的setTimeout 0ms的延迟大约在0.1ms左右,当把循环次数提高的100000后,平均的延迟降到了0.01ms

Hello World 点点滴滴

如何调试压缩后的js—— JavaScript Source Map的使用

随着webApp的流行,前端应用越来越复杂,js脚本越来越多,为了提高页面页面响应速度,我们常常需要对js进行压缩、整合
但是经过压缩后的js,调试起来会特别麻烦,基本是对于chrome/ff这些“现代”浏览器,如果压缩过的js出了错误,报的错误也是让你无所适从
之前的做法是,在开发环境不回js做压缩、整合,而且在上线前最后确认的verify以及线上环境对js做压缩,这么做虽然可以避免上面的问题,但又是js的加载顺序对功能会有影响,所以还是不能完全模拟线上的情况
更何况,线上可能还会出问题的
这里介绍一种可以调试压缩脚本的方法,就是所谓“Source Map”,大概的原理是这样的:
在使用compiler.jar压缩js的时候,同时生成一个“.map”文件,对压缩前的js诸如变量之类的内容做索引,页面加载min版的js,可以通过对应的.map文件找到压缩前的js文件,如果这是js再出错,可以直接将错误定位到压缩前的js文件内
怎么样,听起来是不是很棒
好,下面就详细讲一下用法
比如页面上有一个js文件:t.js,下面对t.js做压缩

1
2
3
4
5
java -jar /usr/local/compiler.jar \
	--js t.js \
	--create_source_map t.js.map \
	--source_map_format=V3 \
	--js_output_file t.min.js

其中,“create_source_map”就是生成.map文件的名字,“source_map_format”是source map的版本
完了页面,在页面调用t.min.js,故意制造个错误,这是你会发现,页面上的错误指示~~~还TM和以前一样

呵呵,因为还有两步我们还没做:
1. 手动往t.min.js结尾处加上下面这行,告诉浏览器.map文件所在的位置:
//@ sourceMappingURL=t.js.map

2. 开启浏览器的source map支持,悲剧的是,目前只有chrome支持source map,具体未知是在:
“开发者工具” -> “设置”(右下角齿轮) -> 勾选“Enable source maps”

ok了,这是再试试,一旦js报错,点击错误,就能定位到压缩前的js文件上
并且,在开发者工具里的“Sources”标签里,还能找到压缩前的js源码

————-
参考:http://www.csdn.net/article/2013-01-25/2813953-JavaScript-Source-Map
ps:关于.map文件内部各项内容的含义,我并没有仔细研究~~~反正只要设置正确,chrome就可以把错误定位到源js文件