首頁  >  文章  >  web前端  >  關於js函數解釋(包括內嵌,物件等)

關於js函數解釋(包括內嵌,物件等)

高洛峰
高洛峰原創
2016-12-06 13:45:43828瀏覽

常用寫法:

function add(a,b)
 
{
 
   return  a + b;
 
}
 
alert(add(1,2));    // 结果 3

   

當我們這樣定義函數的時候,函數內容會被編譯(但不會立即執行,除非我們去呼叫它)。而且,也許你不知道,當這個函數創建的時候有一個同名的物件也被創建。就我們的例子來說,我們現在有一個物件叫做「add」(要更深入了解,看底下函數:物件節。)

匿名函數:

我們也可以透過指派一個變數名稱給匿名函數的方式來定義它。

var add = function(a,b)
 
{
 
   return  a + b;
 
}
 
alert(add(1,2));    // 结果 3

   

這個程式碼和前一個範例做了同樣的事情。也許語法看起來比較奇怪,但它應該更能讓你感覺到函數是一個對象,而我們只是為這個對象指派了一個名稱。可以把它看做和 var myVar=[1,2,3]一樣的語句。以這種方式宣告的函數內容也一樣會被編譯。

當我們指派一個這樣的函數的時候,我們並不一定要求必須是匿名函數。在這裡,我作了和以上相同的事情,但我加了函數名“theAdd”,而且我可以透過呼叫函數名稱或那個變數來引用函數。

var add = function theAdd(a,b)
 
{
 
   return a + b;
 
}
 
alert(add(1,2));      // 结果 3
alert(theAdd(1,2));    // 结果也是 3

   

使用這種方式定義函數在物件導向程式設計中是很有用的,因為我們能像底下這樣使一個函數成為一個物件的屬性。

var myObject = new Object();
 
myObject.add = function(a,b){return a+b};
 
// myObject 现在有一个叫做“add”的属性(或方法)”
 
// 而且我能够象下面这样使用它
 
myObject.add(1, 2);

   

函數:物件

函數是javascript中的一種特殊形式的物件。它是第一個[b〕類別資料型別(classdata type)。這意味著我們能夠給它增加屬性。這裡有一些需要注意的有趣觀點

物件的創建

就像剛才提及的,當我們定義一個函數時,javascript實際上在後台為你創建了一個物件。這個物件的名稱就是函數名稱本身。這個物件的類型是function。在下面的例子,我們也許不會意識到這一點,但我們實際上已經創建了一個物件:它叫做Ball。

function ball()   // 也许看起来有点奇怪,但是这个声明
 
{             // 创建了一个叫做Ball的对象
 
   i = 1;
 
}
 
alert(typeof ball);   // 结果 "function"

   

我們甚至能將這個物件的內容列印出來而且它會輸出這個函數的實際程式碼

alert(ball); 
 
//结果为:
 
//function ball()
 
//{
 
//   i = 1;
 
//}

   

屬性的新增屬性。因為定義一個函數的實質是創建一個物件。我們能夠「暗地裡」為函數新增屬性。例如,我們這裡定義了函數Ball,並且加入屬性callsign。

function Ball()   // 也许看起来有点奇怪,但是这个声明创建了一个叫做Ball的对象,而且你能够引用它或者象下面那样给它增加属性
{         
 
}
 
ball.callsign="The ball"; // 给Ball增加属性
alert(ball.callsign); // 输出 "The ball"

   

指針

因為function是一個對象,我們能夠為一個function分配一個指針。如下例,變數ptr指向了物件myFunction。

function myFunction(message)
{
alert(message);
}
var ptr=myFunction; // ptr指向了myFunction
ptr("hello");     // 这句会执行myFunction:输出"hello"

   

我們能夠運作這個函數,就好像這個函數名稱已經被指標名稱取代了一樣。所以在上面,這行ptr("hello"); 和myFunction("hello");的意義是一樣的。

指向函數的指標在物件導向程式設計中相當有用。例如:當我們有多個物件指向同一個函數的時候(如下):

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"

   

因為只有指標被保存(而不是函數本身),當我們改變函數物件本身的時候,所有指向那個函數的指針都會改變。我們能夠在底下看到:

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();

   

指標的指向

我們能夠在一個函數建立之後重新分配它,但是我們需要指向函數物件本身,而不是指向它的指標。在下例中,我將改變myfunction()的內容。

function myFunction()
{
alert("Old");
}
myFunction(); // 输出 "Old"
myFunction=function()
{
alert("New");
};
myFunction(); // 输出 "New"

   

舊函數哪裡去了? ?被拋棄了。

如果我們需要保留它,我們可以在改變它之前給它一個指標。

function myFunction()
{
alert("Old");
}
var savedFuncion=myFunction;
myFunction=function()
{
alert("New");
};
myFunction();  // 输出 "New"
savedFuncion(); // 输出 "Old"

   

內嵌函數

function get(a,b,c)
 
{
 
   function cal(n)
 
   {
 
      return n/2;
 
   }
 
   var result = “”;
 
   result+=cal(a)+” ”;
 
   result+=cal(b)+” ”;
 
   result+=cal(c);
 
}
 
var resultString = get(10,20,30);
 
alert(resultString);   // 输出 "5 10 15"

   

你只能在內部呼叫嵌套的函數。是說,你不能這麼呼叫:

getHalfOf.calculate(10),因為calculate只有當外部函數(getHalfOf())在運作的時候才會存在。這和我們前面的討論一致(函數會被編譯,但只有當你去呼叫它的時候才會執行)。

呼叫哪個函數?

你也許正在想命名衝突的問題。例如,下面哪一個叫做calculate的函數會被呼叫?

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的新数据类型。

function Ball()
{
}
var ball0=new Ball(); // ball0 现在指向一个新对象
alert(ball0);     // 输出 "Object",因为 ball0 现在是一个对象

   

这样看来,ball0=new Ball()作了什么?new关键字创建了一个类型是Object的新对象(叫做ball0)。然后它会执行Ball(),并将这个引用传给ball0(用于调用对象)。下面,你会看到这条消息:“creating new Ball”,如果Ball()实际上被运行的话。

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行的一个简写:

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和以上中的ptr=myFunction语法一致。

添加属性

当我们象上面那样使用关键字new创建一个对象的时候,一个新的Object被创建了。我们可以在创建之后给这个对象添加属性(就好像我在上面那样添加属性name。而接下来的问题就是如果我们创建了这个对象的另外一个实例,我们得象下面那样再次给这个新对象添加这个属性。)

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中有特别的意义。它指向了调用函数的那个对象。让我们看看下面的另一个示例,这时候我们在构造函数中添加上这些属性:

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里。

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的一个属性.

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);

   

