JavaScript之function类型(引用类型)

来源:互联网 发布:mac怎么卸载百度云盘 编辑:程序博客网 时间:2024/05/14 07:10

函数的创建方法

函数也是对象,每个函数是Function类的一个实例,函数是对象,因此函数名就是指向函数对象的指针,函数的定义常见的有两种:

1、使用关键字function声明,也就是 函数声明

function sum (num1, num2) {    return num1 + num2;}


2、将匿名函数赋值给一个变量,也就是 函数表达式

var sum = function (num1, num2) {return num1 + num2}; //要注意语句后面的分号



由于函数名是指向函数的指针,与包含对象指针的其它变量没什么大的区别,也就是说一个函数可以有多个名字。

function sum (num1, num2) {    return num1 + num2;} var sum1 = sum; //就是这是访问指针sum = null; //重置为空。console.log(sum1(2,3)); //输出5


注意:使用 不带圆括号 的函数名是访问 函数对象的指针,使用 带圆括号 的函数名是 调用该函数。


没有重载(深入理解)

重载:相同的函数名,不同的签名(参数的类型和数量)。Javascript中的函数没有重载,因为函数的参数是由零个或多个值组成的数组,


例如:

function sum1 (num1, num2) {    return num1 * num2;}function sum1 (num1, num2) {    return num + num2;}console.log(sum1(2,3));//返回6

两个函数相同的函数名,显然,后面的函数覆盖了前面的函数。


由下面的例子更直观些:

var sum1 = function (num1, num2) {    return num1 * num2;}var sum1 = function (num1, num2) {    return num + num2;}console.log(sum1(2,3));//返回6

就如:

var a = 100;a = "JavaScript";console.log(a); //返回JavaScript


“JavaScript”覆盖了100。


函数声明与函数表达式



解析器在向执行环境中加载数据时,解析器会率先读取函数声明,使其在执行任何代码之前可访问。而对于函数表达式,则必须等到解析器执行到表达式所在代码行,才真正被解析。

JavaScript有个提升变量声明的作用。

alert(sum(10, 10)); //解析器在执行这段之前,会率先读取函数声明,提升函数声明。function sum (num1, num2){    return num1 + num2;}


以上代码执行时,不会出错,在开始执行前,解析器已经将函数声明提前将函数声明添加到可执行环境中,JavaScript引擎在第一次执行代码前,就将函数声明提前到源代码的顶部,这样即使函数声明在调用它的代码之后,引擎也能将声明提前到调用代码之前,这样就不会出错了。


如果是以下这种情况就会出错了:

alet(sum(10, 10));var sum = function(num1, num2) {return num1 + num2};

执行这段代码时会出错,因为函数在 初始化语句中不在 声明语句中。在执行到函数所在语句(调用函数语句)前,sum中没有保存对函数的引用,即没有声明函数,不能提前函数声明,所以会出错。其实在执行到调用函数这句时应付报错,后面的语句也不会执行了。


作为值(参数)的函数

函数不仅可以作为参数传递给另一个函数,同时也可以作为另一个函数的结果返回。


作为参数的函数:

