文章目录
  1. 1. 内联命名函数
  2. 2. 存储函数
  3. 3. 自记忆函数
    1. 3.1. 缓存记忆昂贵的计算结果
  4. 4. 伪造数组方法
  5. 5. 函数重载
    1. 5.1. 函数的length属性
  6. 6. 利用参数个数进行重载
  7. 7. 函数判断

内联命名函数

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(),而且最终得到的准确信息正是我们所需要的。

文章目录
  1. 1. 内联命名函数
  2. 2. 存储函数
  3. 3. 自记忆函数
    1. 3.1. 缓存记忆昂贵的计算结果
  4. 4. 伪造数组方法
  5. 5. 函数重载
    1. 5.1. 函数的length属性
  6. 6. 利用参数个数进行重载
  7. 7. 函数判断