JavaScript函数

本文只是关于JavaScript函数的一些入门介绍,写的有些宽泛和简单,目的是对JavaScript的函数有一个大概的理解。

函数定义

声明函数

一般创建(声明)一般分成四部分:

  • function关键字
  • 函数名称
  • 函数的参数列表
  • 函数体:包含需要执行的所有语句。特别注意return语句,它决定了调用函数的返回值。

一般格式如下示例:

function findMax(a, b) {
    if (a >b ) {
        return a;
    } else {
        return b;
    }
}
//函数调用
var maxNumber=findMax(12, 56);

函数表达式

把上面函数声明中的代码去除函数名称,就得到了一个函数表达式。一般可以把一个函数表达式赋值给一个变量,
然后我们就可以把这个变量当成函数名称一样使用,进行函数调用。把上面的函数声明修改成表达式方式的创建,如下示例

var findMax = function (a, b){
    if (a >b ) {
        return a;
    } else {
        return b;
    }
}
//函数调用
var maxNumber=findMax(12, 56);

使用 new Function()创建函数

除了上面的函数声明和函数表达式的方式创建函数,我们也可以使用new Function()的方式创建函数。new Function()传入的最后一个参数是函数体,
其他参数是创建的函数的函数参数。上面的示例修改成new Function()方式如下:

var fundMax=new Function('a','b','if(a>b){return a;} else{return b;}');
//函数调用
var maxNumber=findMax(12, 56);

自调用函数

自调用函数一般用于一次性调用,而相比于直接把函数体中的代码写在全局作用域,这种方式可以避免全局作用域变量污染。很多JS类型就是使用这种方式编写的。
示例代码如下:

var maxNumber = (
    function(a, b){
        if (a >b ) {
            return a;
        } else {
            return b;
        }
    }
)(12,56);

函数声明的提升(Hoisting)

函数声明的提升与变量声明的提升是一样的概念,JS解析器会把函数的声明提升到作用域的最上面执行。所以可以在声明函数之前进行函数调用。
注意:函数表达式和new Function()的方式创建函数是不会被提升的。
如下示例:

//在函数声明前进行函数调用
var maxNumber=findMax(12, 56);
//函数声明
function findMax(a, b) {
    if (a >b ) {
        return a;
    } else {
        return b;
    }
}

函数是一种特殊的对象

JS中除了值类型(string/number/boolean/null/undefined)的变量,其他变量都是引用类型。而所有的引用类型的变量都是继承Object对象。
函数也是一种特殊的对象,也是继承自Object。可以使用如下代码进行测试。

function findMax(a, b){
    if (a >b ) {
        return a;
    } else {
        return b;
    }
}
//所有的函数都是Function类型的
console.log(findMax instanceof Function); //输出true
//而Function是继承自Object,所以所有的函数也是继承自Object。(也就是所有的函数都是Object的子类)
console.log(findMax instanceof Object);  //输出true

函数参数

函数的显示参数和隐式参数

在函数声明中的参数列表的参数都称为显示参数,也叫做形参

在函数调用时传入的所有的参数都称为隐式参数,也叫做实参

如下代码示例:

//这是函数声明。a和b两个参数称之为函数的显示参数,也叫做形参。
function findMax(a, b) {
    if (a >b ) {
        return a;
    } else {
        return b;
    }
}
//这是函数调用。传入的12和56成为函数的隐式参数,也叫做实参。
var maxNumber=findMax(12, 56);

函数参数的一些规则

JS是弱类型语言,所以函数参数存在如下一些规则:

  • 声明函数的时候不需要指定形参的数据类型
  • 函数调用的时候不会对实参的数据类型和个数进行检测。

如果需要对函数参数的数据类型和个数进行检测,则需要自己在函数内部编写代码进行参数类型和个数的检测。

函数参数的默认值

很多语言都可以对函数的参数设置默认值:当调用函数时,对应的参数没有传入,则该参数的值就为设置的默认值。
但是在ES6版本之前的JS语法是不支持函数参数默认值设置的。所以我们使用如下的方式实现函数参数默认值的效果:

function findMax(a, b) {
    //相当于给参数a设置默认值为0
    a = a || 0;
    //相当于给参数b设置默认值为0
    b = b || 0;
    
    if (a >b ) {
        return a;
    } else {
        return b;
    }
}

函数的arguments对象

在函数体中我们可以使用arguments对象来获取函数调用时传入的实参数据。这并不受形参的影响。如下代码示例:

//没有一个形参
function findMax(){
    //使用使用arguments.length获取实参个数,可以像数组一样访问实参的值
    for(var i=0, maxNumber=arguments[0]; i< arguments.length; i++){
        if(arguments[i] > maxNumber){
            maxNumber = arguments[i];
        }
    }
    return maxNumber;
}

//函数调用传入的实参
var maxNumber=findMax(12,-1,34,544,55,0);

函数参数的值传递和引用传递

数据类型分成两大类:值类型和引用类型。如果函数调用时,实参是值类型就成为值传递,实参为引用类型就成为引用传递。如果需要深入了解其中的原理,
就需要知道值类型和引用类型变量的内存分配机制和内存中的知识。这里不做深入,相关知识网上有很多。
这里通过如下示例解释一下参数的值传递和引用传递。

function double(number,numberList){
    number *= 2;
    for(var i=0; i< numberList.length; i++){
        numberList[i] *= 2;
    }
}
var num=3,
    numList=[3, 5];
double(num,numList);

//因为num是值类型,所以num在double(num,numList)函数执行后值没有改变:3
console.log(num);
//因为numList是引用类型,所以numList在double(num,numList)函数执行之后值发生了改变:[6,10]
console.log(numList);