function callSomeFunction (someFunction, someArgument) {    return someFunction(someArgument); //无论第一个参数传递进来的是什么函数,都会返回执行一个参数后的结果。}function sum10 (sum) {    return sum + 10; }var result1 = callSomeFunction(sum10, 10); //sum10作为callSomeFunction的结果返回。console.log(result1); // 20function getName (name) {    return "hellow" + name;}var result2 = callSomeFunction(getName, "Javascript");console.log(result2); //hellow,Javascript


无论第一个参数传递进来的是什么函数,都会返回 执行一个参数后 的结果。要访问 函数的指针而不是函数执行后的结果,就要使用不带圆括号的函数名,所以callSomeFunction传递进来的参数是sum10和getName,而不是它们执行后的结果。


作为另一个函数的结果返回:

function bj (personName) {    return function(obj1, obj2) {        var a = obj1[personName];        var b = obj2[personName];              //升序比较函数       if (a < b) {           return -1;        } else if (a > b) {            return 1;        } else {            return 0;        }    };    }var person = [{name : "Nice", age : 28}, {name : "Zi", age : 30}];person.sort(bj("name"));console.log(person[0].name); //Nice

我们创建一个包含两个对象的数组person,其中,每个对象包含name和age两个属性,在默认情况下,sort()方法会调用每个对象(在这里相当于数组的每个项)的toString()方法转换成字符串来确定它们的次序;但得到的结果往往不如意,所以我们自己创建了一个比较函数bj()来排列次序,而结果排列在第一的name是"Nice",因为"Nice"与"Zi"这两个字符串比较大小number()会将它们分别转换成字符编码,"Nice"的首字母比"Zi"的首字母小,所以排列在前面。排列在第一的age是"28"。


函数内部属性

在函数内部,有两个特殊的对象属性:arguments和this。

argument对象


其中,argument我们不陌生,arguments是类数组对象,可以保存传递到函数的所有参数,可以通过arguments的length属性得到参数的数量,通过arguments[0]得到保存到类数组中的第一个参数。argument对象除了有length属性外,还有个callee属性,该属性是一个指针,指向拥有arguments对象的函数


经典的乘阶函数:

function chengjie (num) {    if (num <= 1) {        return 1;    } else {        return num * chengjie(num - 1);    }}

这是一个递归函数,在函数名不变的情况下,这样定义没问题,但问题是这样定义的话就与函名"chengjie"紧紧耦合在一起,在函数内部也有函数名,这样的话,如果我们给其换个函数名的话,也要改变函数内部的函数名。用argument对象的callee属性就可以避免这种情况:

function chengjie (num) {    if (num <= 1) {        return 1;    } else {        return num * argument.callee(num - 1);    }}

这函数与上面的函数没有什么区别,callee属性是一个对象指针,它指向拥有argument对象的函数,即callee就是指向chengjie()这个函数的,这样就避免了与函数名耦合的情况。并且,无论使用什么函数名都能保证递归调用:

var chengjie1 = chengjie; //使用不带圆括号的函数名是访问的引用。console.log(chengjie1(6)); //返回6*5*4*3*2*1=720



深入理解this对象

函数内部还有另一个对象this,this对象所引用的是 函数执行的环境对象--this值。在全局作用域中调用函数时,this对象引用的就是window对象。


window.color = "blue"; //此color为window对象var o = {color : "red"};functin sayColor () { //全局作用域下定义的函数    alert(this.color);} sayColor(); //返回blue,此时this对象引用的是window对象,o.sayColor = sayColor; //将函数引用赋值给o对象并调用o.sayColor,将函数的引用赋值给了o.sayColor,此时this对象引用的是o对象。o.sayColor(); //返回red


"o.syaColor = sayColor;"为对象动态添加属性。

sayColor()是在全局作用域中定义的,它引用了this对象,在全局作用域中调用sayColor()函数时,this对象引用的是window对象,也就是说,对this.color求值就是对 window.color求值即"blue"。当函数sayColor赋值给o对象并调用o.sayColor()时,此时函数sayColor()作为o对象的方法使用, this对象引用的将不是window而是o对象了,所以对this.color求值就是对o.color求值即red。


通过以上我们可以将this对象简单总结为:

this对象引用的是 包含它的函数 作为某个对象的方法被调用时 所属的那个对象也就是说,当包含this对象的函数被某个对象作为方法调用时,这个函数就属这个对象了,那么this引用的就是这个对象。


如同以上的例子一样,在全局作用域中(window对象)调用包含this的函数(sayColor())时,也就是sayColor()作为window对象的方法时,this引用的就是window对象,对this.color的求值就是window.color的求值即"blue"。

而将sayColor()赋值给o对象并调用o.sayColor()时,此时,包含this的函数(sayColor())作为o对象的方法被调用时,this引用的就是o对象了,对this.color的求值就是对o.color的求值即"red"。


我们可以再举例:

var sound = "Roar";function myBeast () {    alert(this); //此时this引用的是window对象,弹出"object window"。    this.style.color = "red"; //此时的color属性是属于window对象,但window对象没有color属性,会报错。    alert(sound);}


弹出的结果为"object window",myBeast()函数是在全局作用域下定义的,且引用了this对象,在全局作用域下调用了myBeast()函数,即myBeast()作为window对象的方法(没有注明的函数是在window对象下的)被调用时,myBeast()所属window对象,那么this对象引用的就是window对象,所以此时的this对象就是window对象,即"object window"。


其实在执行到"this.style.color = 'red'"时就会报错的,因为此时this指向的是window对象,window对象是没有color属性的。


我们知道,this的环境随着函数被赋值给不同的对象而改变的,看以下代码:

var sound = "Roar";function myBeast () {    alert(this); //object HTMLPargraphElement    this.style.color = "red";     alert(sound);}widow.onload = function() {    document.getElementById('a').onclick = myBeast; //此时将myBeast函数的引用赋值给了onlick。那么this对象就引用p对象,即this的执行环境为p对象内的环境。};

html页面代码是"<p id="a">javaScript</p>",此时运行会弹出"object HTMLPargraphElement",表示此时this对象指向的是HTML元素节点p,分析:myBeast()函数引用被赋值给了onclick,onclick包含了this对象, p元素又调用了onclick方法,此时,this引用的是 包含this的函数(onclick)作为某个对象(p)的方法 被调用时所属的那个对象(p)。也就是说此时this引用的是p对象,对this.style.color的求值就是对p.style.color的求值,p对象有color属性的。

我们要记住一点:函数的名字仅仅是一个包含指针的对象而已,即使在不同的环境中,sayColor()函数与o.sayColor()指向的是同一个函数myBeast()与p.onclick指向的也是同一个函数。


caller属性



caller属性保存着 调用当前函数(调用caller属性的函数) 的函数的引用,也就是说指向该函数。通俗点说a函数调用了b函数,b.caller中保存着a函数的引用,即b.caller指向a函数。 在全局作用域中调用当前函数(在javaScript程序顶层调用),它的值为null。

function outer(){    inner(); //outer()函数调用了inner()函数。}function inner() {    alert(inner.caller);//因此caller保存了outer()函数的引用。}outer();

inner.caller指向outer(),弹出outer()的源代码。


以上代码可以改为:

function outer() {    inner();}function inner() {    alert(argument.callee.caller); //相当于inner.caller}outer();



函数属性和方法


每个函数都包含两个特殊的对象:argumentsthis

同理,每个函数都包含两个属性:lengthprototype

length属性


length属性表示函数希望接收的 命名参数(形参)的数量。与arguments.length的区别是:arguments.length表示的是传递给函数的参数(实参)的数量,可以是多个。

function sayName (name) {    alert(name); //一个命名参数。}function sum (num1, num2) {    return num1 + num2; //两个命名参数}function sayHi () {    alert("Hi"); //0个命名参数}console.log(sayName.length); //1console.log(sum.length); //2console.log(sayHi.length); //0


prototype属性

prototype保存了引用类型的所有实例方法。如继承类方法toString()和valueOf()方法都是保存在prototype中的, 在ECMAScript5中,protoytpe是不可枚举的,所以for-in是遍历不到的。

apply()方法和call()方法

apply()和call()是非继承类方法。

apply()方法

应用某一对象的一个属性和方法,用 另一个对象引用 替换 当前对象引用,即当前对象直接调用另一对象的所有属性和方法。apply()方法有两个参数:第一参数是在其中运行函数的作用域第二个是参数数组。第二个参数可以是Array的实例,也可以是arguments类。

function sum (num1, num2) {    return num1 + num2;}function callSum1 (num1, num2) {    return sum.apply(this, arguments); //第二个参数是arguments类,this表示作用域,在window对象下。}function callSum2 (num1, num2) {    return sum.apply(this, [num1, num2]); //第二个参数是Array的实例。}console.log(callSum1(10, 20)); //30console.log(callSum2(5, 5));  //10


这个例子中的意思就是,用sum来替换callSum1和callSum2,注意,是引用的替换,即sum.apply(this. [num1, num2]) == sum(num1, num2),其中,this对象引用的就是callSum1或callSum2,用sum替换了callSum1引用。


再举一例:

function add (a,b) {    return a + b;}function sub (a, b) {    return a - b;}//用sum替换subvar sum = add.apply(sub, [3,1]);console.log(sum); //4

例子中,定义了两个Function实例add()和sub(),一个求和,一个差。其中“add.apply(sum, [3, 1])”就相当于“add(3, 1)”,用add引用替换了sub的引用,把add的方法放在sub上执行,用add对象(另一对象)替换sub对象(当前对象)。


function Animal () {    this.name = "tom";    this.age = "21";       this.sayName = function () {        alert(this.name);    };}function Cat () {    this.name = "bob";}var person = new Animal();var cat = new Cat();//通过apply()或call方法,将原来属性Animal的方法sayName()来交给Cal使用。person.apply(cat, []); //bob//person.call(cat, ",");

上例的意思就是,将animal的方法sayName()方法放在cat上执行,原本cat是没有sayName()方法的,现在把sayName()方法放在cal上执行,所以输出为“bob”。


实现继承

function Animal () {    this.name = "tom";    this.age = "21";       this.sayName = function () {        alert(this.name);    };}function Cat () {    //Animal对象替换了this对象,那么Cat就有了Animal的所有属性和方法,就相当于将Animal构造函数替换了Cat构造函数    Animal.apply(this, [name]);}var cat = new Cat("bob"); //相当于 var cat = new Animal();console.log(cat.age); //21cat.sayName(); //tom

Animal.apply(this)的意思就是用Animal对象替换this对象,那么Cat就有了Animal对象的 所有属性和方法。Cat对象就直接调用Animal对象的属性和方法了。

也就是说,即使“var cat = new Cat("bob")”,"cat.sayName()"调用的也是Animal对象的属性和方法,输出"tom"。


注:使用两个或多个apply()就可以实现多重继承了。


call()方法

call方法与apply()作用是一样的,唯一的区别是call()方法的参数需要一一列举出来。如以上的代码:

function sum (num1, num2) {    renturn num1 + num2;}function callSum2 (num1, num2) {    return sum.call(this, num1, num2); //数组实例列举出来。}console.log(callSum2(30, 40)); //70


apply()和call()真正的用处


apply()和call()并非用在传递参数上,而是用在扩充作用域上

window.color = "red";var o = {color : "blue"};function sayColor () {    alert(this.color);}sayColor(); //red this引用window对象sayColor.call(this); //red this引用的是window对象 sayColor对象替换window对象sayColor.call(window); //red 此时的作用域为全局作用域,在windos对象下。sayColor.call(o); //blue 此时this引用的是o对象,this.color就是o.color

sayColor()是在全局作用域下定义的,当在全局作用域下调用它时,this.color也就是window.color为"red",sayColor.call(this)和sayColor.call(window)是显式地在全局作用域中调用函数,也是返回"red"。而sayColor.call(o),此时this对象引用的是o对象,指向的是o对象,于是结果是blue。

apply()和call()扩充作用域的好处:对象不需要和方法有耦合的关系,前面的例子中,我们先将sayColor()赋值给了o对象,再通过o对象调用sayColor()方法,这样对象与方法就有耦合关系,用call()和apply()就不用多余的步骤了。

"o.sayColor = sayColor; o.sayColor();"就相当于"sayColor.call(o)"。


bind()方法

bind()方法会先创建一个函数,this值 会与 传递给bind()函数的值 绑定。
window.color = "red";var o = {color : "blue"};function sayColor () {    alert(this.color);}var sayColor1 = sayColor.bind(o); //将this值与传递给bind()的值绑定,即this值就是o。sayColor1();

sayColor调用bind()函数并传入对象o,this值等于o。this引用了o对象
,即使在全局作用域下,也能得到blue。

1 0
原创粉丝点击