函数的创建方法
函数也是对象,每个函数是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();
函数属性和方法
每个函数都包含两个特殊的对象: arguments和 this。
同理,每个函数都包含两个属性: length和 prototype。
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。