js是一门函数式语言,因为js的强大威力依赖于是否将其作为函数式语言进行使用。在js中,我们通常要大量使用函数式编程风格。函数式编程专注于:少而精、通常无副作用、将函数作为程序代码的基础构件块。

在函数式编程中,有一种函数称为匿名函数,也就是没有名称的函数,是js中的一个非常重要的概念。通常匿名函数的使用情况是,创建一个供以后使用的函数。比如将匿名函数保存在一个变量里面,或将其作为一个对象方法,更有甚者将其作为一个回调等等之类的。

//保存在变量中,通过fn去引用
var fn=function(){return true;}; //对象方法
var obj={
fn:function(){return ture;}
}; //作为返回值
function method(){
return function(){return true;};
} function method2(callback){
return callback();
} //作为回调
method2(function(){return true;});

尽管匿名函数没有名称,但是其在不同的时机都是可以调用的。对于匿名函数,有一个小细节值得注意的是,将其赋值给一个变量,例如var fn=function(){},不要认为这个变量fn就是这个匿名函数的名称,fn只是一个引用该函数的变量而已,请不要误解。上一篇文章讲过,一个函数声明包含四个部分:

1、function 关键字;

2、函数名称,为可选。没有名称的函数称为匿名函数;

3、圆括号包含的一个以逗号分隔的参数列表,该参数列表可以为空,但是圆括号必须存在;

4、大括号包含的函数体。函数体可以为空,但大括号必须存在。

匿名函数没有名称,如果我们在将它赋值给变量的时候,给它声明一个名称呢,可以吗?当然可以。不过这时,这个匿名函数就变成了内联函数,这个内联函数名称只能在其自身函数中使用。有一个递归的例子就说明了这种情况:

//求和函数。sum是该内联函数名称,该内联函数名称只能在其自身函数内部使用。
var fn=function sum(n){
return n>?sum(n-)+n:;
} //alert(sum(10)); 这种方式使用是错误的--sum未定义
alert(fn());//

当函数调用自身,或调用另外一个函数,但这个函数在调用其他函数的某个地方又调用了自己时,递归就发生了。

我们还可以使用匿名函数来进行递归调用。如:

//使用匿名函数进行递归调用
var myMath={
sum:function(n){
return n>?this.sum(n-)+n:;
}
}; //将匿名函数加上一个函数名称,变为内联函数,这种方式定义的递归比较安全
var myMath2={
sum:function add(n){
return n>?add(n-)+n:;
}
};

将匿名函数加上一个函数名称,变为内联函数,并使用该函数名称定义递归,这种方式定义的递归函数比直接用对象方法定义的要安全得多。因为一个进行递归调用的对象属性引用,与函数的实际名称不同,这种引用可能是暂时的,这种依赖方式会导致混乱。如:

//使用匿名函数进行递归调用
var myMath={
sum:function(n){
return n>?this.sum(n-)+n:;
}
}; var myMath2={
sum2:myMath.sum //直接引用myMath的sum方法
}; //将myMath对象清空
myMath={}; try{
alert(myMath2.sum2()); }catch(e){
alert(e.message);
} //结果:this.sum is not a function

为什么会出现this.sum不是一个函数的错误呢? 因为我们通过myMath2对象去调用的sum2方法,而sum2方法引用的是myMath的sum方法,在sum方法内部的this现在指向的是myMath2对象,而myMath2对象没有sum方法。记住,当一个函数作为方法被调用时,函数上下文,也就是this参数,指的是调用该方法的对象。如果对this参数还有不明白的地方,请去看上一篇文章javascript进阶笔记(1)。在上面的代码中,我们使用内联函数的名称进行递归是很安全的,虽然myMath对象已经被清空了,但是myMath的sum方法依然可以被访问,这是闭包的作用。

尽管可以给内联函数进行命名,但这些名称只能在函数自身内部才是可见的。也就是说,内联函数的名称,它们的作用域仅限于声明它们的函数。

js中的函数与其他语言中的函数不同,js赋予了函数很多特性,其中最重要的特性之一就是将函数作为第一型对象。函数可以有属性,也可以有方法,可以分配给变量和属性,也可以享有所有普通对象所拥有的特性,而且还有一个超级特性,它们可以被调用!!!这句话非常重要!我们可以给js中的函数设置某些属性,让函数拥有状态和缓存记忆。如下代码:

//给函数设置id属性
var store={
nextId:,
cache:{},
add:function(fn){
if(!fn.id)
fn.id=this.nextId++;
return !!(this.cache[fn.id]=fn);
}
};
//素数判断
//如果该函数的属性缓存中有该素数,则从缓存在取,否则就需要进行计算后将结果存于缓存中,并将结果返回。
function isPrime(value){
if(!isPrime.result)
isPrime.result={};
if(isPrime.result[value]!=null)
return isPrime.result[value]; var prime=value!=;
for(var i=;i<value;i++){
if(value%i==){
prime=false;
break;
}
} return isPrime.result[value]=prime;
}

