一、调用
- <strong>方法调用模式</strong>
当一个函数被保存为对象的一个属性时,我们称它为一个方法。如果调用表达式包含一个提取属性的动作,那么它就是被当做一个方法来调用。
方法可以使用this访问自己所属的对象,通过this可取得它们所属对象的上下文的方法称为公共方法。
-
<strong>函数调用模式</strong>
当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用:
var sum = add(3, 4);//7
以此模式调用函数时,this被绑定到全局对象。
这事语言设计的一个错误,错误后果就是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权。
<b>解决方案:</b>
为该方法定义一个变量并给它赋值为this,那么内部函数就可以通过那个变量访问到this:
- <strong>构造器调用模式</strong>
如果在一个函数前面带上 new 来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时 this 会被绑定到那个新对象上。
//创建一个名为Quo的构造器函数。它构造一个带有status属性的对象。
var Quo = function(string){
this.status = string
}
//给Quo的所有实例提供一个名为get_status的公共方法。
Quo.prototype.get_status = function(){
return this.status;
};
//构造一个Quo实例。
var myQuo = new Quo('confused');
myQuo.get_status()//'confused' - <strong>Apply调用模式</strong>
apply方法让我们构建一个参数数组传递给调用函数。它也允许我们选择this的值。apply方法接收两个参数,第一个是要绑定给this的值,第二个就是一个参数数组。
二、扩充类型的功能
-
我们可以通过给Function.prototype 增加方法来使得该方法对所有函数可用
通过给Function.prototype 增加一个method方法,我们下次给对象增加方法的时候就不必键入prototype这几个字符,省掉了一点麻烦。
-
JavaScript没有专门的整数类型,但有时候确实只需要提取数字中的整数部分。JavaScript本身提供的取整方法有些丑陋。我们可以通过Number.prototype增加一个integer方法来改善它。它会根据数字的正负来判断是使用Math.ceiling还是Math.floor。
- PS:这里需要指出的是Javascript的原型继承机制,可以形象的理解为Object是祖师爷爷,Function既当爹又当妈,剩下的Number,String,Array等都是儿女:
Number,String,Array => Function => Object
- 另外,基本类型的原型是公用结构,所以在类库混用时务必小心。一个保险的做法是只在确定没有该方法时才添加它。
//符合条件时才增加方法。
Function.prototype.method = function(name,func){
if(!this.prototype[name]){
this.prototype[name] = func;
}
return this;
}
三、闭包
-
我们通过调用一个函数的形式去初始化myObject,该函数会返回一个对象字面量。函数里定义一个value变量。该变量对increment和getValue方法总是可用的,但是函数的作用域使得它对其他的程序来说是不可见的。
这个例子我们并没有把一个函数赋值给myObject。我们是把调用该函数后返回的结果赋值给它。
当我们调用quo时,它返回包含get_status方法的一个新对象。该对象的一个引用保存在myQuo中。即使quo已经返回了,但get_status方法仍然享有访问quo对象的status属性的特权。get_status方法并不是访问该参数的一个副本,它访问的就是该参数本身。这是可能的,因为该函数可以访问它被创建时所处的上下文环境,这被称为<b>闭包</b>。
-
来看一个更有用的例子
1.我们调用fade,把document.body作为参数传递给它,fade函数设置level为1.它定义一个step函数,接着调用setTimeout,并传递step函数和一个时间(100毫秒)给它。然后它返回,fade函数结束。
2.在大约100毫秒后,step函数被调用。它把fade函数的level变量转换为16位字符。接着,它修改fade函数得到的节点的背景颜色。然后查看fade函数的level变量。如果背景色尚未变成白色,那么它增大fade函数的level变量,接着用setTimeout预定让它自己再次运行。
3.step函数很快再次被调用。但这次,fade函数的level变量值变成2.fade函数在之前已经返回了,但只要fade的内部函数需要,它的变量就会持续保留。 - <b>从一个经典的错误说起</b>
构造六个div,当点击一个div时,按照预期,应该打出相应div的值,但是它总是会显示div的数量。因为事件处理器函数绑定了变量i本身,而不是函数在构造时的变量i的值,i始终无法被释放。
避免在循环中创建函数,它可能只会带来无谓的计算,还会引起混淆,正如上面那个经典的错误。我们可以先在循环外创建一个辅助函数,让这个辅助函数再返回一个绑定了当前i值的函数,这样就不会导致混淆了。改良后的例子,用正确的方式给一个数组中的节点设置事件处理程序
另一种解决办法,这里我们用一个立即执行函数给它包住,我们不再依赖i,而是用另外一个变量n把它保留下来。
四、记忆
-
函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。这种优化被称为<b>记忆</b>。JavaScript的对象和数组要实现这种优化是非常方便的。
比如说,我们想要一个递归函数来计算Fibonacci数列。一个Fibonacci数列是之前两个Fibonacci数字之和。最前面的两个数字是0和1。
但是这样做了很多无谓的工作。fibonacci函数被调用了453次。如果我们让该函数具备<b>记忆</b>功能,就能显著地减少运算量。
我们在一个名为memo的数组里保存我们的存储结果,存储结果可以隐藏在闭包中。当函数被调用时,这个函数首先检查结果是不是已经存在,如果存在,就立即返回这个结果。
这个函数返回的结果相同,但是它只被调用了29次。 -
我们可以编写一个函数来帮助我们构造带<b>记忆</b>功能的函数。memoizer函数取得一个初始的memo数组和formula函数。它返回一个管理meno存储和在需要时调用formula函数的recur函数。我们把这个recur函数和它的参数传递给formula函数:
现在,我们可以使用memoizer函数来定义fibonacci函数,提供其初始的memo数组和formula函数:
通过设计这种产生另一个函数的函数,极大地减少了我们的工作量。例如,要产生一个可以记忆的阶乘函数,我们只需提供基本的阶乘公式即可: