JavaScript入门之基本函数详解


Posted in Javascript onOctober 21, 2011

总的来说,函数在JavaScript中可以:

◆ 被赋值给一个变量

◆ 被赋值为对象的属性

◆ 作为参数被传入别的函数

◆ 作为函数的结果被返回

◆ 用字面量来创建

函数对象

1.1 创建函数

创建JavaScript函数的一种不长用的方式(几乎没有人用)是通过new操作符来作用于Function“构造器”:

var funcName = new Function( [argname1, [... argnameN,]] body );

参数列表中可以有任意多的参数,然后紧跟着是函数体,比如:
var add = new Function("x", "y", "return(x+y)"); 
print(add(2, 4));

将会打印结果:

6

但是,谁会用如此难用的方式来创建一个函数呢?如果函数体比较复杂,那拼接这个String要花费很大的力气,所以JavaScript提供了一种语法糖,即通过字面量来创建函数:

function add(x, y){ 
return x + y; 
}

或:
var add = function(x, y){ 
return x + y; 
}

事实上,这样的语法糖更容易使传统领域的程序员产生误解,function关键字会调用Function来new一个对象,并将参数表和函数体准确的传递给Function的构造器。

通常来说,在全局作用域(作用域将在下一节详细介绍)内声明一个对象,只不过是对一个属性赋值而已,比如上例中的add函数,事实上只是为全局对象添加了一个属性,属性名为add,而属性的值是一个对象,即function(x, y){return x+y;},理解这一点很重要,这条语句在语法上跟:

var str = "This is a string";

并无二致。都是给全局对象动态的增加一个新的属性,如此而已。

为了说明函数跟其他的对象一样,都是作为一个独立的对象而存在于JavaScript的运行系统,我们不妨看这样一个例子:

function p(){ 
print("invoke p by ()"); 
} p.id = "func"; 
p.type = "function"; 
print(p); 
print(p.id+":"+p.type); 
print(p());

没有错,p虽然引用了一个匿名函数(对象),但是同时又可以拥有属性,完全跟其他对象一样,运行结果如下:

function (){
print("invoke p by ()");
}
func:function
invoke p by ()

1.2 函数的参数

在JavaScript中,函数的参数是比较有意思的,比如,你可以将任意多的参数传递给一个函数,即使这个函数声明时并未制定形式参数,比如:

function adPrint(str, len, option){ 
var s = str || "default"; 
var l = len || s.length; 
var o = option || "i"; s = s.substring(0, l); 
switch(o){ 
case "u": 
s = s.toUpperCase(); 
break; 
case "l": 
s = s.toLowerCase(); 
break; 
default: 
break; 
} 
print(s); 
} 
adPrint("Hello, world"); 
adPrint("Hello, world", 5); 
adPrint("Hello, world", 5, "l");//lower case 
adPrint("Hello, world", 5, "u");//upper case

函数adPrint在声明时接受三个形式参数:要打印的串,要打印的长度,是否转换为大小写的标记。但是在调用的时候,我们可以按顺序传递给adPrint一个参数,两个参数,或者三个参数(甚至可以传递给它多于3个,没有关系),运行结果如下:

Hello, world
Hello
hello
HELLO

事实上,JavaScript在处理函数的参数时,与其他编译型的语言不一样,解释器传递给函数的是一个类似于数组的内部值,叫arguments,这个在函数对象生成的时候就被初始化了。比如我们传递给adPrint一个参数的情况下,其他两个参数分别为undefined.这样,我们可以才adPrint函数内部处理那些undefined参数,从而可以向外部公开:我们可以处理任意参数。

我们通过另一个例子来讨论这个神奇的arguments:

function sum(){ 
var result = 0; 
for(var i = 0, len = arguments.length; i < len; i++){ 
var current = arguments[i]; 
if(isNaN(current)){ 
throw new Error("not a number exception"); 
}else{ 
result += current; 
} 
} return result; 
} 
print(sum(10, 20, 30, 40, 50)); 
print(sum(4, 8, 15, 16, 23, 42));//《迷失》上那串神奇的数字 
print(sum("new"));

函数sum没有显式的形参,而我们又可以动态的传递给其任意多的参数,那么,如何在sum函数中如何引用这些参数呢?这里就需要用到arguments这个伪数组了,运行结果如下:

150
108
Error: not a number exception

函数作用域

作用域的概念在几乎所有的主流语言中都有体现,在JavaScript中,则有其特殊性:JavaScript中的变量作用域为函数体内有效,而无块作用域,我们在Java语言中,可以这样定义for循环块中的下标变量:

public void method(){
for(int i = 0; i < obj1.length; i++){
//do something here;
}
//此时的i为未定义
for(int i = 0; i < obj2.length; i++){
//do something else;
}
}
而在JavaScript中:

function func(){ 
for(var i = 0; i < array.length; i++){ 
//do something here. 
} 
//此时i仍然有值,及I == array.length 
print(i);//i == array.length; 
}

