在ECMAScript中,Function(函数)类型实际上是对象。每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针。
1.普通函数的声明
function box(num1,num2){
return num1+num2
}
console.log(box(4,5))//9
这种的函数声明是最为常见的一种,也是我们平时使用的比较多的一种.
2.函数表达式
var box = function(num1.num2){
return num1+num2
}
console.log(box(4,5))//9
这也是属于函数声明中的一种。
那么问题来了,假设我在页面书写同一个函数名,分别用函数声明和函数表达式,那么谁会生效呢?
<script>
var box = function(){
alert(2)
}
function box(){
alert(1)
}
box()//2
</script>
我们在上述的代码中可以发现,会发现弹出一个2,为什么会这样呢?
我们要知道,在javascript中有个东西叫做,变量提升的一个概念,我们可以理解为,var 定义的东西,它的变量都回提升,那么这个时候,普通的函数声明是不能覆盖我们的函数表达式.
二.函数作为值返回
我们都都知道,函数里面有个返回值叫做return,这个可以返回我们的基本数据类型,那么我们来考虑一下,是否能够返回一个函数呢?还有函数分为匿名函数和有名称的函数,那么他们也可以作为返回值吗?
function box(){
var b = 1
function a(){
alert(b)
}
return a//返回一个有名称的函数
}
var res = box()
res()//1
function box(){
var a = 1;
return function(){
alert(a)
}
}
var res = box()
res()//1
三.函数内部属性
在函数内部,有两个特殊的对象:arguments和this。arguments是一个类数组对象,包含着传入函数中的所有参数,主要用途是保存函数参数。但这个对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。
我们来看下一个很经典的一个递归案例,阶乘n!
function sum(num){
if(num<=1){
return 1
}else{
return num*sum(num-1)
}
}
console.log(sum(5))//120
对于阶乘函数一般要用到递归算法,所以函数内部一定会调用自身;如果函数名不改变是没有问题的,但一旦改变函数名,内部的自身调用需要逐一修改。为了解决这个问题,我们可以使用arguments.callee来代替。
所以,以上的程序可以修改成
function sum(num){
if(num<=1){
return 1
}else{
return num*arguments.callee(num-1)
}
}
console.log(sum(5))//120
函数内部另一个特殊对象是this,其行为与Java和C#中的this大致相似。换句话说,this引用的是函数据以执行操作的对象,或者说函数调用语句所处的那个作用域。PS:当在全局作用域中调用函数时,this对象引用的就是window。换句白话来说就是,this指的是函数运行时所在的环境.
那么如何来理解这个运行时所在的环境呢?
我们定义一个对象
var obj = {
var name = "咖啡",
getName:function(){
alert(this.name)
}
}
函数调用
obj.getName()//咖啡
上述的结果是完全没有问题的,因为我们这个getName()执行的环境就是在这个obj中,那么他在obj中找到了这个name,那么输出的结果就是咖啡.
那么我们把上述的代码修改一下,
var name = "绿茶"
var obj = {
var name = "咖啡",
getName:function(){
alert(this.name)
}
}
var test = obj.getName //注意这里并没有执行,只是简单的把函数进行了一个赋值.
test()//绿茶
当我们执行这个test方法的时候,我们就会发现这个时候它输出的其实就是一个"绿茶",那么为什么会是绿茶呢?
我们来思考一下,现在我的这个test方法实在哪里运行的?,这个时候,test方法它并不是在我们的这个obj中运行,而是在我们的window下面运行,所以这个时候,它就回去全局寻找这个name.刚好发现,全局有个name叫做"绿茶",那么输出的结果就是"绿茶"
为了加深理解,我们来看下面这个demo
var name = "绿茶"
var obj = {
name:"咖啡"
}
function getName(){
alert(this.name)
}
getName()//绿茶
obj.getName = getName
obj.getName()//咖啡
它的原理与上面的是一样的,请读者自行推理....
四.函数属性和方法
ECMAScript中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:length和prototype。其中,length属性表示函数希望接收的命名参数的个数。
function box(name, age) {
alert(name + age);
}
alert(box.length); //2
对于prototype属性,它是保存所有实例方法的真正所在,也就是原型。而prototype下有两个方法:apply()和call(),每个函数都包含这两个非继承而来的方法。这两个方法的用途都在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。
其实,call()和apply()方法.这两个都是对象冒充的意思,但这两者还是有不同的地方。
function box(num1,num2){
retutn num1+num2
}
function sum(num1,num2){
return box.apply(this,[num1,num2])
}
alert(sum(10,10)) //20
我来解释一下以上代码,我们要知道,初始状态下,sum函数是没有相加的这个功能的.我们通过apply的方法,来对box函数进行对象的冒充.那么这个时候,sum函数就认识了box函数里面相加的方法.而apply(this)=>window,而apply它的第二个参数是接受,一个数组参数的.
也就是函数(你要冒充的函数).apply(obj,[num1,num2])
我们也可以把上述的代码修改成
function sayBox2(num1, num2) {
return box.apply(this, arguments); //arguments对象表示box所需要的参数
}
alert(sayBox2(10,10))//20
call()方法于apply()方法相同,他们的区别仅仅在于接收参数的方式不同。对于call()方法而言,第一个参数是作用域,没有变化,变化只是其余的参数都是直接传递给函数的。
function box(num1, num2) {
return num1 + num2;
}
function callBox(num1, num2) {
return box.call(this, num1, num2); //和apply区别在于后面的传参
}
alert(callBox(10,10));
事实上,传递参数并不是apply()和call()方法真正的用武之地;它们经常使用的地方是能够扩展函数赖以运行的作用域。
var color = '红色的'; //或者window.color = '红色的';也行
var box = {
color : '蓝色的'
};
function sayColor() {
alert(this.color);
}
sayColor(); //作用域在window
sayColor.call(this); //作用域在window
sayColor.call(window); //作用域在window
sayColor.call(box); //作用域在box,对象冒充
这个例子是之前作用域理解的例子修改而成,我们可以发现当我们使用call(box)方法的时候,sayColor()方法的运行环境已经变成了box对象里了。
使用call()或者apply()来扩充作用域的最大好处,就是对象不需要与方法发生任何耦合关系(耦合,就是互相关联的意思,扩展和维护会发生连锁反应)。也就是说,box对象和sayColor()方法之间不会有多余的关联操作,比如 box.sayColor = sayColor;
那么,今天就分享到这里,我会在每周五都会和大家分享一篇文章,有兴趣可以订阅以下。^.^
关于javascript中的funtion类型
最新推荐文章于 2023-02-12 21:07:03 发布