函数表达式

函数定义的两种方式:

  • 函数声明(函数声明提升,非标准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. vue中v-for索引不要用key

    今天发现在给元素v-for渲染的时候,想给元素添加key特性存储索引,发现不奏效: <div class="apic" v-for="(pic,index) in ...

  2. 数据库范式1NF 2NF 3NF详细阐述

    范式:关系数据库中的关系是要满足一定要求的,满足不同程度要求的不同范式.满足最低要求的叫第一范式,简称1NF ,在第一范式中满足进一步要求的为第二范式,其余以此类推.通俗来说是满足数据库关系表中的一套 ...

  3. 使用pandas库实现csv行和列的获取

    1.读取csv import pandas as pd df = pd.read_csv('路径/py.csv') 2.取行号 index_num = df.index 举个例子: import pa ...

  4. Java中JVM相关面试题-整理

    1.JVM内存模型 •程序计数器:当前线程字所执行节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有. •Java虚拟机栈:存放基本数据类型,对象的引用,方法出口等,线程私有. •本地方 ...

  5. Spring JSR-250 注释

    Spring还使用基于 JSR-250 注释,它包括 @PostConstruct 注释 @PreDestroy 注释 @Resource 注释 @PostConstruct 和 @PreDestro ...

  6. poj3694 连通无向图图加边后有多少桥

    Network Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 10261   Accepted: 3807 Descript ...

  7. 非常实用的织梦dede所有标签调用方法大全

    关键描述调用标签: <meta name="keywords" content="{dede:field name='keywords'/}">&l ...

  8. shiro的ssm集成和简单的开发尝试

    配置web.xml <!-- 配置shiro的集成开始 --> <filter> <filter-name>shiroFilter</filter-name& ...

  9. css3,transition,animation两种动画实现区别

    我们为页面设置动画时,往往会用到transition还有animation以及transfrom属性或者用到js. 其实通常情况下,对于使用js我们更加倾向于使用css来设置动画. transfrom ...

  10. MySQL8多实例安装与mycat连接,最详细版本。

    [版权所有,转载请注明出处!违者必究!] 最近在搞mycat去实现主从库读写分离,所以博主就在自己的windows机器上进行了环境的搭建,在搭建MySQL多实例的时候还算顺利,就是mysql8和myc ...