layout: post
title: JavaScript函數(shù)
tags: [JavaScript, 函數(shù)]
author: 楊比軒
函數(shù)定義和調(diào)用
簡單的函數(shù)定義
//求絕對值
function abs(x){
if(x < 0)
return -x;
else
return x;
}
和java語法類似:
-
function函數(shù)關(guān)鍵字申明 -
abs函數(shù)名 -
(x)參數(shù) -
{....}函數(shù)體
JS如果沒有return語句,函數(shù)執(zhí)行完畢會返回
undefined
本著萬物皆對象的原則,function也是對象,所以:
var abs = function(x){
....
};
此時函數(shù)是一個匿名函數(shù),變量abs指向匿名函數(shù),本質(zhì)上和上面那種方式?jīng)]有區(qū)別,但是這里有一點要注意,此處其實是一個賦值語句,所以末尾要加上;
函數(shù)的調(diào)用
正常調(diào)用不做贅述,說說比較特殊的地方。
JS函數(shù)允許傳遞多個參數(shù)或者不傳遞參數(shù),所以:
abs(1,2,“123”,4,5);//后面的參數(shù)不會生效,結(jié)果返回 1
abs(); //結(jié)果返回 NaN
返回NaN解釋:函數(shù)傳遞的x變成
undefined,計算結(jié)果變成NaN
所以,為了避免這種情況的產(chǎn)生,可以做健壯性處理:
function abs(x){
if(x !== 'number'){
throw 'not a number';
}
if(x > 0)
return -x;
else
return x;
}
arguments
JS的函數(shù)默認會有一個arguments參數(shù),此參數(shù)指向函數(shù)的參數(shù)集,有點類似于array,但是不是array。
可以使用arguments的length屬性,來判斷參數(shù)的個數(shù)和對參數(shù)進行訪問
demo:
function getSum(x,y,z){
var num = 0;
console.log("arguments length:" + arguments.callee);
for(var i = 0 ; i < arguments.length ; i++){
console.log(arguments[i]);
num = num + arguments[i];
}
return num;
}
console.log(getSum(1,2,3,4,5,6,7,8,9,10));
//輸出:1,2,3,4,5,6,7,8,9,10,55
rest
ES6引入的新標準,用于處理多余傳入函數(shù)內(nèi)的參數(shù),用法:
function restDemo(x, y, ...rest){
console.log("x="+x);
console.log("y="+y);
console.log("rest=" + rest);
}
restDemo(1,2,3,4,5,6);
//輸出:
//x=1
//y=2
//rest=3,4,5,6
關(guān)于return
JS有自動在行末加;的機制,所以要小心return語句
demo:
//return了一個匿名對象,這種是常規(guī)情況,可以正常運行
function returnDemo(){
return {name:'luke'};
}
//return的代碼過長時,需要換行,此時js會自動加`;`在行末,所以返回undefined
function returnDemo(){
return
{ name:'luke'};
}
//這種的也正常,{ 表示此行未結(jié)束,會阻斷添加 ;
function returnDemo(){
return {
name:'luke'
};
}
變量的作用域
JS的函數(shù)可以嵌套,所有當兩個嵌套的函數(shù)的變量重名后,內(nèi)部可以調(diào)用外部變量,外部則不能訪問內(nèi)部。
'use strict';
function foo() {
var x = 1;
function bar() {
var y = x + 1; // bar可以訪問foo的變量x!
}
var z = y + 1; // ReferenceError! foo不可以訪問bar的變量y!
此時如果兩個參數(shù)名字相同,則內(nèi)部參數(shù)相當于重寫了外部參數(shù),會達到屏蔽的效果。
'use strict';
function foo() {
var x = 1;
function bar() {
var x = 'A';
console.log('x in bar() = ' + x); // 'A'
}
console.log('x in foo() = ' + x); // 1
bar();
}
變量的自動提升
在JS中可以引用稍后申明的變量而不會引發(fā)異常就是因為有自動變量提升(variable hoisting),但是提升也會帶來一些問題。
console.log(x === undefined);//logs true
var x = 3;
//上述代碼相當于
var x;
console.log(x === undefined);
x = 3;
分析:
var x = 3;相當于一個申明變量語句和一個賦值語句,但是自動提升只會提升申明語句var x;僅此而已。所以log輸出就是true
所以,一段代碼或者函數(shù)中的var語句應(yīng)該盡可能的放在接近代碼段的頂部位置,一避免產(chǎn)生麻煩。
全局變量
全局變量相當于全局對象的屬性,在網(wǎng)頁中全局對象是window,所以可是使用window.variable來訪問和設(shè)置全局變量。
所以在函數(shù)體之用var或者const申明的變量既是全局變量,也是window對象的屬性,。
無論是function還是var聲明的變量,在js中都是變量,也就是window的屬性,當前前提是function和var都全局的。
局部變量
在for或者if中聲明的變量其實在{}也可以引用,因為js的變量作用域其實是函數(shù)內(nèi)部,而for等不是函數(shù)。
ES6引入了新標準,let關(guān)鍵字可以替代var可以申明塊級的作用域的變量。
{
let abc = "qweqweqweqweqweqweq";
}
console.log(abc);//logs:Uncaught ReferenceError: abc is not defined(…)
常量
ES6添加的新特性,const關(guān)鍵字申明一個固定值的常量。
命名空間
javascript實際上只有一個全局作用域。任何變量如果在當前函數(shù)作用域中找到,就會繼續(xù)往上找(上指的是父類),最后再全局作用中也沒有找到,就匯報ReferenceError錯誤。
全局變量會綁定到window上,不同的js文件如果使用了相同的全局變量,就會造成明明沖突,并且難以發(fā)現(xiàn)。
避免這個問題的方法就是把所有的變量和函數(shù)全部綁定到一個全局變量中,如:
//全局變量
var MMP = {};
//其它變量
MMP.a='a';
MMP.b = function(){
console.log("this is function");
}
這樣就可以將需要的變量申明在MMP下。
對象方法
在對象內(nèi)部定義的函數(shù)也叫方法,和普通的函數(shù)其實沒啥區(qū)別,但是此種方法可以使用this關(guān)鍵字來指向當前調(diào)用方法的對象。
var person = {
name:"bixuan",
age:23
//此方法就是返回對象的age屬性的value
getAge:function(){
return this.age;
}
}
如果此時getAge()方法寫在person對象之外時,當person.getAge()種調(diào)用時,可以獲得和上面一樣的結(jié)果,但是要是單獨的調(diào)用getAge()
var person = {
name:"bixuan",
age:23,
getage:getAge
}
function getAge(){
return this.age;
}
person.getage();// 結(jié)果就是23
getAge();//結(jié)果是 undefined
person.getage指向的就是函數(shù)getAge(),函數(shù)中的this指向的就是person對象,所以返回的就是23,而直接運行getAge()時this指向的是window對象,因為沒有age屬性,所以結(jié)果是undefined。
還有一種情況就是函數(shù)的多層嵌套,也會導(dǎo)致this的調(diào)用出現(xiàn)問題。
var person = {
name:"bixuan",
age:23,
getage:function(){
function getAge(){
return this.age;
}
}
}
person.getage();//結(jié)果是 undefined
原因就是this又重新指向了undefined了,當然如果處于非strict模式下,thi又會指向window。為了避免這種情況的發(fā)生,可以在外層加上一句:
...
var that = this; //用that來存儲this變量,供內(nèi)層調(diào)用
function getAge(){
return that.age;
}
...
apply()
函數(shù)對象都會有一個apply()方法,此方法接收兩個參數(shù),一個是需要綁定的this對象,第二個參數(shù)是一個Array,存儲的是函數(shù)本來的參數(shù)。
function sum(x, y){
console.log(this.age);
console.log( x + y);
}
sum.apply({age:23},[1,2]);
//輸出 23,3
call()和apply類似,不同的是call第二個參數(shù)不是array,而是按順序?qū)⒃瘮?shù)所有的參數(shù)順序傳入。`call({},參數(shù)1,參數(shù)2,...)
利用apply的這個特性,可以用來實現(xiàn)裝飾器。
裝飾器:對已有的函數(shù)方法進行裝飾,在實現(xiàn)原有功能的基礎(chǔ)上,添加額外的功能,或者增強原有功能。
function pri(){
console.log("this is a demo");
}
var count = 0;
(function newPro(){
count++;
pri.apply(null,[]);
})();
上述例子就實現(xiàn)了對函數(shù)pri的裝飾,在執(zhí)行原有代碼的同時,對其運行的次數(shù)進行了統(tǒng)計。
高階函數(shù)
普通函數(shù)接受的參數(shù)都是數(shù)據(jù)類型,高階函數(shù)就是把函數(shù)對象當作參數(shù)進行傳遞。
function sum(x, y){
return x + y;
}
function useSum(x, y, sum){
return sum(x, y);
}
- map/reduce
map()方法相當于對arr中的數(shù)據(jù)按照傳入的參數(shù)函數(shù)進行一次迭代。
function pow(x){
return x*x;
}
var arr = [1, 2, 3, 4, 5];
arr.map(pow);// [1,2,9,16,25]
reduce()其實也是對arr中的數(shù)據(jù)進行迭代,但是和map不同的是,reduce()相當于嵌套迭代。
需要注意的是:
reduce()必須接收兩個參數(shù)的函數(shù)
function sum(x, y){
return x + y;
}
var arr = [1, 2, 3, 4, 5];
arr.reduce(sum);//15
**2. filter **
不知道官方的叫法是什么,應(yīng)該翻譯做過濾器吧。使用arr調(diào)用,并傳入返回true/false來對arr中的元素進行判定從而進行過濾。
var arr = [1,2,3,4,5];
var newArr = arr.filter(function(x){
if(x%2 === 0){
return true;
}else{
return false;
}
})
console.log(arr);//[1,2,3,4,5]
console.log(newArr);//[2,4]
filter()不會更改調(diào)用數(shù)組的值,而是返回一個新的arr對象。
回調(diào)函數(shù)
filter接受回調(diào)函數(shù)。
- 當回調(diào)函數(shù)一個參數(shù)時,代表數(shù)組的元素
- 當對調(diào)函數(shù)三個參數(shù)時,分別為:數(shù)組的元素,元素索引,數(shù)組本身
arr.filter(function(a,b,c){
console.log(a);
console.log(b);
console.log(c);
})
所以可以利用filter來實現(xiàn)對arr的去重復(fù),去空元素等等操作。
//去掉arr中的重復(fù)元素
//這里利用的就是indexOf返回的是數(shù)組中該元素第一次出現(xiàn)的索引
//來實現(xiàn)對arr的去重讀
var strs = ['a','b','c','d','e','b','c','e','g','a'];
var newStrs = strs.filter(function(element, index, arr){
if(arr.indexOf(element) == index){
return true;
}
else{
return false;
}
})
console.log(newStrs);
3.sort
sort應(yīng)該可以翻譯為排序器吧,類似java中的comparable,只需要傳入一個排序的方法,就能對調(diào)用的數(shù)組進行排序。
sort默認是將數(shù)組的元素轉(zhuǎn)換為
String對象后進行排序,排序的依據(jù)是ASCII值的大小。
//按從小到大的循序排序
var nums = [12312,345345,24654356,234234,2342,4564,2342,6786,34,85,24558,62,48,4,512,5,95623];
console.log(nums.sort(function(x,y){
if(x > y){
return 1;
}else{
return -1;
}
return 0;
}))
通過傳入的比較方法的不同,可實現(xiàn)不同的實現(xiàn)效果,所以重點在于傳入的比較函數(shù)。
閉包
一般的方法和函數(shù)都是返回計算的結(jié)果,也就是基本數(shù)據(jù)類型和對象。當函數(shù)返回的是包含參數(shù)的函數(shù)對象時,這種的就稱為閉包。
var nums = [12312,345345,24654356,234234,2342,4564,2342,6786,34,85,24558,62,48,4,512,5,95623];
function lazy_sum(arr){
var sum = function(){
return arr.reduce(function(x,y){
return x + y;
})
}
return sum;
}
var result = lazy_sum(nums);
console.log(result);//[Function: sum]
console.log(result());//25383212
打印結(jié)果說明,result此時是一個函數(shù),調(diào)用此函數(shù),輸出結(jié)果為sum()函數(shù)執(zhí)行的結(jié)果。
若此時繼續(xù)執(zhí)行代碼:
nums.push(123123);
console.log(result());//25506335
但需要注意的是,此時因為傳入的引用數(shù)據(jù)類型,所以當此數(shù)據(jù)的值發(fā)生變化時,最終的結(jié)果也會受到影響。
箭頭函數(shù)
箭頭函數(shù)有點類似于java 8中的新特性。
var f = x => x+1;
//和下面的寫法實現(xiàn)了相同的功能
var f = function(x){
return x+1;`
}
此處由于函數(shù)體比較簡單,所以省略了{...} return,并且當參數(shù)不止一個時,也需要使用(...)括起來。所以,箭頭函數(shù)也有以下幾種寫法:
(x,y) => x + y; //多個參數(shù)需要用括號括起來
(x,y) => { //實現(xiàn)復(fù)復(fù)雜邏輯體時,需要使用{}括起來
if(x > y)
return true;
else
return false;
}
/*空參也需要括號。像這種直接返回對象的時候,需要使用()把括號
把對象括起來,否則引起函數(shù)體和對象體的語法沖突而報錯
*/
() => ({name:"bixuan"}) 。
箭頭函數(shù)同時也修復(fù)了this的歷史遺留問題,不是在多層調(diào)用時指向window或者undefined了,而是指向詞法作用域,也就是外層調(diào)用這的OBJ。
//以前的寫法會出現(xiàn)undefined或者指向的了window
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}
};
//而箭頭函數(shù)則不會有這個問題出現(xiàn)
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj對象
return fn();
}
};
obj.getAge(); // 25
但是有一點也需要注意,就是箭頭函數(shù)因為綁定了this,所以再調(diào)用調(diào)用call()或者apply()的時候,傳入的第一個參數(shù)無法對this進行綁定,還是會和上面一樣,綁定在外層調(diào)用者上,所以call({...},X,Y)的第一個對象參數(shù)會被忽略掉。
var obj = {
birth: 1990,
getAge: function(year){
var b = this.birth;//1990
var fn = (y) => y - this.birth;//此時,this.birth還是1990
return fn.call({birth:3000},year);
}
};
var obj2 = {
birth: 1990,
getAge: function(year){
var b = this.birth;//1990
// var fn = (y) => y - this.birth;//此時,this.birth還是1990
var fn = function(y){
return y - this.birth;
};
return fn.call({birth:3000},year);
}
};
console.log(obj.getAge(2016));//結(jié)果是26
console.log(obj2.getAge(2016));//-984