JavaScript的函数是在局部作用域内运行的,在局部作用域内运行的函数体可以访问其外层的(可能是全局作用域)的变量和函数。JavaScript的作用域为词法作用域,所谓词法作用域是说,其作用域为在定义时(词法分析时)就确定下来的,而并非在执行时确定,如下例:
var str = "global"; 
function scopeTest(){ 
print(str); 
var str = "local"; 
print(str); 
} scopeTest();

运行结果是什么呢?初学者很可能得出这样的答案:

global
local

而正确的结果应该是:

undefined
local

因为在函数scopeTest的定义中,预先访问了未声明的变量str,然后才对str变量进行初始化,所以第一个print(str)会返回undifined错误。那为什么函数这个时候不去访问外部的str变量呢?这是因为,在词法分析结束后,构造作用域链的时候,会将函数内定义的var变量放入该链,因此str在整个函数scopeTest内都是可见的(从函数体的第一行到最后一行),由于str变量本身是未定义的,程序顺序执行,到第一行就会返回未定义,第二行为str赋值,所以第三行的print(str)将返回”local”。

函数上下文

在Java或者C/C++等语言中,方法(函数)只能依附于对象而存在,不是独立的。而在JavaScript中,函数也是一种对象,并非其他任何对象的一部分,理解这一点尤为重要,特别是对理解函数式的JavaScript非常有用,在函数式编程语言中,函数被认为是一等的。

函数的上下文是可以变化的,因此,函数内的this也是可以变化的,函数可以作为一个对象的方法,也可以同时作为另一个对象的方法,总之,函数本身是独立的。可以通过Function对象上的call或者apply函数来修改函数的上下文:

call和apply

call和apply通常用来修改函数的上下文,函数中的this指针将被替换为call或者apply的第一个参数,我们不妨来看看JavaScript入门之对象与JSON中的例子:

//定义一个人,名字为jack
var jack = {
name : "jack",
age : 26
}

//定义另一个人,名字为abruzzi
var abruzzi = {
name : "abruzzi",
age : 26
}

//定义一个全局的函数对象
function printName(){
return this.name;
}

//设置printName的上下文为jack, 此时的this为jack
print(printName.call(jack));
//设置printName的上下文为abruzzi,此时的this为abruzzi
print(printName.call(abruzzi));

print(printName.apply(jack));
print(printName.apply(abruzzi));
只有一个参数的时候call和apply的使用方式是一样的,如果有多个参数:

setName.apply(jack, ["Jack Sept."]);
print(printName.apply(jack));

setName.call(abruzzi, "John Abruzzi");
print(printName.call(abruzzi));
得到的结果为:

Jack Sept.
John Abruzzi
apply的第二个参数为一个函数需要的参数组成的一个数组,而call则需要跟若干个参数,参数之间以逗号(,)隔开即可。

使用函数

前面已经提到,在JavaScript中,函数可以

◆ 被赋值给一个变量

◆ 被赋值为对象的属性

◆ 作为参数被传入别的函数

◆ 作为函数的结果被返回

我们就分别来看看这些场景:

赋值给一个变量:

//声明一个函数,接受两个参数,返回其和
function add(x, y){
return x + y;
}

var a = 0;
a = add;//将函数赋值给一个变量
var b = a(2, 3);//调用这个新的函数a
print(b);
这段代码会打印”5”,因为赋值之后,变量a引用函数add,也就是说,a的值是一个函数对象(一个可执行代码块),因此可以使用a(2, 3)这样的语句来进行求和操作。

赋值为对象的属性:

var obj = { 
id : "obj1" 
} obj.func = add;//赋值为obj对象的属性 
obj.func(2, 3);//返回5

事实上,这个例子与上个例子的本质上是一样的,第一个例子中的a变量,事实上是全局对象(如果在客户端环境中,表示为window对象)的一个属性。而第二个例子则为obj对象,由于我们很少直接的引用全局对象,就分开来描述。

作为参数传递:

//高级打印函数的第二个版本
function adPrint2(str, handler){
print(handler(str));
}

//将字符串转换为大写形式,并返回
function up(str){
return str.toUpperCase();
}

//将字符串转换为小写形式,并返回
function low(str){
return str.toLowerCase();
}

adPrint2("Hello, world", up);
adPrint2("Hello, world", low);
运行此片段,可以得到这样的结果:

HELLO, WORLD
hello, world

应该注意到,函数adPrint2的第二个参数,事实上是一个函数,将这个处理函数作为参数传入,在adPrint2的内部,仍然可以调用这个函数,这个特点在很多地方都是有用的,特别是,当我们想要处理一些对象,但是又不确定以何种形式来处理,则完全可以将“处理方式”作为一个抽象的粒度来进行包装(即函数)。

作为函数的返回值:

先来看一个最简单的例子:

