内联命名函数
1 2 3 4 5 6 7 8 9
| var ninja = function myNinja(){ //在内联函数中,验证两个名字是等价的 assert(ninja == myNinja,"Thi function is named two things at once!"); }; ninja(); //调用函数执行内部验证 //验证内联函数的名称在内联外部是不是可用的 assert(typeof myNinja == 'undefined', 'But myNinja isn't defined outside of the function);
|
尽管可以给内联函数进行命名,但这些名称只能在自身函数内部才是可见的。匿名函数的名称和变量名称有点像,它们的作用域仅限于它们的函数。
这就是为什么要将全局函数作为window的方法进行创建的原因。不使用window的属性,我们没有办法引用这些函数。
存储函数
有时候,我们可能需要存储一组相关但独立的函数,事件回调管理是最明显的例子。向这个函数添加函数时,我们面临的挑战是要确定哪些函数在集合众不存在,而应该添加,以及哪些函数已经存在并且不需要再添加了。
显而易见但天真的做法是,将所有的函数保存在一个数组里,然后遍历数组检查重复的函数。只不过这种方式很一般,我们可以利用函数属性的特性,给函数添加一个附加属性从而实现上述目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| var store = { //持续跟踪要赋值的nextId 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.");
|
自记忆函数
缓存记忆是构建函数的过程,这种函数能够记忆先前计算的结果。通过避免已经执行过的不必要复杂的计算,这种方式可以显著提高性能。
缓存记忆昂贵的计算结果
作为一个基本的示例,让我们看一个计算素数的简单算法(当然不是特别有效)。这只是一个复杂计算的简单例子,但这种方法却很容易适用于其它昂贵的计算,例如字符串的MD5哈希计算,比这里的实例复杂得多。
记忆之前计算出的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function isPrime(value) { !isPrime.answers && isPrime.answers = {}; //创建缓存 //检测缓存过的值 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(vaule % i ==0) { prime = false; break; } } return isPrime.answers[value] = prime; //保存计算出的值 } //测试是否正常 assert(isPrime(5), "5 is prime!"); assert(isPrime.answers[5],"The answer was cached!");
|
伪造数组方法
有时,我们可能想创建一个包含一组数据的对象。如果只是集合,则只需要创建一个数组即可。但在某些情况下,除了集合本身,可能会有更多的状态需要保存,比如集合项有关的一些数据。
Array(构造器)上已经存在可以进行集合处理的方法。可以将这些功能嫁接到我们自己的对象上。
模拟类似数组的方法
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
| <body> <input id="first"/> <input id="second"/> <script> var elems = { //保存元素的个数。我们要假装成是数组,那就需要保存元素项的个数 length:0, //实现将元素添加到集合的方法。Array的原型中的一个方法可以做到。 add: function(elem) { Array.prototype.push.call(this, elem); }, //实现一个gather()方法,根据id值查找元素,并将其添加到集合中 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> </body>
|
函数重载
函数的length属性
所有的函数都有一个有趣的属性,它并不为人知,但却让我们可以了解函数的声明,那就是length属性。不要将该属性和arguments参数的length属性弄混淆了。该属性值等于该函数声明时所要传入的形参数量。
因此,如果声明一个接收单个参数的函数,那这个函数的length属性值应该是1.
1 2 3 4
| function makeNinja(name) {} function makeSamurai(name,rank){} assert(makeNinja.length == 1,"Only expecting a single argument"); assert(makeSamurai.length == 2, "Two arguments expected");
|
因此,对于一个函数,在参数方面,我们可以确定两件事。
通过其length属性,可以知道声明了多少命名参数。
通过arguments.length,可以知道在调用时传入了多少参数。
利用参数个数进行重载
基于传入的参数,有很多种方法可以判断并进行函数重载。一种通用的方法是,根据传入参数的类型执行不同的操作。另一种方法是,可以通过某些特定参数是否存在来进行判断。还有一种方法是通过传入参数的个数进行判断。
重载函数的方法
1 2 3 4 5 6 7 8 9 10
| function addMethod(object,name,fn) { //保存原有的函数,因为调用的时候可能不匹配传入的参数个数 var old = object[name]; object[name] = function(){ //如果该匿名函数的形参个数和实参个数匹配,就调用该函数 if (fn.length == arguments.length) return fn.apply(this, arguments) //如果传入的参数不匹配,则调用原有的参数 else if (typeof old == 'function') return old.apply(this.arguments); } }
|
测试addMethod()函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| //创建一个基础对象,实现加载一些测试数据 var ninjas = { values: ['Dean Edwards', 'Sam Stephenson', 'Alex Russell']; }; //在基础对象上绑定一个无参数方法 addMethod(ninjas, 'find', function(){ return this.values; }); //在基础对象上绑定一个单参数的方法 addMethod(ninjas, 'find',function(name){ var ret = []; for (var i = 0; i < this.values.length; i++) if (this.values[i].indexOf(name) == 0) ret.push(this.values[i]); return ret; }); //在基础对象上绑定一个两个参数的方法 addMethod(ninjas, 'find', function(first, last) { var ret = []; for (var i = 0; i < this.values.length; i++) if (this.values[i] == (first + ' ' + last)) ret.push(this.values[i]); return ret; });
|
重载只适用于不同数量的参数,但并不区分类型,参数名称或其它东西。
这样的重载方法会有一些函数调用的开销,要考虑在高性能时用的情况。
函数判断
1 2 3
| function isFunction(fn) { return Object.prototype.toString.call(fn) == '[object Function]' }
|
为什么不直接调用fn.toString()获取结果,原因有两个。
不同的对象可能有自己的toString()方法实现
JavaScript 中的大多数类型都已经有一个预定义的toString方法覆盖了Object.prototype提供的toString()方法
通过直接访问Object.prototype的方法,可以确保我们得到的不是覆盖版本的toString(),而且最终得到的准确信息正是我们所需要的。