月度归档:2013年11月

Javascript中的函数声明和函数表达式

Javascript有很多有趣的用法,在Google Code Search里能找到不少,举一个例子:
<span>&lt;script&gt;</span>
<span>~function() {</span>
<span>    alert("hello, world.");</span>
<span>}();</span>
<span>&lt;/script&gt;</span>
试一下就知道这段代码的意思就是声明一个函数,然后立刻执行,因为Javascript中的变量作用域是基于函数的,所以这样可以避免变量污染,但这里的位运算符『~』乍一看让人摸不到头脑,如果去掉它再运行则会报错:SyntaxError。
在阐述为什么之前,让我们先来明确Javascript中的两个概念:函数声明和函数表达式:
先来看看什么样的是函数声明:
 
<span>&lt;script&gt;</span>
<span>function() {</span>
<span>    alert("hello, world.");</span>
<span>};</span>

function foo() {
    alert(“hello, world.”);</script>

再来看看什么样的是函数表达式:
<span>&lt;script&gt;</span>
<span>var foo = function() {</span>
<span>    alert("hello, world.");</span>
<span>};</span>
<span>&lt;/script&gt;</span>
现在回头看看文章开头的问题,为什么去掉位操作符『~』后运行会报错,这是因为从语法解析的角度看,Javascript不允许在函数声明的后面直接使用小括号,而函数表达式则没有这个限制,通过在函数声明前面加上一个『~』操作符,就可以让语法解析器把后面看成是函数表达式,同样的,在函数声明前面加上『!,+,-』等操作符也是可行的。
那我们为什么不使用下面这种函数表达式的方式呢?
<span>&lt;script&gt;</span>
<span>var foo = function() {</span>
<span>    alert("hello, world.");</span>
<span>}();</span>
<span>&lt;/script&gt;</span>
虽然从语法解析的角度看没有问题,但是上面的代码存在弊端,它引入了一个变量,可能会污染现有的运行环境,带来潜在的问题。
使用位操作符“~”的方法显得有点奇技淫巧,其实把函数声明用小括号套起来更易读:
<span>&lt;script&gt;</span>
<span>(function() {</span>
<span>    alert("hello, world.");</span>
<span>})();</span>
<span>&lt;/script&gt;</span>
弄明白了原理,不管遇到什么写法,都不会再丈二和尚摸不着头脑了。
This entry was posted in Technical and tagged Javascript by 老王. Bookmark the permalink.
8 THOUGHTS ON “JAVASCRIPT中的函数声明和函数表达式”

转载地址:http://huoding.com/2011/03/02/48
转载目的:用于记录保存资料和学习,感谢原作者分享

AngularJs中的Modules详解

一、什么是Module?
  很多应用都有一个用于初始化、加载(wires是这个意思吗?)和启动应用的main方法。angular应用不需要main方法,作为替代,module提供有指定目的的声明式,描述应用如何启动。这样做有几项优点:
  1. 这过程是声明描述的,更加容易读懂。
  2. 在单元测试中,不需要加载所有module,这对写单元测试很有帮助。
  3. 额外的module可以被加载到情景测试中,可以覆盖一些设置,帮助进行应用的端对端测试(end-to-end test)。
  4. 第三方代码可以作为可复用的module打包到angular中。
  5. module可以通过任意顺序或并行加载(取决于模块执行的延迟性,due to delayed nature of module execution)。
二、The Basics(基础)
  我们很迫切想知道如何让Hello World module能够工作。下面有几个关键的事情要注意:
module API(http://code.angularjs.org/1.0.2/docs/api/angular.Module)
注意的提及的在<html ng-app=”myApp”>中的myApp module,它让启动器启动我们定义的myApp module。
<span>&lt;!DOCTYPE HTML&gt;</span>
<span>&lt;html lang="zh-cn" ng-app="myApp"&gt;</span>
<span>&lt;head&gt;</span>
<span>    &lt;meta charset="UTF-8"&gt;</span>
<span>    &lt;title&gt;basics&lt;/title&gt;</span>
<span>    &lt;style type="text/css"&gt;</span>
<span>        .ng-cloak {</span>
<span>            display: none;</span>
<span>        }</span>
<span>    &lt;/style&gt;</span>
<span>&lt;/head&gt;</span>
<span>&lt;body&gt;</span>
<span>&lt;div&gt;</span>
<span>    {{'Kitty' | greet}}</span>
<span>&lt;/div&gt;</span>
<span>&lt;script src="../angular-1.0.1.js" type="text/javascript"&gt;&lt;/script&gt;</span>
<span>&lt;script type="text/javascript"&gt;</span>
<span>    var simpleModule = angular.module("myApp", []);</span>
<span>    simpleModule.filter("greet", function () {</span>
<span>        return function(name) {</span>
<span>            return "Hello " + name + " !";</span>
<span>        }</span>
<span>    });</span>
<span>&lt;/script&gt;</span>
<span>&lt;/body&gt;</span>
<span>&lt;/html&gt;</span>
三、(Recommended Setup)推荐设置
  虽然上面的例子很简单,它不属于大规模的应用。我们建议将自己的应用按照如下建议,拆分为多个module:
  1. service module,用于声明service。
  2. directive module,用于声明directive。
  3. filter module,用于声明filter。
  4. 应用级别的module,依赖上述的module,并且包含初始化的代码。
  这样划分的理由是,当我们在测试的时候,往往需要忽略那些让测试变得困难的初始化代码。通过将代码分成独立的module,在测试中就可以很容易地忽略那些代码。这样,我们就可以更加专注在加载相应的module进行测试。
  上面的只是一个建议,可以随意地按照自己的需求制定。
四、Module Loading & Dependencies(模块加载和依赖)
  module是配置(configuration)的集合,执行在启动应用的进程中应用的块(blocks)。在它的最简单的形式中,由两类block组成:
  1.配置块(configuration blocks):在provider注册和配置的过程中执行的。只有provider和constant(常量?)可以被注入(injected)到configuration blocks中。这是为了避免出现在service配置完毕之前service就被执行的意外。
  2.运行块(run blocks):在injector创建完成后执行,用于启动应用。只有实例(instances)和常量(constants)可以被注入到run block中。这是为了避免进一步的系统配置在程序运行的过程中执行。
<span>angular.module('myModule', []).</span>
<span>    config(function(injectables) { // provider-injector</span>
<span>    // 这里是config block的一个例子</span>
<span>    // 我们可以根据需要,弄N个这样的东东</span>
<span>    // 我们可以在这里注入Providers (不是实例,not instances)到config block里面</span>
<span>    }).</span>
<span>    run(function(injectables) { // instance-injector</span>
<span>    // 这里是一个run block的例子</span>
<span>    // 我们可以根据需要,弄N个这样的东东</span>
<span>    // 我们只能注入实例(instances )(不是Providers)到run block里面</span>
<span>});</span>
  a) Configuration Blocks(配置块)
  有一个方便的方法在module中,它相当于config block。例如:
<span>angular.module('myModule', []).</span>
<span>  value('a', 123).</span>
<span>    factory('a', function() { return 123; }).</span>
<span>    directive('directiveName', ...).</span>
<span>    filter('filterName', ...);</span>
// 等同于
<span>angular.module('myModule', []).</span>
<span>  config(function($provide, $compileProvider, $filterProvider) {</span>
<span>    $provide.value('a', 123)</span>
<span>    $provide.factory('a', function() { return 123; })</span>
<span>    $compileProvider.directive('directiveName', ...).</span>
<span>    $filterProvider.register('filterName', ...);</span>
<span>});</span>
  configuration blocks被应用的顺序,与它们的注册的顺序一致。对于常量定义来说,是一种额外的情况,即放置在configuration blocks开头的常量定义。
  b) Run Blocks(应用块)
  run block是在angular中最接近main方法的东东。run block是必须执行,用于启动应用的代码。它将会在所有service配置、injector创建完毕后执行。run block通常包含那些比较难以进行单元测试的代码,就是因为这个原因,这些代码应该定义在一个独立的module中,让这些代码可以在单元测试中被忽略。
  c) Dependencies(依赖)
  一个module可以列出它所依赖的其他module。依赖一个module,意味着被请求(required)的module(被依赖的)必须在进行请求(requiring)module(需要依赖其他module的module,请求方)加载之前加载完成。换一种说法,被请求的module的configuration block会在请求的module的configuration block执行前执行(before the configuration blocks or the requiring module,这里的or怎么解释呢?)。对于run block也是这个道理。每一个module只能够被加载一次,即使有多个其他module需要(require)它。
  d) Asynchronous Loading(异步加载)
  module是管理$injector配置的方法之一,不用对加载脚本到VM做任何事情。现在已经有现成的项目专门用于处理脚本加载,也可以用到angular中。因为module在加载的过程中不做任何事情,它们可以按照任意的顺序被加载到VM中。脚本加载器可以利用这个特性,进行并行加载。
