- 浏览: 454238 次
- 性别:
- 来自: 西安
文章分类
最新评论
-
进退取舍:
谢谢,这个用上了!!
Java 一个线程池的示例 -
pb_water:
感谢楼主,打算买楼主的书,支持一下,楼主功德无量
JavaScript内核系列第0版整理稿下载 -
lancezhcj:
有图会直观的多呢,再摸索摸索
有限自动机与建模 -
hsmsyy:
这里应该是原创了吧,楼主我觉得闭包的作用:实现面向对象。有待商 ...
JavaScript内核系列 第7章 闭包 -
wll52:
在应用退出之前,需要释放连接 con.disconnect() ...
使用smack与GTalk通信
第四章 函数
函数,在C语言之类的过程式语言中,是顶级的实体,而在Java/C++之类的面向对象的语言中,则被对象包装起来,一般称为对象的方法。而在JavaScript中,函数本身与其他任何的内置对象在低位上是没有任何区别的,也就是说,函数本身也是对象。
总的来说,函数在JavaScript中可以:
- 被赋值给一个变量
- 被赋值为对象的属性
- 作为参数被传入别的函数
- 作为函数的结果被返回
- 用字面量来创建
4.1函数对象
4.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));
将会打印结果:
但是,谁会用如此难用的方式来创建一个函数呢?如果函数体比较复杂,那拼接这个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虽然引用了一个匿名函数(对象),但是同时又可以拥有属性,完全跟其他对象一样,运行结果如下:
print("invoke p by ()");
}
func:function
invoke p by ()
4.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
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这个伪数组了,运行结果如下:
108
Error: not a number exception
4.2函数作用域
作用域的概念在几乎所有的主流语言中都有体现,在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();
运行结果是什么呢?初学者很可能得出这样的答案:
local
而正确的结果应该是:
local
因为在函数scopeTest的定义中,预先访问了未声明的变量str,然后才对str变量进行初始化,所以第一个print(str)会返回undifined错误。那为什么函数这个时候不去访问外部的str变量呢?这是因为,在词法分析结束后,构造作用域链的时候,会将函数内定义的var变量放入该链,因此str在整个函数scopeTest内都是可见的(从函数体的第一行到最后一行),由于str变量本身是未定义的,程序顺序执行,到第一行就会返回未定义,第二行为str赋值,所以第三行的print(str)将返回”local”。
4.3函数上下文
在Java或者C/C++等语言中,方法(函数)只能依附于对象而存在,不是独立的。而在JavaScript中,函数也是一种对象,并非其他任何对象的一部分,理解这一点尤为重要,特别是对理解函数式的JavaScript非常有用,在函数式编程语言中,函数被认为是一等的。
函数的上下文是可以变化的,因此,函数内的this也是可以变化的,函数可以作为一个对象的方法,也可以同时作为另一个对象的方法,总之,函数本身是独立的。可以通过Function对象上的call或者apply函数来修改函数的上下文:
4.4 call和apply
call和apply通常用来修改函数的上下文,函数中的this指针将被替换为call或者apply的第一个参数,我们不妨来看看2.1.3小节的例子:
//定义一个人,名字为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则需要跟若干个参数,参数之间以逗号(,)隔开即可。
4.5使用函数
前面已经提到,在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
应该注意到,函数adPrint2的第二个参数,事实上是一个函数,将这个处理函数作为参数传入,在adPrint2的内部,仍然可以调用这个函数,这个特点在很多地方都是有用的,特别是,当我们想要处理一些对象,但是又不确定以何种形式来处理,则完全可以将“处理方式”作为一个抽象的粒度来进行包装(即函数)。
作为函数的返回值:
先来看一个最简单的例子:
function currying(){
return function(){
print("curring");
}
}
函数currying返回一个匿名函数,这个匿名函数会打印”curring”,简单的调用currying()会得到下面的结果:
function (){
print("curring");
}
如果要调用currying返回的这个匿名函数,需要这样:
currying()();
第一个括号操作,表示调用currying本身,此时返回值为函数,第二个括号操作符调用这个返回值,则会得到这样的结果:
附:由于作者本身水平有限,文中难免有纰漏错误等,或者语言本身有不妥当之处,欢迎及时指正,提出建议,参与讨论,谢谢大家!
评论
似乎我就是一个。。一部小心点 成隐藏了。。
函数本身与其他任何的内置对象在【低位】上是没有任何区别的
创建JavaScript函数的一种不【长用】的方式(几乎没有人用)
http://www.iteye.com/topic/652093
谢谢你的关注,说到大的项目,我们用了一年多的时间用JavaScript来做一个产品,现在还没有发布,不好透露。就是因为在这个产品的开发中,发现开发人员对JavaSccript的基本概念不熟悉,导致了很多的bug,才有感而发,整理的这个系列。
掌握这些基础概念对用JavaScript开发大一点的项目绝对是有很大作用的。
简直太棒了~~~
楼主辛苦~~
由于JavaScript的双重特性,jQuery只是在函数式这个特征上对JavaScript做了延伸,所以使用jQuery写代码的时候会感觉到非常的舒服,比如jQuery中的map/grep,以及遍历集合用的each等,都直接来源于lisp。
其实jQuery的集合对象,几乎完全可以和Lisp中的列表对应起来。
var str = "global"; function scopeTest(){ print(str); var str = "local"; print(str); } scopeTest();
这个例子非常有迷惑性,可以讲得更深入点。因为提到“词法作用域”,就必须得同时提到“作用域链”,两者结合才能有效理解。
对此例我的解释如下,不对请指正:
第1行的str(global)在定义时首先被存入作用域链的头部即全局作用域;
在调用scopeTest时,js解释器生成1个调用对象(call object),并将scopeTest内通过var定义的str设为它的一个属性,然后再把这个对象加到作用域链的最前端,通俗理解即具有最优先的访问权。
在访问str时,首先查询scopeTest的调用对象,如果从中没找到str则查询仅挨该对象的上一个作用域,以此类推直至全局作用域。
此例中从调用对象中即可查到str,因此全局对象中的str被覆盖:第3行,调用对象中有str定义但未赋值,因此为undefined;第4行,赋值;第5行,有值,所以是local。
var str = "global"; function scopeTest(){ print(str); //global } scopeTest();
为什么这里是global?因为scopeTest的调用对象里没有str的定义,因此必须查询全局作用域,所以得出global。
为什么“词法作用域”的效果是“函数在定义它的作用域中执行,而不是执行它的环境”?那是因为函数始终只能沿着定义它时就确定了的“作用域链”逐级访问变量,与外界执行环境无关:
var str = "global"; function scopeTest(){ alert(str); } (function(){ var str="aaa"; scopeTest();//global })();
个人理解:“词法分析”的作用是确定函数的上级作用域链,这点待求证。
这个朋友分析的非常好,调用对象(call object)我没有提及,是一个严重的bug,谢谢你的指正。
也希望大家多提意见,最后我会一一检查,汇总成比较完善的系列,谢谢!
var str = "global"; function scopeTest(){ print(str); var str = "local"; print(str); } scopeTest();
这个例子非常有迷惑性,可以讲得更深入点。因为提到“词法作用域”,就必须得同时提到“作用域链”,两者结合才能有效理解。
对此例我的解释如下,不对请指正:
第1行的str(global)在定义时首先被存入作用域链的头部即全局作用域;
在调用scopeTest时,js解释器生成1个调用对象(call object),并将scopeTest内通过var定义的str设为它的一个属性,然后再把这个对象加到作用域链的最前端,通俗理解即具有最优先的访问权。
在访问str时,首先查询scopeTest的调用对象,如果从中没找到str则查询仅挨该对象的上一个作用域,以此类推直至全局作用域。
此例中从调用对象中即可查到str,因此全局对象中的str被覆盖:第3行,调用对象中有str定义但未赋值,因此为undefined;第4行,赋值;第5行,有值,所以是local。
var str = "global"; function scopeTest(){ print(str); //global } scopeTest();
为什么这里是global?因为scopeTest的调用对象里没有str的定义,因此必须查询全局作用域,所以得出global。
为什么“词法作用域”的效果是“函数在定义它的作用域中执行,而不是执行它的环境”?那是因为函数始终只能沿着定义它时就确定了的“作用域链”逐级访问变量,与外界执行环境无关:
var str = "global"; function scopeTest(){ alert(str); } (function(){ var str="aaa"; scopeTest();//global })();
个人理解:“词法分析”的作用是确定函数的上级作用域链,这点待求证。
就是因为基础书太多,所以这个系列里没有诸如关键字,语句,表达式这样的内容,像犀牛这样的书,只能在书桌上读,想找本能捧在手里的还真不容易,所以想把这些比较核心的东西整理出来。
个人倾向于学院派,现在比较流行的面向实践的书,环境配置占一定的篇幅,各种框架的比较占一定的篇幅,基础知识占一定的篇幅,所以书本弄的非常厚,内容庞杂,反而失之精简。
JavaScript 扣肉
JavaScript 核心技术 ???
JavaScript核心技术貌似已经出版过一本书了,用别人的书名的话肯定不妥,呵呵。
Core或者Kernel直译过来就是内核了,或者该叫JavaScript Core Concepts.
JavaScript 扣肉
JavaScript 核心技术 ???
还有一本Good part of JavaScript里面把各个关键字都拿出来并且还用JS实现了一遍,感觉不错.里面将function 和 new关键字, constructor函数, prototype 等联系起来将整个闭包,继承之类的实现了一遍,受益匪浅.
楼主也可以以另类视角来剖析一番.期待..
嗯,你的建议确实都很有意思。我可以参考下这些牛人的资料,在消化之后再做整理。这个系列后边的章节中,面向对象的JavaScript会对原型链,继承等概念进行实例化的讨论。而函数式的JavaScript会涉及闭包,柯里化,map等的讨论,尽量写的详细些,并结合实例来讲.
谢谢你的建议!
发表评论
-
JavaScript内核系列 第15章 服务器端的JavaScript
2012-02-12 21:39 2380第15章已经在icodeit上发布,这一章分为上/下两篇,请朋 ... -
使用vim开发python及graphviz绘图
2011-12-23 14:49 6491基本需求 使用vim中的autocmd命令可以很容易的将正在 ... -
Java脚本技术应用实例
2011-01-22 11:24 4309前言 一直以来都很喜欢可以自由扩展的软件,这一点应该已经在很 ... -
可编程计算器(phoc)的设计与实现
2011-01-17 11:34 2012前言 借助JavaScript脚本 ... -
函数式编程(javascirpt)
2009-04-18 22:18 1289前言 Javascript,有人称 ... -
C和指针
2009-05-21 23:15 1134前言 指针是C的灵魂,正是指针使得C存在了这么多年,而且将长 ... -
C和指针(续)
2009-05-25 23:41 1384前言 上一篇《C和指针》可能对关于C和指针的有些内容没有说透 ... -
有限自动机与建模
2009-06-06 10:48 1827前言 在学校学程序设计语言的时候,能接触到的所有例子没有一个 ... -
事件和监听器
2009-06-21 22:06 1454前言 事件监听器是经 ... -
基于总线的消息服务(BBMS)的设计与实现
2009-07-25 22:19 1370前言 异步事件的通知机制在比较有规模的软件设计中必然会有涉及 ... -
JavaScript内核系列 第9章 函数式的Javascript
2010-05-13 19:20 3812第九章 函数式的Javascript 要说Ja ... -
JavaScript内核系列 第8章 面向对象的JavaScript(下)
2010-05-06 09:40 3681接上篇:JavaScript内核系列 第8章 面向对象的Jav ... -
JavaScript内核系列 第8章 面向对象的JavaScript(上)
2010-05-06 09:26 2930第八章 面向对象的 Javascript ... -
JavaScript内核系列 第7章 闭包
2010-05-04 08:48 3926第七章 闭包 闭包向来给包括JavaScript程序 ... -
JavaScript内核系列 第6章 正则表达式
2010-04-27 19:44 4079第六章 正则表达式 正则表达式是对字符串的结构 ... -
JavaScript内核系列 第5章 数组
2010-04-24 15:17 4594第五章 数组 JavaScript的数组也是一个比较 ... -
Swing小应用(Todo-List)之三
2010-04-22 20:47 2148前言 去年9月份开发的那个小工具sTodo,只是做到了能用, ... -
JavaScript内核系列 第3章 对象与JSON
2010-04-12 09:12 6181第三章 对象与JSON JavaScript对象与传 ... -
JavaScript内核系列 第2章 基本概念
2010-04-03 19:44 5740第二章 基本概念 ... -
JavaScript内核系列 第1章 前言及概述
2010-04-01 23:15 9979前言 从2006年第一次接触JavaScript至今,算来也 ...
相关推荐
### JavaScript内核系列知识点概述 #### 一、JavaScript发展历程与特性 **JavaScript简史:** JavaScript的历史始于20世纪90年代,随着互联网的发展,静态网页已无法满足日益增长的用户交互需求。1996年,网景公司...
第一章 概述 1.1 Javascript简史 在20世纪90年代,也就是早期的WEB站点上,所有的网页内容都是静态的,所谓静态是指,除了点击超链接,你无法通过任何方式同页面进行交互,比 如让页面元素接受事件,修改字体等。...
JavaScript的核心是其语言内核,包含了操作符、表达式、语句和子程序等基本元素。此外,JavaScript还有两个主要的应用领域:客户机端和服务器端。在客户机端,JavaScript主要用于增强用户体验,控制浏览器行为,如...
`ExternalInterface.call`的第一个参数是JavaScript函数的名称,第二个参数是传递给该函数的值。在对应的HTML页面`FlexConnectJavaScript.html`中,我们需要引入这段JavaScript代码: ```javascript function ...
E4A类库是这个平台上的一个重要组成部分,它们是一系列预先编写好的函数和模块,开发者可以直接调用,简化代码编写过程。 X5内核是腾讯公司开发的一款高性能的浏览器渲染引擎,广泛应用于QQ浏览器、微信内置浏览器...
2. **Web浏览器支持库**:这里提到了“精易Web浏览器支持库”,这是一个用于易语言的第三方库,可能提供了与Chromium内核交互的接口,允许易语言程序调用Chromium的功能,如加载网页、执行JavaScript等。 3. **...
2. **Web交互**:实现JavaScript和C#之间的交互,例如从C#调用JavaScript函数,或从JavaScript触发C#事件。 3. **控制功能**:控制浏览器的行为,如前进、后退、刷新、加载特定URL、拦截请求等。 4. **安全性**:...
4. **事件处理和回调函数**:在易语言中,你需要设置适当的事件处理函数,以便当用户与浏览器交互时,可以响应这些事件。例如,页面加载完成、点击链接、表单提交等,都需要相应的回调函数进行处理。 5. **内存管理...
6. **JavaScript交互**:MiniBlink支持与JavaScript的交互,可以实现C#代码调用JavaScript函数,或者JavaScript调用C#方法。这对于实现诸如自动化测试、页面自动化操作等场景非常有用。 7. **安全与隐私**:由于...
第4章 01 上节课复习 02 创建用户相关的文件 03 用户增删该查及组相关操作 04 对文件的权限管理 05 对目录的权限管理 06 权限管理补充 07 属主属组及基于数字的权限管理 第5章 01 上节课复习 02 文件合并与文件...
下面将详细介绍四个常用的跨浏览器必备函数。 首先,我们需要了解如何检测浏览器。对于JavaScript开发者而言,了解当前用户使用的是哪种浏览器是非常重要的,因为不同的浏览器可能会有不同的对象和方法。通过编写...
4. **处理事件**:由于`ChromiumWebBrowser`控件的事件处理方式与`WebBrowser`控件有所不同,需要重新绑定事件处理函数,例如`LoadError`、`FrameLoadStart`和`FrameLoadEnd`等。 5. **加载网页内容**:使用`...
第四章:算法和流程控制 算法和流程控制的效率对JavaScript性能有直接影响。应该避免使用for-in循环进行大量对象属性的遍历,因为这样做可能导致性能问题。了解和处理栈溢出错误也是提高JavaScript性能的一个重要...
在.NET环境中,我们可以借助第三方库,如Awesomium或CEFSharp,这些库基于WebKit或Chromium,为.NET开发者提供了封装好的WebKit内核,以便在.NET应用中嵌入Web浏览功能。 标题提到的".NET webkit为内核的浏览器打开...
4. **JavaScript交互**:CefSharp支持JavaScript的交互,你可以使用`ExecuteScriptAsync`方法执行JavaScript代码,并通过回调函数获取执行结果。 5. **事件处理**:CefSharp提供了丰富的事件,如`...
#### 四、JavaScript引擎发展 - **IE6**:2001年发布,首次实现了JavaScript引擎的优化。 - **Chrome V8**:2008年由Google发布,以其高效性能著称,能直接将JavaScript代码转化为机器码执行。 - **Firefox**: - ...
4. **大规模搜索技术** (第 4 章): 针对大数据环境下的搜索算法,可能涵盖了快速检索策略、时间复杂度分析和不同数据结构的应用。 5. **XML验证器设计** (第 5 章): 讨论了如何设计XML验证器,从最初的简单实现到...
4. **第4章 低层操作**:这部分可能涵盖了对硬件直接操作的技巧,如直接内存访问(DMA)、中断处理、注册表操作、硬件设备驱动程序的接口等。在DELPHI中,低层操作通常涉及到PInvoke(Platform Invoke)来调用非托管...
1. **Trident内核**:主要由微软的Internet Explorer使用,包括一些基于IE内核的第三方浏览器如GreenBrowser和Maxthon。Trident内核在早期版本中存在与W3C标准脱节的问题,导致对网页标准支持不足,同时也存在大量...