JavaScript之function类型(引用类型)

函数的创建方法

函数也是对象,每个函数是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); // 20




function 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); //1


console.log(sum.length); //2


console.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)); //30

console.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替换sub
var 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); //21
cat.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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值