内容要点:

一.可选形参

当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。

例如:

//将对象o中可枚举的属性名追加至数组

//如果省略a,则创建一个新数组并返回这个新数组。

function getPropertyNames(o,/*optional*/a){

if(a === undefined) a= [];

for(var property in o) a.push(property);

return a;

}

//这个函数调用可以传入1个或2个实参

var a = getPropertyNames(o); //将o的属性存储到一个新数组中

getPropertyNames(p,a); //将p的属性追加至数组a中

需要注意的是,当用这种可选实参来实现函数时,需要将可选实参放在实参列表的最后。那些调用你的函数的程序员是没办法省略第一个实参并传入第二个实参的,它必须将undefined作为第一个实参显式传入。同样注意在函数定义中使用注释/*option*/来强调形参是可选的。

if(a ===undefined ) a = []; //如果未定义,则使用新数组

相等于 a = a || [];

"||"运算符,如果第一个实参是真值的话就返回第一个实参;否则返回第二个实参。在这个场景下,如果作为第二个实参传入任意对象,那么函数就会使用这个对象。如果省略掉第二个实参(或者传递null以及其他任何假值),那么就创建一个空数组,并赋值给a。

需要注意的是,使用 "||"运算符代替if语句的前提是a必须预先声明,否则a=a||[]会报引用错误,在这个例子中a是作为形参传入的,相当于var a,即已经声明了a,所以这样用是没有 问题的。

二.可变长的实参列表:实参对象

当调用函数的时候传入的实参个数超过函数定义时的形参个数时,没有办法直接获得未命名值得引用。参数对象解决了这个问题。

在函数体内,标识符arguments是指向实参对象的引用,实参对象是一个类数组对象,这样可以通过数字下标就能访问传入函数的实参值,而不用非要通过名字来得到实参。

实参对象在很多地方都非常有用,下面的例子展示了使用它来验证实参的个数,从而调用正确的逻辑,因为JS本身不会这么做:

function f(x,y,z){

//首先,验证传入实参的个数是否正确

if(arguments.length!=3){

throw new Error("function f called with" + arguments.length +"arguments,but it expects 3 arguments");

}

//再执行函数的其他逻辑...

}

需要注意的是,通常不必这样检测实参个数。大多数情况下JS的默认行为是可以满足需要的:省略的实参都将是undefined,多出的参数会自动省略。

实参对象有一个重要的用处,就是让函数可以操作任意数量的实参。下面的函数就可以接收任意数量的实参,并返回传入实参的最大值

function max(/* ... */){

var max = Number.NEGATIVE_INFINITY;

//遍历实参,查找并记住最大值

for(var i =0 ;i < arguments.length; i++){

if(arguments[i]>max) max = arguments[i];

//返回最大值

return max;

}

}

var largest = max(1,10,100,2,3,1000,4,5,10000,6); //=>10000

类似这种函数可以接收任意个数的实参,这种函数也称为 "不定实参函数" ,这个术语源自古老的C语言

注意,不定实参函数的实参个数不能为零,arguments[]对象最适合的应用场景是在这样一类函数中,这类函数包含固定个数的命名和必需参数,以及随后个数不定的可选实参。

记住,arguments并不是真正的数组,它是一个实参对象。每个实参对象都包含以数字为索引的一组元素以及length属性,但它毕竟不是真正的数组。

数组对象包含一个非同寻常的特性。在非严格模式下,当一个函数包含若干形参,实参对象的数组元素是函数形参所对应实参的别名,实参对象中以数字索引,并且形参名称可以认为是相同变量的不同命名。

通过实参名字来修改实参值的话,通过arguments[]数组也可以获取到更改名的值,下面这个例子清楚地说明了这一点:

function f(x){

console.log(x); //输出实参的初始值

arguments[0] = null; //修改实参数组的元素同样会修改x的值

console.log(x); //输出"null"

}

如果实参对象是一个普通数组的话,第二条console.log(x)语句的结果绝对不会是null,

