JavaScript基础——引用类型(三)Function类型

在ECMAScript中函数实际上是对象。每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。函数通常是使用函数声明语法定义的,如:

function sum(num1 , num2){

returnnum1 + num2;

}

这与下面使用函数表达式定义函数的方式几乎相差无几:

var sum = function(num1 , num2){

returnnum1 + num2;

};

以上代码定义了变量sum并将其初始化为一个函数,上面的例子中function关键字后面没有函数名。这是因为在使用函数表达式定义函数时,没有必要使用函数名——通常变量sum即可以引用函数。另外,还要注意函数末尾有一个分号,就像声明其他变量一样。

最后一种定义函数的方式是使用Function构造函数。Function构造函数可以接收任意数量的参数,但最后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数,如:

var sum = new Function(“num1” , “num2” , “returnnum1 + num2”);//不推荐

从技术角度讲,这是一个函数表达式。但是,不推荐使用这种方法定义函数,因为这种语法会导致解析两次代码(第一次是解析常规ECMAScript代码,第二次是解析传入构造函数中的字符串),从而影响性能。不过,这种语法对于理解“函数是对象,函数名是指针”的概念倒是非常直观的。

由于函数名仅仅是执行函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话说,一个函数可能会有多个名字。

没有重载

将函数名想象为指针,也有助于理解为什么ECMAScript中没有函数重载的概念,声明多个同名的函数,即使传入的参数的个数不一样,该名字也只属于最后一个函数。

函数声明与函数表达式

解析器在向执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行,如:

alert(sum(10 , 20));//30

function sum(num1 , num2){

return num1 + num2;

}

//alert(add(10 , 20));//报错,停止向下解析执行

var add =
function(sum1 , sum2){

return sum1 + sum2;

};

在上面的例子中分别使用函数声明和函数表达式定义了两个函数sum(num1 , num2)和add(sum1 , sum2),同样是函数,但是调用的结果却大相径庭。在sum前调用sum函数,能够正常执行,也就是在sum声明代码行前调用该函数,当前环境中即存在该函数;但是对于add函数,在声明代码行对其进行调用却报错了。

接下来将调用代码调整到add函数声明的后面:

var add =
function(sum1 , sum2){

return sum1 + sum2;

};

alert(add(10 , 20));//30

此时的结果就正常了,从上面的对比中可以得出这样的结论,使用第一种方式声明的函数会在代码解析前被解析到当前环境中,而使用第二种方式声明的函数只有在解析器解析到对应的代码行时才会在当前环境中存在,此时看下面的例子就比较容易理解了:

function sum(num1 , num2){

return
"第一个sum函数";

}

alert(sum(10, 20));//第三个sum函数

var sum =
function(sum1 , sum2){

return
"第二个sum函数";

};

function sum(num1 , num2){

return
"第三个sum函数";

}

alert(sum(10 , 20));//第二个sum函数

上面的例子中声明了3个同名的函数,上面说到,JavaScript中没有重载的概念,函数名属于最后一个声明的函数实例。对上面代码中的第一个alert(),这个结论没有错,但是对于最后的alert(),显然sum这个函数名指向的是第二个函数实例,造成这样的结果是因为第二个函数实例是最后一个被解析的,也就是说,环境中最终的sum变量指向了第二个函数实例。因此,此时就可以将上面的结论修改为:在JavaScript中没有重载的概念,多个重名的函数声明,该函数名属于最后一个被解析的函数实例。

也可以同时使用函数声明和函数表达式,如varsum = function sum(num1 , num2){},与单独使用函数表达式是等价的。不过,这种语法在Safari中会导致错误。

作为值的函数

因为ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。如:

//将一个函数作为参数传递给另一个函数

function add(num1 , num2){

return num1 + num2;

}

function raise(add , num1 , num2){

return add(num1 , num2)+1;

}

alert(raise(add, 10 , 10));//21

在Array类型中,其迭代方法的形式与上例中一致,如:

var numbers = [0,1,2,3];

var everyResult = numbers.every(function(item , index , array){

return (item > 2);

});

alert(everyResult);//false

var someResult = numbers.some(function(item , index , array){

return (item > 2);

});

alert(someResult);//true

将函数作为返回值返回是一种极为有用的技术,例如在数组排序时需要项sort()方法中传入一个比较函数,如果想在传入的函数中指定排序的方式,则可以如下进行操作:

function compare(asc){

alert(asc);

if(asc){

return
function
(value1 , value2){

if(value1 < value2){

return -1;

} else
if
(value1 > value2){

return 1;

}else{

return 0;

}

};

}else{

return
function
(value1 , value2){

if(value1 > value2){

return -1;

} else
if
(value1 < value2){

return 1;

}else{

return 0;

}

};

}

}

var array = [0,5,1,15,10];

array.sort(compare(false));

alert(array);

在compare函数中根据指定的asc属性判断返回降序排列的函数还是升序排列的函数。

函数内部属性

在函数内部,有两个特殊的对象:arguments和this。其中arguments是一个类数组对象,包含着函数中的所有参数。虽然arguments的主要用途是保存函数参数,但这个对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数,看下面的经典的阶乘函数:

function factorial(num){

if(num <=1){

return 1;

}else{

return num*factorial(num-1);

}

}

alert(factorial(5));//120

定义阶乘函数一般都要用到递归算法;如上面的代码所示,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名factorial紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用arguments.callee:

function factorial(num){

if(num <=1){

return 1;

}else{

return num*arguments.callee(num-1);

}

}

alert(factorial(5));//120

这个重写后的factorial()函数的函数体内,没有再引用函数名factorial。这样,无论引用函数时使用的是什么名字,都可以保证正常完成递归调用,例如:

function factorial(num){

if(num <=1){

return 1;

}else{

return num*arguments.callee(num-1);

}

}

var trueFactorial = factorial;

factorial = function(){

return 0;

};

alert(trueFactorial(5));//120

alert(factorial(5));//0

在此,变量trueFactorial获得了factorial的值,实际上是在另一个位置上保存了一个函数的指针。然后,我们又将一个简单地返回0的函数赋值给factorial变量。如果像原来的factorial()那样不使用arguments.callee,调用trueFactorial()就会返回0。可是,在解除了函数体内的代码与函数名的耦合状态之后,trueFactorial()仍然能够正常地计算阶乘;至于factorial(),它现在只是一个返回0的函数。

函数内部的另一个特殊对象是this,其行为与Java和C#中的this大致类似。换句话说,this引用的是函数执行的环境对象——或者也可以说是this值(当在网页的全局作用域中调用函数时this对象引用的就是winsow)。如:

console.log(this);//window

window.hello = "hello world";

alert(this.hello);//hello world

ECMAScript 5 也规范化了另一个函数对象的属性:caller。除了Opera的早期版本不支持,其他浏览器都支持这个ECMAScript3并没有定义的属性。这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null。如:

function outer(){

inner();

}

function inner(){

alert(inner.caller);

}

outer();

上面的的代码会导致警告框中显示outer()函数的源代码。因为outer()调用了inner(),所以inner.caller就指向outer()。为了实现更松散的耦合,也可以通过arguments.callee.caller来访问同样的信息。

function outer(){

inner();

}

function inner(){

alert(arguments.callee.caller);

}

outer();

IE、Firefox、Chrome和Safari的所有版本以及Opera9.6都迟滞caller属性。

当函数在严格模式下运行时,访问arguments.callee会导致错误。ECMAScript5还定义了arguments.caller属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是undefined。定义这个属性是为了分清arguments.caller和函数的caller属性。以上变化否是为了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其它代码了。

严格模式还有一个限制:不能为函数的caller属性赋值,否则会导致错误。

函数属性和方法

ECMAScript中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性length和prototype。其中length属性表示函数希望接收的命名参数的个数。

在ECMAScript核心所定义的全部属性中,最耐人寻味的就要数prototype属性了。对于ECMAScript中的引用类型而言,proptotype是保存它们所有实例方法的真正所在。换句话说,诸如toString()和valueOf()等方法实际上都保存在prototype名下,只不过是通过各自对象的实例访问罢了。在创建自定义引用类型即实现继承时,prototype属性的作用是极为重要的。在ECMAScript5中,prototype属性是不可枚举的,因此使用for-in无法发现。