function currying(){ 
return function(){ 
print("curring"); 
} 
}

函数currying返回一个匿名函数,这个匿名函数会打印”curring”,简单的调用currying()会得到下面的结果:
function (){ 
print("curring"); 
}

如果要调用currying返回的这个匿名函数,需要这样:

currying()();
第一个括号操作,表示调用currying本身,此时返回值为函数,第二个括号操作符调用这个返回值,则会得到这样的结果:

currying

Javascript 相关文章推荐
在网页中使用document.write时遭遇的奇怪问题
Aug 24 Javascript
深入理解JavaScript系列(7) S.O.L.I.D五大原则之开闭原则OCP
Jan 15 Javascript
使用javascript实现json数据以csv格式下载
Jan 09 Javascript
js表单中选择框值的获取及表单的序列化
Dec 17 Javascript
移动端jQuery修正Web页面滑动时div问题的两则实例
May 30 Javascript
JS实现列表的响应式排版(推荐)
Sep 01 Javascript
bootstrap侧边栏圆点导航
Jan 11 Javascript
10道典型的JavaScript面试题
Mar 22 Javascript
详解微信小程序 相对定位和绝对定位
May 11 Javascript
详解js静态检查工具eslint配置文件
Nov 23 Javascript
Vue+Node实现商品列表的分页、排序、筛选,添加购物车功能详解
Dec 07 Javascript
微信小程序上传帖子的实例代码(含有文字图片的微信验证)
Jul 11 Javascript
JavaScript入门之对象与JSON详解
Oct 21 #Javascript
JavaScript内核之基本概念
Oct 21 #Javascript
输入框的字数时时统计—关于 onpropertychange 和 oninput 使用
Oct 21 #Javascript
学习JavaScript的最佳方法分享
Oct 21 #Javascript
修复IE9&amp;safari 的sort方法
Oct 21 #Javascript
修复ie8&amp;chrome下window的resize事件多次执行
Oct 20 #Javascript
jquery ajax return没有返回值的解决方法
Oct 20 #Javascript
You might like
PHP spl_autoload_register实现自动加载研究
2011/12/06 PHP
php自动给网址加上链接的方法
2015/06/02 PHP
PHP数组相加操作及与array_merge的区别浅析
2016/11/26 PHP
Symfony2针对输入时间进行查询的方法分析
2017/06/28 PHP
(function($){...})(jQuery)的意思
2010/07/22 Javascript
关于递归运算的顺序测试代码
2011/11/30 Javascript
javascript学习(二)javascript常见问题总结
2013/01/02 Javascript
jQuery不间断滚动效果(模拟百度新闻支持文字/图片/垂直滚动)
2013/02/05 Javascript
jquery统计输入文字的个数并对其进行判断
2014/01/07 Javascript
jQuery在线选座位插件seat-charts特效代码分享
2015/08/27 Javascript
利用nodejs监控文件变化并使用sftp上传到服务器
2017/02/18 NodeJs
Vue.js 中的 $watch使用方法
2017/05/25 Javascript
Webpack打包css后z-index被重新计算的解决方法
2017/06/18 Javascript
解决Vue使用mint-ui loadmore实现上拉加载与下拉刷新出现一个页面使用多个上拉加载后冲突问题
2017/11/07 Javascript
create-react-app构建项目慢的解决方法
2018/03/14 Javascript
vue mounted组件的使用
2018/06/18 Javascript
layer提示框添加多个按钮选择的实例
2019/09/12 Javascript
js实现录音上传功能
2019/11/22 Javascript
js实现随机抽奖
2020/03/19 Javascript
Antd-vue Table组件添加Click事件,实现点击某行数据教程
2020/11/17 Javascript
使用python实现http及ftp服务进行数据传输的方法
2018/10/26 Python
Python 实现文件打包、上传与校验的方法
2019/02/13 Python
python单线程下实现多个socket并发过程详解
2019/07/27 Python
简单了解Python3 bytes和str类型的区别和联系
2019/12/19 Python
Python网页解析器使用实例详解
2020/05/30 Python
python 怎样进行内存管理
2020/11/10 Python
CSS3 Columns分列式布局方法简介
2014/05/03 HTML / CSS
HTML5时代CSS设置漂亮字体取代图片
2014/09/04 HTML / CSS
美国购买新书和二手书网站:Better World Books
2018/10/31 全球购物
印度手工编织服装和家居用品商店:Fabindi
2019/10/07 全球购物
关于青春的演讲稿三分钟
2014/08/22 职场文书
简单的辞职信怎么写
2015/02/28 职场文书
2015年宣传工作总结
2015/04/08 职场文书
创业计划书之冷饮店
2019/09/27 职场文书
如何用Node.js编写内存效率高的应用程序
2021/04/30 Javascript
Spring Cloud 中@FeignClient注解中的contextId属性详解
2021/09/25 Java/Android