JavaScript忍者秘籍——函数(下)
概要:本篇博客主要介绍函数的一些类型以及常见示例
1.匿名函数
使用匿名函数的常见示例:
window.onload = function(){
assert(true,'power!');
}; //创建一个匿名函数作为事件处理程序,这里无需定义函数名,直接在其位置为其赋值即可; var ninja = {
shout: function(){
assert(true,"Ninja");
}
};
ninja.shout(); //创建一个函数,将其作为ninja的一个方法,使用shout属性去调用该方法; setTimeout(function(){
assert(true,"Forever!");
},500); //将函数作为callback回调传递给setTimeout()函数,以便在定时器到期时进行调用。
2.递归
当函数调用自身,或调用另一个函数,但这个函数的调用树中的某个地方又调用了自己时,递归就发生了。
- 普通命名函数中的递归
例如:用于检测回文的递归函数。
回文的定义:
(1)单个和零个字符都是一个回文
(2)如果字符串的第一个字符和最后一个字符相同,并且除了两个字符以外剩余的其他字符也是一个回文的话,我们称原字符串是一个回文
基于该定义的实现如下:
function isPalindrome(text){
if(text.length <=1 ) return true;
if(text.charAt(0) != text.charAt(text.length - 1)) return false;
return isPalindrome(text.substr(1,text.length - 2));
}
再例如:忍者之间也经常需要沟通,通常用自然声作掩护。在这里,我们给忍者赋予发出"啾啾"声的能力,利用不同数量的啾啾声编码不同的消息。代码如下:
function chirp(n){
return n > 1 ? chirp(n-1) + "-chirp" : "chirp";
}
assert(chirp(3) == "chirp-chirp-chirp","Calling the named function comes naturally.");
声明一个chirp()的函数,该函数通过调用自身函数名进行递归,就像回文的例子一样。
- 方法中的递归
声明一个递归函数并将其作为ninja对象的方法来完成这个任务。
var ninja = {
chirp: function(n){
return n>1 ? ninja.chirp(n-1) + "-chirp" : "chirp";
}
};
assert(ninja.chirp(3) == "chirp-chirp-chirp","An object property isn't too confusing, either.");
在上述代码中,我们将递归函数定义成一个匿名函数,并将其引用到ninja对象的chirp属性。在该函数内,我们通过对象的ninja.chirp()属性递归调用了函数自身。但这么做会导致引用的丢失问题,解决办法就是使用函数上下文进行引用,示例如下:
var ninja = {
chirp : function(n){
return n > 1 ? this.chirp(n-1) + "-chirp" : "chirp";
}
};
- 内联命名函数
当一个匿名函数取名之后就称为了内联函数,这么做事为了解决不同对象的属性都引用匿名函数的问题。
例如:
var ninja = {
chirp : function signal(n){
return n > 1 ? signal(n - 1) + "-chirp" : "chirp";
}
};
assert(ninja.chirp(3) == "chirp-chirp-chirp","Works as we would expect it to!");
var samurai = { chirp: ninja.chirp };
ninja = {};
assert(samurai.chirp(3) == "chirp-chirp-chirp","The method correctly calls itself.");
上述代码中,我们给内联函数取了一个名字signal,并在函数体内使用该名称进行递归引用,然后验证了,作为ninja对象的方法进行调用时执行正常。之后将该函数的引用复制给samurai.chirp,并清空原始ninja对象。将其作为samurai的方法进行调用,我们发现一切代码都正常运行,因为清除ninja对象的chirp属性时,没有影响给内联函数所取的用于递归调用的名字。
3.将函数视为对象
- 函数存储
有时候,我们可能需要存储一组相关但又独立的函数,事件回调管理是最明显的例子。我们可以利用函数的属性特性,给函数添加一个附加属性从而实现上述目的:
var store = {
nextId: 1,
cache: {},
add: function(fn){
if(!fn.id){
fn.id = store.nextId++;
return !!(store.cache[fn.id] = fn);
}
}
};
function ninja(){};
assert(store.add(ninja),
"Function was safely added.");
assert(!store.add(ninja),
"But it was only added once.");
在上述代码中,我们创建一个对象并赋值给store变量,我们将在该对象里存储一组独立的函数。该对象有两个数据属性:一个用于存储下一个可用的id值,另外一个cache用于存储函数。函数是通过add()方法添加到cache中的。
在add()中,我们首先要检查要添加的函数是否有一个id属性,如果有,则表示函数已经被处理过,那就忽略它。如果没有,我们就给函数分配一个id属性。
然后,通过将函数转换为等效的布尔函数,我们返回了true,以便知道在调用add()之后,函数是否成功添加进去。
提示:!!构造是一个可以将任意JavaScript表达式转化为其等效布尔值的简单方式。例如,!!"He shot me down" ===true和!!0===false。
另一个有用的技巧是,通过暴露函数属性,我们可以对函数自身进行修改。该技巧可以用于记住以前计算的值,以便在未来计算时节约时间。
- 自记忆函数
缓存记忆是构建函数的过程,这种函数能够记住先前计算的结果。我们先通过保存昂贵计算的结果来了解一下这种技术,然后再看一个更实际的例子,在列表里存储已经遍历过的DOM元素。
缓存记忆昂贵的计算结果
function isPrime(value){
if(!isPrime.answers) isPrime.answers = {};
if(isPrime.answers[value] != null){
return isPrime.answers[value];
};
var prime = value != 1; // 1 can never be prime
for(var i =2; i < value; i++){
if(value % i == 0){
prime = false;
break;
};
};
return isPrime.answers[value] = prime;
};
assert(isPrime(5),"5 is prime!");
assert(isPrime.answers[5],"The answer was cached!");
在isPrime()函数里,首先检测用于缓存结果的answers属性是否存在,如果不存在,就创建它。初始空对象的创建只在函数第一次调用的时候进行。之后缓存就已经存在了。然后,检查缓存answers里是否已经存在参数值对应的结果,在该缓存里,我们使用参数值作为属性的key,使用布尔值作为属性的值进行保存,如果发现缓存结果,则直接返回它。如果没有找到缓存值,则继续判断该值是否是素数,然后将结果存储在缓存中,并返回它。
缓存记忆主要有两个优点:
■ 在函数调用获取之前计算结果的时候,最终用户享有性能优势。
■ 发生在幕后,完全无缝,最终用户和页面开发人员都无需任何特殊操作或为此做额外的初始化工作。
但它也有缺点:
■ 为了提高性能,任何类型的缓存肯定会牺牲掉内存。
■ 纯粹主义者可能认为缓存这个问题不应该与业务逻辑放在一起,一个函数或方法应该只做一件事,并把它做好。
■ 很难测试或测量一个算法的性能,就像本例一样。
缓存记忆DOM元素
function getElements(name){
if(!getElements.cache) getElements.cache = {};
return getElements.cache[name] = getElements.cache[name] || document.getElementsByTagName(name);
}
将状态和缓存信息存储在一个封装的独立位置上,不仅在代码组织上有好处,而且外部存储或缓存对象无需污染作用域就可以获得性能提升。
-伪造数组方法
有时,我们可能想创建一个包含一组数据的对象。如果只是集合,则只需要创建一个数组即可。但在某些情况下,除了集合本身,可能会有更多的状态需要保存——比如与集合项有关的一些元数据。一种选择可能是,每次创建对象新版本的时候都创建一个新数组,然后将元数据作为属性或方法添加到这个新数组上。例如:
<input id = "first"/>
<input id = "second"/> <script>
var elems = {
length: 0 ,
add: function(elem){
Array.prototype.push.call(this,elem);
},
gather: function(id){
this.add(document.getElementById(id));
},
}; elems.gather('first');
assert(elems.length == 1 && elems[0].nodeType,
"Verify that we have an element in our stash");
elems.gather('second');
assert(elems.length == 2 && elems[1].nodeType,
"Verify the other insertion");
</script>
在本例中,我们创建了一个"普通"对象,并添加了一些模拟数组的行为。首先,定义了一个length属性,像数组一样,记录所保存元素的个数。然后,再定义一个方法用于将元素添加到模拟数组的结尾,只需调用add()方法即可。利用原生的JavaScript数组方法:Array.prototype.push。
4.可变长度的参数列表
- 使用apply()支持可变参数
function(array){
return Math.min.apply(Math,array);
};
function largest(array){
return Math.max.apply(Math,array);
};
assert(smallest(([0,1,2,3])) == 0,
"Located the smallest value.");
assert(largest([0,1,2,3]) == 3,
"Located the largest value.");
在上述代码中,我们定义了两个函数:一是查找数组中的最小值,另外一个是查找数组中的最大值。调用smallest(),传入数组[0,1,2,3],其调用Math.min()的结果等价于如下代码:Math.min(0,1,2,3)。
- 函数重载
所有函数都隐式传递了内置arguments参数,这使函数有能力处理任意数量的参数。即使我们只定义固定数量的形参,通过arguments参数我们还是总是能够访问到传递给函数的所有参数。我们大概看一下如何实现函数重载。
检测并遍历参数
JavaScript重载函数时通过传入参数的特性和个数进行相应修改来达到目的。例如,遍历可变长度的参数列表:
function merge(root){
for(var i = 1; i < arguments.length; i++){
for(var key in arguments[i]){
root[key] = arguments[i][key];
};
};
return root;
}; var merged = merge(
{name: "Batou"},
{city: "Niihama"});
assert(merged.name == "Batou",
"The original name is intact.");
assert(merged.city == "Niihama",
"And the city has been copied over.");
尽管merge()函数的签名里只声明了一个root参数,但是我们在调用的时候可以传入任意数量的参数也可以什么都不传。不过只能用root这个参数作为传入参数的第一个。
提醒:要检查对应于已命名的形参的参数是否传入,可以使用表达式:paramname === undefined,如果没有对应的参数,则会返回true。
记住,我们要做的事情,是想第二个甚至第n个参数上的属性合并到传入的root对象中,所以在遍历列表中的参数时,为了跳过第一个参数,索引要从1开始。
每次遍历时,被遍历项目就是传递给函数的一个对象,然后遍历传入对象的所有属性,并将这些属性复制到root对象上。
通过root可以获取第一个参数,而arguments参数指向的是所有传入参数的集合。
- 对arguments列表进行切片(slice)和取舍(dice)
构建一个函数,将第一个参数与剩余参数的最大值进行相乘。首先获取第一个参数,然后将其与剩余参数上调用Math.max()函数的结果进行相乘。由于给Math.max()传递的参数要从数组的第二个元素开始,所以我们使用数组的slice()方法重新创建一个省略了第一个元素的新数组。
function multiMax(multi){
return multi * Math.max.apply(Math,arguments.slice(1));
}
assert(multiMax(3,1,2,3) == 9 , "3*3 = 9 (First arg , by largest.)")
arguments参数引用的不是真正的数组。尽管它看起来,且感觉上很像——例如,我们可以使用for循环进行遍历——但其缺乏基本数组应该有的所有方法,包括非常方便的slice()。我们可以创建自己的切片和取舍方法——参数伪装。
function multiMax(multi){
return multi * Math.max.apply(Math,Array.prototype.slice.call(arguments,1));
}
assert(multiMax(3,1,2,3)==9,"3*3=9(First arg,by largest.)");
JavaScript忍者秘籍——函数(下)的更多相关文章
- JavaScript忍者秘籍——函数(上)
概要:本篇博客主要介绍了JavaScript的函数. 1.第一型对象 JavaScript的函数可以像对象一样引用,因此我们说函数是第一型对象.除了可以像其他对象类型一样使用外,函数还有一个特殊的功能 ...
- 深入理解JavaScript中的函数操作——《JavaScript忍者秘籍》总结
匿名函数 对于什么是匿名函数,这里就不做过多介绍了.我们需要知道的是,对于JavaScript而言,匿名函数是一个很重要且具有逻辑性的特性.通常,匿名函数的使用情况是:创建一个供以后使用的函数.简单的 ...
- JavaScript忍者秘籍——驯服线程和定时器
1.定时器和线程 - 设置和清除定时器 JavaScript提供了两种方式,用于创建定时器以及两个相应的清除方法.这些方法都是window对象上的方法. 方法 格式 描述 setTimeout i ...
- JavaScript忍者秘籍——原型
概要:本篇博客主要介绍JavaScript的原型 1.对象实例化 - 初始化的优先级 初始化操作的优先级如下: ● 通过原型给对象实例添加的属性 ● 在构造器函数内给对象实例添加的属性 在构造器内的绑 ...
- JavaScript忍者秘籍——闭包
概要:本篇博客主要介绍了JavaScript的闭包 1.闭包的工作原理 简单地说,闭包就是一个函数在创建时允许该自身函数访问并操作该自身函数之外的变量时所创建的作用域. 例如: var outerVa ...
- JavaScript忍者秘籍——运行时代码求值
1. 代码求值机制 JavaScript中,有很多不同的代码求值机制. ● eval()函数 ● 函数构造器 ● 定时器 ● <script>元素 - 用eval()方法进行求值 作为定义 ...
- javascript 忍者秘籍读书笔记
书名 "学徒"=>"忍者" 性能分析 console.time('sss') console.timeEnd('sss') 函数 函数是第一类对象 通过字 ...
- javascript 忍者秘籍读书笔记(二)
闭包的私有变量 function Ninja() { let feints = 0; this.getFeints = function () { return feints }; this.fein ...
- 【JavaScript忍者秘籍】定时器
随机推荐
- [HMLY]8.Cocoa
cocoa是苹果公司为mac os x所创建的原声面向对象API,是mac os x上五大API之一,其他四个是carbon,posiX,x11,java. 苹果的面向对象开发框架,用来生成 Mac ...
- 第10章:DOM
- ubuntu 下 编译ffmpeg 3.1.1
1,下载ggmpeg源码:http://ffmpeg.org/download.html 2.下载ndk ,百度 3.配置环境 3.1 编译FFMPEG时,出现了 ffmpeg yasm not fo ...
- Mac + nginx + lua + luasocket + cjson
1. Lua // 官方网站 http://www.lua.org/ 下载源码包// 由于配合 nginx 使用, 所以使用 5.1.X 版本 $ .tar.gz $ cd lua- $ make m ...
- leetcode 024
Given a linked list, swap every two adjacent nodes and return its head. For example,Given 1->2-&g ...
- Jquery Easyui与Jquery Bootstrap(Metronic Bootstrap)的简单比较
1,通常来看WEB前端用 bootstrap 比较好,后台用EASYUI比较好.Easyui适合工厂企业的管理系统如ERP,CRM之类的,Bootstrap适用于多屏跨设备浏览. 简言之,一个适合上班 ...
- app兼容性测试的几种方案
1.统计自己的应用被使用的数据 通过友盟或Flurry等在应用嵌入,得到应用在哪些机型上被安装了,排名前十的就是测试的重点机型 2.可参考兼容性测试平台的测试结果 比如Testin或百度的MTC平台, ...
- 我是如何给discuz模板做语法高亮的
本人一直做ASP.NET开发,近期接到任务要用Discuz开发一个社区,第一次接触PHP,PHP灵活的语法,天生的模块化,各种语法糖深深的震惊了我,我从内心深处感受到了PHP是最牛逼的语言!!! 好了 ...
- Android开源库loopj的android-async-http的 JsonHttpResponseHandler 存在死循环GC_CONCURRENT
我现在用的是 AndroidAsyncHttp 1.4.4 版本,之前遇到一个很奇怪的问题, 当使用 JsonHttpResponseHandler 解析请求的页面出现服务器错误或其他情况返回的内容不 ...
- SpringMVC 学习-异常处理 SimpleMappingExceptionResolver 类
Spring3.0 对异常的处理方式总共有两种: 一种是使用 HandlerExceptionResolver 接口,并且 Spring 已经提供默认的实现类 SimpleMappingExcepti ...