函数调用

把函数调用表达式看作一个值使用

可以把函数调用看作一个值来使用,这个值是由函数的返回值决定的。如下示例代码:

function findMax(){
    for(var i=0,maxNumber=arguments[0]; i<arguments.length; i++){
        if(maxNumber<arguments[i]){
            maxNumber=arguments[i];
        }
    }
    return arguments[i];
}
//这里我们就可以把函数调用findMax(12,45,2)看作一个整数,因为该函数调用表达式返回一个整数。
var maxDouble=findMax(12,45,2) * 2;

全局函数

在全局作用域中创建的函数叫做全局函数。而web应用中所有的全局变量和函数都属于全局对象window的属性。
所以我们可以把全局函数看作是window对象的方法。如下示例代码:

function sum(a, b){
    return a+b;
}
var a;
//下面两个调用是等价的
a= sum(1,3);
a=window.sum(1,3);

函数中的this关键字

函数中的this是一个关键字,this指向调用当前函数的对象。如下示例:

function setFirstName(firstName){
    this.firstName=firstName;
}
//在全局作用域中直接调用,则this指向全局对象(window)。因为在全局调用setFirstName()相当于window.setFirstName()
setFirstName("james");  //等价于window.setFirstName("james");

var obj={
    firstName:"pual",
    setFirstName:setFirstName
};
//这是this指向obj对象。
obj.setFirstName("james");

//把函数当成构造函数使用时,this指向构造的对象。
var obj1=new setFirstName("james");

函数作为方法调用

当函数作为对象成员的时候,我们称该函数为对象的方法。这时我们可以通过对象来调用该方法。
如下示例:

var person={
    firstName:"james",
    lastName:"young",
    getFullName:function(){
        return this.firstName + " " + this.lastName;
    }
};
//通过对象调用该对象的方法
var fullName=person.getFullName();

构造函数

构造函数本身就是一个普通的函数。所以所有的函数都可以是构造函数,只需要在函数调用的前面加上new关键字时,
我们就称该函数为构造函数。但是构造函数的一般情况下主要目的是用于构造对象。
如下示例:

function Person(name,age){
    this.name=name;
    this.age=age;
    this.grow=function(){
        this.age += 1;
    }
}
//使用上面的函数构造对象,则这个函数就称为构造函数了
var person=new Person("赵四",34);
//使用对象成员(属性和方法)
console.log(person.name);
person.grow();

函数的call和apply方法

函数是对象类型的,所以函数也有属性和方法。函数有两个常用的方式:callapply
如果存在一个函数dumpObject,即便它不是某个对象obj的方法,
也可以通过dumpObject函数的call或者apply方法实现:对象obj对函数dumpObject的调用,
也就是函数dumpObject中的this关键字指向对象obj
如下示例:

function dumpObject(prefix,suffix){
    var dumpString=prefix;
    for(var prop in this){
        dumpString += prop + ':' + this[prop] + '\n\r';
    }
    dumpString += suffix;
    console.log(dumpString);
}
//创建一个对象,该对象不存在dumpObject方法
var obj={
    name:"赵四",
    age:78,
    score:70
};
//可以使用函数的call或者apply方法实现:obj像调用自己方法一样的效果调用dumpObject函数
var prefix='***start***\n\r',
    suffix='***start***\n\r';
dumpObject.call(obj,prefix,suffix);
//从函数调用中可以看出,apply与call的唯一不用是:apply方法通过数组的形式,把函数dumpObject的参数组织成apply的一个参数。
dumpObject.apply(obj,[prefix,suffix]);

闭包

什么是闭包

闭包的概念其实很简单,一句话就可以表达。

闭包是可以访问上一层函数作用域里变量的函数,即便上一层函数已经关闭。

但是需要理解这句话还是需要些功夫的。首先提取一下这句话,得到三个关键点:

  • 闭包是函数
  • 闭包可以访问上一层函数作用域变量:闭包是内嵌函数,而且可以访问了上一层函数的局部变量。
  • 即便是上一层函数已经关闭:需要把闭包使用return语句返回。
    还需要清楚的一个概念是:JavaScript中所有函数都能访问它们上层作用域中的变量。
    闭包实现一个计数器的实例代码:
//使用一个自调用函数,得到了一个闭包
var counter=(
    function(){
        var count = 0;
        //返回的函数就是闭包,访问了上一层函数的count局部变量
        return function(){
            count += 1;
        }
    }
)();

为什么需要闭包

上面介绍了什么是闭包。但是我们为什么需要闭包呢?需要明白这个问题前需要了解局部变量全局变量的局限性,已经闭包的特性。
局部变量的特点是:函数执行完就被销毁,所以无法实现变量的累积(如计数器的功能)。
全局变量的特点是:在全局都可以访问,只有在页面关闭后才销毁。可以实现指定的计数器功能。问题在于:全局变量是所有地方都可以修改,
而且尽量少使用全局变量,避免全局变量污染

针对全局变量局部变量在这种场景下存在的问题,闭包的相关特性正好能够解决这个问题。因为闭包可以使用上一层函数的局部变量,
这样避免了全局变量的污染。而且因为闭包被上一层函数返回,并且被外部引用,所以闭包引用了的上层函数的变量在上层函数结束后依然不会被销毁。
这样又避免了普通局部变量在函数执行完就被销毁,无法累积的问题。

所以这就是闭包的特性。也是闭包被使用的场景。

在编写javascript代码时,我们也要警惕闭包带来的问题:引用的局部变量迟迟不能释放,容易导致内存的泄露。

这里只是简单的介绍了一下闭包,以后有机会可以专门写一篇关于闭包的文章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容