在这个例子中,arguments[0]和x指代同一个值,修改其中一个的值会影响到另一个。

在ES5中移除了实参对象的这个特殊特性。在严格模式下还有一点(和非严格模式下相比的)不同,在非严格模式下,函数里的arguments仅仅是一个标识符,在严格模式中,它变成了一个保留字。严格模式中的函数无法使用arguments作为形参名或局部变量名,也不能给arguments赋值。

callee和caller属性

除了数组元素,实参对象还定义了callee和caller属性。在ES5严格模式中,对这两个属性的读写操作都会产生一个类型错误。而在非严格模式下,ES标准规范规定callee属性指代当前正在执行的函数。通过caller属性可以访问调用栈。callee属性在某些时候会非常有用,比如在匿名函数中通过callee来递归地调用自身。

var factorial = function(x){

if(x<=1) return 1;

return x*arguments.callee(x-1);

};

三.将对象属性用做实参

当一个函数包含三个形参时,对于程序员来说,要记住调用函数中实参的正确顺序实在让人头疼。

为了解决这个问题,最好通过名/值对的形式来传入参数,这样参数的顺序就无关紧要了。

为了实现这种风格的方法调用,定义函数的时候,传入的实参都写入一个单独的对象之中,在调用的时候传入一个对象,对象中的名/值对是真正需要的实参数据。

例如:

//将原始数值的length元素复制至目标数组

//开始复制原始数组的form_start元素

//并且将其复制至目标数组的to_start中

//要记住实参的顺序并不容易

function arraycopy(/*array*/ from,/*index*/from_start,/*array*/to,/*index*/to_start,/*index*/length){
            for(var i=from_start;i<length;i++){           
                to[to_start]=from[i];    
                to_start++;                   
            }  
        
       }
       //这个版本的实现
       function easycopy(args){
         arraycopy(args.from,args.from_start || 0,args.to,args.to_start || 0,args.length);   
       }
       
       var a = [1,2,3,4],b=[];
       easycopy({to:b,length:4,from:a,from_start:2});
       console.log(a);  //[1,2,3,4]
       console.log(b);  //[3,4]

四.实参类型

JS方法的形参并未声明类型,在形参传入函数体之前也未做任何类型检查。

可以采用语义化的单词给函数实参命名,或者像刚才的示例代码中的arraycopy()方法一样给实参补充注释,以此使代码自文档化,对于可选的实参来说,可以在注释中补充一下“这个参数是可选的”。

当一个方法可以接收任意数量的实参时,可以使用省略号:function max( /*number...*/ ){ /* 代码区 */ }

两个函数:

//返回数组(或类数组对象)a的元素的累加和

//数组a中必须为数字、null和undefined的元素都将忽略

function sum(a){

if(isArrayLike(a)){

var total = 0;

for(var i=0;i<a.length;i++){

var element =a[i];

if(element ==null) continue;

if(isFinite(element)) total +=element;

else throw new Error("sum():elements must be finite numbers");

}

return total;

}

else throw new Error("sum():argument must be array-like");

}

//isArrayLike函数

function isArrayLike(o){

if(o &&            //o非null、undefined等

typeof o === "object" &&  //o是对象

isFinite(o.length) &&  //o.length是有限数组

o.length>=0 &&       //o.length为非负数

o.length===Math.floor(o.length) &&  //o.length是整数

o.length<4294967296)       //o.length< 2^23

return true;              // o是类数组对象

else

return false;       //否则它不是

}

//JS是一种非常灵活的弱类型语言,有时适合编写实参类型和实参个数的不确定性的函数。

//flexisum()方法:它可以接收任意数量的实参,并可以递归地处理实参是数组的情况,

//这样的话,它就可以用做 不定实参函数或者实参是数组的函数。

//此外,这个方法尽可能的在抛出异常之前将非数字转换为数字:

