函数
函数的一些特征
- 函数形参类似函数内的局部变量
- 函数没有return语句就返回undefined
- 除了函数实参之外,每次调用还会有另一个值——本次调用的上下文——就是this关键字的值
如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文(context),也就是该函数的this的值。用于初始化一个新创建的对象的函数称为构造函数(constructor)。
在javascript里,==函数即对象==,程序可以随意操控它们。比如,javascript可以把函数赋值给变量,或者作为参数传递给其他函数。因为函数就是对象,所以可以给他们设置属性,甚至调用他们的方法。
javascript的函数可以嵌套在其他函数中定义,这样他们就可以访问它们被定义时所处的作用域中的任何变量。这意味着javascript函数构成了一个==闭包(closure)==,它给javascript带来了非常强劲的编程能力。
展示一个小例子,闭包demo:
var Counter=(function(){
//赋初值
var count=0;
//外部调用时形成闭包
function f(){
return ++count;
}
return f;
})();
Counter();//1
Counter();//2
Counter();//3
函数定义
定义函数的两种方式:==函数声明==和==函数表达式==,下面是一些函数使用的常用场景:
//输出o的每个属性的名称和值,返回undefined
function printprops(o){
for(var p in o)
console.log(p + ": " + o[p] + "\n");
}
//计算两个笛卡尔坐标(x1,y1)和(x2,y2)之间的距离
function distance(x1,y1, x2,y2){
var dx = x2 - x1;
var dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
//计算阶乘的递归函数(调用自身的函数)
//x的值是从x到x递减的值的累乘
function factorial(x){
if(x <= 1) return 1;
return x * factorial(x - 1);
}
//这个函数表达式定义了一个函数用来求传入参数的平方
//注意我们把它赋值给一个变量
var square = function(x){ return x * x; }
//函数表达式可以包含名称,这在递归时很有用
var f = function fact(x){
if(x <= 1) return 1;
return x * fact(x - 1);
}
//函数表达式也可以作为参数传给其他函数
data.sort(function(a,b){return a - b;});
//函数表达式有时定义后立即调用
var tensquared = (function(x){ return x * x; }(10));
注:
- 前三个function是函数声明方式,后四个是函数表达式
- 以表达式方式定义函数,函数的名称是可选的。
- 函数声明语句实际上声明了一个变量,并把一个函数对象赋值给它。定义函数表达式时并没有声明一个变量。
- 函数声明定义函数会提前到代码顶部,就是说在函数定义之前的代码可以调用该函数。而函数定义表达式却没有“提前”
- 如果一个函数定义表达式包含名称,函数的局部作用域将会包含一个绑定到函数对象的名称。实际上,函数的名称将成为函数内部的一个局部变量。 (其实就是说,如果一个函数定义表达式如果包含名称,那么函数的内部可以通过这个名称来访问这个函数,类似上面的阶乘函数)
函数命名
- 函数名称一般由字母、数字、美元符号和下划线组成
- 函数名称通常是动词或动词为前缀的词组。
- 当函数名称包含多个单词时,一般有两种命名方式 :liek_this(),likeThis()——camel式(小驼峰式)
在一些编程风格中,或者编程框架里,通常==为那些经常调用的函数指定短暂的名称==,比如jQuery就将最常用的方法重命名为$()
。
嵌套函数
p180
函数调用
构成函数主体的javascript代码在定义之时并不会执行,只有在调用该函数时,他们才会执行。有4种方式来调用javascript函数:
- 作为函数(定义一个function funname(),然后执行funname()调用该函数)
- 作为方法(对象o.funname())
- 作为构造函数(new funname())
- 通过他们的call()和apply()方法简洁调用
作为函数调用
函数如果有return语句,那么执行到return语句返回,没有return语句,则返回undefined。
根据ECMAScript 3和非严格的ECMAScript 5对函数调用规定,调用上下文(this的值)是全局对象。然而,在严格模式下,调用上下文则是undefined。
//'use strict'
var strict = (function(){
return this;
}());
console.log(strict);//window
//如果取消'use strict`的注视,那么返回undefined
或者:
//'use strict'//非严格模式
function per(){
var self = this;//Window(非严格模式)
}
per('');
//去掉第一行注视(严格模式下),pre里面的self变量等于undefined了。
其实很好理解,因为调用(上面提到的作为函数调用)pre函数的是window对象,所以this是window(严格模式等于undefined是规定的,死记就好)。
其实这些是有章可循的,即看这个函数被当作什么来调用,从而可以确定它(函数内部的this对象)的所有者,this就指代这个所有者。函数里面的this对象指代规则如下:
- 作为函数调用,(严格模式下this是undefined,非严格模式下是window)
- 作为方法调用,this是调用当前方法的对象
- 作为构造函数调用,this是当前new出来的对象
- 通过函数的call()或apply()调用,this就是传递进去的对象(这两个方法主要用来改变函数内部的thi对象)
以函数形式调用的函数通常不使用this关键字(有时候也会这么做,当你需要写一个js的基类,然后很多子类都会继承该类,并可以在子类里面覆盖基类时,通过这种方法可以减少很多重复性的代码)。不过,“this”可以用来判断当前是否是严格模式。
//定义并调用一个函数来确定当前脚本运行时是否为严格模式
var strict = (function(){
return !this;
}());
注:函数调用这里的函数
指不依赖或不挂载在其他对象下面的方法,就是一个简简单单的 function定义的表达式。
作为方法调用
一个方法无非是个保存在一个对象的属性里面的javascript函数。如果有一个函数f和一个对象o,则可以用下面的代码给o定义一个名为m()的方法。
o.m = f;
给对象o定义了方法m(),调用它时就像这样:
o.m();
上面代码意味着该函数被当作一个方法,而不是作为一个普通函数来调用。
==对方法调用的参数和返回值的处理,和上面所描述的普通函数调用完全一致。但是,方法调用和函数调用有一个重要区别==,即:==调用上下文==。属性访问表达式由两部分组成:一个对象(本例中的o)和属性名称(m)。在像这样的方法调用表达式里,对象o成为调用上下文,函数体可以使用关键字this引用该对象。下面是一个具体的例子:
var calculator = { //对象直接量
operand1: 1,
operand2: 1,
add: function(){
//注意this关键字的用法,this指代当前对象
this.result = this.operand1 + this.operand2;
}
};
calculator.add(); //这个方法调用计算1+1的结果
calculator.result; //返回2
方法链:当方法的返回值是一个对象,这个对象还可以再调用它的方法。这种方法调用序列中(通常称为“链”或者“级联”)每次的调用结果都是另外一个表达式的组成部分。比如,基于jQuery库,我们常常会这样写代码:
//找到所有的header,取得它们id的映射,转换为数组并对它们进行排序
$(":header").map(function(){return this.id}).get().sort();
==当方法并不需要返回值时,最好直接返回this==。如果在设计API就可以进行“链式调用”风格编程。在这种编程风格中,只要指定一次要调用的对象即可,余下的方法都可以基于此进行调用:
shape.setX(100).setY(100).setSize(50).setOutline("red").setFill("blue").draw();
自己写的一个demo:
function shape(){
var self = this;
self.x = 0;
self.y = 0;
self.size = 0;
self.setX = function(_x){
self.x = _x;
return self;
};
self.setY = function(_y){
self.y = _y;
return self;
};
self.setSize = function(_size){
self.size = _size;
return self;
};
self.getX = function(){return self.x; };
self.getY = function(){return self.y;};
self.getSize = function(){return self.size;};
}
var s1 = new shape();
s1.setX(1).setY(2).setSize(3);//链式写法
var x = s1.getX();//1
var y = s1.getY();//2
var size = s1.getSize();//3
需要注意的是,this是一个关键字,不是变量,也不是属性名。javascript的语法不允许给this赋值。
和变量不同,关键字this没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。如果嵌套函数作为方法调用,其this的值指向调用它的对象。如果嵌套函数作为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下)。很多人误以为调用嵌套函数时,this会指向调用外层函数的上下文。如果你想访问这个外部函数的this值,需要将this的值保存在一个变量里,这个变量和内部函数都在同一个作用域内。通常使用变量self来保存this,比如(理解这段话,最好先看明白如下例子):
var o = { //对象o
m: function(){ //对象中的方法m()
var self = this; //将this的值保存至一个变量中
console.log(this === o); //输出true,this就是这个对象o
f(); //调用辅助函数f()——将f作为函数调用
function f(){ //定义一个嵌套函数
console.log(this === o);//false:this的值是全局对象或undefined
console.log(self === o);//true:self值外部函数的this值
}
}
};
o.m(); //调用对象o的方法m()
结论:
- 嵌套函数中,外部函数的this和内部(嵌套)函数内部的this是两个不同的对象,并且都通过this引用该对象,如果想在嵌套函数内部调用外部函数的this对象,必须将外部函数保存到一个变量——不然内部函数this关键字只哪个呢,记住,他们都通过this关键字调用的。
- (内部)函数里面的this对象指代什么,依赖上面提到的(函数被当作什么来调用)。实际上可能应该是所有函数里面的this对象指代什么都需要看当前函数被当作什么来调用。
作为构造函数调用
==构造函数调用和普通的函数调用以及方法调用在实参处理、调用上下文和返回值方面都有不同。==
如果构造函数没有形参,那么小括号是可以省略的,比如,下面这两行代码就是等价的。
var o = new Object();
var o = new Object;
==构造函数调用创建一个新的空对象,这个对象继承自构造函数的prototype属性。构造函数试图初始化这个新创建的对象,并将这个对象用做其调用上下文,因此构造函数可以使用this关键字来引用新创建的对象。== 注意,尽管构造函数看起来像一个方法调用,它依然会使用这个新对象作为调用上下文。也就是说,在表达式new o.m()
中,调用上下文并不是o。
辅助解读:
上面提到“这个对象继承自”,继承自哪里?可以通过对象.__proto__
得到,如下:
function shape(){}
var s1 = new shape();
console.log(s1.__proto__ === shape.prototype);//true
console.log(s1 instanceof shape);//true
间接调用
javascript中的函数也是对象,和其他javascript对象没什么两样,函数对象也可以包含方法。其中的两个方法call()和apply()可以用来间接地调用函数。两个方法都允许显示指定调用所需的this值,也就是说,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。两个方法都可以指定调用的实参。call()方法使用它自由的实参列表作为函数的实参。apply()方法则要求以数组的形式传入参数。
函数的实参和形参
javascript中的函数定义并未指定函数形参的类型,函数调用也未对传入的实参做任何类型检查。实际上,javascript函数调用甚至不检查传入形参的个数。下面几节将讨论当调用函数时的形参和实参个数不匹配时出现的状况,同样说明了如何显示测试函数实参的类型,以避免非法的实参传入函数。
可选形参
==当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值==。因此在调用函数时形参是否可选以及是否可以省略应该保持较好的适用性。==为了做到这一点,应该给省略的参数赋一个合理的默认值==,看下面的例子:
//将对象o中可枚举的属性名追加至数组a中,并返回这个数组a
//如果省略a,则创建一个新数组并返回这个新数组
function getPropertyNames(o,/* optional */ a){
if(a === undefined) a = []; //如果为定义,则使用新数组
// a = a || []; //同上面的if语句
for(var property in o)
a.push(property);
return a;
}
//这个函数调用可以传入1个或2个实参
var a = getPropertyNames(o) //将o的属性存储到一个新数组中
getPropertyNames(p,a); //将p的属性追加至数组a中
需要注意的是,当用这种可选实参来实现函数时,需要将可选实参放在实参列表的最后。因为js没法省略第一个实参并传入第二实参的,它必须将undefined作为第一个实参显示传入。同样注意在函数定义中使用注释/*optional*/
来强调形参是可选的。
可变长的实参列表:实参对象
当调用函数的时候传入的实参个数超过函数定义时的形参个数时。没有办法直接获得未命名值的引用。参数对象解决了这个问题。在函书体内,标识符arguments
是指向实参对象的引用,实参对象是一个类数组对象
,这样可以通过数字下标就能访问传入函数的实参值,而不用非要通过名字来得到实参。
实参对象在很多地方都非常有用,下面的例子展示了使用它来验证实参的个数,从而调用正确的逻辑,因为javascript本身不会这么做:
function f(x, y, z){
//首先,验证传入实参的个数是否正确
if(arguments.length != 3){
throw new Error("函数f使用" + arguments.length + "个参数,但它期望有3个参数");
}
//再执行函数的其他逻辑……
}
需要注意的是,通常不必像这样检查实参个数。大多数情况下javascript的默认行为是可以满足需求的:==省略的实参都将是undefined,多出的参数会自动省略==。
实参对象有一个重要的用处,就是让函数可以操作任何数量的实参。下面的函数就可以接收任意数量的实参,并返回传入实参的最大值(内置函数Max.max()的功能与之类似):
function max(/* ... */){
var max = Number.NEGATIVE_INFINITY;
//便利实参,查找并记住最大值
for(var i = 0; i < arguments.length; i++){
if(arguments[i] > max)
max = arguments[i];
}
//返回最大值
return max;
}
var largest = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6);
注:arguments
==并不是真正的数组,它是一个实参对象。每个实参对象都包含以数字为索引的一组元素以及length属性,但它毕竟不是真正的数组。可以这样理解,它是一个对象,只是碰巧具有以数字为索引的属性。==
辅助解读(证明arguments对象并不是数组):
function fun(){
//[object Array]
console.log(Object.prototype.toString.apply([]));
//[object Arguments]
console.log(Object.prototype.toString.apply(arguments));
}
fun();
只是一个key类似索引的对象:
紧要了。为了实现这种风格的方法调用,定义函数的时候,传入的实参都写入一个单独的对象之中,在调用的时候传入一个对象,对象中的key/value对是真正需要的实参数据。下面就展示了这种风格:
//将原始数组的length元素复制至目标数组
//开始复制原始数组的from_start元素
//并且将其复制至目标数组的to_start中
//要记住实参的顺序并不容易
function arraycopy(/* array */ from, /* index */ from_start,
/* array */ to, /* index */ to_start,
/* integer */ length){
//逻辑代码
}
//这个版本的实现效率稍微有点底,但你不必再去记住实参的顺序
//并且from_start和to_start都默认为0
function easycopy(args){
arraycopy(args.from,
args.from_start || 0,//注意这里设置了默认值
argt.to,
args.to_start || 0, args.length );
}
//来看如何调用easycopy()
var a = [1, 2, 3, 4], b = [];
easycopy({from:a, to: b, length:4});//省略了两个参数
实参类型
因为javascript没有严格的参数类型限制,==所以我们应当添加一些类似的实参类型检查逻辑,因为宁愿程序在传入非法值时报错,也不愿非法值导致程序在执行时报错,相比而言,逻辑执行时的报错不甚清晰且更难处理。==
//返回数组(或类数组对象)a的元素的累加和
//数组a中必须为数字、null和undefined的元素都将忽略
function sum(a){
if(isArrayLike(a)){
var total = 0;
for(var i = 0; i < a.length; i++){//遍历所有元素
var element = a[i];
if(element == null) continue;//跳过null和undefined
if(isFinite(element)) total += element;
else throw new Error("sum(): 数组a中的元素必须是一个有效数字 ");
}
return total;
}else{
throw new Error("参数a必须是数组对象");
}
}
作为值的函数
==函数不仅是一种语法。也是值,也就是说,可以将函数赋值给变量,存储在对象的属性或数组的元素中,作为参数传入另外一个函数等。==
为了便于理解javascript中的函数时如何用做数据的以及javascript语法,来看一下这样一个函数定义:
function square(x){ return x * x; }
这个定义创建一个新的函数对象,并将其赋值给变量square。==函数的名字实际上是看不见的,它(square)仅仅是变量的名字,这个变量指代函数对象。== 函数还可以赋值给其他的变量,并且仍可以正常工作:
var s = square; //现在s和square指代同一个函数
square(4); //16
s(4); //16
自定义函数属性
javascript中的函数并不是原始值,而是一种特殊的对象,也就是说,函数可以拥有属性。当函数需要一个“静态”变量来在调用时保持某个值不变,最方便的方式就是给函数定义属性,而不是定义全局变量,显然定义全局变量会让命名空间变得更加杂乱无章。比如,假设你想写一个返回一个唯一整数的函数,不管在哪里调用函数都会返回这个整数。而函数不能两次返回同一个值,为了做到这一点,函数必须能够跟踪它每次返回的值,而且这些值的信息需要在不同的函数调用过程中持久化。可以将这些信息存放到全局变量中,但这并不是必需的,因为这个信息仅仅是函数本身用到的。最好将这个信息保存到函数对象的一个属性中,下面这个例子就实现了这样的函数,每次调用函数都返回一个唯一的整数:
//初始化函数对象的计数器属性
//由于函数声明被提前了,因此这里是可以在函数声明
//之前给它的成员复制的
uniqueInteger.counter = 0;
//每次调用这个函数都回返回一个不同的整数
//它使用一个属性来记住下一次将要返回的值
function uniqueInteger(){
console.log(uniqueInteger.counter);
return uniqueInteger.counter++;
}
new uniqueInteger();//output 0
new uniqueInteger();//output 1
new uniqueInteger();//output 2
作为命名空间的函数
前面已经说到过函数作用域的概念:在函数中声明的变量在整个函数体内都是可见的(包括在嵌套的函数中),在函数的外部是不可见的。不在任何函数内声明的变量是全局变量,在整个javascript程序中都是可见的。在javascript中是无法声明只在一个代码块内可见的变量的(只有函数作用域,没有块级作用域)。基于这个原因,我们常常简单地定义一个函数用作临时的命名空间,==在这个命名空间内定义的变量都不会污染到全局命名空间==。
比如,假设你写了一段javascript模块代码,这段代码将要用在不同的javascript程序中。和大多数代码一样,假定这段代码定义了一个用以存储中间计算结果的变量。这样问题就来了,当模块代码放到不同的程序中运行时,你无法得知这个变量是否已经创建了,如果已经存在这个变量,那么将会和代码发生冲突。解决办法当然是将代码放入一个函数内,然后调用这个函数。这样全局变量就变成了函数内的局部变量:
function mymodule(){
//模块代码
//这个模块所用的所有变量都是局部变量
//而不会污染全局命名空间
}
mymodule();//不要忘了还要调用这个函数
这段代码仅仅定义了一个单独的全局变量:名叫“mymodule”的函数。这样还是太麻烦,可以直接定义一个匿名函数,并在单个表达式中调用它:
(function(){//mymodule()函数重写为匿名的函数表达式
//模块代码
}()); //结束函数定义并立即调用它
这种定义匿名函数并立即在单个表达式中调用它的写法非常常见,已经成为一种惯用法了。注意上面代码的圆括号的用法,function之前的做圆括号是必须的,因为如果不写,javascript解释器会试图将关键字function解析为函数声明语句。使用圆括号javascript解析器才会解析为函数定义表达式。
辅助解读:
实际上,如果没有左圆括号是会报错的
function(){}() //SyntaxError
(function(){}()) //下面这两种都可以
(function(){})()
p193 函数自定义属性这段不太了解
闭包
p194
阅读这一小节之前建议先复习《javascript权威指南》第 3.10(变量作用域)、3.10.3(作用域链)。
并参考:JavaScript 开发进阶:理解 JavaScript 作用域和作用域链
闭包的最常见形式:当一个函数嵌套了另外一个函数,外部函数将嵌套的函数对象作为返回值返回的时候就是一个闭包的形式。
文章开头给了一个闭包的小demo,这里我复制以下:
var Counter=(function(){
//赋初值
var count=0;
//外部调用时形成闭包
function f(){
return ++count;
}
return f;
})();
Counter();//1
Counter();//2
Counter();//3
这段代码刚好就是上面对闭包描述的代码表示形式了。
总结,闭包的特点:
- 两个函数嵌套;
- 调用外部函数返回内部函数(内部函数引用,不是执行结果);
- 内部函数引用外部函数变量(这是闭包的用处之一——这样做的目的是:虽然外部函数执行结束了,但是函数的变量并没有被回收)
注意:上面代码并不能简写成如下方式:
var Counter=(function(){
//赋初值
var count=0;
//外部调用时形成闭包
function f(){
return ++count;
}
return f;
});
//看上去和上面调用的方式差不多,但其实不然:
//上面的Counter对象保存的是一个执行后的结果(对函数f的引用),然后多次Counter()都是调用f()函数;
//而这种方式虽然也是多用调用f(),但是也多次重新调用Counter去获取对f的引用了。
Counter()();//1
Counter()();//1
Counter()();//1
可以改成如下方式:
var Counter=(function(){
//赋初值
var count=0;
//外部调用时形成闭包
function f(){
return ++count;
}
return f;
});
var temp = Counter();//将对f的引用保留起来
//然后执行该引用(方法)
temp();//1
temp();//2
temp();//3
函数属性、方法和构造函数
p 200
length属性
函数的length属性代表函数形参个数。
prototype属性
call()方法和apply()方法
我们可以将call()和apply()看作是某个对象的方法,通过调用方法的形式来间接调用函数。call()和apply()的第一个实参是要调用函数的母对象,它是调用上下文,在函数体内通过this来获取对它的引用。要想以对象o的方法来调用函数f()(即:o.f
()的方式调用f()函数),可以这样使用call()和apply():
function f(){
var self = this;
self.name = "aaaa";
}
var o = {name:'alleanxu'};
f.call(o);
f.apply(o);
注:这么调用其实就是为了改变函数f()中this的值,call()、apply()第一个参数即为这个this。
在ECMAScript 5的严格模式中,call()和apply()的第一个实参都会变为this的值,哪怕传入的实参是原始值甚至是null或undefined。在ECMAScript 3和非严格模式中,传入的null和undefined都会被全局对象代替,而其他原始值则会被相应的包装对象(wrapper objcet)所替代。
apply() 和 call() 方法的作用是类似的,只是参数不一样:
如下:
function f(num1, num2){
var self = this;
self.name = "aaaa";
}
var o = {name:'alleanxu'};
f.call(o,1,2);//
f.apply(o,[1,2]);//
第一个参数是o,想要以该对象调用函数f();第二个参数,call()是一个任意长度的参数,而apply()是一个任意长度的数组(类数组也行,例如函数的 arguments 对象可以当作apply()的第二个参数)
//将对象o中名为m()的方法替换为另一个方法
//可以在调用原始的方法之前和之后记录日志信息
function trace(o, m){
var original = o[m]; //在闭包中保存原始方法
o[m] = function(){ //定义新的方法
console.log(new Date(), "Entering:", m); //输出日志消息
var result = original.apply(this, arguments); //调用原始函数
console.log(new Date(), "Exiting:", m); //输出日志消息
return result; //返回结果
}
}
var o = {
name: "alleanxu",
sayHello: function(msg){
console.log(msg + "," + this.name);
}
};
var temp = trace(o, "sayHello");
var res = o.sayHello("hello");
trace()函数接受两个参数,一个对象和一个方法名,它将指定的方法替换为一个新方法,这个新方法是“包裹”原始方法的另一个“泛函数”。这种动态修改已有方法的做法有时称做“monkey-patching”。
bind()方法
bind()是在ECMAScript 5中新增的方法,但在ECMAScript 3中可以轻易模拟bind()。从名字就可以看出,这个方法的主要作用就是将函数绑定至某个对象。当在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数。(以函数调用的方式)调用新的函数将会把原始的函数f()当作o的方法来调用。传入新函数的任何实参都将传入原始函数,比如:
function f(msg){
console.log(msg);
}
var o = {};
//将函数f()绑定至对象o
//挡在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数。
var result = f.bind(o);
//以函数调用的方式调用新的函数
//将会把原始函数f()当作o的方法来调用
//传入新函数的任何实参都将传入原始函数
result("hello");
或者如下代码(也演示了bind的使用) :
function f(y){ return this.x + y ;} //这个是待绑定的函数
var o = {x: 1}; //将要绑定的对象
var g = f.bind(o); //通过调用g(x)来调用o.f(x)
g(2); //3
可以通过如下代码轻易地实现这种绑定:
//返回一个函数,通过调用它来调用o中的方法f(),传递它所有的实参
function bind(f, o){
if(f.bind) return f.bind(o); //如果bind()方法存在的话,使用bind()方法
else return function(){ //否则,这样绑定
return f.apply(o, arguments);
};
}
toString()方法
p204
Function()构造函数
......
函数式编程
p206
......