五、Unit Testing(单元测试)
  在单元测试的最简单的形式中,其中一个是在测试中实例化一个应用的子集,然后运行它们。重要的是,我们需要意识到对于每一个injector,每一个module只会被加载一次。通常一个应用只会有一个injector。但在测试中,每一个测试用例都有它的injector,这意味着在每一个VM中,module会被多次加载。正确地构建module,将对单元测试有帮助,正如下面的例子:
  在这个例子中,我们准备假设定义如下的module:
<span>angular.module('greetMod', []).</span>
<span>factory('alert', function($window) {</span>
<span>     return function(text) {</span>
<span>       $window.alert(text);</span>
<span>     };</span>

<span>})</span>
<span>.value('salutation', 'Hello')</span>
<span>.factory('greet', function(alert, salutation) {</span>
<span>     return function(name) {</span>
<span>       alert(salutation + ' ' + name + '!');</span>
<span>     };</span>
<span>});</span>
  让我们写一些测试用例:
<span>    beforeEach(module('greetMod', function($provide) {//这里看来是要将真正的$window替换为以下的东东</span>
<span>      $provide.value('$window', {</span>
<span>        alert: jasmine.createSpy('alert')</span>
<span>      });</span>
<span>    }));</span>

<span>    // inject()会创建一个injector,并且注入greet和$window到测试中。</span>
<span>    // 测试不需要关心如何写应用,只需要关注如何测试应用。</span>
<span>    it('should alert on $window', inject(function(greet, $window) {</span>
<span>      greet('World');</span>
<span>      expect($window.alert).toHaveBeenCalledWith('Hello World!');</span>
<span>    }));</span>

<span>    // 这里是在测试中通过行内module和inject方法来覆盖配置的方法</span>
<span>    it('should alert using the alert service', function() {</span>
<span>      var alertSpy = jasmine.createSpy('alert');</span>
<span>      module(function($provide) {</span>
<span>        $provide.value('alert', alertSpy);</span>
<span>      });</span>
<span>      inject(function(greet) {</span>
<span>        greet('World');</span>
<span>        expect(alertSpy).toHaveBeenCalledWith('Hello World!');</span>
<span>      });</span>
<span>    });</span>

豆瓣音乐人app的PhoneGap实践(转)

豆瓣音乐人app2011年开发时,便采用了基于原生与webapp混合架构的PhoneGap框架,直到今天。这也是目前豆瓣唯一一款使用PhoneGapapp。最近我们刚发布了音乐人appios新版,仍然保持这一架构,在原生方面的功能上做了一些增强。PhoneGap为音乐人app的顺利发布带来很大帮助,当然同时也造成了一些局限。

2149343247-0

当时如何做出使用PhoneGap的决定

我们之所以在技术选型时作出这一选择,主要有以下几个方面的原因:

开发效率的考虑

尽管豆瓣音乐人的用户对app有很强的需求,但音乐人app的定位和发展方向,当时处于不断探索和快速迭代的阶段,这样的情况意味着,音乐产品线希望使用尽可能简单的方式、占用较少的人力资源进行开发,尽快在多个平台发布,并且对于迭代的需求能够快速响应。在各种因素的权衡中,优先考虑满足上述需求。

对于原生app好还是webapp好这个问题,似乎一直有很大争议,实际上我不认为这是一个纯粹的技术问题。webapp在开发效率上的优势,原生app在性能和开发自由度上的优势,都是不言自明的,一个app是否采用混合架构,在我看来,最重要的因素还是产品定位和发展策略,如果希望尽快发布、跨平台、能快速响应可以预见的迭代,那么混合架构就很值得考虑。如果有足够的开发人员覆盖各平台、产品设计成熟度高、产品周期上可接受相对较长的开发时间,那么原生显然是更好的选择。

跨平台的考虑

豆瓣音乐人会是一个以展示内容和收听流媒体音乐为主的app,那些只有原生代码才可以实现的功能,我们需要得比较少。这意味着,如果我们采用混合架构,需要实现的原生特性与需要解决的跨平台问题会较少,混合架构的优势会被放大。

即使考虑了第一个因素后认为值得使用混合架构,如果app本身的特性不适合webapp的方式,那也会显得没有这个必要。Webapp之所以开发效率高,一方面在于html+css+js能做的事情,比用原生代码做同样的事情要简单得多,另一方面在于方便跨平台。如果app里面要实现的功能,很多都没法用html做,必须用原生代码,那这两方面的优势都消失殆尽。