缓存记忆有两个优点:

1、享有性能优势。直接从以前计算的结果集合中取出结果,如果没有,就计算后将结果保存在缓存中;

2、发生在幕后,无需用户或开发人员做任何特殊操作或为此做任何额外的初始化工作。

缺点:

1、牺牲内存。

接下来我们实现一个伪数组对象,不错,就是伪造一个数组。代码如下:

var elems={
length:,//实现一个数组必须的属性,集合中元素的个数
add:function(elem){
Array.prototype.push.call(this,elem);
},
gather:function(elem){
this.add(elem);
},
delete:function(){
Array.prototype.pop.call(this);
}
};

本来Array这个构造器的原型对象prototype就有关于操作数组的方法,我们可以直接拿来用,不要觉得不要意思,都是自家东西,哈哈!Array.prototype.push方法是通过其函数上下文操作自身数组的。每次调用push方法,程序将会增加length的属性值,然后给对象添加一个数字属性,并将其引用到传入的元素上。

在js中,在函数的实际调用中,我们可以给函数传递一个可变的实际参数列表,也就是说,js灵活且强大的特性之一就是函数可以接受任意数量的参数。我们通过Math对象的max和min方法来举例。如下代码:

var myMath={
//求数组中的最大值 array数组的元素个数是可变的
max:function(array){
return Math.max.apply(Math,array);
},
//求数组中的最小值 array数组的元素个数是可变的
min:function(array){
return Math.min.apply(Math,array);
}
}; var maxValue=myMath.max([3,2,5,6,1]);//6
maxValue=myMath.max([5,8]);//8

因为js中没有针对数组求最大值和最小值的方法,因此我们可以自己定义。举此例的目的,是为了接下来引入函数重载的概念。

js不像其他语言一样,可以进行函数的重载。在js中,没有函数重载的方式。那么在js中还能进行函数的重载吗?当然可以!不过需要绕一个弯。

所有的函数都有一个length属性,这个属性等于该函数在声明时需要传入形参的个数。请不要将函数的length属性与arguments的length属性混淆,arguments的length属性是实际传入参数的个数。

因此,对于一个函数,在参数方面,我们可以确定两件事情:

1、通过函数的length属性,可以知道该函数声明了多少个形参;

2、通过arguments.length,可以知道函数在调用时传入了多少个实参。

我们可以利用以上参数个数的差异化来实现函数的重载。

首先,来看通过传入参数的个数去执行不同的操作。如果想要冗长且完整的函数,可以像如下定义重载:

var persons={
find:function(){
switch(arguments.length){
case :
/*do something*/
break;
case :
/*do something*/
break;
case :
/*do something*/
break;
default:
/*do something*/
break;
}
}
};

在这种方式中,通过arguments参数获取实际传入的参数个数进行判断,每一种情况都会执行不同的操作。但是这种判断方式不是很整洁,也比较冗长。当然,有的时候也需要做这样的操作。

在此,我们可以换一种思路,通过一个addMethod函数来给persons对象重载find方法。代码如下:

function addMethod(object,methodName,fn){
//将旧方法保存下来,在old.apply那一步会依次循环 匹配实参与形参的个数是否相等。循环最重要的事情是 每调用一次addMethod方法,就会将fn和old变量保存在当前对应的闭包中
var old=object[methodName];
//重载该对象的方法
object[methodName]=function(){
//如果该匿名函数的形参个数和实参个数匹配,就调用该函数
if(fn.length==arguments.length)
return fn.apply(this,arguments);
//如果传入的参数不匹配,则调用原有参数的方法
else if(typeof old =='function')
return old.apply(this,arguments);
};
} var persons={};
//给persons对象创建一个find方法
addMethod(persons,"find",function(){
//为了方便调试,就直接返回形参的个数,
return ;
});
//给persons对象重载一个find方法
addMethod(persons,"find",function(name){
return ;
});
//给persons对象重载一个find方法
addMethod(persons,"find",function(first,last){
return ;
}); //接下来调用persons的find方法
alert(persons.find());//
alert(persons.find('sj'));//
alert(persons.find(,));//

看一下浏览器对persons的find方法的解析:

这是一个绝佳的技巧,因为这些绑定函数实际上并没有存储于任何典型的数据结构中,而是在闭包里作为引用进行存储。

在使用这种特定技巧的时候,需要注意以下几点:

1、这种重载方式只适用于不同数量的参数,但并不区分类型、参数名称或其他东西;

2、这种重载方式会有一些函数调用的开销,我们要考虑在高性能时的情况。

