函数表达式

函数定义的两种方式:

  • 函数声明(函数声明提升,非标准name属性可访问给函数指定的名字)

函数声明提升:执行代码前先读取函数声明

 function functionName(arg0, arg1, arg2){
//函数体
}
  • 函数表达式(name属性为空字符串,匿名函数)
 var functionName = function(arg0, arg1, arg2){
//函数体
}; //注意这个分号

与if···else···语句结合使用只能用函数表达式(理解函数声明提升的关键--函数声明和函数表达式的区别)

7.1 递归

递归函数:一个函数通过名字调用自身

递归函数调用容易出问题:这里用一个最简单的阶乘函数来表示

 function factorial(num){
if (num <= 1){
return 1;
}else{
return num * factorial(num-1);
}
}

使用下面的代码会让他出错:

 var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //出错!

这里的anotherFactorial是factorial的一个副本,但factorial函数内仍在调用factorial函数,但此时的factorial函数已经被置空。所以这里会出现因为命名问题而产生的错误。

解决方法:

  1. arguments.callee 指向正在执行函数的指针(避免直接写明函数名带来的问题)  但严格模式下不能通过脚本访问
  2. 命名函数表达式
 var factorial = (function f(num){
if(num <= 1){
return 1;
}else{
return num * f(num-1);
}
});

这种方法在严格模式下也能使用

7.2 闭包

闭包:有权访问另一个函数作用域中变量的函数 (常见情况就是在一个函数内部创建另一个函数)

之所以内部函数可以访问到外部函数的变量,是因为内部函数的作用域链中包含了外部函数的作用域    =>   函数第一次被调用的时候发生了什么?

  当某个函数第一次被调用时,会创建一个执行环境和相应的作用域链,并把作用域链赋值给一个特殊的内部属性([[Scope]])。然后再用this,arguments和其他命名参数来初始化函数的活动对象。在作用域链中,外部函数的活动对象始终处于第二位。

  执行环境有一个与之关联的变量对象,环境中有权访问的所有变量和函数都在这个变量对象中。为了保证对这些变量对象的有序访问,出现了作用域链。作用域链从自身的环境的活动对象开始,一直链接到全局环境的活动对象。

  当一个函数创建时,会创建一个预先包含全局变量对象的作用域链保存在[[Scope]]属性中。在调用函数时,会为函数创建一个执行环境,通过复制[[Scope]]中的对象构建起执行环境的作用域链。之后每有一个活动对象被创建就被推入作用域链的前端。(是引用而不是变量对象本身)

  函数执行完毕,局部变量就会被销毁,内存中仅保留全局作用域。

闭包的特殊性:对于闭包,内部函数被单独调用时,作用域链会包括外部函数的执行环境。所以外部函数执行结束后,其作用域链会被销毁,但其活动对象仍留在内存中。直到其内部函数被销毁后才会被销毁。

 //闭包
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;
}
};
}
 //创建函数
var compareNames = createComparisonFunction("name");
//调用函数
var result = compareNames({name: "Nicholas" }, {name: "Grey"});
//删除对函数的引用,以释放内存
compareNames = null;

过度使用闭包会造成内存占用过多,慎重使用。

7.2.1 闭包与变量

作用域链配置机制的副作用:闭包只能取得包含函数中任何变量的最后一个值。

 function createFunctions(){
var result = new Array(); for (var i = 0; i < 10; i++){
result[i] = function(){
return i;
};
} return result;
}
 function createFunctions(){
var result = new Array(); for (var i = 0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
}
}(i);
} return result;
}

第一种情况下,数组每一次都返回10。因为每个函数的作用域链都保存着外部函数的活动对象,所以引用的都是同一个变量i,外部函数返回后,变量i的值是10,所以每一个函数内部的i值都是10。

第二种情况可以符合预期。这里没有直接把闭包赋值给数组,而是定义了一个匿名函数,把实时的i值赋给num。

7.2.2 关于this对象

匿名函数的执行环境具有全局性,其this对象通常指向window。

 var name = "The Window";

 var object = {
name: "My Object",
getNameFunc: function(){
return function(){
return this.name;
};
}
}; alert(object.getNameFunc()()); //"The Window"