实际上,就在我们第一个版本发布前不久,设计方面进行了一次review,然后对app的整体外观风格和某些功能与交互做了大幅修改。但其实只用了几天,设计的修改就被完全实现了,这样的速度对web前端开发来说当然不是什么难事,但对原生app来说却是难以想象的。

App架构与开发工作流

PhoneGap只是个原生外壳,app的内核是一个完整的webapp,需要调用的原生功能将以原生插件的形式实现,以暴露js接口的方式调用。在webapp框架的选择上,我调研了当时的一些专用于webappjs框架,几乎都不大成熟,没什么合适的,当然现在的情况已经大不一样了。由于音乐人app的规模不算大,而且在移动设备的webview中性能非常重要,我决定把一些小工具组合成一套微型框架来使用,尽可能优化执行效率。框架大致由以下小零件组成:

  • jQuery
  • iScroll4(模拟app风格的滚动);
  • js模板机制;
  • url分发与访问历史管理;
  • 页面关系与页面切换机制;
  • 基于Jsonp的带用户认证的api接口封装。

这样就简洁地实现了最小化的js框架。之所以使用jsonp的方式通信,是因为我非常希望能使用chrome进行调试,这样开发时就很方便,只需要双击本地的html文件,chrome就会成为一个完美的移动设备模拟器,我可以使用自己喜欢的任意前端开发调试工作流,这比任何移动设备模拟器都要方便得多。

有了框架,接下来只需要一个页面一个页面实现就好了,我把webapp部分作为git submoduleiosandroid的仓库都包含它,打包时使用各自的编译发布流程即可。

PhoneGap开发中遇到的问题

大致说一下遇到的印象深刻的问题。其实PhoneGap现在的版本已经有很大改进,而且主流手机的性能已经比以前好太多,现在新开发PhoneGap的话,应该会轻松很多。

css3性能问题

我们开始的设计中,有一些半透明和投影等效果,但我发现用css3实现后,会导致性能不好,这跟原生开发时可能遇到的半透明性能问题是一样的。Webkit并不如我们想象的那样有保障。后来,我们为此修改了设计,尽可能使用不透明的元素,去掉投影等效果,并减少dom复杂度,性能得到了明显提升。

像素密度问题

对于不同像素密度的屏幕,需要准备不同的图片,然后在css里面使用媒体选择器,根据 –webkit-device-pixel-ratio,分别插入密度为0.75(老android手机),1(非retina iPhone),1.5(一些android手机),2retina)的不同css,以使用不同的背景图片。只需要修改一个css文件,MakeFile脚本会自动生成其他的几个css文件。当然,我还需要保证这四套图片是存在并且正确命名的。

Mp3播放问题

iOSwebview支持mp3播放是没有问题的,但当时会有一个限制,就是如果用户没有主动操作,webview就不能自动开始播放,为它做一个workaround也就能够解决了。比较麻烦的是android的某些较老的版本,虽然支持audio标签,但是不支持mp3格式的音频,事实上它不支持任何格式的音频。所以对于这种情况,只能使用PhoneGap自带的音频播放功能。对用户而言,效果是一样的,但这增加了webapp的依赖,使webapp部分变得复杂了。

不同系统的行为差异

虽然大体上来说,iOSandroid使用的webview,其行为都是差不多的,而且由于都是webkit,样式会非常接近,几乎是自动完美跨平台。但是实际开发中发现,还是会有一些区别,例如:

  • app ready时触发的事件不一样,当然这和PhoneGap封装有关;
  • android有时候会有软键盘问题,有个插件可以解决;
  • 处理打开外部url时行为不一致;
  • 支持的动画方式有区别,对不同平台需要尽可能使用高效、硬加速的动画方式。
  • 需要为android的几个实体按键写专门的处理函数。

仍然需要编写原生代码

有的需求在webapp内无法做到,这是经常遇到的事。例如:

  • 推送消息;
  • 状态栏提醒;
  • 打开内置浏览器访问一些url
  • 绑定社交平台账号;
  • 缓存下载的图片和音乐等文件。

很多都可以找到插件来解决,现在市面上的插件比当时开发音乐人app最初版本时,已经丰富得多了,但仍然可能无法满足需求,这时就需要自己写插件来完成,每出现一个这样的需求,就意味着要为每个系统做一次原生解决方案。我们正准备把音乐人app中使用的“推荐到社交平台”的插件进行开源,提供给其他有类似需求的开发者使用。