没个函数都包含两个非继承而来的方法:apply()和call(),这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array的实例,也可以是arguments对象。如:

function sum(num1 , num2){

return num1 + num2;

}

function callSum1(num1 , num2){

return sum.apply(this , arguments);//传入arguments对象

}

function callSum2(num1 , num2){

return sum.apply(this , [num1 , num2]);//传入数组

}

alert(callSum1(10,10));//20

alert(callSum2(10,10));//20

在上面这个例子中,callSum1()在执行sum()函数时传入了this作为this值(因为是在全局作用域中调用的,所以传入的就是window对象)和arguments对象。而callSum2同样也调用了sum()函数,但它传入的则是this和一个参数数组。这两个函数都会正常执行并返回正确的结果。

在严格模式下,为指定环境对象而调用函数,则this只不会转型为window。除非把函数添加到某个对象或者调用apply()或call(),否则this值将是undefined。

call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法而言,第一个参数是this值没有变化,变化的是其余参数都是直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来,如:

function sum(num1 , num2){

return num1 + num2;

}

function callSum1(num1 , num2){

return sum.call(this , num1 , num2);

}

alert(callSum1(10,10));//20

在使用call()方法的情况下,callSum()必须明确地传入每一个参数。结果与使用apply()没有什么不同。至于是使用apply()还是call(),完全取决于你采用哪种给函数传递参数的方式最方便。

事实上,传递参数并非apply()和call()真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。如:

window.color = "red";

var o = {color:"blue"};

function sayColor(){

alert(this.color);

}

sayColor();//red

sayColor.call(this);//red

sayColor.call(window);//red

sayColor.call(o);//blue

这个例子中sayColor()是作为全局函数定义的,而且当在全局作用域中调用它时,它确实会显示”red”——因为this.color的值会转换成对window.color的求值。而sayColor.call(this)和sayColor.call(window),则是两种显式地在全局作用域中调用函数的方式,结果当然都会显示”red”。但是,当运行sayColor.call(o)时,函数的执行环境就不一样了,因为此时函数体内的this对象指向了o,于是结果显示的是”blue”。

使用call()或(apply())来扩充作用域的最大好处就是对象不需要与方法有任何耦合关系。在前面例子的第一个版本vzhong,先将sayColor()函数放到了对象o中,然后再通过o来调用它们的;而在这里重写的例子中,就不需要先前那个多余的步骤了。

ECMAScript5还定义了一个方法bind()。这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。如:

window.color = "red";

var o = {color:"blue"};

function sayColor(){

alert(this.color);

}

var objectSayColor = sayColor.bind(o);

objectSayColor();//blue

在这里,sayColor()调用bind()并传入对象o,创建了objectSayColor()函数。objectSayColor()函数的this等于o,因此即使是在全局作用域中调用这个函数,也会看到”blue”。

支持bind()方法的浏览器有IE9+、Firefox4+、Safari5.1+、Opera12+和Chrome。

每个函数继承的toLocaleString()和toString()方法始终都返回函数的代码。返回代码的格式因浏览器而异。

时间: 2024-08-05 10:09:15

JavaScript基础——引用类型(三)Function类型的相关文章

JavaScript基础(三)