function flexisum(a){

var total = 0;

for(var i =0;i<arguments.length;i++){

var element = arguments[i],n;

if(element ==null) continue; //忽略null和undefined实参

if(Array.isArray(element)) //如果实参是数组

n=flexisum.apply(this,element);

else if(typeof element === "function") //否则,如果是函数

n=Number(element()); //调用它并做类型转换

else

n=Number(element);  //否则直接做类型转换

if(isNaN(n)) //如果没法转换为数字,则抛出异常

throw Error("flexisum():can't convert"+element+"to number");

total +=n; 否则,将n累加到total

}

return total;

}

//var g =[1,2,null,undefined];//3
         //var g =[1,2,null,undefined,"123"];//126
         //var g =[1,2,null,true];//4
         //var g =[1,2,null,[{}]];//Error: flexisum():can't convert[object Object]to numbe

// var g =[1,2,null,undefined,{a:1,b:2,c:3}] //Error: flexisum():can't convert[object Object]to number
         //var g =[1,2,null,true,function(){console.log("error")}];//Error: flexisum():can't convertfunction (){console.log("error")}to number
         //var g =[1,2,null,undefined,[1,2,3]] //9
             var hh=flexisum(g);
             console.log(hh);

《JS权威指南学习总结--8.3 函数的实参和形参》的更多相关文章

  1. 简单物联网:外网访问内网路由器下树莓派Flask服务器

    最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...

  2. 利用ssh反向代理以及autossh实现从外网连接内网服务器

    前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...

  3. 外网访问内网Docker容器

    外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...

  4. 外网访问内网SpringBoot

    外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...

  5. 外网访问内网Elasticsearch WEB

    外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...

  6. 怎样从外网访问内网Rails

    外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...

  7. 怎样从外网访问内网Memcached数据库

    外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...

  8. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...

  9. 怎样从外网访问内网DB2数据库

    外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...

  10. 怎样从外网访问内网OpenLDAP数据库

    外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...

随机推荐

  1. CSS3的radial-gradient(径向渐变)

    所谓径向渐变,如图下,类似光晕 语法: radial-gradient(  [    [渐变大小]?    [ at 渐变圆心坐标]?  ,]?  颜色[ 开始位置]  [,颜色[ 开始位置]]+); ...

  2. UOJ#117. 欧拉回路

    #117. 欧拉回路 题目描述 有一天一位灵魂画师画了一张图,现在要你找出欧拉回路,即在图中找一个环使得每条边都在环上出现恰好一次. 一共两个子任务: 这张图是无向图.(50分) 这张图是有向图.(5 ...

  3. python绝技 — 嗅探FTP登录口令

    代码: #!/usr/bin/python #--*--coding=utf-8--*-- import optparse from scapy.all import * def ftpsniff(p ...

  4. trove design翻译

    trove的设计 高水平的描述 trove的目的是支持单租户数据库,在一个nova的实例中.没有限制nova是如何配置的,因为trove与其他OpenStack组件纯粹通过API. Trove-api ...

  5. 【Python】32. Longest Valid Parentheses

    Given a string containing just the characters '(' and ')', find the length of the longest valid (wel ...

  6. Ubuntu中Qt5.7.0无法输入中文

    把libfcitxplatforminputcontextplugin.so复制到安装的Qt目录下的两个文件夹中 sudo apt install fcitx-frontend-qt5 sudo cp ...

  7. strstr库函数实现

    #include<stdio.h> #include<assert.h> char *strstr(char* src,char *sub) { if(src==NULL||N ...

  8. arm指令集

    http://blog.chinaunix.net/uid-20769502-id-112445.html

  9. List list = new ArrayList()

    方便以后扩展List是一个接口,而ArrayList 是一个类. ArrayList 继承并实现了List.List list = new ArrayList();这句创建了一个ArrayList的对 ...

  10. 自定义报表开发(HTML/XML)

    定义报表执行的包或存储过程: --创建包头 CREATE OR REPLACE PACKAGE XXPLM_AARONTEST001 IS PROCEDURE MAIN(errbuf OUT VARC ...