函数也是一个对象。所以你可以让一个函数作为一个对象的一个属性。下面,我将添加两个函数getSalary和addSalary。

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演示了几种将函数赋给属性的不同方法。
 
如前面数次提到的,一个函数声明的结果是一个对象 被创建。
 
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)。

看看下面的代码来理解一下上面所描述的内容。

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"
 
// 因为两个对象都指向同一个函数
 
// 当修改其中一个的时候,会影响所有的实例(所以两个都输出“boss2”).

   

也许不是重要的事情,但这里有一些关于运行类似上面的getSalary的内嵌函数的结论:

1) 需要更多的存储空间来存储对象(因为每一个对象实例都会有它自己的getSalary代码拷贝);

2) javascript需要更多时间来构造这个对象。

让我们重新写这个示例来让它更有效率些。

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属性,因为在每一个构造函数中都有它的存在。你可以看看下面的例子:

function Test()
{
}
alert(Test.prototype); // 输出 "Object"

   

给prototype添加属性

就如你在上面所看到的,prototype是一个对象,因此,你能够给它添加属性。你添加给prototype的属性将会成为使用这个构造函数创建的对象的通用属性。

例如,我下面有一个数据类型Fish,我想让所有的鱼都有这些属性:

livesIn="water"和price=20;为了实现这个,我可以给构造函数Fish的prototype添加那些属性。

function Fish(name, color)
{
this.name=name;
this.color=color;
}
Fish.prototype.livesIn="water";
Fish.prototype.price=20;

   

接下来让我们作几条鱼:

var fish1=new Fish("mackarel", "gray");
var fish2=new Fish("goldfish", "orange");
var fish3=new Fish("salmon", "white");

   

再来看看鱼都有哪些属性:

for (int i=1; i<=3; i++)
{
var fish=eval("fish"+i);  // 我只是取得指向这条鱼的指针
alert(fish.name+","+fish.color+","+fish.livesIn+","+fish.price);
}

   

输出应该是:

"mackarel, gray, water, 20"
"goldfish, orange, water, 20"
"salmon, white water, 20"

   

你看到所有的鱼都有属性livesIn和price,我们甚至都没有为每一条不同的鱼特别声明这些属性。这时因为当一个对象被创建时,这个构造函数将会把它的属性prototype赋给新对象的内部属性__proto__。这个__proto__被这个对象用来查找它的属性。

你也可以通过prototype来给所有对象添加共用的函数。这有一个好处:你不需要每次在构造一个对象的时候创建并初始化这个函数。

用prototype给对象添加函数

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;
}

   

我们可以象通常那样创建对象:

var boss1=new Employee("Joan", 200000);
var boss2=new Employee("Kim", 100000);
var boss3=new Employee("Sam", 150000);

   

并验证它:

alert(boss1.getSalary());  // 输出 200000
alert(boss2.getSalary());  // 输出 100000
alert(boss3.getSalary());  // 输出 150000

   

这里有一个图示来说明prototype是如何工作的。这个对象的每一个实例(boss1, boss2, boss3)都有一个内部属性叫做__proto__,这个属性指向了它的构造器 (Employee)的属性prototype。当你执行getSalary或者addSalary的时候,这个对象会在它的__proto__找到并执行这个代码。


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn