使用jshint 的同学,可否晒一下你的您已使用临时配置文件登录

最专业的Docker文章,最权威的Docker新闻.关注容器生态圈的发展.
你可能会忽略的 Git 提交规范
一直是 ESLint 的忠实用户,深知规范的重要性。然而,在新项目交接中,我被 Git Commit 规范逼疯了。才意识到自己的疏忽,于是便有了一探究竟的想法。一、为什么需要规范?无规矩不成方圆,编程也一样。如果你有一个项目,从始至终都是自己写,那么你想怎么写都可以,没有人可以干预你。可是如果在团队协作中,大家都张扬个性,那么代码将会是一团糟,好好的项目就被糟践了。不管是开发还是日后维护,都将是灾难。这时候,有人提出了何不统一标准,大家都按照这个标准来。于是 ESLint,JSHint 等代码工具如雨后春笋般涌现,成为了项目构建的必备良品。Git Commit 规范可能并没有那么夸张,但如果你在版本回退的时候看到一大段糟心的 Commit,恐怕会懊恼不已吧。所以,严格遵守规范,利人利己。二、具体规则先来看看公式:&(&): &type用于说明 commit 的类别,只允许使用下面7个标识。feat:新功能(feature)fix:修补bugdocs:文档(documentation)style: 格式(不影响代码运行的变动)refactor:重构(即不是新增功能,也不是修改bug的代码变动)test:增加测试chore:构建过程或辅助工具的变动scope用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。subject是 commit 目的的简短描述,不超过50个字符。以动词开头,使用第一人称现在时,比如change,而不是changed或changes第一个字母小写结尾不加句号(.)规范参考自阮一峰老师的文章:Commit message 和 Change log 编写指南[1]。三、异常处理我们先来看看这个异常提醒:INVALID COMMIT MSG: does not match "&type&(&scope&): &subject&" !jartto:fix bug这里之所以报出这个警告,是因为我的提交出现了两个问题:其一,使用了规范外的关键字;其二,很细节的问题,jartto:后少了空格。这时候我才回忆起来,当时提交一直失败,情急之下直接强制提交,所以以后的提交都会抱出这个异常。大致意思就是:你的之前的 Commit 不合格~你的之前的 Commit 不合格~你的之前的 Commit 不合格这时候就很烦了,我们只能去将之前的错误修正,那么如何操作呢?四、如何修改之前的 Commit 信息?其实并不复杂,我们只需要这样做:1、将当前分支无关的工作状态进行暂存git stash2、将 HEAD 移动到需要修改的 commit 上git rebase ^ --interactive3、找到需要修改的 Commit,将首行的 pick 改成 edit4、开始着手解决你的 bug5、 git add 将改动文件添加到暂存6、 git commit –amend 追加改动到提交7、git rebase –continue 移动 HEAD 回最新的 commit8、恢复之前的工作状态git stash pop大功告成,是不是想把整个 Commit 都修改一遍,逃~此处参考自:修改 Commit 日志和内容[2]五、项目中使用这时候问题又来了,为什么我提交的时候会有警告,这个又是如何做到的呢?这时候,我们需要一款 Node 插件 validate-commit-msg 来检查项目中 Commit message 是否规范。1、首先,安装插件:npm install 2、使用方式一,建立 .vcmrc 文件:{
"types": ["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert"],
"scope": {
"required": false,
"allowed": ["*"],
"validate": false,
"multiple": false
"warnOnFail": false,
"maxSubjectLength": 100,
"subjectPattern": ".+",
"subjectPatternErrorMsg": "subject does not match subject pattern!",
"helpMessage": "",
"autoFix": false}3、使用方式二:写入 package.json{
"config": {
"validate-commit-msg": {
}}4、可是我们如果想自动使用 ghooks[3] 钩子函数呢?{
"config": {
"ghooks": {
"pre-commit": "gulp lint",
"commit-msg": "validate-commit-msg",
"pre-push": "make test",
"post-merge": "npm install",
"post-rewrite": "npm install",
…}在 ghooks 中我们可以做很多事情,当然不只是 validate-commit-msg 哦。更多细节请参考:validate-commit-msg[4]六、Commit 规范的作用提供更多的信息,方便排查与回退;过滤关键字,迅速定位;方便生成文档。七、生成 Change log正如上文提到的生成文档,如果我们的提交都按照规范的话,那就很简单了。生成的文档包括以下三个部分:New featuresBug fixesBreaking changes每个部分都会罗列相关的 commit ,并且有指向这些 commit 的链接。当然,生成的文档允许手动修改,所以发布前,你还可以添加其他内容。这里需要使用工具 Conventional Changelog[5] 生成 Change log :npm install -g conventional-changelogcd jartto-domoconventional-changelog -p angular -i CHANGELOG.md -w为了方便使用,可以将其写入 package.json 的 scripts 字段。{
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -w -r 0"
}}这样,使用起来就很简单了:npm run changelog到这里,我们所有的问题都搞明白了,
没有更多推荐了,上一篇,我们是正式将ABP生成的代码项目,跑起来了,然后演示了下多租户的不同。那么这篇我们就来实现下SwaggerUI。 Q:SwaggerUI是干什么的呢? A:他是一个能将我们的webapi,通过Swagger Api来生成一个交互式的文档。通过他可以对你的接口进行调式。 1、引入Swashbuckle.core 选择PhoneBook.WebApi,然后添加nuget包(当然你也可以通过命令行添加)。 输入“Swashbuckle.core”
引入到我们的项目中。 打开 项目中的“PhoneBookWebApiModule.cs”文件. 创建一个方法“ConfigureSwaggerUi();” /// &summary&
/// 配置SwaggerUi
/// &/summary&
private void ConfigureSwaggerUi()
Configuration.Modules.AbpWebApi().HttpConfiguration
.EnableSwagger(c =&
c.SingleApiVersion("v1", "YoYoCMS.PhoneBookAPI文档");
c.ResolveConflictingActions(apiDescriptions =& apiDescriptions.First());
.EnableSwaggerUi();
然后在Initialize()中对他进行调用。
2、运行项目
运行项目,打开”/swagger/ui/index”路径
得到的效果
到此呢,ABP实现SwaggerUI的功能已经算是可以了。但是我们不能就这么满足了。还不够,为什么呢。因为我们还要讲我们的注释显示出来。
这样其他开发人员看到了接口名称才能说叫做API文档嘛。
3、对API文档进行增强
大家要特别注意:
这里生成的XML文档,一定要注意是application类库中的,不是webapi类型的
是application类库、application类库、application类库
重要的事情重复三次。( 觉得好的右边求打赏
首先我们回到 方法:ConfigureSwaggerUi中,对他进行改造。
首先打开application类库的属性设置,然后在生成中找到XML文档文件,启用生成
然后再对ConfigureSwaggerUi方法进行改造
//将application层中的注释添加到SwaggerUI中
var baseDirectory = AppDomain.CurrentDomain.BaseD
var commentsFileName = "Bin//YoYoCMS.PhoneBook.Application.xml";
var commentsFile = Path.Combine(baseDirectory, commentsFileName);
//将注释的XML文档添加到SwaggerUI中
c.IncludeXmlComments(commentsFile);
添加以下到方法中。
然后运行项目:
咦。。。怎么没有。。注释呢。。。。
当然这一切的一切只是我们可以加注释,来现在我们把注释加上。。
然后再来运行一次项目。。。
上图我们就可以看到了。已经得到了注释信息,以后开发再也不怕哪个接口是哪个了。
4、修改访问方式
到目前为止我们访问SwaggerUI的方式都是”/swagger/ui/index/” 这样的路径访问方式。
个人感觉不是很方面我们对它进行稍微的优化下。
我们可以看到EnableSwaggerUi,F12转到定义下。
支持路由重定向,那么我们就开始改造吧。
其实就这么一句话,改造后的访问路径“/apis/index”就可以了。
看看效果。
这样操作接口就比较方便了。
5、进行将提示语言修改为中文
.EnableSwaggerUi("apis/{*assetPath}", b =&
b.InjectJavaScript();
b.InjectStylesheet();
一个 是注入JavaScript文件,一个是输入css文件。所以如果这里可以对我们的SwaggerUI界面进行自定义修改的。
需要将js文件路径注入到SwaggerUI中。
.EnableSwaggerUi("apis/{*assetPath}", b =&
//对js进行了拓展
b.InjectJavaScript(Assembly.GetExecutingAssembly(), "YoYoCMS.PhoneBook.SwaggerUi.scripts.swagger.js");
//YoYoCMS.PhoneBook.SwaggerUi.scripts.swagger.js是你的命名空间加上文件的路径.
很多人在这一步翻船
YoYoCMS.PhoneBook 是你的项目默认命名空间
然后接下来才是你的各种文件夹结构。。
swagger.js需要设置为嵌入的资源。
请注意你的项目结构。每一个.代表的是一个文件夹
swagger.js 也放出来。
'use strict';
* Translator for documentation pages.
* To enable translation you should include one of language-files in your index.html
* after &script src='lang/translator.js' type='text/javascript'&&/script&.
* For example - &script src='lang/ru.js' type='text/javascript'&&/script&
* If you wish to translate some new texsts you should do two things:
* 1. Add a new phrase pair ("New Phrase": "New Translation") into your language file (for example lang/ru.js). It will be great if you add it in other language files too.
* 2. Mark that text it templates this way &anyHtmlTag data-sw-translate&New Phrase&/anyHtmlTag& or &anyHtmlTag data-sw-translate value='New Phrase'/&.
* The main thing here is attribute data-sw-translate. Only inner html, title-attribute and value-attribute are going to translate.
window.SwaggerTranslator = {
_words: [],
translate: function () {
var $this = this;
$('[data-sw-translate]').each(function () {
$(this).html($this._tryTranslate($(this).html()));
$(this).val($this._tryTranslate($(this).val()));
$(this).attr('title', $this._tryTranslate($(this).attr('title')));
_tryTranslate: function (word) {
return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] :
learn: function (wordsMap) {
this._words = wordsM
/* jshint quotmark: double */
window.SwaggerTranslator.learn({
"Warning: Deprecated": "警告:已过时",
"Implementation Notes": "实现备注",
"Response Class": "响应类",
"Status": "状态",
"Parameters": "参数",
"Parameter": "参数",
"Value": "值",
"Description": "描述",
"Parameter Type": "参数类型",
"Data Type": "数据类型",
"Response Messages": "响应消息",
"HTTP Status Code": "HTTP状态码",
"Reason": "原因",
"Response Model": "响应模型",
"Request URL": "请求URL",
"Response Body": "响应体",
"Response Code": "响应码",
"Response Headers": "响应头",
"Hide Response": "隐藏响应",
"Headers": "头",
"Try it out!": "试一下!",
"Show/Hide": "显示/隐藏",
"List Operations": "显示操作",
"Expand Operations": "展开操作",
"Raw": "原始",
"can't parse JSON.
Raw result": "无法解析JSON. 原始结果",
"Model Schema": "模型架构",
"Model": "模型",
"apply": "应用",
"Username": "用户名",
"Password": "密码",
"Terms of service": "服务条款",
"Created by": "创建者",
"See more at": "查看更多:",
"Contact the developer": "联系开发者",
"api version": "api版本",
"Response Content Type": "响应Content Type",
"fetching resource": "正在获取资源",
"fetching resource list": "正在获取资源列表",
"Explore": "浏览",
"Show Swagger Petstore Example Apis": "显示 Swagger Petstore 示例 Apis",
"Can't read from server.
It may not have the appropriate access-control-origin settings.": "无法从服务器读取。可能没有正确设置access-control-origin。",
"Please specify the protocol for": "请指定协议:",
"Can't read swagger JSON from": "无法读取swagger JSON于",
"Finished Loading Resource Information. Rendering Swagger UI": "已加载资源信息。正在渲染Swagger UI",
"Unable to read api": "无法读取api",
"from path": "从路径",
"server returned": "服务器返回"
$(function () {
window.SwaggerTranslator.translate();
* 关于新版的ABP继续翻船的地方
这两天有陆续看到留言说 提示使用动态请求的时候报错400
原因是在1.0后ABP开启了一个功能”Cross-Site Request Forgery”CSRF 中文叫做跨站请求伪造
怎么搞定呢。
1种是在我们的swaggerui的js中添加:
var getCookieValue = function(key) {
var equalities = document.cookie.split('; ');
for (var i = 0; i & equalities. i++) {
if (!equalities[i]) {
var splitted = equalities[i].split('=');
if (splitted.length !== 2) {
if (decodeURIComponent(splitted[0]) === key) {
return decodeURIComponent(splitted[1] || '');
var csrfCookie = getCookieValue("XSRF-TOKEN");
var csrfCookieAuth = new SwaggerClient.ApiKeyAuthorization("X-XSRF-TOKEN", csrfCookie, "header");
swaggerUi.api.clientAuthorizations.add("X-XSRF-TOKEN", csrfCookieAuth);
以上JavaScript代码。
还有一种是在webapimodule中关闭 CSRF功能
public override void PreInitialize()
////关闭跨站脚本攻击
Configuration.Modules.AbpWeb().AntiForgery.IsEnabled = false;
6、最终运行的效果
SwaggerUI在生产环境的隐忧
有小伙伴在群里问SwaggerUI这么强大,到了生产环境是不是要手动屏蔽?
刚刚经过我的实际测试发现无须担忧这个问题。
当我们把项目发布到IIS后。
再次打开SwaggerUI
就会发现报500错误,所以这个隐忧看看不用考虑了
如果你觉得本文章对你有帮助,可以对我打赏哦。屏幕右方
群里可以下载源代码
交流QQ群:
如此。The End
阅读(...) 评论()webApp开发之提升webApp性能和用户体验的一些方案
webApp开发之提升webApp性能和用户体验的一些方案
摘要:本文将带你了解webApp开发之提升webApp性能和用户体验的一些方案,希望本文对大家学WEBAPP有所帮助。
&性能优化性能优化,顾名思义是要提升webApp的响应速度并减少不必要的内存和网络开销。这个有很多方案啊,雅虎14条性能优化的方案&这里几乎都能用上,这里不抄书了,直接结合我的实战经验谈谈具体的做法。压缩&合并自从有了NodeJS的支持,前端静态资源压缩变得非常简单和快捷。这方面的技术很多,比如grunt、gulp、brower、webpack等,不同的工具API和功能也不尽相同。我用的比较熟练的是gulp,这里也抛砖引玉,具体介绍用gulp自动压缩和合并前端资源的策略:1、图片(压缩图片支持jpg、png、gif)&2、样式 (支持sass 同时支持合并、压缩、重命名)&3、javascript (检查、合并、压缩、重命名)&4、html (压缩)&5、客户端同步刷新显示修改&6、构建项目前清除发布环境下的文件(保持发布环境的清洁)选择gulp组件通过gulp plugins,寻找对于的gulp组件gulp-imagemin: 压缩图片&gulp-ruby-sass: 支持sass&gulp-minify-css: 压缩css&gulp-jshint: 检查js&gulp-uglify: 压缩js&gulp-concat: 合并文件&gulp-rename: 重命名文件&gulp-htmlmin: 压缩html&gulp-clean: 清空文件夹&gulp-livereload: 服务器控制客户端同步刷新(需配合chrome插件LiveReload及tiny-lr)安装Gulp组件npm install gulp-util&gulp-imagemin&gulp-ruby-sass&gulp-minify-css&gulp-jshint&gulp-uglify&gulp-rename&gulp-concat&gulp-clean&gulp-livereload&tiny-lr&--save-dev·&1项目目录结构project(项目名称)|–.git 通过git管理项目会生成这个文件夹|–node_modules 组件目录|–dist 发布环境&&&&|–css 样式文件(style.css style.min.css)&&&&|–images 图片文件(压缩图片)&&&&|–js js文件(main.js main.min.js)&&&&|–index.html 静态文件(压缩html)|–src 生产环境&&&&|–sass sass文件&&&&|–images 图片文件&&&&|–js js文件&&&&|–index.html 静态文件|–.jshintrc jshint配置文件|–gulpfile.js gulp任务文件gulp基础语法gulp的语法相当简单,通过gulpfile文件来完成相关任务,因此项目中必须包含gulpfile.js。&gulp有五个方法:src、dest、task、run、watch|-src和dest:指定源文件和处理后文件的路径&|- watch:用来监听文件的变化&|- task:指定任务&|- run:执行任务编写gulp任务/**&* 组件安装&* npm install gulp-util gulp-imagemin gulp-ruby-sass gulp-minify-css gulp-jshint gulp-uglify gulp-rename gulp-concat gulp-clean gulp-livereload tiny-lr --save-dev&*/// 引入 gulp及组件var&gulp &&&= require('gulp'), &&&&&&&&&&&&&&&&//基础库&&&&imagemin = require('gulp-imagemin'), &&&&&&//图片压缩&&&&sass = require('gulp-ruby-sass'), &&&&&&&&&//sass&&&&minifycss = require('gulp-minify-css'), &&&//css压缩&&&&jshint = require('gulp-jshint'), &&&&&&&&&&//js检查&&&&uglify &= require('gulp-uglify'), &&&&&&&&&//js压缩&&&&rename = require('gulp-rename'), &&&&&&&&&&//重命名&&&&concat &= require('gulp-concat'), &&&&&&&&&//合并文件&&&&clean = require('gulp-clean'), &&&&&&&&&&&&//清空文件夹&&&&tinylr = require('tiny-lr'), &&&&&&&&&&&&&&//livereload&&&&server = tinylr(),&&&&port = 35729,&&&&livereload = require('gulp-livereload'); &&//livereload// HTML处理gulp.task('html', function()&{&&&&var&htmlSrc = './src/*.html',&&&&&&&&htmlDst = './dist/';&&&&&gulp.src(htmlSrc)&&&&&&&&.pipe(livereload(server))&&&&&&&&.pipe(gulp.dest(htmlDst))});// 样式处理gulp.task('css', function&()&{&&&&var&cssSrc = './src/scss/*.scss',&&&&&&&&cssDst = './dist/css';&&&&&gulp.src(cssSrc)&&&&&&&&.pipe(sass({ style: 'expanded'}))&&&&&&&&.pipe(gulp.dest(cssDst))&&&&&&&&.pipe(rename({ suffix: '.min'&}))&&&&&&&&.pipe(minifycss())&&&&&&&&.pipe(livereload(server))&&&&&&&&.pipe(gulp.dest(cssDst));});// 图片处理gulp.task('images', function(){&&&&var&imgSrc = './src/images/**/*',&&&&&&&&imgDst = './dist/images';&&&&gulp.src(imgSrc)&&&&&&&&.pipe(imagemin())&&&&&&&&.pipe(livereload(server))&&&&&&&&.pipe(gulp.dest(imgDst));});// js处理gulp.task('js', function&()&{&&&&var&jsSrc = './src/js/*.js',&&&&&&&&jsDst ='./dist/js';&&&&&gulp.src(jsSrc)&&&&&&&&.pipe(jshint('.jshintrc'))&&&&&&&&.pipe(jshint.reporter('default'))&&&&&&&&.pipe(concat('main.js'))&&&&&&&&.pipe(gulp.dest(jsDst))&&&&&&&&.pipe(rename({ suffix: '.min'&}))&&&&&&&&.pipe(uglify())&&&&&&&&.pipe(livereload(server))&&&&&&&&.pipe(gulp.dest(jsDst));});// 清空图片、样式、jsgulp.task('clean', function()&{&&&&gulp.src(['./dist/css', './dist/js', './dist/images'], {read: false})&&&&&&&&.pipe(clean());});// 默认任务 清空图片、样式、js并重建 运行语句 gulpgulp.task('default', ['clean'], function(){&&&&gulp.start('html','css','images','js');});// 监听任务 运行语句 gulp watchgulp.task('watch',function(){&&&&&server.listen(port, function(err){&&&&&&&&if&(err) {&&&&&&&&&&&&return&console.log(err);&&&&&&&&}&&&&&&&&&// 监听html&&&&&&&&gulp.watch('./src/*.html', function(event){&&&&&&&&&&&&gulp.run('html');&&&&&&&&})&&&&&&&&&// 监听css&&&&&&&&gulp.watch('./src/scss/*.scss', function(){&&&&&&&&&&&&gulp.run('css');&&&&&&&&});&&&&&&&&&// 监听images&&&&&&&&gulp.watch('./src/images/**/*', function(){&&&&&&&&&&&&gulp.run('images');&&&&&&&&});&&&&&&&&&// 监听js&&&&&&&&gulp.watch('./src/js/*.js', function(){&&&&&&&&&&&&gulp.run('js');&&&&&&&&});&&&&});&});LiveReload配置1、安装Chrome LiveReload&2、通过npm安装http-server ,快速建立http服务npm install http-server&-g·&13、通过cd找到发布环境目录dist&4、运行http-server,默认端口是8080&5、访问路径localhost:8080&6、再打开一个cmd,通过cd找到项目路径执行gulp,清空发布环境并初始化&7、执行监控 gulp&8、点击chrome上的LiveReload插件,空心变成实心即关联上,你可以修改css、js、html即时会显示到页面中缓存在Web应用程序中,系统的瓶颈常在于系统的响应速度。如果系统响应速度过慢,用户就会出现埋怨情绪,系统的价值也因此会大打折扣。因此,提高系统响应速度,是非常重要的。缓存方案有2种,一种布在服务端,用于加快客户端请求的响应速度,比如 MemCache、Redis等;另一种是应用在客户端,目的是减少http请求,降低服务器压力,加快页面渲染速度。服务端缓存MemCachememcache是一套分布式的高速缓存系统,由LiveJournal的Brad Fitzpatrick开发,但目前被许多网站使用以提升网站的访问速度,尤其对于一些大型的、需要频繁访问数据库的网站访问速度提升效果十分显著[1] 。这是一套开放源代码软件,以BSD license授权发布。MemCache的工作流程如下:先检查客户端的请求数据是否在memcached中,如有,直接把请求数据返回,不再对数据库进行任何操作;如果请求的数据不在memcached中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到memcached中(memcached客户端不负责,需要程序明确实现);每次更新数据库的同时更新memcached中的数据,保证一致性;当分配给memcached内存空间用完之后,会使用LRU(Least Recently Used,最近最少使用)策略加上到期失效策略,失效数据首先被替换,然后再替换掉最近未使用的数据。Memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是将数据调用到内存中,然后从内存中读取,从而大大提高读取速度。Memcache是danga的一个项目,最早是LiveJournal 服务的,最初为了加速 LiveJournal 访问速度而开发的,后来被很多大型的网站采用。Memcached是以守护程序(监听)方式运行于一个或多个服务器中,随时会接收客户端的连接和操作。Redisredis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。客户端缓存客户端缓存的方案常用的是 ajax缓存、资源离线存储、Storage缓存等。Ajax缓存Ajax异步请求数据虽然不影响页面的同步加载,但是过多的ajax请求也会给服务器带来压力,因此我们的策略是缓存不必要或者相同的请求,把请求返回的数据用变量存在本地,然后给事件触发点设置 缓存标志,只要是相同的ajax请求就从本地取数据,或者直接return。另外再掺插一个用户体验的优化建议,就是ajax加载时的缓冲动画,动画可以用gif图片替代。/*** [开启缓冲动画]* @return&{[type]} [void]*/showLoadAnimate:function(){&&&&var&self=&&&&_loadAnimate.show();&&&&_timer=window.setTimeout(function(){&&&&&&&&self.closeLoadAnimate();&&&&&&&&self.myalert('加载失败了')&&&&},2000);},/*** [关闭缓冲动画]* @return&{[type]} [void]*/closeLoadAnimate:function(){&&&&clearTimeout(_timer);&&&&_loadAnimate.fadeOut('fast');}/*触发ajax*/$('#btn').on('click',function(){&&&&if(!$('#find-shareBody').hasClass('isCahed')){&&&&&&&&loadData();&&&&&&&&$('#find-shareBody').addClass('isCahed');&&&&}});/*数据加载函数*/function&loadData(){&&&&_util.showLoadAnimate(); //加载缓冲动画&&&&$.ajax({&&&&&&&&url: _self.APIURL+'/product/productTypes.json',&&&&&&&&type: 'GET',&&&&&&&&dataType: 'json',&&&&&&&&data: {pId: _tId},&&&&&&&&success:function(res){&&&&&&&&&&&&var&types=res.types,subTypes=res.subT&&&&&&&&&&&&var&tempStr=&&,len1=types.length,len2=subTypes.&&&&&&&&&&&&/*加载一级分类*/&&&&&&&&&&&&for(i=0;i&len1;i++){&&&&&&&&&&&&&&&&if(i===0){&&&&&&&&&&&&&&&&&&&&tempStr+=&&li class='category-item active' data-id=&+types[i].id+&&&+types[i].typeName+&&/li&&;&&&&&&&&&&&&&&&&}else{&&&&&&&&&&&&&&&&&&&&tempStr+=&&li data-id=&+types[i].id+&&&+types[i].typeName+&&/li&&;&&&&&&&&&&&&&&&&}&&&&&&&&&&&&}&&&&&&&&&&&&_categoryWrap.html(tempStr);&&&&&&&&&&&&/*加载二级分类*/&&&&&&&&&&&&tempStr=&&;&&&&&&&&&&&&for(i=0;i&len2;i++){&&&&&&&&&&&&&&&&tempStr+=&&li data-id=&+subTypes[i].id+&&&img alt='&+subTypes[i].typeName+&' src=&+subTypes[i].thumb+&_100 /&&p&&+subTypes[i].typeName+&&/p&&/li&&;&&&&&&&&&&&&}&&&&&&&&&&&&_subCategoryWrap.html(tempStr);&&&&&&&&&&&&_util.closeLoadAnimate(); //关闭缓冲动画&&&&&&&&}&&&&});}AppCache如果你的Web应用中有一部分功能(或者整个应用)需要在脱离服务器的情况下使用,那么就可以通过AppCache来让你的用户在离线状态下也能使用。你所需要做的就是创建一个配置文件,在其中指定哪些资源需要被缓存,哪些不需要。此外,还能在其中指定某些联机资源在脱机条件下的替代资源。AppCache的配置文件通常是一个以.appcache结尾的文本文件(推荐写法)。文件以CACHE MANIFEST开头,包含下列三部分内容:·&CACHE – 指定了哪些资源在用户第一次访问站点的时候需要被下载并缓存·&NETWORK – 指定了哪些资源需要在联机条件下才能访问,这些资源从不被缓存·&FALLBACK – 指定了上述资源在脱机条件下的替代资源举个例子&首先,你需要在页面上指定AppCache的配置文件:&!DOCTYPE html&&html&manifest=&manifest.appcache&&...&/html&在这里千万记得在服务器端发布上述配置文件的时候,需要将MIME类型设置为text/cache-manifest,否则浏览器无法正常解析。接下来是创建之前定义好的各种资源。我们假定在这个示例中,你开发的是一个交互类站点,用户可以在上面联系别人并且发表评论。用户在离线的状态下依然可以访问网站的静态部分,而联系以及发表评论的页面则会被其它页面替代,无法访问。好的,我们这就着手定义那些静态资源:CACHE MANIFESTCACHE:/about.html/portfolio.html/portfolio_gallery/image_1.jpg/portfolio_gallery/image_2.jpg/info.html/style.css/main.js/jquery.min.js注意:配置文件写起来有一点很不方便。举例来说,如果你想缓存整个目录,你不能直接在CACHE部分使用通配符(*),而是只能在NETWORK部分使用通配符把所有不应该被缓存的资源写出来。你不需要显式地缓存包含配置文件的页面,因为这个页面会自动被缓存。接下来我们为联系和评论的页面定义FALLBACK部分:FALLBACK:/contact.html&/offline.html/comments.html&/offline.html最后我们用一个通配符来阻止其余的资源被缓存:NETWORK:*最后的结果就是下面这样:CACHE MANIFESTCACHE:/about.html/portfolio.html/portfolio_gallery/image_1.jpg/portfolio_gallery/image_2.jpg/info.html/style.css/main.js/jquery.min.jsFALLBACK:/contact.html&/offline.html/comments.html&/offline.htmlNETWORK:*还有一件很重要的事情要记得:你的资源只会被缓存一次!也就是说,如果资源更新了,它们不会自动更新,除非你修改了配置文件。所以有一个最佳实践是,在配置文件中增加一项版本号,每次更新资源的时候顺带更新版本号:CACHE MANIFEST# version 1&CACHE:...Storage存储如果你想在Javascript代码里面保存些数据,那么这两个东西就派上用场了。前一个可以保存数据,永远不会过期(expire)。只要是相同的域和端口,所有的页面中都能访问到通过LocalStorage保存的数据。举个简单的例子,你可以用它来保存用户设置,用户可以把他的个人喜好保存在当前使用的电脑上,以后打开应用的时候能够直接加载。后者也能保存数据,但是一旦关闭浏览器窗口(译者注:浏览器窗口,window,如果是多tab浏览器,则此处指代tab)就失效了。而且这些数据不能在不同的浏览器窗口之间共享,即使是在不同的窗口中访问同一个Web应用的其它页面。注意:有一点需要提醒的是,LocalStorage和SessionStorage里面只能保存基本类型的数据,也就是字符串和数字类型。其它所有的数据可以通过各自的toString()方法转化后保存。如果你想保存一个对象,则需要使用JSON.stringfy方法。(如果这个对象是一个类,你可以复写它默认的toString()方法,这个方法会自动被调用)。举个例子&我们不妨来看看之前的例子。在联系人和评论的部分,我们可以随时保存用户输入的东西。这样一来,即使用户不小心关闭了浏览器,之前输入的东西也不会丢失。对于jQuery来说,这个功能是小菜一碟。(注意:表单中每个输入字段都有id,在这里我们就用id来指代具体的字段)$('#comments-input, .contact-field').on('keyup', function&()&{// let's check if localStorage is supported&&&if&(window.localStorage) {&&&&&&localStorage.setItem($(this).attr('id'), $(this).val());&&&}});每次提交联系人和评论的表单,我们需要清空缓存的值,我们可以这样处理提交(submit)事件:$('#comments-form, #contact-form').on('submit', function&()&{// get all of the fields we saved&&&$('#comments-input, .contact-field').each(function&()&{// get field's id and remove it from local storage&&&&&&localStorage.removeItem($(this).attr('id'));&&&});});最后,每次加载页面的时候,把缓存的值填充到表单上即可:// get all of the fields we saved$('#comments-input, .contact-field').each(function&()&{// get field's id and get it's value from local storage&&&var&val = localStorage.getItem($(this).attr('id'));// if the value exists, set it&&&if&(val) {&&&&&&$(this).val(val);&&&}});IndexedDB在我个人看来,这是最有意思的一种技术。它可以保存大量经过索引(indexed)的数据在浏览器端。这样一来,就能在客户端保存复杂对象,大文档等等数据。而且用户可以在离线情况下访问它们。这一特性几乎适用于所有类型的Web应用:如果你写的是邮件客户端,你可以缓存用户的邮件,以供稍后再看;如果你写的是相册类应用,你可以离线保存用户的照片;如果你写的是GPS导航,你可以缓存用户的路线……不胜枚举。IndexedDB是一个面向对象的数据库。这就意味着在IndexedDB中既不存在表的概念,也没有SQL,数据是以键值对的形式保存的。其中的键既可以是字符串和数字等基础类型,也可以是日期和数组等复杂类型。这个数据库本身构建于存储(store,一个store类似于关系型数据中表的概念)的基础上。数据库中每个值都必须要有对应的键。每个键既可以自动生成,也可以在插入值的时候指定,也可以取自于值中的某个字段。如果你决定使用值中的字段,那么只能向其中添加Javascript对象,因为基础数据类型不像Javascript对象那样有自定义属性。举个例子&在这个例子中,我们用一个音乐专辑应用作为示范。不过我并不打算在这里从头到尾展示整个应用,而是把涉及IndexedDB的部分挑出来解释。如果大家对这个Web应用感兴趣的话,文章的后面也提供了源代码的下载。首先,让我们来打开数据库并创建store:// check if the indexedDB is supportedif (!window.indexedDB) {&&&&throw 'IndexedDB is not supported!';// of course replace&that with&some&user-friendly notification}&// variable which will hold the database&connection&// open the database// first argument is database's name, second is it's version (I will talk about versions in a while)var request = indexedDB.open('album', 1);&request.onerror = function (e) {&&&&console.log(e);};&// this will fire when the version of the database changesrequest.onupgradeneeded = function (e) {&// e.target.result holds the connection to database&&&&db = e.target.&&// create&a store to&hold the data&// first&argument is&the store's name, second is for options&// here we specify the field that will serve as the key and also enable the automatic generation of keys with autoIncrement&&&&var objectStore = db.createObjectStore('cds', { keyPath: 'id', autoIncrement: true });&&&&&// create an index to search cds by title&&&&// first argument is the index's name, second&is&the field in&the value&// in&the last&argument we specify other options, here we only&state that the index is&unique, because there can be only&one album with&specific title&&&&objectStore.createIndex('title', 'title', { unique: true&});&&// create&an index to&search cds by&band&// this one is&not&unique, since one band can have several albums&&&&objectStore.createIndex('band', 'band', { unique: false&});};相信上面的代码还是相当通俗易懂的。估计你也注意到上述代码中打开数据库时会传入一个版本号,还用到了onupgradeneeded事件。当你以较新的版本打开数据库时就会触发这个事件。如果相应版本的数据库尚不存在,则会触发事件,随后我们就会创建所需的store。接下来我们还创建了两个索引,一个用于标题搜索,一个用于乐队搜索。现在让我们再来看看如何增加和删除专辑:// adding$('#add-album').on('click', function&()&{// create the transaction// first argument is a list of stores that will be used, second specifies the flag// since we want to add something we need write access, so we use readwrite flag&&&&var&transaction = db.transaction([ 'cds'&], 'readwrite');&&&&transaction.onerror = function&(e)&{&&&&&&&&console.log(e);&&&&};&&&&var&value = { ... }; // read from DOM// add the album to the store&&&&var&request = transaction.objectStore('cds').add(value);&&&&request.onsuccess = function&(e)&{// add the album to the UI, e.target.result is a key of the item that was added&&&&};});// removing$('.remove-album').on('click', function&()&{&&&&var&transaction = db.transaction([ 'cds'&], 'readwrite');&&&&var&request = transaction.objectStore('cds').delete(/* some id got from DOM, converted to integer */);&&&&request.onsuccess = function&()&{// remove the album from UI&&&&}});是不是看起来直接明了?这里对数据库所有的操作都基于事务的,只有这样才能保证数据的一致性。现在最后要做的就是展示音乐专辑:request.onsuccess = function&(e)&{&&&&if&(!db) db = e.target.&&&&&var&transaction = db.transaction([ 'cds'&]); // no flag since we are only reading&&&&var&store = transaction.objectStore('cds');// open a cursor, which will get all the items from database&&&&store.openCursor().onsuccess = function&(e)&{&&&&&&&&var&cursor = e.target.&&&&&&&&if&(cursor) {&&&&&&&&&&&&var&value = cursor.&&&&&&&&&&&&$('#albums-list tbody').append(''+ value.title +''+ value.band +''+ value.genre +''+ value.year +''); // move to the next item in the cursor&&&&&&&&&&&&cursor.continue();&&&&&&&&}&&&&}}这也不是十分复杂。可以看见,通过使用IndexedDB,可以很轻松的保存复杂对象,也可以通过索引来检索想要的内容:function&getAlbumByBand(band)&{&&&&var&transaction = db.transaction([ 'cds'&]);&&&&var&store = transaction.objectStore('cds');&&&&var&index = store.index('band');// open a cursor to get only albums with specified band// notice the argument passed to openCursor()&&&&index.openCursor(IDBKeyRange.only(band)).onsuccess = function&(e)&{&&&&&&&&var&cursor = e.target.&&&&&&&&if&(cursor) {// render the album// move to the next item in the cursor&&&&&&&&&&&&cursor.continue();&&&&&&&&}&&&&});}使用索引的时候和使用store一样,也能通过游标(cursor)来遍历。由于同一个索引值名下可能有好几条数据(如果索引不是unique的话),所以这里我们需要用到IDBKeyRange。它能根据指定的函数对结果集进行过滤。这里,我们只想根据指定的乐队进行检索,所以我们用到了only()函数。也能使用其它类似于lowerBound(),upperBound()和bound()等函数,它们的功能也是不言自明的。CDNCDN的全称是Content Delivery Network,即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。提供cdn的服务商很多,我所熟悉的是&又拍云,它不仅提供cdn加速服务,还有云处理的能力,比如 图片压缩、打水印、锐化等 ,个人比较推荐使用这个。你可以把 项目发布的静态资源(图片、样式、脚本)托管到又拍云上,图片还可以添加云处理如打水印等,相当方便。懒加载懒加载是一种资源加载方案,符合人脑认识事物的规则,用户滚动页面的时候自动获取更多的数据,而新得到的数据不会影响原有数据的显示,同时最大程度上减少服务器端的资源耗用。Web应用程序做的最多就是和后台数据库交互,而查询数据库是种非常耗时的过程。当数据库里记录过多时,查询优化更显得尤为重要。为了解决这种问题,有人提出了缓存的概念。缓存就是将用户频繁使用的数据放在内存中以便快速访问。在用户执行一次查询操作后,查询的记录会放在缓存中。当用户再次查询时,系统会首先从缓存中读取,如果缓存中没有,再查询数据库。缓存技术在一定程度上提升了系统性能,但是当数据量过大时,缓存就不太合适了。因为内存容量有限,把过多的数据放在内存中,会影响电脑性能。而另一种技术,懒加载可以解决这种问题。图片懒加载图片懒加载的实现思路很简单,一般是针对首页面内容比较长的页面,根据鼠标的滚动逐渐显示当屏的所有图片。你可能觉得这和 “瀑布流” 有点相似,但其实两者有本质差异。瀑布流是为了替代短分页,每次执行瀑布流会执行ajax操作。但是懒加载不一样,大家应该知道影响页面加载速度罪魁祸首就是图片,加载图片需要从服务端把图片download到浏览器然后渲染出来,这个过程非常耗时。但是 从服务端 下载图片的 url然后存储在DOM节点里是相当快速的。上面说的比较拗口,还是来个例子演示一下。先下载 jQuery的 lazyload插件,我们把图片真正的url存在img标签的data-original里,src属性 可以放一个 加载动画的gif图片的url,这样可以优化用户体验。&div&class=&new-img-box&&&&&&&img&data-original=&{{item.picture}}&&class=&lazyload&&src=&http://baaistatic.b0.upaiyun.com/Static/HK/load.gif_400&&alt=&&&/&&/div&/*执行图片加载缓冲*/$('.lazyload').lazyload({effect : &fadeIn&});这样,随着鼠标滚动栏滚动,进入当屏焦点的图片就会被&渐变加载。用户体验用户体验优化,又叫交互优化,目的是让webApp更好用。主要是通过替换方案改变 用户感知到的响应速度,结合移动设备特点提供更有针对性、更好用的体验。点击这个问题在我之前的文章里有详细介绍,感兴趣的同学可以&戳这里&。当然这里不做长篇大论复述,只说下原理和方案。300ms延迟存在的理由就目前而言,还有很多的站点没有做移动端分辨率兼容,因此部分区域需要放大、缩小来浏览。而移动端常用放大缩小的方案就是通过 “双击” ,然后在指定区域放大或缩小显示。那么问题来了,怎样区分用户是单击还是双击呢?PC端我们是通过鼠标连续两次点击的时间差来确定这个事件的触发,同样的道理,移动端我们也需要用户手指连续两次点击的这个时间差,也就是300ms延迟。tap事件的原理解决300ms延迟,最简单的办法是引入移动端的框架如zepto,用它提供的tap事件替代click事件。当然我并不强制要求你用zepto框架来做webApp开发,这里我重点是想说 tap事件的原理。首先我们要明确,tap事件不是移动端浏览器原生支持的事件,而是通过 touchstart -& touchmove -& touchend 这三个事件模拟出来的。基本条件有2个:1)从接触到离开时间间隔短&2)从起点到终点的距离小只要能监听3个事件满足上面2个条件,你可以自己模拟出一个 tap 事件。比如我之前说的&fastClick。除此之外,如果你使用的是其他框架,有一个一劳永逸的方法就是拓展&on&方法,比如针对 jQuery:&&&&/**&&&&&* [针对移动端的click事件优化]&&&&&* @type {Boolean}&&&&&*/&&&&var&isTouch = ('ontouchstart'&in&document.documentElement) ? 'touchstart'&: 'click', _on = $.fn.&&&&if(!$.fn.quickOn){&&&&&&&&$.fn.quickOn= function(){&&&&&&&&&&&&arguments[0] = (arguments[0] === 'click') ? isTouch: arguments[0];&&&&&&&&&&&&return&$.fn.on.apply(this, arguments); &&&&&&&&&&&};&&&&}&&&&&/*注意:iOS设备quickOn会有默认事件,因此用quickOn绑定的click事件需要阻止鼠标的默认事件*/&&&&$('#btn').quickOn('click',function(e){&&&&&&&&e.preventDefault();&&&&&&&&//....&&&&});点击态什么是点击态?就是&给用户明确的点击反馈,提升用户体验。用两种常见的方案:1)使用:active伪类 提供一个点击态样式。这个是通过CSS这门语言本身的特性来解决的问题,最容易让人接受,但是它有一个致命的缺点——页面滚动的时候也会触发这个伪类的样式。2)通过添加一个class类如&.active&,tap触发的时候添加这个样式,tap结束后通过 Javascript的setTimeout&延迟150ms后移除这个样式。比如:$('#btn').on('tap',function(e){&&&&var&_target=$(e.target);&&&&_target.addClass('active');&&&&&setTimeout(function(){&&&&&&&&_target.removeClass('active');&&&&},150);});滚动全局滚动,滚动条在body节点或更顶层。&局部滚动,滚动条在body下的某一个DOM节点上。弹性滚动针对IOS设备IOS设备&全局滚动&默认支持 弹性滚动,局部滚动默认不支持 弹性滚动,所以需要通过css设置。body{&&&&-webkit-overflow-scrolling:}/*局部滚动的dom节点*/.scroll-el{&&&&overflow:}这个属性建议挂在body上,这样子节点可以继承这个属性,可以避免不必要的bug。针对Andriod设备定制版本较多,表现各异,虽然默认没有弹性滚动的效果,但很多版本支持 力度滚动,而且也没有IOS上默认生硬的滚动效果效果。&-webkit-overflow-scrolling&默认浏览器是不支持的,所以不会起效果。IOS出界困扰什么时候会触发出界呢?&全局滚动:滚动到页面顶部(或底部)时继续向下(向上)滚动,就会出现。&局部滚动:滚动到页面顶部(或底部)时,手指离开停下,再继续向下(向上)滑动,就会出现。解决方案局部滚动:使用 scrollFixif(startTopScroll&=0){&&&&elem.scrollTop=1;}if(startTopScroll+elem.offsetHeight&=elem.scrollHeight){&&&&elem.scrollTop=elem.scrollHeight-elem.offsetHeight-1;}除此之外,对于局部滚动页面的固定区域要禁止touchmove默认事件。全局滚动:&暂时没有找到好的解决方法,可以考虑把全局滚动改成局部滚动。不过从iOS8开始Safari的出界部分的背景色和body的背景色保持一致。Android 蛋疼的局部滚动andriod下使用局部滚动,会导致滚动条显示异常,且滚动不流畅。因此建议只使用全局滚动,固定元素可以通过定位在顶部或底部,然后给body添加内上边距或内下边距 模拟局部滚动的效果。流畅滚动的N条军规1)body上加上&-webkit-overflow-scrolling:touch&2)iOS尽量使用局部滚动&3)iOS引进scrollFix避免出界&4)andriod下尽量使用全局滚动:尽量不用overflow:auto&,使用&min-height:100%代替&height:100%。&5)iOS下带有滚动条且position:absolute的节点不要设置背景色。键盘定制定制软键盘样式可以通过设置input的&type&属性:&form&&&&&&input type=&email&&/&&&&&&input type=&url&&/&&&&&&input type=&tel&&/&&&&&&input type=&number&&/&&&&&&input type=&search&&/&&/form&这里要注意一点,需要给form设置阻止默认onsubmit事件。另外还有个纯数字键盘的方案,注意pattern的属性值只能是&[0-9]*&form&&&&&&input&type=&text&&pattern=&[0-9]*&&/&&/form&定制软键盘行为可以通过配置input节点的&autocapitalize、autocorrect&属性。1)输入英文用户名首字母自动大写的别扭。&input type=&text&&autocapitalize=&off&&/&2)自动纠错&input type=&text&&autocorrect=&on&&/&&本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之WebApp频道!
看完这篇文章有何感觉? 已经有0人表态,0%的人喜欢
大家都在看
(C)2015 www.zhizuobiao.com All Rights Reserved
点击进入频道
请先登录您的账号
您还未登录,还不能发表评论

我要回帖

更多关于 配置文件 的文章

 

随机推荐