初涉JavaScript模式 (10) : 函数 【进阶用法】
写在前面
不知不觉写到第10篇了。这篇写起来很忐忑,终于和高级搭上边了(呵呵),这篇我们 主要 说一下 JS 方法的部分高级用法(我知道的),笔者水平有限,难免有错。废话不多少,进入正文。
初始化
我们在看一些别人写的优秀的代码,特别是组件时,我们经常能发现有init或initializa这样的方法,它一般执行的都是初始化。那初始化一般都有几种呢,我们来一一介绍:
初始化对象
初始化对象,顾名思义,就是对带着一堆具体逻辑的对象进行初始化。直接上代码:
```javascript
var Wr = {
name: "WeiRan",
age: 21,
init: function() {
// body ...
console.log("初始化完成!");
}
}
Wr.init(); //初始化完成!
```
这种方法,有个弊端就是可能会污染全局作用域。这样有时候就可以采用另一种方法来初始化:
```javascript
({
name: "WeiRan",
age: 21,
init: function() {
// body ...
console.log("初始化完成!");
}
}).init(); //初始化完成!
```
这种方法的有点和上篇所说的即时函数的有点是相同的:可以在执行一次性的初始化任务时保护全局命名空间,如果初始化任务更加复杂,他会使整个初始化过程显得更有结构化。
值得注意的是,这种模式主要适用于一次性的任务,而且在init完毕以后也没有该对象的访问(如果想有,也可以有,在init方法底部加个return this即可)
初始化分支
初始化分支也叫做加载时分支[load-time branching] ,是一种优化模式。当知道某些属性在整个程序生命周期中都不会发生变化时,该模式就显得很重要了。浏览器特性嗅探就是一个典型的例子,通常情况下在site上写JavaScript要考虑到兼容性,例如addEventListener在早期的IE上就是不支持的,在IE上支持的是attachEvent(更早的甚至是on+type),而如果我们每次绑定事件的时候都要做个判断,这样会产生大量的冗[rǒng]余代码,但是初始化分支可以很好的解决这个问题。代码如下:
```javascript
//初始化分支
if(typeof window.addEventListener === "function"){
utils.addListener = function(el, type, fn){
el.addEventListener(type, fn, false);
}
utils.removeListener = function(el, type, fn){
el.removeEventListener(type, fn, false);
}
}
// if IE
else if(typeof document.attachEvent === "function"){
utils.addListener = function(el, type, fn){
el.attachEvent('on' + type, fn);
}
utils.removeListener = function(el, type, fn){
el.detachEvent('on' + type, fn);
}
}
//long long ago Browser
else{
utils.addListener = function(el, type, fn){
el['on' + type] = fn;
}
utils.removeListener = function(el, type, fn){
el['on' + type] = null;
}
}
```
初始化模块
在上一篇中,我写了一个叫做暴露接口的用法,在 @北川 的纠正下,我发现我写的不是很严谨,对于用法也写的太含糊,特意去详细了解了下 Module模式。在这里谢谢北川的指正。关于详细的Module模式,我会在后面专门写一篇。在回到我们的正文,在模块化编程中(利用r.js 或者 seaJS),我们经常要对某个模块进行初始化,而这时,这种模式就显得更有用了。代码如下:
```javascript
function Wr(n){
var name;
return {
init : function(n){
name = n;
},
getName : function(){
console.log(name);
}
}
}
var wr1 = new Wr();
wr1.init("Weiran");
wr1.getName(); //WeiRan
```
上面init很简单,但是具体到工作中,可能初始化做的就不会这么简单,总之它很好的提高了模块的可扩展性。让我们的代码结构更加清晰。
缓存函数结果
对于一些操作非常非常复杂的函数,我们没必要每次都去真的执行一遍。举个例子,我们在百度搜索一个关键词,百度并不是真的去搜一遍,而是去看以前有木有查过,然后才返回结果(这就是有时候,百度的结果有点旧了)。在函数中也是一样,对于逻辑极度复杂,但是参数变化不大的函数(结果也不经常变化),我们可以缓存计算结果。代码如下:
```javascript
var fun1 = function(param){
if(!fun1.cache[param]){
var result = {};
// 复杂的逻辑 ...
fun1.cache[param] = result; //把计算结果缓存
}
return fun1.cache[param];
}
```
上面的代码假定该函数只需要一个参数,如果有更多及更复杂的参数,通常的做法是将参数序列化。代码如下:
```javascript
var func2 = function(){
//JSON 序列化
var cacheKey = JSON.stringify(Array.prototype.slice.call(arguments)),
result;
if(!func2.cache[cacheKey]){
result = {};
// 复杂的逻辑
func2.cache[cacheKey] = result;
}
return func2.cache[cacheKey];
};
func2.cache = {};
```
值得注意的是,在JSON序列化的过程中,因为本质上都是被序列化成了字符串,所有对象也被序列化成了字符串,对于两个不同对象但具有相同的属性,这两个对象就会被当成同一个,共享一条缓存条目。
柯里化
柯里化(curry),貌似很高端的样子,那他是什么呢,我们先假设有一个需求(这里我借用腾讯的案例。):
假设在实现一个计算每月花费的函数, 每天结束前我们都要记录今天花了多少钱, 但我们只关心月底的花费总值, 无需每天都计算一次.
如果使用curry怎么实现,废话不多说,直接上代码:
```javascript
var curry = function(fn){
var args = [];
return function(){
if(arguments.length === 0){
return fn.apply(this,args);
}
args.push.apply(args, arguments);
return fn;
}
}
var exc = curry(function(){
var nums = [].slice.call(arguments),
result = 0;
nums.forEach(function(val){
result += val;
});
return result;
});
exc(500);
exc(1000);
console.log(exc()); //1500
```
初看上去还是有点绕的,我们切开来看,我们传给curry一个匿名函数,curry返回一个经过处理的函数。而重点来了,我们来看curry是怎么处理的,首先是个判断,判断这个函数接受的参数是否是空[arguments.length === 0] 如果不为空,把当前的参数push到args里面去,这里要注意的是curry 返回的是一个方法,所以就形成了一个闭包,而闭包的一个特性就是可以访问返回他的函数的变量和方法,故这个方法可以改变args的值(把当前参数push到返回他的函数的args里去),如果为空的话,他才会执行fn并把参数数组通过apply的形式传给fn。
这种方式的最大好处就是可以延迟到最后一刻才一起计算, 在很多场合可以避免无谓的计算, 节省性能, 也是实现惰性求值的一种方案.
柯里化就是用闭包原理实现函数参数的合并,然后再运行函数。
如果以上还不够明确,我们在来看一个例子:
```javascript
//Curry
var curry = function(fn){
var slice = Array.prototype.slice,
storedArgs = slice.call(arguments, 1);
return function(){
var newArgs = slice.call(arguments);
var args = storedArgs.concat(newArgs);
return fn.apply(null, args);
};
}
var result = curry(function(x, y){
return x + y;
}, 5)(10);
console.log(result); //15
```
上面的代码,需要注意的是 storedArgs = slice.call(arguments, 1) 这段代码的意思是把fn之外的参数存储起来(我们第一次传个curry的那个5),然后我们调用这个经过curry处理过后的函数时,他会把我们新传的参数和原来我们传的那个5结合组成新的参数数组进行处理[var args = storedArgs.concat(newArgs)],具体处理的代码是fn.apply(null, args)。
当我们发现正在调用一个函数,并且传递的大部分参数都是相同的,那么该函数使用curry化是一个很好的方案。可以通过将一个curry化,从而动态产生一个新的函数,这个新的函数会保存重复的参数(因此不必每次都传递这些参数),并且还会使用预填充原始函数所期望的完整参数列表。
结语
本来准备也把反柯里化写了,奈何最后发现,对于这东西,自己还不知道怎么把它完整的表诉清楚,可能还是自己没完全吃透吧,所以也暂时不写了,JS的路还很长啊。
如果你在文中发现错误,欢迎指正。
初涉JavaScript模式 (10) : 函数 【进阶用法】的更多相关文章
- 初涉JavaScript模式 (8) : 函数 【概述】
什么是函数 函数,是一个大型程序中的某部份代码,由一个或多个语句块组成.它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性.(维基百科) 函数的特点 第一类对象 在JavaScript世界中 ...
- 初涉JavaScript模式 (9) : 函数 【常用方式】
回调模式 上一篇,对JavaScript函数进行了大体的介绍,这一篇对一些在工作中经常遇到的情况进行扩展. 在工作中,我们经常遇到很多需求,比如现在有一个需求: 一栋10层的大楼,当我们在坐电梯时,电 ...
- 初涉JavaScript模式系列 阶段总结及规划
总结 不知不觉写初涉JavaScript模式系列已经半个月了,没想到把一个个小点进行放大,竟然可以发现这么多东西. 期间生怕对JS的理解不到位而误导各位,读了很多书(个人感觉JS是最难的oo语言),也 ...
- 初涉JavaScript模式 (2) : 基本技巧
尽量少用全局变量 大量使用全局变量会导致的后果 全局变量创建以后会在整个JavaScript应用和Web页面中共享.所有的全局变量都存在于一个全局命名空间内,很容易发生冲突 不知不觉创建了全局变量 其 ...
- 【读书笔记】读《JavaScript模式》 - 函数复用模式之现代继承模式
现代继承模式可表述为:其他任何不需要以类的方式考虑得模式. 现代继承方式#1 —— 原型继承之无类继承模式 function object(o) { function F() {}; F.protot ...
- 【读书笔记】读《JavaScript模式》 - 函数复用模式之类式继承模式
实现类式继承的目标是通过构造函数Child()获取来自于另外一个构造函数Parent()的属性,从而创建对象. 1.类式继承模式#1 —— 默认方式(原型指向父函数实例) function Paren ...
- 初涉JavaScript模式 (11) : 模块模式
引子 这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中去,在北川的提醒下,我才发觉这是非常不严谨的,于是我把这些内容拎出来,这就是这篇的由来. 什么是模块模式 在 ...
- 常用的JavaScript字符串处理函数及用法
最近参加了几次前端实习生招聘的笔试,发现很多笔试题都会考到字符串的处理,比方说去哪儿网笔试题.淘宝的笔试题等,如果你经常参加笔试或者也是一个过来人,相信你也跟我一样,发现字符串的处理是前端招聘过程中最 ...
- 初涉JavaScript模式 (12) : 沙箱模式
引子 上一篇说了模块模式,而对于其中的命名空间模式其实也是有着一些问题,比如每添加一个模块或则深入叠加都会导致长命名,并且对于多个库的不同版本同时运行,一不小心就会污染全局标识,而这两天也发现了JSe ...
随机推荐
- zoj3229 Shoot the Bullet(有源汇有上下界的最大流)
题意: 一个屌丝给m个女神拍照,计划拍照n天,每一天屌丝给给定的C个女神拍照,每天拍照数不能超过D张,而且给每个女神i拍照有数量限制[Li,Ri],对于每个女神n天的拍照总和不能少于Gi,如果有解求屌 ...
- margin:-75px的理解及妙用——纯CSS制作水平/垂直都居中短边为50px/长边为150px的红色十字架
有这么一个题目: 使用重构的方式制作出一个如下图的水平.垂直都居中短边为50px,长边为150px的红色十字架. 要求只使用2个div完成 答案: <!DOCTYPE html PUBLIC & ...
- [基础] 重载的时候什么时候用引用&
一般返回值还要继续被处理,而不仅仅是得到其值的时候,返回引用& 一般有[], =, ++, --, 还有输入输出运算符<<, >> Classtype &ope ...
- <离散数学>学习笔记1--逻辑和证明
今天开始离散数学的自学旅程. 主题:逻辑和证明 逻辑规则给出数学语句的准确含义.逻辑对计算机科学有着重要作用.为了理解数学,我么必须理解正确的数学论证是由什么组成的.只要证明一个数学语句是真的,我们就 ...
- 虚拟机linux配置nginx 为什么win7通过Ip访问不到
第一步,你应该先检查网络win机ping linux机,通了进行下一步第二步,检查端口netstat -antl | grep 你开启的服务端口比如你开了http,那就是80端口或者你自定义的端口,我 ...
- set和replace方法的区别
对已有值的元素处理上两者是相同的,但是对于一个不存在的元素,set的作用就和add相当,replace则是只能对已经存在的元素进行处理如:表中某个字段值是空(null),如果某个字段为空,则通过查询方 ...
- lucene4.5近实时搜索
近实时搜索就是他能打开一个IndexWriter快速搜索索引变更的内容,而不必关闭writer,或者向writer提交,这个功能是在2.9版本以后引入的,在以前没有这个功能时,必须调用writer的c ...
- 给想上MIT的牛学生说几句
[来信] 老师您好! 非常冒昧的来打搅您,仅仅是在学习上实在有些困惑才来向您求教一番. 我是计算机科学与技术的大一学生,我非常喜欢我自己的专业,可是学校里讲的东西太慢,太浅,所以我一般都是自学,我在自 ...
- EEPlat 的数据层模式
EEPlat 的数据库底层架构能够同一时候支持多种数据库的集成应用.同一时候能够支持分布式数据库的集成应用.业务对象通过指定数据源与对应的数据库通过数据源层进行数据交互,数据源层通过数据库种类.自己主 ...
- Qt深入:不能不知道的Type、Attribute和Flags
Qter高手与新手的区别在于:知道还是不知道 Qt不是开发语言,所以无所谓谁厉害.但使用他的Qter却有着差异,也许是面向对象语言本身的.或者实际经验上的.而对于Qt本身来说,高手与新手最显著的差异在 ...