當咱們這麼定義函數的時候,函數內容會被編譯(但不會當即執行,除非咱們去調用它)。並且,也許你不知道,當這個函數建立的時候有一個同名的對象也被建立。就咱們的例子來講,咱們如今有一個對象叫作「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的圖表做一下對比)。