JavaScript基础(三) for循环嵌套 循环嵌套不是什么特殊语法,就是说js中的许多语法结构都可以嵌套使用,for也可以 循环嵌套时,代码执行次数变多,通常使用是在2层左右 while循环 规则: 先进行条件检测,如果为true,执行循环体代码 再检测条件,再为true再执行循环体,以此类推 直到条件为false时循环结束 while (条件) { // 循环体 } for和while的使用场景 for循环适用于次数确定的重复操作(for使用较多) while循环适用于不确定次数的重复操

引用类型之Function类型

Function类型 ECMAScript中最有意思的就是函数了,有意思的根源,在于函数实际上是对象.每个函数都是Function的实例,具有属性和方法.而重要的一点是,函数名,不过是指向函数的指针,不会与某个函数绑定. 1.函数定义 (1)创建函数有函数声明法和函数表达式法.(2)函数名仅仅是指向函数的指针,所以一个函数可能会有多个名字.(3)函数没有重载,后面会覆盖前面.(4)函数声明会最先被解析,而函数表达式则不会. 1 alert(sum(10,10)); 2 function sum(

JavaScript基础——引用类型(二)日期类型Date、正则表达式类型RegExp

Date类型 ECMAScript中的Date类型是在早期Java中的java.util.Date类基础上构建的.为此,Date类型使用自UTC(CoordinatedUniversal Time,国际协调时间)1970年1月1日午夜(零时)开始经过的毫秒数来保存日期.在使用这种数据存储格式的条件下,Date类型保存的日期能够精确到1970年1月1日之前或之后的285 616年. 要创建一个日期对象,使用new 操作符和Date构造函数即可,如: var now = new Date(); 在调

JavaScript基础——引用类型(一)Object类型、Array类型

引用类型简介 引用类型的值(对象)是引用类型的一个实例.在ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起.它也常被称为类,但这种称呼并不妥当(虽然不妥当,在别人提起的时候,就是指ECMAScript中的引用类型,在一次面试的时候,有人就和面试官争论说"JS中没有'类'这个概念,结果可想而知--").尽管ECMAScript从技术上讲是一门面向对象的语言,但它不举杯传统的面向对象语言所支持的类和接口等基本结构.引用类型有时候也被称为对象定义,因为它们描述的是一

JavaScript基础——引用类型(四)基本包装类型(Boolean、Number、String)、单体内置对象(Global、Math)

基本包装类型 为了便于操作基本类型值,ECMAScript还提供了3个特殊的引用类型:Boolean.Number和String.这些类型与其它引用类型相似,但同时也具有与各自的基本类型相应的特殊行为.实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据.如: var s1 = "some text"; var s2 = s1.substring(2); 这个例子中的变量s1包含一个字符串,字符串当然是基本类型值.而下

javascript基础(第三天)

开胃菜, var a = [2,3,1,3,8,4,3]; 如何快速找到8的索引位置? 开始的一句话: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference  这个星球最好的js文档之一 ie9+ (2012年才基本普及,跟现在的es6差不多) in操作符的使用, 查找属性是否存在当前对象(包括原型也算) var a = {b:'bbb',c:'ccc'}; function b(){ this.d='ddd' };

在JavaScript中引用类型和值类型的区别

一.存储方式不一样 基本数据类型 变量存储的是简单的数据段,存储的是具体的值,是轻量级的数据存储方式 引用类型 引用类型的值,可以由多个值构成的对象,引用类型的变量存储的是对象引用地址.引用类型是重量的数据存储方式,分配在堆内存,频繁创建对象有损性能. 引用类型(N多) Object.Array.Date.Function.-- 二.动态属性不一样 1.引用类型能动态添加属性 var person=new Object(); person.name="jack"; person.age

JavaScript基础学习(三)&mdash;数组

一.数组简介     JavaScript数组的每一项都可以保存任何类型的数据,也就是说数组的第一个位置保存字符串,第二个位置可以保存数值,第三个位置可以保存对象,而且数组的大小是可以动态调整的,即可以随着数据的添加而自动增长以扩容纳新增数据.   二.数组的操作 1.创建 //创建一个空数组 var arr = []; var arr2 = [1,true,new Date()]; arr2.length = 2; alert(arr2); //true   2.pop和push方法     

JavaScript 基础(三)

数据类型 1.字符串 2.数值型 3.布尔型 4.对象 5.数组 为了更规范,使自己将来在工作时更容易与方便维护,采用一些数据类型的名命习惯 类型 前缀 例子 数组 a aArray 布尔值 b bMale 浮点 f fTax 函数 fn fnSwap 整型 i iAge 对象 o oCar 正则 re RePattern 字符串 s sUniversity 一.字符串 String var sMyString = "hello world"; 字符串可以是引号中的任意文本.可使用单引