内部函数搜索this,arguments(调用时会自动获取)时会到其活动对象为止,不可能访问外部函数中的这两个变量。

解决方法:

 var name = "The Window";

 var object = {
name: "My Object", getNameFunc: function(){
var that = this;
return function(){
return that.name;
};
}
}; alert(object.getNameFunc()()): //"My Object"

7.2.3 内存泄漏

如果闭包的作用域链中保存了一个HTML元素,那么该元素无法被销毁。

 function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}

由于在匿名函数中调用了包含函数的活动变量element,所以只要匿名函数还存在,element的引用数至少是1(参考垃圾清理机制),内存永远不会被回收。

解决方法:

 function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}

在闭包中删除了对HTML元素的循环引用(id)。但还要element变量置为null,因为闭包会引用整个包含函数的活动变量,包括element(即使不直接引用)。

7.3 模仿块级作用域

JavaScript没有块级作用域的概念(比如for循环中的i,在for循环之外也可以被访问到)。对已声明的变量重复声明会被忽略,但初始化会改变值。

解决方法:通过匿名函数来模拟块级作用域

(function(){
//块级作用域
})():

实际上是一个函数表达式(不能去掉function外层的圆括号,因为JavaScript将function关键字当作一个函数声明的开始,然而函数声明后是不可以跟圆括号的,加这层圆括号可以将函数声明转换为函数表达式)

作用:临时需要一些变量,限制向全局作用域添加过多的变量和函数。

这样可以减少闭包占用的内存问题,因为没有指向匿名函数引用。只要函数执行完毕,作用域链就可以被销毁了。

7.4 私有变量

私有变量:在函数中定义的变量(不能在函数外部访问)

创建访问私有变量的公有方法:通过闭包(可以通过自己的作用域链访问这些私有变量)

特权方法:有权访问私有变量和私有函数的公有方法

在对象上创建特权方法的方式:构造函数中定义特权方法

function MyObject(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function(){
privateVariable++;
return privateFunction();
};
}

除了使用publicMethod()之外无法直接访问私有变量。

还可以利用这个特性来隐藏那些不应该被直接修改的数据。

7.4.1 静态私有变量

在私有作用域中定义变量或函数,从而创建特权方法。

(function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
} //构造函数,全局变量(没有用var)
MyObject = function(){
}; //公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();

公有方法是在原型上定义的(原型模式),使用的函数表达式而不是函数声明(函数声明只能创建局部函数)。

与构造函数法的区别是,私有变量和函数是由实例共享的(在原型上定义的)。

这种情况下,私有变量就成为了静态的、所有实例共享的。不足之处在于,多查找作用域链的一个层次就会在一定程度上影响查找速度。

7.4.2 模块模式

模块模式:为单例创建私有变量和特权方法(单例:只有一个实例的对象)  =>  用对象字面量的方法来创建

使用返回对象的匿名函数的方法。可以应用在需要对单例进行某些初始化,但是又要维护其私有变量的时候。

7.4.3 增强的模块模式

单例必须是某种类型的实例,必须添加某些属性和方法对其增强的情况。

2020/06/05 JavaScript高级程序设计 函数表达式的更多相关文章

  1. JavaScript高级程序设计--函数小记

    执行环境和作用域链   每个函数都有自己的执行环境.当执行流进入一个函数时,函数 的环境就会被推入一个环境栈中.而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境.   当代码在一个环境中 ...

  2. Javascript高级程序设计——函数声明与函数表达式的区别

    在Javascript中,函数是Functioin类型的实例,所以函数也具备属性和方法,因为函数是对象,所以函数名自然就是指向对象的指针啦. 函数可以通过声明语法和表达式来定义: 声明:functio ...

  3. 7. javacript高级程序设计- 函数表达式

    1. 函数表达式 1.1 函数定义 函数定义的方式有两种:一种是函数声明,另一种就是函数表达式. (1). 函数声明:函数声明的重要特征就是函数声明提示,函数声明会在函数执行前执行 function ...

  4. Javascript高级程序设计——函数

    函数Function 通过函数封装多条语句,在任何地方执行.javascript函数不会重载,相同名字函数,名字属于后定义的函数通过function关键词声明. function functionNa ...

  5. Javascript高级程序设计——函数内部属性与函数属性

    函数内部属性 函数内部有两个特殊的属性arguments和this.其中,arguments是类数组对象,包含传入函数中的所有值,这个arguments还有一个属性:callee,这个属性是一个指针, ...

  6. 2020/6/10 JavaScript高级程序设计 BOM

    BOM(浏览器对象模型):提供用于访问浏览器的对象. 8.1 window对象 window是BOM的核心对象,表示浏览器的一个实例. JavaScript访问浏览器窗口的接口 ECMAScript规 ...

  7. 2020/6/11 JavaScript高级程序设计 DOM

    DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序接口).他描绘了一个层次化的节点树,允许开发人员添加.移除和修改页面的某一部分. 10.1 节点层次 DOM将任何HTML和XML ...

  8. JavaScript高级程序设计(读书笔记)之函数表达式

    定义函数的方式有两种:一种是函数声明,另一种就是函数表达式. 函数声明的一个重要特征就是函数声明提升(function declaration hoisting),意思是在执行代码前会先读取函数声明. ...

  9. 读书笔记(06) - 语法基础 - JavaScript高级程序设计

    写在开头 本篇是小红书笔记的第六篇,也许你会奇怪第六篇笔记才写语法基础,笔者是不是穿越了. 答案当然是没有,笔者在此分享自己的阅读心得,不少人翻书都是从头开始,结果永远就只在前几章. 对此,笔者换了随 ...

随机推荐

  1. 二、YARN

    一.YARN 介绍 yarn 是下一代 MapReduce,即 MRv2,是在第一代 MapReduce 基础上演变而来的,主要是为了解决原始 Hadoop 扩展性较差,不支持多计算框架而提出的,通俗 ...

  2. React组件setState

    注意: 1. 自定义组件首字母必须大写.这里是以函数表达式的方式定义子组件的. 2. 使用 ES6 的 class 关键字创建的 React 组件,组件中的方法遵循与常规 ES6 class 相同的语 ...

  3. Java基础之数据类型

    一.数据类型 基本数据类型介绍 byte 1字节 char 2字节 short 2字节 int 4字节 long 8字节 float 4字节 double 8字节 以上有Java中八大基本类型的7种, ...

  4. 发现用System.Net.Mail发邮件(代码附后),附件稍微大一点就会造成程序假死. 有没有什么简单的解决办法呢? 多谢!!

    附件大,上传,发送一定会慢.程序卡,应该是主线程正在发送,邮件造成的.创建其他线程在后台去发.这样就不影响主线程做其他工作了   using System; using System.Collecti ...

  5. Mybaties 的缓存

    1.mybaties 默认会话是一级缓存,没有开启二级缓存.开启方式在mapper文件中设置<cache /> 2.<cache eviction="LRU" f ...

  6. [软件版本贴]SD.TEAM软件集

    手机轰炸机-版本:v1.0.下载地址:http://pan.baidu.com/1.zip! 营销软件盒-版本:v1.0.下载地址:http://pan.baidu.com/2.zip! 不加群提取群 ...

  7. String类练习

    1.模拟一个trim方法,去除字符串两端的空格 2.将一个字符串进行反转.将字符串中指定部分进行反转 3.获取一个字符串在另一个字符串中出现的次数 4.获取两个字符串中最大相同子串 5.对字符串中字符 ...

  8. pandas DataFrame 的横向纵向拼接组合

    concat 与其说是连接,更准确的说是拼接.就是把两个表直接合在一起.于是有一个突出的问题,是横向拼接还是纵向拼接,所以concat 函数的关键参数是axis . 函数的具体参数是: concat( ...

  9. Linux suid 提权

    SUID (Set owner User ID up on execution) 是给予文件的一个特殊类型的文件权限.在 Linux/Unix中,当一个程序运行的时候, 程序将从登录用户处继承权限.S ...

  10. 附019.Rancher搭建及使用

    一 Rancher概述 1.1 什么是Rancher Rancher 是为使用容器的公司打造的容器管理平台.Rancher 简化了使用 Kubernetes 的流程,方便开发者可以随处运行 Kuber ...