javascript进阶笔记(2)的更多相关文章

  1. javascript进阶笔记(1)

    学习js已经有一段时间了,大大小小还是能够做出一些东西来.不过觉得可惜的是,还是对js本身这门语言不是很熟悉,总有一点雾里看花的感觉,看得见,但是看不清楚.最近发现有一本关于js的叫做<忍者秘籍 ...

  2. javascript进阶笔记(3)

    本篇文章我们来学习和讨论一下js中的闭包.闭包是纯函数式编程的一个特性,因为它们能够大大简化复杂的操作.在js中,闭包的重要性不言而喻! 简单的说,闭包(closure)是 一个函数在创建时 允许 该 ...

  3. #笔记#JavaScript进阶篇一

    #JavaScript进阶篇 http://www.imooc.com/learn/10 #认识DOM #window对象 浏览器窗口可视区域监测—— 在不同浏览器(PC)都实用的 JavaScrip ...

  4. JavaScript学习笔记 - 进阶篇(1)- JS基础语法

    前言 JavaScript能做什么? 1.增强页面动态效果(如:下拉菜单.图片轮播.信息滚动等) 2.实现页面与用户之间的实时.动态交互(如:用户注册.登陆验证等) JS进阶篇学习什么? 在JavaS ...

  5. JavaScript进阶--慕课网学习笔记

                         JAVASCRIPT—进阶篇 给变量取个名字(变量命名) 变量名字可以任意取,只不过取名字要遵循一些规则: 1.必须以字母.下划线或美元符号开头,后面可以跟字 ...

  6. 4、JavaScript进阶篇①——基础语法

    一.认识JS 你知道吗,Web前端开发师需要掌握什么技术?也许你已经了解HTML标记(也称为结构),知道了CSS样式(也称为表示),会使用HTML+CSS创建一个漂亮的页面,但这还不够,它只是静态页面 ...

  7. JavaScript基础笔记二

    一.函数返回值1.什么是函数返回值    函数的执行结果2. 可以没有return // 没有return或者return后面为空则会返回undefined3.一个函数应该只返回一种类型的值 二.可变 ...

  8. JavaScript基础笔记一

    一.真假判断 真的:true.非零数字.非空字符串.非空对象 假的:false.数字零.空字符串.空对象.undefined 例: if(0){ alert(1) }else{ alert(2) } ...

  9. Java程序猿的JavaScript学习笔记(汇总文件夹)

    最终完结了,历时半个月. 内容包含: JavaScript面向对象特性分析,JavaScript高手必经之路. jQuery源代码级解析. jQuery EasyUI源代码级解析. Java程序猿的J ...

随机推荐

  1. Java中 Tomcat 是干什么的?

    Tomcat是web容器.它的作用稍后给你解释. 你在做web项目时,多数需要http协议,也就是基于请求和响应,比如你在百度输入一行内容搜索, 那么百度服务器如何处理这个请求呢,他需要创建servl ...

  2. HTML如何实现滚动文字

    HTML如何实现滚动文字 一.总结 一句话总结:marquee标签,也可以用js和css来实现 marquee标签 也可jss和css <marquee><span style=&q ...

  3. 一次dropzone体验

    对于前端,本人不是太擅长,对于当前的一些网上的样例,也许是习武悟性太差,不是太透,所以只能通过blog的方式记录下一些武功套路,便于以后查询使用 首先,我们需要知道这个武功适应的战场. 什么是drop ...

  4. HTTP Status 405 - HTTP method GET is not supported by this URL

    问题概述: 借助MyEclipse直接建立了一个Servlet类,每次访问这个Servlet都能访问.可自己建立一个Servlet为什么总提示:HTTP Status 405 - HTTP metho ...

  5. 51Nod 1810 连续区间

    https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1810 题目给出一个1~n的排列,问有多少连续区间.连续区间的定义为区间 ...

  6. Hadoop/HBase Capacity Planning

    http://blog.cloudera.com/blog/2010/08/hadoophbase-capacity-planning/

  7. luffy后端之跨域corf的解决方法

    跨域CORS 我们现在为前端和后端分别设置两个不同的域名 window 系统: C:\Windows\System32\drivers\etc\host linux/mac系统: /etc/hosts ...

  8. ACM基础(一)

    比较大的数组应尽量声明在main函数外,否则程序可能无法运行. C语言的数组并不是“一等公民”,而是“受歧视”的.例如,数组不能够进行赋值操作: 在程序3-1中,如果声明的是“int a[maxn], ...

  9. java面试之谈

    半个多月的找工作时间,不是在去面试路上,就是在面试中,经历了大概有近10家的面试,虽然很多家都是一回了无音讯,对自己收获还是有的,至少让自己认识到了自身基础不牢固和技术知识面的狭隘.之前从事的工作主要 ...

  10. C# 语法特性 - 泛型(C#2.0)

    泛型将 类型参数 的概念引入了 .NET Framework. 泛型主要有两个优点: 1.编译时可以保证类型安全. 2.不用做类型转换,获得一定的性能提升. 泛型方法.泛型委托.泛型接口 除了泛型类之 ...