JavaScript高级程序设计:第七章
函数表达式
1.函数表达式的特征:
定义函数的方式有两种:一种是函数声明,另一种就是函数表达式。函数声明的语法是这样的:
function functionName(arg0,arg1,arg2){
//函数体
}
首先是函数function关键字,然后是函数的名字,这就是指定函数名的方式。关于函数声明,它的一个重要特征就是函数声明提升,意思是在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。
sayHi();
function sayHi(){
alert(“Hi!”);
}
这个例子不会抛出错误,因为在代码执行之前会先读取函数声明。
第二种创建函数的方式是使用函数表达式。函数表达式有几种不同的语法形式。下面就是一种最常见的形式:
var functionName = function(arg0,arg1,arg2){
//函数体
};
这种形式看起来好像是常规的变量赋值语句,即创建一个函数并将它赋值给变量functionName。这种情况下创建的函数叫做匿名函数,因为function关键字后面没有标识符。匿名函数的name属性是空字符串。
函数表达式与其他表达式一样,在使用前必须先赋值。以下代码会导致错误:
sayHi(); //错误:函数还不存在
var sayHi = function(){
alert(“Hi!”);
};
理解函数提升的关键,就是理解函数声明与函数表达式之间的区别。
2.递归
递归函数是在一个函数通过名字调用自身的情况下构成的。如下所示:
function factorial(num){
if(num<=1){
return 1;
} else {
return num * factorial(num-1);
}
}
这是一个经典的递归阶乘函数。
arguments.callee是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用,例如:
function factorial(num){
if(num<=1){
return 1;
} else {
return num*arguments.callee(num-1);
}
}
3.闭包
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。以前面createComparisonFunction()为例。
function createComparisonFunction(propertyName){
return function(object1,object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if(value1 < value2){
return -1;
} else if(value1 > value2){
return 1;
} else {
return 0;
}
};
}
在这个例子中,突出的那两行代码是内部函数中的代码,这两行代码访问了变量propertyName。即使这个内部函数被返回了,而且是在其他地方被调用了,但它仍然可以访问变量propertyName。之所以还能够访问这个变量,是因为内部函数的作用域链中包含createComparisonFunction()的作用域。
(1)闭包与变量
作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。下面这个例子可以清晰地说明这个问题:
function createFunctions(){
var result = new Array();
for(var i=0; i<10; i++){
result[i] = function(){
return i;
};
}
return result;
}
这个函数会返回一个数组。表面上看,似乎每个函数都应该返回自己的索引值,即位置0的函数都返回0,位置1的函数都返回1。
(2)关于this对象
在闭包中使用this对象也可能会导致一些问题。我们知道,this对象是在运行时基于函数的执行环境绑定的:在全局数据中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此this对象通常指向window。有时候由于编写闭包的方式不同,这一点可能不会这么明显。下面来看一个例子:
var name = “The Window” ;
var object = {
name : “My Object” ,
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()())。 //“The Window”(在严格模式下)
以上代码先创建了一个全局变量name,又创建了一个包含name属性的对象。这个对象还包含一个方法——getNameFunc(),它返回一个匿名函数,而匿名函数又返回this.name。由于getNameFunc()返回一个函数,因此调用object.getNameFunc()()就会立刻调用它返回的函数,结果就是返回一个字符串。
(3)内存泄露
闭包在IE版本中会导致一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。来看下面的例子:
function assignHandler(){
var element = document.getElementById(“someElement”);
element.onclick = function(){
alert(element.id);
};
}
以上代码创建了一个作为element元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用。由于匿名函数保存了一个对assignHandler()活动对象的引用,因此就会导致无法减少element的引用数。只要匿名函数存在,element的引用数至少也是1,因此它所占用的内存就永远不会被回收。不过,这个问题可以稍微改写一下代码来解决:
function assignHandler(){
var element = document.getElementById(“someElement”);
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
在上面的代码中,通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。要记住,闭包会引用包含函数的整个活动对象,而其中包含着element。
4.模仿块级作用域
(1)如前所述,javascript没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的,来看下面的例子:
function outputNumbers(count){
for(var i=0;i<count;i++){
alert(i);
}
alert(i); //计数
}
这个函数中定义了一个for循环,而变量i的初始值被设置为0。在java、c++等语言中,变量只会在for循环的语句块中没有定义,循环一旦结束,变量i就会被销毁。可是javascript中,变量是定义字ouputNumbers()的活动对象中的,因此从它有定义开始,就可以在函数内部随处访问它。
5.私有变量
严格来讲,javascript中没有私有成员的概念;所有对象属性都是共有的。不过,倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。来看下面的例子:
function add(num1 , num2){
var sum = num1+num2 ;
return sum ;
}
在这个函数的内部,有3个私有变量:num1,num2和sum。在函数内部可以访问这几个变量,但在函数外部则不能访问它们。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。
我们把有权访问私有变量和私有函数的公有方法称为特权方法。有两种在对象上创建特权方法的方式。第一种是在构造函数中定义特权的方法,基本模式如下:
function MyObject(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function(){
privateVariable++;
return privateFunction();
};
}
这个模式在构造函数内部定义了所有私有变量和函数。然后又继续创建了能够访问这些私有成员的特权的方法。能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。对这个例子而言,变量privateVariable和函数privateFunction()只能通过特权方法publicMethod()来访问。在创建Object的实例后,除了使用publicMethod()这一个途径外,没有任何办法可以直接访问privateVariable和privateFunction()。
利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。例如:
function Person(name){
this.getName = function(){
return name;
};
this.setName = function (value){
name = value;
};
}
var person = new Person(“Nicholas”);
alert(person.getName()); //“Nicholas”
person.setName(“Greg”);
alert(person.getName()); //“Greg”
JavaScript高级程序设计:第七章的更多相关文章
- 《JavaScript高级程序设计》——第二章在HTML使用JavaScript
这章讲的是JavaScript在HTML中的使用,也就是<script>元素的属性.书中详细讲了async.defer.src和type四个<script>的属性. 下面是对第 ...
- JavaScript 高级程序设计 第5章引用类型 笔记
第五章 引用类型 一.object类型 1.创建方法: 1.使用new 操作符创建 var person=new object() Person.name=”Nicholasa” Porson.age ...
- JavaScript高级程序设计第20章JSON 笔记 (学习笔记)
第二十章 JSON 1.Json 可以表示三种类型的值: 1.简单值: 表示数值:5 表示字符串:“hello wrold”注表示字符串时必须使用双引号 2.对象: {“name”:“mi”,”ag ...
- JavaScript高级程序设计第14章表单脚本 (学习笔记)
第十四章 表单脚本 1.阻止默认表单提交 1.提交表单数据 1.使用type=submit提交按钮 2.使用submit():方法 注意:当用户点击提交按钮时,会触发submit事件,从而在这里我们有 ...
- 《JAVASCRIPT高级程序设计》第一章
在使用调制解调器的时代,频繁的表单验证对客户端来说是一个很大的负担,javascript,作为一种专门进行表单验证的客户端脚本语言诞生了.到今天,javascript早已超越了当初设定的角色.Java ...
- 《JavaScript 高级程序设计》第一章:简介
JavaScript 历史 JavaScript的诞生的主要是当时的 netspace 公司谋求为自己的浏览器 Navigator 添加一种脚本语言,以便在本地客户端进行一些行为操作,而这一功能的需求 ...
- 《JavaScript高级程序设计》——第一章JavaScript简介
第一章主要讲了JavaScript的诞生和发展.刚刚接触JavaScript的我,似乎对这些内容并不感兴趣,快速看了一遍就开始去看第二章了. 看完第一章,收获也就是了解到JavaScript由ECMA ...
- javascript高级程序设计第5章,引用类型
object类型: 创建object实列的方式有两种,一种是new()方法,一种是对象字面量表示法: 第一种法方: var obj = new object(); obj.name = 'name' ...
- javascript高级程序设计第四章 变量、作用域和内存问题
变量包含两种,,基本类型和引用类型 基本类型是指一些简单的字段: 引用类型是☞由多个值构成的对象 引用类型的值是保存在内存中的对象,在javascript中是不允许直接访问内存中的位置; 函数的参数 ...
- JavaScript高级程序设计:第九章
第九章 一.使用能力检测 能力检测的目标不是识别特定的浏览器,而是识别浏览器的能力.能力检测的基本模式如下: if ( object.propertyInQuestion ) { //使用object ...
随机推荐
- 我喜欢的两个js类实现方式
闭包实现 变量是不会变的:) var myApplication = function(){ var name = 'Yuri'; var age = '34'; var status = 'sing ...
- js的变量声明以及变量提升
js的变量声明: js正常的变量声明就不多讲了,形如var a=1;这样的变量声明在实际开发中最常用. var a=1,b=2;这种以逗号分隔开的一次声明多个变量,其实相当于var a=1; var ...
- Busy Beavers(暴力模拟)
由于排版问题,题目无法显示,可以到 http://7xjob4.com1.z0.glb.clouddn.com/e4872a15819b6bf9d1e5250bacc2a30b 查看 题目大意是有只 ...
- unity 组件开发
//在unit菜单加入Component->b2Components->b2BodyComponent [AddComponentMenu("b2Components/b2Bod ...
- openstack私有云布署实践【3 keepalive配置(VRRP虚IP)】
这里只用到keepalived 或者pacemaker两种方法,可二选一来配置,官方提供的是pacemaker,但我们实际测试环境和生产环境上使的是keepalive 首先在4台control ...
- android 实现透明状态栏
主要使用https://github.com/jgilfelt/SystemBarTint这个开源库 1 ,导入SystemBarTintManager类 2 ,BaseFragmentActivit ...
- JS逻辑运算大于小于比较
遇到这个问题,结果测试了好半天终于发现原因, 例子: var az = $('#a').css('zIndex'); // 1001 var bz = $('#b').css('zIndex'); / ...
- control
group:多个脚本之间按照独立设置模式跑,各个脚本可以单独设置虚拟用户.运行时间等 scenario:多个脚本之间按照相同的模式跑,将总的虚拟用户数按照一定的比例分配给各个脚本 ---------- ...
- gRPC编码初探(java)
背景:gRPC是一个高性能.通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众 ...
- Java中泛型 类型擦除
转自:Java中泛型是类型擦除的 Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类 ...