Javascript二(函數詳解)
一.函數
Javascript是一門基於對象的腳本語言,代碼復用的單位是函數,但它的函數比結構化程序設計語言的函數功能更豐富。JavaScript語言中的函數是“一等公民”,它可以獨立存在;而且JavaScript的函數完全可以作為一個類來使用(而且它還是該類唯一的構造器);與此同時,函數本身也是一個對象,函數本身是function
實例。
函數的最大作用是提供代碼復用,將需要重復使用的代碼塊定義成函數,提供更好的代碼復用。函數可以有返回值,可以沒有返回值
1.定義函數的三種方式
a)定義命名函數,
語法格式如下:
function functionName(param1,param1,...){ staments; }
b)定義匿名函數
語法格式如下:
function(parameter list){
staments
};
與命名函數的區別是沒有函數名,函數後面有個分號。
當通過這種語法格式定義了函數之後,實際上就定義了一個函數對象(即function實例),接下來可以將這個對象賦給另外一個變量。例如下面代碼:
<script type=‘text/javascript‘> var f = function(name) { document.writeln(‘匿名函數<br/>‘); document.writeln(‘你好‘+name); } f(‘yukey‘); </script>
使用匿名函數提供更好的可讀性。
c)使用function類匿名函數
JavaScript提供了一個function類,該類也可以用於定義函數,Function類的構造器的參數個數可以不受限制,function可以接受一系列的字符串參數,其中
最後一個字符串參數是函數的執行體,執行體的各語句以分號(;)隔開,而前面的各字符串參數則是函數的參數,看下面定義函數的方式:
<script type=‘text/javascript‘> var f = new Function(‘name‘,"document.writeln(‘Function定義的函數<br/>‘);" +"document.writeln(‘你好‘+name);"); f(‘yukey‘); </script>
2.局部函數
局部函數在函數裏定義,看下面代碼
<script type="text/javascript"> function outer(){ function inner1(){ document.write("局部函數11111<br/>"); } function inner2(){ document.write("局部函數22222<br/>"); } inner1(); inner2(); document.write("結束測試局部函數<br/>"); } outer(); document.write("調用outer之後..."); </script>
在外部函數裏調用局部函數並不能讓局部函數獲得執行的機會。只有當外部函數被調用時,外部函數裏調用的局部函數才獲得執行的機會。
3.數,方法,對象,變量和類 函數是JavaScript的“一等公民”,函數是JavaScript變成裏非常重要的一個概念,當使用JavaScript定義了一個函數之後,實際上可以得到如下四項。
函數:就像Java的方法一樣,這個函數可以被調。
對象:定義一個函數時,系統也會創建一個對象,該對象時Function類的實例
方法:定義一個函數時,該函數通常會附加給某個對象,作為該對象的方法
變量:在定義一個函數的同時,也會得到一個變量
類:在定義函數的同時,也得到一個與函數同名的類
定義函數之後,有如下兩種方式調用函數
直接調用函數:直接調用函數總是返回該函數體內最後一條return語句的返回值;如果該函數體內不包含return語句,則直接調用函數沒有返回值。
使用new關鍵字直接調用函數:通過這種方式調用總有返回值,返回值就是一個Javascript對象。
<script type="text/javascript"> var test = function(name) { return "你好,"+name; } var rval = test(‘Sherman‘); var obj = new test("Sherman"); alert( rval+"\n"+obj); </script>
可以看出,第一種方式直接調用函數,返回的是return語句返回值,第二種使用new關鍵字調用給函數,也就是將函數當成類來使用,得到的是一個對象。
下面程序定義了一個person函數,也就定義了一個person類,該person函數也會作為Person類唯一的一個構造器,定義了person函數時希望為該函數定義
了一個方法。
<script type="text/javascript"> function Person(name ,age){ this.name = name; this.age = age; this.info=function(){ document.writeln("我的名字是"+this.name+‘<br/>‘); document.writeln("我的年紀是"+this.age+‘<br/>‘); } } var p = new Person(‘Sherman‘,24); p.info(); </script>
被this關鍵字修飾的的變量不再是局部變量,它是該函數的實例屬性。
JavaScript定義的函數可以“附加”到某個對象上,作為該對象的方法。實際上如果沒有明確指定將函數“附加”到哪個對象上,該函數默認“附加”到window
對象上,作為window對象的方法。
例如如下代碼:
<script type="text/javascript"> function hello(name) { document.write(name+",您好<br/>") } window.hello("孫大聖"); p = { //定義一個函數,該函數屬於p對象 walk:function(){ for(let i = 0 ; i < 2 ; i++){ document.write("慢慢地走...<br/>"); } } }; p.walk(); </script>
4.函數的實例屬性和類屬性
由於JavaScript函數不僅僅是一個函數,而且是一個類,該函數還是此類唯一的構造器,只要在調用函數時使用new關鍵字,就可返回一個object,這個object
不是函數的返回值,而是函數本身產生的對象。因此JavaScript中定義的變臉不僅有局部變量,還有實例屬性和類屬性兩種。根據函數中聲明變量的方式,
中的變量有三種
a)局部變量:在函數中以var聲明的變量
b)實例屬性:在函數中以this前綴修飾的變量
c)類屬性:在函數中以函數名前綴修飾的變量
局量只能在函數裏訪問的變量。實例屬性和類屬性是面向對象的概念:實例屬性是屬於單個對象的,因此必須通過對象來訪問,類屬性是屬於整個類本身
(也就是函數)的,因此必須通過類來訪問。
同一個類只占用一塊內存,因此每個類屬性只占用一塊內存;同一個類每創建一個對象,系統將為該對象的實例屬性分配一塊內存。
<script type="text/javascript"> function Person(national,age){ this.age = age; Person.national = national; var bb = 0; } var p1 = new Person(‘中國‘,29); with(document){ writeln("創建第一個Person對象"); writeln("p1的age屬性為:"+p1.age+"<br/>"); writeln("p1的national屬性為:"+p1.national+"<br/>"); writeln("通過Person訪問靜態national屬性為:"+Person.national+"<br/>"); writeln("p1的bb屬性為"+p1.bb+"<br/><hr/>"); } var p2 = new Person(‘美國‘,32); with(document){ writeln("創建兩個Person對象中後<br/>"); writeln("p1的age屬性為:"+p1.age+"<br/>"); writeln("p1的national屬性為:"+p1.national+"<br/>"); writeln("p2的age屬性為:"+p2.age+"<br/>"); writeln("p2的national屬性為:"+p2.national+"<br/>"); writeln("通過Person訪問靜態national屬性為:"+Person.national+"<br/>"); } </script>
瀏覽器輸出:
創建第一個Person對象 p1的age屬性為:29
p1的national屬性為:undefined
通過Person訪問靜態national屬性為:中國
p1的bb屬性為undefined
創建兩個Person對象中後
p1的age屬性為:29
p1的national屬性為:undefined
p2的age屬性為:32
p2的national屬性為:undefined
通過Person訪問靜態national屬性為:美國
值得指出的是,JavaSript和java不一樣,它是一種動態語言,它允許隨時為對象增加屬性和方法,當直接為對象的某個屬性賦值時,即可視為給對象增加屬性
5.調用函數的3種方式
5.1 直接調用函數
如下代碼:
//調用window對象的alert方法 window.alert(); //調用p對象的walk方法 p.walk();
當程序使用window對象調用方法時,window調用者可以省略
5.2 以call方式調用函數
直接調用函數的方式簡單易用,但這種調用方式不夠靈活,有時候調用函數時需要動態的傳入一個函數引用,此時為了動態地調用函數,就需要call方法。call調用函數
語法格式為
函數引用.call(調用者,參數1,參數2,...)
下面通過call方法調用each函數:
<script type="text/javascript"> var each = function(array,fn){ for(var index in array){ fn.call(null,index,array[index]); } } each([4,20,3],function(index,ele){ document.writeln("第"+index+"個元素是:"+ele+"<br/>"); }); </script>
瀏覽器輸出:
第0個元素是:4
第1個元素是:20
第2個元素是:3
5.3 以apply()方法調用函數
apply()方法和call()方法比較類似,都可以動態的調用函數,他們的區別是:
a)通過call()方法調用函數時,必須在括號中列出每個參數
b)通過apply()動態地調用函數時,需要以數組形式一次性傳入所有調用函數
以下代碼示範了call()和apply()的關系
<script type="text/javascript"> var myfun = function(a,b){ alert(‘a的值是‘+a+‘\nb的值是‘+b); } //以call()方式動態的調用函數 myfun.call(window,5,20); //以apply()方式動態的調用函數 myfun.apply(window,[3,12]); var example = function(num1,num2){ //直接用arguments代表調用example函數時傳入的所有函數 myfun.apply(this,arguments); } example(20,40); </script>
由此可見,apply()和call()對應關系如下:
函數引用.call(調用者,參數1,參數2,...); = 函數引用.apply(調用者,[參數1,參數2,...]);
6.函數獨立性
雖然定義函數時可以將函數定義成某個類的方法,或定義成某個對象的方法。但JavaScript的函數是“一等公民”,他永遠是獨立的,函數永遠不會從屬於其他類,對象。
下面代碼示範了函數的獨立性:
<script type="text/javascript"> function Person(name){ this.name = name; this.info = function (){ alert("我的name是:"+this.name); } } var p = new Person("Sherman"); //調用p對象的info方法 p.info(); var name = "測試名稱"; //以window對象作為調用者來調用p對象的info方法 p.info.call(window); </script>
當使用匿名內嵌函數定義某個類的方法是時,該內嵌函數一樣是獨立存在的,該函數也不是作為該類實例的附庸存在,這些內嵌函數也可以被分離出來獨立使用,成為另一個對象的函數。如下代碼再次證明函數的獨立性:
<script type="text/javascript"> function Dog(name,age,bark) { this.name = name; this.age = age; this.bark = bark; //使用內嵌函數為Dog實例定義方法 this.info = function(){ return this.name+"的年齡為:"+this.age+",它的叫聲為:"+this.bark; } } var dog = new Dog("旺財",3,"汪汪,汪汪..."); function Cat(name,age){ this.name = name; this.age = age; } //將dog實例的info方法分離出來,在通過call方法調用info方法 //此時cat為調用者 var cat = new Cat("Kitty",2) alert(dog.info.call(cat)); </script>
7.函數提升
JavaScript允許先調用函數,然後再在後面定義函數,這就是典型的函數提升:JavaScript會將全局函數提升到根元素<script.../>元素的頂部定義
例如如下代碼:
<script type="text/javascript"> console.log(add(2,5)); function add(a,b){ console.log("執行add函數") return a+b; } </script>
和下面代碼效果是一樣的:
<script type="text/javascript"> function add(a,b){ console.log("執行add函數") return a+b; } console.log(add(2,5)); </script>
效果如圖:
如果使用程序先定義匿名函數,然後將匿名函數賦值給變量,在這種方式下依然會發生函數提升,但此時只提升被賦值的變量,函數定義本省不提升。例如
<script type="text/javascript"> console.log(add(2,5)); var add = function(){ console.log("執行add函數"); return a+b; } </script>
效果如圖:
局部函數會被提升到所在函數的頂部,如
<script type="text/javascript"> function test() { function add(a, b) { console.log("執行add函數") return a + b; } console.log(add(2, 5)); } test(); </script>
JavaScript編程時應盡量避免變量名和函數名同名。否則會發生覆蓋的情形:分兩種情況
a)定義變量時只用var定義變量,不分配初始值,此時函數的優先值更高,函數會覆蓋變量。
b)定義變量時為變量值指定了初始值,此時變量的優先值更高,變量會覆蓋函數
測試代碼如下:
<script type="text/javascript"> function a(){} var a; console.log(a); var b; function b(){} console.log(b); var c = 1; function c(){}; console.log(c); function d(){} var d = 1; console.log(d); </script>
效果如下:
8.箭頭函數
箭頭函數相當於其他語言的Lambda表達式或閉包語法,箭頭函數是普通函數的簡化寫法。語法格式如下:
(param1,param2,param3,...) => {staments}
相當於定義了如下函數:
function(param1,param2,param3,...){}
如果箭頭函數的執行體只有一條return語句,則允許省略函數執行體的花括號和return關鍵字。
如果箭頭函數的形參只有一個參數,則允許省略形參列表的圓括號。
如果箭頭函數沒有形參,則圓括號不可以省略。
(param1,param2,param3,...) => expression //等同於(param1,param2,param3,...) =>{return expression} singleParam => {staments} //等同於(singleParam) => {staments}
下面代碼示範了箭頭函數代替傳統函數:
<script type="text/javascript"> var arr = ["yuekey","fkit","leegang","sczit"]; var newArr1 = arr.map(function(ele){ return ele.length; }); var newArr2 = arr.map((ele) =>{return ele.length}); var newArr3 = arr.map(ele => ele.length); console.log(newArr3); arr.forEach(function(ele){ console.log(ele); }); arr.forEach((ele) => {console.log(ele);}) arr.forEach(ele => console.log(ele)); </script>
與普通函數不同的是,箭頭函數並不擁有自己的this關鍵字,對於普通函數而言,如果程序通過new調用函數創建對象,那麽該函數中的this代表所創建的對象;
如果直接調用普通函數,那麽該函數的this代表全局對象(window)。例如,如下代碼示範了this關鍵字的功能。
<script type="text/javascript"> function Person(){ this.age = 0;//Person()作為構造器使用時,this代表構造器創建的對象 setInterval(function growUp(){ console.log (this=== window);//對於普通函數來說,this代表全局對象window,總是返回true this.age++; },1000); } var p = new Person(); setInterval(function(){ console.log(p.age);//此處訪問p對象的age,總是0 },1000); </script>
箭頭函數中的this總是代表包含箭頭函數的上下文,例如:
<script type="text/javascript"> function Person(){ this.age = 0; setInterval(() => { console.log(this === window); this.age++;//this總是代表包含箭頭函數的上下文 },1000); } var p = new Person(); setInterval(() => console.log(p.age),1000);//此處訪問的是p對象的age,總是不斷加1 </script>
如果在全局範圍內定義箭頭函數,那麽箭頭函數的上下文就是window本身,this代表全局對象window對象。
<script type="text/javascript"> var f = () => {return this;}; console.log(f() === window);//輸出true </script>
箭頭函數並不綁定arguments,因此不能在箭頭函數中通過arguments來訪問調用箭頭函數的參數,箭頭函數的arguments總是引用當前上下文的arguments。例如
<script type="text/javascript"> var arguments = "Sherman"; var arr = () => arguments; console.log(arr()); function foo(){ var f = (i) => ‘Hello,‘+arguments[1]; return f(2); } console.log(foo("Sherman","Leegang"));//箭頭函數中的arguments引用當前上下文的arguments,此時代表調用foo函數的參數 </script>
9.函數的參數處理
大部分時候,函數都需要接受參數傳遞。與Java完全類似,JavaScript的參數傳遞也全部采用值傳遞方式。
9.1基本類型和復合類型的參數傳遞
對於基本類型參數,JavaScript采用值傳遞方式,當通過實參調用函數時,傳入函數裏的並不是實參本身,而是實參的副本,因此在函數中修改參數值並不會對實參
有任何影響:
<script type="text/javascript"> function change(arg1) { arg1 = 10; document.writeln("函數執行中arg1的值為:" + arg1 + "<br/>"); } var x = 5; document.writeln("函數調用前x的值為"+x+"<br/>"); change(x); document.writeln("函數調用之後的x值為"+x+"<br/>"); </script>
對於復合類型的參數,實際上采用的依然是值傳遞方式,只是很容易混淆。看如下程序
<script type="text/javascript"> function changeAge(person) { person.age = 10; document.writeln("函數執行中age的值為:" + person.age + "<br/>"); person = null; } var person = {age:5}; document.writeln("函數調用之前age的值為"+person.age+"<br/>"); changeAge(person); document.writeln("函數調用之後的age值為"+person.age+"<br/>"); document.writeln("person對象為"+person); </script>
9.2空參數
在JavaScript中,在函數聲明時包含了參數,但調用時沒有傳入實參,這種情況是允許的,JavaScript會自動將參數值設置為undefined值,對於JavaScript來說,函數名就是函數的唯一標識。
如果先後定義兩個同名,形參列表不同的函數,這不是函數重載,這種情況後面定義的函數會覆蓋前面的函數。
9.3參數類型
JavaScript是弱類型語言,參數列表無需聲明參數類型。
“鴨子類型”的理論認為,弱類型語言的函數需要接收參數時,則應先判斷參數類型,判斷參數是否包含了需要訪問的屬性、方法。當條件都滿足時 ,程序才會真正
執行。看如下代碼
<script type="text/javascript"> function changeAge(person) { if (typeof person == ‘object‘ && typeof person.age == ‘number‘) { document.writeln("函數調用之前age的值為" + person.age + "<br/>"); person.age = 10; document.writeln("函數執行中age的值為:" + person.age + "<br/>"); } else { document.writeln("參數類型不符合" + typeof person + "<br/>") } } changeAge(); changeAge("Sherman"); changeAge(true); p = {abc:34};//json格式創建第一個對象 changeAge(p); person = {age:25};//json格式創建第二個對象 changeAge(person); </script>
Javascript二(函數詳解)