可以移植到更多平台,但也需要一些工作

我简单试过WebOS,样式上会非常一致,但存在一些其他问题,为稳定起见,我们没有发布WebOS的打包。另外,移动版IE10已经是对标准非常友好的浏览器,但样式上跟webkit仍然有差异,不能把webkitwebapp直接拿来就用,需要做适配。感觉上又回到了桌面web开发的世界。

有趣的是,使用MacGapapp核心部分打包成Mac的桌面应用,倒是完全无痛,几乎直接就可以用。

总结

使用感觉上来说, iOS上的效果要略好于android上的效果,几乎跟原生界面没太大区别。虽然跟原生app相比,渲染速度等细节上仍然略微吃亏,但总的看来是完全可以接受的水平。

总结一下的话,我们对基于PhoneGap得到的成果是满意的,开发性价比很高。用户对音乐人app的评价也比较好。将来在产品稳定后,我们是否会使用纯原生app替代PhoneGap,现在还不知道,这将取决于产品未来的决策。希望得到什么,也就同时决定了会放弃什么,一切都是权衡的结果,框架没有银弹。

关于作者

苏丹,北京师范大学数学系毕业,现任豆瓣音乐Techleader

转载目的:用于记录保存资料和学习,感谢原作者分享

$http 相关资料

1. REST 客户端部分

客户端AngularJS:Accept,Content-Type
例如AngularJS发送请求:
    $http({method: 'GET', url: '/someUrl'})
    success(function(data, status, headers, config) {
        // this callback will be called asynchronously
        // when the response is available
    }).
    error(function(data, status, headers, config) {
        // called asynchronously if an error occurs
        // or server returns response with an error status.
    });
AngularJS Setting HTTP Headers
默认的HTTP头:
The $http service will automatically add certain HTTP headers to all requests. These defaultscan be fully configured by accessing the$httpProvider.defaults.headers configurationobject, which currently contains this default configuration:
$httpProvider.defaults.headers.common (headers that are common for all requests):
Accept: application/json, text/plain, * / *
X-Requested-With: XMLHttpRequest
$httpProvider.defaults.headers.post: (header defaults for POST requests)
Content-Type: application/json
$httpProvider.defaults.headers.put (header defaults for PUT requests)
Content-Type: application/json
如何修改HTTP头:
To add or overwrite these defaults, simply add or remove a property from these configuration objects. To add headers for an HTTP method other than POST or PUT, simply add a new objectwith the lowercased HTTP method name as the key, e.g.$httpProvider.defaults.headers.get[‘My-Header’]=’value’.
Additionally, the defaults can be set at runtime via the $http.defaults object in the same fashion.
e.g. 修改$httpProvider.defaults.headers(prevent angular.js $http object from sending X-Requested-With header)
angular.module('myModule', [])
    .config(['$httpProvider', function($httpProvider) {
        delete $httpProvider.defaults.headers.common["X-Requested-With"]
    }])
e.g. 使用$http.defaults
$http({
    method: 'POST',
    url: url,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}, //headers参数可以设置Accept、Content-Type
}).success(function () {});
客户端:$httpProvider.defaults.transformRequest/Response
AngularJS Transforming Requests and Responses
Both requests and responses can be transformed using transform functions. By default, Angularapplies these transformations:
Request transformations:
If the data property of the request configuration object contains an object,serialize it intoJSON format.
Response transformations:
If XSRF prefix is detected, strip it (see Security Considerations section below).
If JSON response is detected, deserialize it using a JSON parser.
To globally augment or override the default transforms, modify the $httpProvider.defaults.transformRequest and$httpProvider.defaults.transformResponse properties. These properties are by default an array of transform functions, which allows you topush or unshift a new transformation function into the transformation chain. You can also decide to completely override any default transformations by assigning yourtransformation functions to these properties directly without the array wrapper.
Similarly, to locally override the request/response transforms, augment thetransformRequestand/ortransformResponseproperties of the configuration object passed into$http.
e.g. local配置:在发送请求时设置transformRequest:
$http({
    method: 'POST',
    url: url,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    transformRequest: function(obj) {
        var str = [];
        for(var p in obj)
        str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
        return str.join("&amp;");
    },
    data: xsrf
}).success(function () {});
e.g. Global配置:$httpProvider.defaults.transformRequest
var module = angular.module('myApp');
module.config(function ($httpProvider) {
    $httpProvider.defaults.transformRequest = function(data){
        if (data === undefined) {
            return data;
        }
        return $.param(data);
    }
});