当咱们这么定义函数的时候,函数内容会被编译(但不会当即执行,除非咱们去调用它)。并且,也许你不知道,当这个函数建立的时候有一个同名的对象也被建立。就咱们的例子来讲,咱们如今有一个对象叫作“add”(要更深刻了解,看底下函数:对象节。)
这个代码和前一个例子作了一样的事情。也许语法看起来比较奇怪,但它应该更能让你感受到函数是一个对象,并且咱们只是为这个对指派了一个名称。能够把它看作和 var myVar=[1,2,3]同样的语句。以这种方式声明的函数内容也同样会被编译。
当咱们指派一个这样的函数的时候,咱们并不必定要求必须是匿名函数。在这里,我做了和ExampleD2同样的事情,但我加了函数名“theAdd”,并且我能够经过调用函数名或者是那个变量来引用函数。
我在这里有两个参数叫作a和b,而函数体返回a和b的和。请注意new Function(...)使用了大写F,而不是小写f。 这就告诉javascript,咱们将要建立一个类型是Function的对象。 还要注意到,参数名和函数体都是做为字符串而被传递。咱们能够为所欲为的增长参数,javascript知道函数体会是右括号前的最后一个字符串(若是没有参数,你可以只写函数体)。你不必将全部东西都写在一行里(使用\或者使用字符串链接符+来分隔长代码)。\标记告诉JavaScript在下一行查找字符串的其他部分。例子以下:
CODE:
function createMyFunction(myOperator)
{
return new Function("a", "b", "return a" + myOperator + "b;");
}
var add=createMyFunction("+"); // 建立函数 "add"
var subtract=createMyFunction("-"); // 建立函数 "subtract"
var multiply=createMyFunction("*"); // 建立函数 "multiply"
// test the functions
alert("加的结果="+add(10,2)); // 结果是 12
alert("减的结果="+subtract(10,2)); // 结果是 8
alert("乘的结果="+multiply(10,2)); // 结果是 20
alert(add);
这个有趣的例子建立了三个不一样的function,经过实时传递不一样的参数来建立一个新Function。由于编译器无法知道最终代码会是什么样子的,因此new Function(...)的内容不会被编译。那这有什么好处呢?嗯,举个例子,若是你须要用户可以建立他们本身的函数的时候这个功能也许颇有用,好比在游戏里。咱们也许须要容许用户添加“行为”给一个“player”。可是,再说一次,通常状况下,咱们应该避免使用这种形式,除非有一个特殊的目的。
函数:对象
函数是javascript中的一种特殊形式的对象。它是第一个[b〕类数据类型(class data type)。这意味着咱们可以给它增长属性。这里有一些须要注意的有趣观点:
对象的建立
就像刚才说起的,当咱们定义一个函数时,javascript实际上在后台为你建立了一个对象。这个对象的名称就是函数名自己。这个对象的类型是function。在下面的例子,咱们也许不会意识到这一点,但咱们实际上已经建立了一个对象:它叫作Ball。
Example 1
CODE:
function Ball() // 也许看起来有点奇怪,可是这个声明
{ // 建立了一个叫作Ball的对象
i=1;
}
alert(typeof Ball); // 结果 "function"
咱们甚至能将这个对象的内容打印出来并且它会输出这个函数的实际代码,Example2: 点击 alert(Ball);来看看Ball的内容。
属性的添加
咱们可以添加给Object添加属性,包括对象function。由于定义一个函数的实质是建立一个对象。咱们可以“暗地里”给函数添加属性。好比,咱们这里定义了函数Ball,并添加属性callsign。
CODE:
function Ball() // 也许看起来有点奇怪,可是这个声明
{ // 建立了一个叫作Ball的对象,并且你可以
} // 引用它或者象下面那样给它增长属性
Ball.callsign="The Ball"; // 给Ball增长属性
alert(Ball.callsign); // 输出 "The Ball"
指针
由于function是一个对象,咱们可以为一个function分配一个指针。以下例,变量ptr指向了对象myFunction。
CODE:
function myFunction(message)
{
alert(message);
}
var ptr=myFunction; // ptr指向了myFunction
ptr("hello"); // 这句会执行myFunction:输出"hello"
咱们可以运行这个函数,就好像这个函数名已经被指针名代替了同样。因此在上面,这行ptr("hello"); 和myFunction("hello");的意义是同样的。
指向函数的指针在面向对象编程中至关有用。例如:当咱们有多个对象指向同一个函数的时候(以下):
Example 4A
CODE:
function sayName(name)
{
alert(name);
}
var object1=new Object(); // 建立三个对象
var object2=new Object();
var object3=new Object();
object1.sayMyName=sayName; // 将这个函数指派给全部对象
object2.sayMyName=sayName;
object3.sayMyName=sayName;
object1.sayMyName("object1"); // 输出 "object1"
object2.sayMyName("object2"); // 输出 "object2"
object3.sayMyName("object3"); // 输出 "object3"
由于只有指针被保存(而不是函数自己),当咱们改变函数对象自身的时候,全部指向那个函数的指针都会发生变化。咱们可以在底下看到:
Example 5:
CODE:
function myFunction()
{
alert(myFunction.message);
}
myFunction.message="old";
var ptr1=myFunction; // ptr1 指向 myFunction
var ptr2=myFunction; // ptr2 也指向 myFunction
ptr1(); // 输出 "old"
ptr2(); // 输出 "old"
myFunction.message="new";
ptr1(); // 输出 "new"
ptr2(); // 输出 "new"
指针的指向
咱们可以在一个函数建立以后从新分配它,可是咱们须要指向函数对象自己,而不是指向它的指针。在下例中,我将改变myfunction()的内容。
Example 6:
CODE:
function myFunction()
{
alert("Old");
}
myFunction(); // 输出 "Old"
myFunction=function()
{
alert("New");
};
myFunction(); // 输出 "New"
旧函数哪里去了??被抛弃了。
若是咱们须要保留它,咱们能够在改变它以前给它分配一个指针。
Example 6A:
CODE:
function myFunction()
{
alert("Old");
}
var savedFuncion=myFunction;
myFunction=function()
{
alert("New");
};
myFunction(); // 输出 "New"
savedFuncion(); // 输出 "Old"
不过要当心,象下面这样的例子并不会有做用,由于是建立了另外一个叫作myFunctionPtr的函数而不是修改它。
Example 6B:
CODE:
function myFunction()
{
alert("Old");
}
var savedFunc=myFunction;
savedFunc=function()
{
alert("New");
};
myFunction(); // 输出 "Old"
savedFunc(); // 输出 "New"
内嵌函数
咱们还可以在一个函数中嵌套一个函数。下例,我有一个叫作getHalfOf的函数,而在它里面,我有另外一个叫作calculate的函数。
Example 7
CODE:
function getHalfOf(num1, num2, num3)
{
function calculate(number)
{
return number/2;
}
var result="";
result+=calculate(num1)+" ";
result+=calculate(num2)+" ";
result+=calculate(num3);
}
var resultString=getHalfOf(10,20,30);
alert(resultString); // 输出 "5 10 15"
你只能在内部调用嵌套的函数。就是说,你不能这么调用:getHalfOf.calculate(10),由于calculate只有当外部函数(getHalfOf())在运行的时候才会存在。这和咱们前面的讨论一致(函数会被编译,但只有当你去调用它的时候才会执行)。
调用哪一个函数?
你也许正在想命名冲突的问题。好比,下面哪个叫作calculate的函数会被调用?
Example 8
CODE:
function calculate(number)
{
return number/3;
}
function getHalfOf(num1, num2, num3)
{
function calculate(number)
{
return number/2;
}
var result="";
result+=calculate(num1)+" ";
result+=calculate(num2)+" ";
result+=calculate(num3);
}
var resultString=getHalfOf(10,20,30);
alert(resultString); // 输出 "5 10 15"
在这个例子中,编译器会首先搜索局部内存地址,因此它会使用内嵌的calculate函数。若是咱们删除了这个内嵌(局部)的calculate函数,这个代码会使用全局的calculate函数。
函数:数据类型及构造函数
让咱们来看看函数的另外一个特殊功能--这让它和其它对象类型大相径庭。一个函数可以用来做为一个数据类型的蓝图。这个特性一般被用在面向对象编程中来模拟用户自定义数据类型(user defined data type)。使用用户自定义数据类型建立的对象一般被成为用户自定义对象(user defined object)。
数据类型
在定义了一个函数以后,咱们也同时建立了一个新的数据类型。这个数据类型可以用来建立一个新对象。下例,我建立了一个叫作Ball的新数据类型。
Example DT1
CODE:
function Ball()
{
}
var ball0=new Ball(); // ball0 如今指向一个新对象
alert(ball0); // 输出 "Object",由于 ball0 如今是一个对象
这样看来,ball0=new Ball()做了什么?new关键字建立了一个类型是Object的新对象(叫作ball0)。而后它会执行Ball(),并将这个引用传给ball0(用于调用对象)。下面,你会看到这条消息:“creating new Ball”,若是Ball()实际上被运行的话。
Example DT2
CODE:
function Ball(message)
{
alert(message);
}
var ball0=new Ball("creating new Ball"); // 建立对象并输出消息
ball0.name="ball-0"; // ball0如今有一个属性:name
alert(ball0.name); // 输出 "ball-0"
咱们能够把上面这段代码的第6行看作是底下的代码6-8行的一个简写:
CODE:
function Ball(message)
{
alert(message);
}
var ball0=new Object();
ball0.construct=Ball;
ball0.construct("creating new ball"); // 执行 ball0.Ball("creating..");
ball0.name="ball-0";
alert(ball0.name);
这行代码ball0.construct=Ball和Example 4中的ptr=myFunction语法一致。
若是你仍是不明白这行的含义那就回过头再复习一下Example 4。注意:你也许考虑直接运行ball0.Ball("..."),可是它不会起做用的,由于ball0并无一个叫作Ball("...")的属性,而且它也不知道你究竟想做些什么。
添加属性
当咱们象上面那样使用关键字new建立一个对象的时候,一个新的Object被建立了。咱们能够在建立以后给这个对象添加属性(就好像我在上面那样添加属性name。而接下来的问题就是若是咱们建立了这个对象的另一个实例,咱们得象下面那样再次给这个新对象添加这个属性。)
Example DT3 (creates 3 ball objects)
CODE:
function Ball()
{
}
var ball0=new Ball(); // ball0 如今指向了类型Ball的一个新实例
ball0.name="ball-0"; // ball0 如今有一个属性"name"
var ball1=new Ball();
ball1.name="ball-1";
var ball2=new Ball();
alert(ball0.name); // 输出 "ball-0"
alert(ball1.name); // 输出 "ball-1"
alert(ball2.name); // 哦,我忘记给ball2添加“name”了!
我忘记给ball2添加属性name了,若是在正式的程序中这也许会引起问题。有什么好办法能够自动增长属性呢?嗯,有一个:使用this关键字。this这个词在function中有特别的意义。它指向了调用函数的那个对象。让咱们看看下面的另外一个示例,这时候咱们在构造函数中添加上这些属性:
Example DT4
CODE:
function Ball(message, specifiedName)
{
alert(message);
this.name=specifiedName;
}
var ball0=new Ball("creating new Ball", "Soccer Ball");
alert(ball0.name); // prints "Soccer Ball"
请记住:是new关键字最终使得构造函数被执行。在这个例子中,它将会运行Ball("creating new Ball", "Soccer Ball");而关键字this将指向ball0。
所以,这行:this.name=specifiedName变成了ball0.name="Soccer Ball"。
它主要是说:给ball0添加属性name,属性值是Soccer Ball。
咱们如今只是添加了一个name属性给ball0,看起来和上一个例子中所作的很象,但倒是一个更好更具扩展性的方法。如今,咱们能够为所欲为的建立许多带有属性的ball而无需咱们手动添加它们。并且,人们也但愿建立的Ball对象可以清晰的看懂它的构造函数而且可以轻松找出Ball的全部属性。让咱们添加更多属性到Ball里。
Example DT5
CODE:
function Ball(color, specifiedName, owner, weight)
{
this.name=specifiedName;
this.color=color;
this.owner=owner;
this.weight=weigth;
}
var ball0=new Ball("black/white", "Soccer Ball", "John", 20);
var ball1=new Ball("gray", "Bowling Ball", "John", 30);
var ball2=new Ball("yellow", "Golf Ball", "John", 55);
var balloon=new Ball("red", "Balloon", "Pete", 10);
alert(ball0.name); // 输出 "Soccer Ball"
alert(balloon.name); // 输出 "Balloon"
alert(ball2.weight); // 输出 "55"
嘿!使用面向对象术语,你可以说Ball是一个拥有以下属性的对象类型:name, color, owner, weight。
将对象赋给属性
咱们并没被限制只能添加形如字符串或者数字之类的简单数据类型做为属性。咱们也可以将对象赋给属性。下面,supervisor是Employee的一个属性.
Example DT6
CODE:
function Employee(name, salary, mySupervisor)
{
this.name=name;
this.salary=salary;
this.supervisor=mySupervisor;
}
var boss=new Employee("John", 200);
var manager=new Employee("Joan", 50, boss);
var teamLeader=new Employee("Rose", 50, boss);
alert(manager.supervisor.name+" is the supervisor of "+manager.name);
alert(manager.name+"\'s supervisor is "+manager.supervisor.name);
会输出什么呢?
就像你在上面这个例子中看到的那样,manager和teamLeader都有一个supervisor属性,而这个属性是类型Employee的一个对象。
将函数做为属性
任何类型的对象均可以做为一个属性,回忆一下前面的Example 4(不是Example DT4),函数也是一个对象。因此你可让一个函数做为一个对象的一个属性。下面,我将添加两个函数getSalary和addSalary。
Example DT7
CODE:
function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}
var boss=new Employee("John", 200000);
boss.addSalary(10000); // boss 长了 10K 工资……为何老板工资能够长这么多:'(
alert(boss.getSalary()); // 输出 210K……为何默认工资也那么高……:'(
addSalary和getSalary演示了几种将函数赋给属性的不一样方法。若是你记得咱们最开始的讨论;我讨论了三种声明函数的不一样方式。全部那些在这里都是适用的,可是上面展现的两个最经常使用。
让咱们看看有什么不一样。下面,注意一下9-12行的代码。当这部分代码执行的时候,函数getSalary被声明。如前面数次提到的,一个函数声明的结果是一个对象被建立。因此这时候boss被建立(接下来的第19行),而boss里有一个getSalary属性。
CODE:
function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}
var boss=new Employee("John", 200000);
var boss2=new Employee("Joan", 200000);
var boss3=new Employee("Kim", 200000);
当你建立这个对象的更多实例时(boss2和boss3),每个实例都有一份getSalary代码的单独拷贝;而与此相反,addSalary则指向了同一个地方(即addSalaryFunction)。
看看下面的代码来理解一下上面所描述的内容。
Example DT8
CODE:
function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}
var boss1=new Employee("John", 200000);
var boss2=new Employee("Joan", 200000);
// 给getSalary函数对象添加属性
boss1.getSalary.owner="boss1";
boss2.getSalary.owner="boss2";
alert(boss1.getSalary.owner); // 输出 "boss1"
alert(boss2.getSalary.owner); // 输出 "boss2"
// 若是两个对象指向同一个函数对象,那么
// 上面两个输出都应该是“boss2”。
// 给addSalary函数对象添加属性
boss1.addSalary.owner="boss1";
boss1.addSalary.owner="boss2";
alert(boss1.addSalary.owner); // 输出 "boss2"
alert(boss2.addSalary.owner); // 输出 "boss2"
// 由于两个对象都指向同一个函数,(子乌注:原文写are not pointing to the same function,疑为笔误)
// 当修改其中一个的时候,会影响全部的实例(因此两个都输出“boss2”).
也许不是重要的事情,但这里有一些关于运行相似上面的getSalary的内嵌函数的结论: 1) 须要更多的存储空间来存储对象(由于每个对象实例都会有它本身的getSalary代码拷贝);2) javascript须要更多时间来构造这个对象。
让咱们从新写这个示例来让它更有效率些。
Example DT9
CODE:
function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=getSalaryFunction;
}
function getSalaryFunction()
{
return this.salary;
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}
看这儿,两个函数都指向同一个地方,这将会节约空间和缩短构造时间(特别是当你有一大堆内嵌函数在一个构造函数的时候)。这里有另一个函数的功能可以来提高这个设计,它叫作prototype,而咱们将在下一节讨论它。
函数:原型
每个构造函数都有一个属性叫作原型(prototype,下面都再也不翻译,使用其原文)。这个属性很是有用:为一个特定类声明通用的变量或者函数。
prototype的定义
你不须要显式地声明一个prototype属性,由于在每个构造函数中都有它的存在。你能够看看下面的例子:
Example PT1
CODE:
function Test()
{
}
alert(Test.prototype); // 输出 "Object"
给prototype添加属性
就如你在上面所看到的,prototype是一个对象,所以,你可以给它添加属性。你添加给prototype的属性将会成为使用这个构造函数建立的对象的通用属性。
例如,我下面有一个数据类型Fish,我想让全部的鱼都有这些属性:livesIn="water"和price=20;为了实现这个,我能够给构造函数Fish的prototype添加那些属性。
Example PT2
CODE:
function Fish(name, color)
{
this.name=name;
this.color=color;
}
Fish.prototype.livesIn="water";
Fish.prototype.price=20;
接下来让咱们做几条鱼:
CODE:
var fish1=new Fish("mackarel", "gray");
var fish2=new Fish("goldfish", "orange");
var fish3=new Fish("salmon", "white");
再来看看鱼都有哪些属性:
CODE:
for (int i=1; i<=3; i++)
{
var fish=eval("fish"+i); // 我只是取得指向这条鱼的指针
alert(fish.name+","+fish.color+","+fish.livesIn+","+fish.price);
}
输出应该是:
CODE:
"mackarel, gray, water, 20"
"goldfish, orange, water, 20"
"salmon, white water, 20"
你看到全部的鱼都有属性livesIn和price,咱们甚至都没有为每一条不一样的鱼特别声明这些属性。这时由于当一个对象被建立时,这个构造函数将会把它的属性prototype赋给新对象的内部属性__proto__。这个__proto__被这个对象用来查找它的属性。
你也能够经过prototype来给全部对象添加共用的函数。这有一个好处:你不须要每次在构造一个对象的时候建立并初始化这个函数。为了解释这一点,让咱们从新来看Example DT9并使用prototype来重写它:
用prototype给对象添加函数
Example PT3
CODE:
function Employee(name, salary)
{
this.name=name;
this.salary=salary;
}
Employee.prototype.getSalary=function getSalaryFunction()
{
return this.salary;
}
Employee.prototype.addSalary=function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}
咱们能够象一般那样建立对象:
CODE:
var boss1=new Employee("Joan", 200000);
var boss2=new Employee("Kim", 100000);
var boss3=new Employee("Sam", 150000);
并验证它:
CODE:
alert(boss1.getSalary()); // 输出 200000
alert(boss2.getSalary()); // 输出 100000
alert(boss3.getSalary()); // 输出 150000
这里有一个图示来讲明prototype是如何工做的。这个对象的每个实例(boss1, boss2, boss3)都有一个内部属性叫作__proto__,这个属性指向了它的构造器(Employee)的属性prototype。当你执行getSalary或者addSalary的时候,这个对象会在它的__proto__找到并执行这个代码。注意这点:这里并无代码的复制(和Example DT8的图表做一下对比)。