JavaScript是一门函数式语言,function是这门语言的一等公民。所以理解function,我个人觉得是学好JavaScript这门语言最重要的一部分。
function通常作为我们的逻辑单元,通过被调用(call)来完成一个大程序里面的一部分功能。但是在JavaScript里面,一个function可以作为构造函数,通过new
关键字来实例化一个对象,例如:
function DOG(){}
let dog = new DOG();
console.log(dog.constructor === DOG); // true
但是我们常常说:“function是JavaScript的第一型对象(first-class object)”。在这句话里面有个非常重要的信息就是:function是对象。
是的,在JavaScript里,function是对象,而且就跟一般的对象一样有属性
和方法
。function与一般对象唯一的区别就是,function可以被调用。那既然function是对象,那他们是否也有构造函数呢?
是的,JavaScript里面所有的function的构造函数都是Function
。那既然Function是构造函数,那么是否可以通过new
关键字来创建一个对象(也就是一个function)呢?答案是“yes”:
let sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6)); // 8
用Function来创造一个function的语法是:
new Function ([arg1[, arg2[, ...argN]],] functionBody)
最后一个参数是函数体,前面是参数列表。
但是我们常常不通过Function来new一个function,通常还是利用function
字面量来创建一个方法。
funtion的属性和方法
前面我们说到JavaScrip里所有的function都是对象,且他们的构造器(constructor)都是Function,那么所有的function势必会从Function上继承到一些属性和方法。下面我们就来都有哪些:
Function上可用的属性:
1: Function.prototype.length
2: Function.prototype.name
3: Function.prototype.constructor
Function上可用的方法:
1: Function.prototype.apply()
2: Function.prototype.call()
3: Function.prototype.bind()
4: Function.prototype.toString()
下面我们来对上面几个重要的属性和方法进行讲解:
1: Function.prototype.length -- 函数形参的个数
function testLength(a, b) {
console.log(testLength.length);
}
testLength(1, 2, 3); // 2
这里一定要和arguments.length区分开。每个函数都会隐式地传入this和arguments这2个参数。arguments表示调用函数的时候,实际传入的参数,它是一个类数组对象,它也有一个length属性。arguments.length就表示实际传入的参数个数。
我们知道JavaScript是允许形参和实参个数不一致的,所以Function.prototype.length
表示形参的个数,arguments.length
表示实参的个数。他们俩不一定相等,所以一定要把他俩区分开。
2: Function.prototype.name -- 函数的名字
每一个函数都有一个name
属性,它是一个字符串,用来表示这个函数的名字。name
这个属性,有些复杂,接下来我们一一来看一下:
1: 在全局作用域内定义一个函数
function funX() {};
console.log(window.funX.name);//"funX"
JavaScript有个特性就是:如果在全局作用域内定义一个function,相应地会自动在window对象上创建一个和funciton同名的属性,且把这个function赋给这个属性。
从上面的代码可以验证这一点,我们创建了funX()这个function之后,在window上有了funX这个属性,且它有一个name属性,而且name的值就是funX()的名字,也就是"funX"。
2: 在全局范围内定义一个函数表达式:
var x = function funX(){};
console.log(x.name);//"funX"
var y = function(){};
console.log(y.name);//"y"
当我们定义了一个命名函数表达式的时候,name属性就是函数的名字,而不是变量的名字;但是,当我们把一个匿名函数表达式赋值给一个变量的时候,name就是变量的名字。
以上代码是在chrome里面测试的结果,我个人觉得匿名函数表达式的时候,结果和我想的不一样,我以为这种情况name会是一个空字符串,没想到这个时候name就是变量名字。
有一点需要注意:在全局作用域下面定义函数表达式,并不会在window上创建一个同名属性。
3: Function.prototype.constructor
constructor用来表示函数的构造函数。我们说所有的函数都是Function的一个实现,那么所有函数的constructor都应该是Function。所以,如果在需要判断一个变量是否是一个函数类型的时候,我们可以这么做:
function funX() {};
if(funX.constructor === Function){ // true
}
通常情况下,上面的代码是没有问题的。但是constructor这属性,我们却可以随意修改:
function funX() {};
funX.constructor = {};
if(funX.constructor === Function){ // false
}
像这种情况,再通过constructor来判断是否是函数类型,就不行了。最经典和保险的做法是使用Object.prototype.toString()方法来判断是否为函数类型:
function funX() {};
funX.constructor = {};
if(Object.prototype.toString.call(funX)=== "[object Function]"){ //true
console.log('is function');
}
toString()方法返回一个对象的描述。我们之所以要通过call()来调用Object.prototype.toString()而不是调用函数本身的toString()方法,是因为toString()也有可能会被重写,而Object.prototype.toString()是最原始的蓝本,没有人能重写它。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。