Angular 与 jQuery 加载顺序的问题

最近打算把一个自己写的angular directive开源,于是花了点时间写demo,但是在demo里曾经没有什么问题的插件不能正常的工作,令人匪夷所思。 发现问题之后,困扰了一段时间,把所有能怀疑的对象都检查了一遍,依然没发现问题,最后在无意间对比过去使用这个directive的项目代码和demo代码发现二者的angular.js与jquery.js加载顺序不同,于是调换了一下顺序,果然问题就解决了。瞬间有种要掀桌的感觉(╯°Д°)╯︵ ┻━┻。 不过这个问题是为什么呢,百思不得其解,在Google了一番之后,在Google Group上找到了类似的问题。在原po的提示下,通过研究angular源码发现,原来angular会在加载到内存之后,查找 window.jQuery 如果存在,就对其进行扩展,并将jQLite绑定到jQuery上,以后创建出来的jQLite对象实际上也是jQuery对象。如果没找到,就使用angular自带的jQLite。以下是部分代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function bindJQuery() {
// bind to jQuery if present;
jQuery = window.jQuery;
// reset to jQuery or default to us.
if (jQuery) {
jqLite = jQuery;
extend(jQuery.fn, {
scope: JQLitePrototype.scope,
isolateScope: JQLitePrototype.isolateScope,
controller: JQLitePrototype.controller,
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
});
// Method signature:
// jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments)
jqLitePatchJQueryRemove('remove', true, true, false);
jqLitePatchJQueryRemove('empty', false, false, false);
jqLitePatchJQueryRemove('html', false, false, true);
} else {
jqLite = JQLite;
}
angular.element = jqLite;
}

Angular在加载到内存之后便会执行这段代码。以后所有的angular.element()创建或包装的对象都是jqLite对象。如果弄错了加载顺序,那么使用$()创建出来的对象与angular的jqLite对象便存在兼容性问题,导致一些奇怪的现象。

###正确的做法### 如果你要在项目中同时使用jQuery与AngularJS那么,一定要让jQuery在angularjs之前加载。比如下面这样写才能保证程序不出现奇怪的问题:

1
2
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>
Comments

用Grunt与livereload构建实时预览的开发环境

自从用了yeoman来开发angular.js之后,就喜欢上这个工具了,顺带也了解了整个workflow所用的所有工具,yeoman给我最深的印象就是它可以在开发的时候启动一个localhost的server来预览你的前端项目,并且能够实时反应你对文件的修改。想想把,你不在需要依赖昂贵又不准确的IDE进行WYSIWYG开发,也不需要自己动手去按F5不停的刷新页面,还要担心缓存问题。现在只要在命令行运行 grunt serve 就可以做到了 这么好的功能当然不是yeoman特有的功能,只要你会用Grunt就可以做到。如果你还不熟悉Gruntjs那么,它的官方页面可以帮你快速熟悉常用配置(是的,你不需要知道怎么写grunt plugin)

###我们需要哪些Grunt插件?###

过去这个任务需要connect-livereload, grunt-contrib-connect, grunt-contrib-watch来配合完成. 感谢connect和grunt插件的开发者,我们现在只需要2个插件就可以做到这一切了:

  • grunt-contrib-connect, 用来充当一个静态文件服务器,本身集成了livereload功能,因此不再需要connect-livereload中间件
  • grunt-contrib-watch, 用来监视文件的改变,然后执行一些任务,同时保持 grunt-contrib-connect 的服务器一直开启

为了不用不厌其烦的写grunt.loadNpmTask(),我们使用 load-grunt-tasks 来帮助我们自动加载这些插件,为了能看到grunt任务执行时间,我们加入了 time-grunt 插件,这几个插件对于本文的目的来说都不是必须的

下面是配置好的package.json

1
2
3
4
5
6
7
8
9
10
11
{
"name": "grunt-livereload-demo",
"version": "0.0.1",
"devDependencies": {
"grunt": "~0.4.2",
"grunt-contrib-connect": "~0.6.0",
"grunt-contrib-watch": "~0.5.3",
"load-grunt-tasks": "~0.3.0",
"time-grunt": "~0.2.9"
}
}

接下来运行 npm install 来安装就可以了。

###配置Gruntfile###

现在假设你已经知道Gruntfile的结构。那么让我们开始先搭建一个可以serve静态文件的服务器

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
30
31
32
33
34
35
36

module.exports = function(grunt) {

// Load all grunt tasks automatically
require('load-grunt-tasks')(grunt);

// Time how long grunt task take. Can help when optimizing build times
require('time-grunt')(grunt);

//Configure grunt
grunt.initConfig({

// The actual grunt server settings
connect: {
options: {
port: 9000,
hostname: 'localhost', // Change this to '0.0.0.0' to access the server from outside.
keepalive: true // keep the server alive. so the grunt task won't stop
},
all: {
options: {
open: true,
base: [
'examples' // This is the base file folder. we suppose our index.html is located in this folder
// replace with the directory you want the files served from
]
}
}
}
});

// Creates the 'serve' task
grunt.registerTask('serve', [
'connect:all'
]);
};

我们用grunt-contrib-connect创建了一个静态服务器,并且能自动打开浏览器。我们把配置好的任务加入到自己创建的serve任务里,别在意这里只有一个子任务,我们接下来会加入其他任务到这个serve任务里。现在你可以在命令行里运行 grunt serve (从Gruntfile所在的目录)。grunt会自动打开浏览器并访问 http://localhost:9000 并保持服务器一直运行下去(任务不会结束)。

###Watch和livereload###

虽然我们已经配置好了一个静态文件服务器。但是 grunt-contrib-connect 并不会帮助我们监视文件变化并自动加载,我们需要 grunt-contrib-watch 来完成这项工作,并触发 grunt-contrib-connect 里面的livereload功能。所以我们要修改一下Gruntfile

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
module.exports = function(grunt) {

// Load all grunt tasks automatically
require('load-grunt-tasks')(grunt);

// Time how long grunt task take. Can help when optimizing build times
require('time-grunt')(grunt);

//Configure grunt
grunt.initConfig({

// The actual grunt server settings
connect: {
options: {
port: 9000,
// Change this to '0.0.0.0' to access the server from outside.
hostname: 'localhost',
livereload: 35729 // This does not perform live reloading. this port is used by watch task to trigger a live reloading action.
},
all: {
options: {
open: true,
base: [
'examples'
]
}
}
},

//Watch files for changes, and run tasks base on the changed files.
watch: {

livereload: {
options: {
livereload: '<%= connect.options.livereload %>' // this port must be same with the connect livereload port
},
// Watch whatever files you needed.
files: [
'examples/*.html',
'examples/styles/{,*/}*.css',
'examples/scripts/(,*/}*.js',
'examples/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
]
}
}
});

// Creates the 'serve' task
grunt.registerTask('serve', [
'connect:all',
'watch'
]);
};

我们新修改的配置把connect任务的keepalive选项去掉了,换成livereload。因为watch会帮我们让grunt task挂起而不停止,所以不再需要keepalive。这里我们将livereload的端口号设置为35729。你也可以设置成其他的端口,不过必须要在watch的livereload选项里设置成相同的端口。 新加入了一个watch任务,这里我们配置了livereload的端口。配置了需要监视的文件。当这些文件发生改变时,watch任务就会触发livereload。让浏览器刷新页面。最后,我们把这两个任务按照先后顺序组合成serve任务,注意watch必须在后面,因为watch之后的任务永远不会被执行,同时我们也需要watch帮我们保持服务器一直运行。

接下来运行 grunt serve 就可以看到一个跟刚才一样的页面在浏览器窗口中打开了。不同的是,这次你修改任何监视范围内的文件,都会实时的反映在浏览器上,可以说做到的了实时预览。当你需要结束服务器的时候,使用Ctrl+C。 watch并不止能做这些,它还可以让less自动编译,自动运行jshint检查js文件的语法。connect还可以和grunt-connect-proxy结合来制作本地代理访问其他域名的api而不用处理跨域问题。当你觉得某种开发方式不够酷的时候,想想grunt吧。如果你没有找到合适的插件,就自己编写一个。Have a good day!

Comments

Hello nya.io

似乎很早就有搭建blog来记录一些技术上的心得这样的想法,不过碍于自己的行动力方面有些不足加上租用主机搭建wordpress又有些麻烦,而Octopress是ruby写的又不想学ruby,就一直都没有付诸行动。不过最近对NodeJS的兴趣和发现了Hexo这样的好工具,让我有了想要尝试blog的想法,于是这个blog就这样诞生了。

说起来,这个域名起的也很随便,一开始只是想要一个又短又好记的域名,没想到这个域名可以满足全部的要求,只是现在都没想好站名。索性就叫nyanyanya好了。域名注册过程要感谢Phenix Nemo的帮助,他提供了域名注册的代理服务和域名挂靠的服务。另外hexo这个好用的工具也是他推荐的。没有他的帮助,这个blog也不会这么快的搭建起来。

最近我在为公司制作一个统计系统的前端页面,用到了Yeoman,AngularJS,Bootstrap,NodeJS,expressjs这些工具和技术,所以近期会尝试写一些关于这方面的文章吧。鉴于本人水平有限,如果有什么错误或者不够清晰的地方,希望能够批评指正。

Comments
NEWER POSTS