定义函数的方式有两种:

1.函数声明(特征:函数声明提升,在执行代码之前会先读取函数声明,这就意味着可以把函数声明放在调用它的语句之后)

2.函数表达式(函数表达式与其他表达式一样,使用之前必须先声明)

递归

递归函数是一个函数通过名字调用自生的情况下构成的,递归函数必须要有结束条件。

function factorial(num) {
console.log('执行');
if (num <= 1) {
return 1;
}
return num * factorial(num - 1)
}
var fn = factorial;
factorial = null;
fn(5)

上面我们定义了一个阶乘函数,我们把factorial函数保存在变量fn中,然后设置factorial为null,解除对函数的应用,用来模拟意外修改factorial的值,然后执行fn函数,就会出现报错,

arguments.callee是一个指向正在执行函数的指针,所以上面函数修改如下:

function factorial(num) {
console.log('执行');
if (num <= 1) {
return 1;
}
return num * arguments.callee(num - 1)
}
var fn = factorial;
factorial = null;
fn(5)

这样子我们就做到了防止意外情况修改factorial值而导致函数运行的错误,但是arguments.callee在严格模式下是不支持的,所以再次修改,使得递归函数在严格模式下也能支持上面函数的特定。代码如下:

var factorial = (
function f(num) {
console.log('执行');
if (num <= 1) {
return 1;
}
return num * f(num - 1)
}
)
var fn = factorial;
factorial = null;
console.log(fn(5));

这样子一来,不管factorial的值怎么变,它在变化之前所指向的函数的功能是永远生效的,此时在外部作用域是访问不到f这个函数的,所以就无法破坏该函数的功能,这样子一来,在严格模式下,我们也能实现比较安全的递归函数。

闭包

闭包是指有权访问另一个函数作用中变量的函数。所以要构成必报必须要有两个条件:

1.函数内部定义一个函数

2.内部函数能够访问外部函数的活动对象(变量)

下面是一个简单的闭包函数:

function addNum(num) {
return function () {
return num + 1;
}
}
var num = addNum(10);
console.log(num);
console.log(num())//
num = null;
console.log(num);

函数addNum返回了一个函数,该函数使用了函数addNum的变量num,导致即使addNum执行完毕之后,返回的函数还是能够操作num。这样子就实现了变量常驻内存,但是很明显这样比较浪费内存,所以我们把外部函数num设置null,解除对返回的函数的引用,以此来释放内存。

当某个哈数调用的时候,会创建一个执行环境及其相应的作用域链。然后使用arguments和其他命名参数的值初始化函数的活动对象。但在作用域链中,外部函数的活动对象始终处于第二外,外部函数的外部函数的活动对象处于第三位,......直至作为作用域链终点的全局执行环境。在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。后台的每个执行环境都有一个表示变量的对象,函数这样子的局部环境的变量对象,则只在函数执行过程中存在,在创建外部函数addNum的时候,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中,当调用addSum函数的时候,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端。显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是,闭包的情况又有所不同。在一个外部函数内部定义函数会将包含函数(即外部函数)的活动对象添加到它的作用链中,所以当外部函数执行完毕之后,它的活动对象也不会被销毁,因为内部函数的作用域链依然在引用这写活动对象,直到内部函数销毁之后,活动变量对象才会被销毁,从内存中清除。

这个就是闭包实现的底层原理。

内存泄漏

看下面的代码

function assginHandler() {
var element = document.getElementById("ydb");
element.onclick = function () {
console.log(element.id);
}
}

由于匿名函数保存了一个对assginHandler()的活动对象的引用,因此无法减少element的引用数。只要匿名函数存在,element的引用数至少为1,因此它所占用的内存空间就永远不会被回收。

修改代码如下:

function assginHandler() {
var element = document.getElementById("ydb");
var id = element.id;
element.onclick = function () {
console.log(id);
}
element = null;
}

在上面的代码中,通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。但仅仅做到这一代呢是不够的,不要忘记了:闭包会引用包含函数(外部函数)的整个活动对象,而其中包含element。即使闭包不直接引用element,包含函数的活动对象也仍然会保存一个引用。因此,有必要把element变量设置为null。这样子就能够解除对DOM对象的引用,顺利地减少引用数,确保正常回收其占用的内存。

模仿块级作用域

JavaScript中没有块级作用域的概念。这就意味着在块语句中定义的变量,实际上是包含函数中而非语句中创建的。看下面代码:

function example() {
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i);//
})
}
  var i;
  console.log(i);
}
example();

上面的输出语句,都会输出10,这个例子也证明了上述的观点,没有块作用域。在for语句执行完成之后,函数内部还是能访问i变量,匿名函数可以又怕没回来模仿块级作用域并避免这个问题:

function example() {
(function(){
for (var i = 0; i < 10; i++) {
console.log(i); // 正常输出
}
})()
console.log(i);//Error i is not defined
}
example();

这样子一来,函数内部就不能访问i变量,因为(function(){})()形成了一个块级作用域,作用域之外访问变量是访问不到的。

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

私有变量

任何在函数内部定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。私有变量包括函数的参数,局部变量和在函数内部定义的其他函数。

看下面的例子:

function Person(name) {
this.getName = function () {
return name;
}
this.setName = function (value) {
name = value;
}
}
var perosn1 = new Person('1');
console.log(perosn1.getName());//
perosn1.setName('2');
console.log(perosn1.getName());//

我们利用私有和特权方法,可以隐藏那些不应该直接修改的数据,必须通过特权方法去修改私有变量。

不过在构造中定义特权方法有一个缺点,那就是你必须使用构造函数模式来达到这个目的。

模块模式

模块模式则是为单例创建私有的变量和特权方法。所谓单例,指的就是只有一个实例的对象。

var application = function () {
// 私有变量和函数
var components = [];
// 初始化
components.push(new BaseComponent());
// 公共方法
return {
getComponetnCount: function () {
return components.length;
},
registerComponent: function (component) {
if (typeof component == 'object') {
components.push(component);
}
}
}
}();

简言之,如果必须创建一个对象并以某些数据对其惊醒初始化,同时还要公开一些能够访问这些私有变量数据的方法,那么就可以使用模块模式。

增强模块模式

在模块模式的基础上,在返回对象之前加入对其增强的代码。这种模式适合那些单例必须是某种类型的实例。

var application = function () {
// 私有变量和函数
var components = [];
// 初始化
components.push(new BaseComponent());
// 创建application的一个局部副本
var app = new BaseComponent();
// 公共方法
app.getComponetnCount = function () {
return components.length;
},
app.registerComponent = function (component) {
if (typeof component == 'object') {
components.push(component);
}
}
//返回这个副本
return app;
}();

从头认识js-函数表达式的更多相关文章

  1. js函数表达式和函数声明的区别

    我们已经知道,在任意代码片段外部添加包装函数,可以将内部的变量和函数定义"隐 藏"起来,外部作用域无法访问包装函数内部的任何内容. 例如: var a = 2; function ...

  2. JS 函数表达式

    定义函数的方式有两种, 一种是函数声明,一种就是函数表达式了 函数声明最常见了, sayHi(); // 函数声明会发生提升 function sayHi () { alert('Hi') } 函数表 ...

  3. JS函数表达式

    导图

  4. js立即调用的函数表达式

    1.多种实现 // 下面2个括弧()都会立即执行 (function () { /* code */ } ()); // 推荐使用这个 (function () { /* code */ })(); ...

  5. JS中函数声明与函数表达式的不同

    Js中的函数声明是指下面的形式: function functionName(){   } 这样的方式来声明一个函数,而函数表达式则是类似表达式那样来声明一个函数,如 var functionName ...

  6. [JS]深入理解JavaScript系列(4):立即调用的函数表达式

    转自:汤姆大叔的博客 前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行.在详细了解这个之前,我们来谈了解一下"自执行"这个叫法 ...

  7. js学习之函数声明与函数表达式区别[原创]

    作为一名js初学者,与大家分享下.Javascript中有函数声明提升的功能,会优先编译函数声明部分.比如, ff(); function ff(){ alert("hello world. ...

  8. js学习之函数表达式及闭包

    来自<javascript高级程序设计 第三版:作者Nicholas C. Zakas>的学习笔记(七) 直接切入主题~ 定义函数的方式有两种: 函数声明 function functio ...

  9. 重操JS旧业第九弹:函数表达式

    函数表达式,什么概念,表达式中的函数表达式. 1 函数申明 function 函数名([函数参数]){ //函数体 } js中无论像这样的显示函数什么放在调用之前还是调用之后,都不影响使用,因为js解 ...

  10. JS立即执行函数表达式(IIFE)

    原文为 http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife ----------------- ...

随机推荐

  1. 洛谷-P5357-【模板】AC自动机(二次加强版)

    题目传送门 -------------------------------------- 过年在家无聊补一下这周做的几道AC自动机的模板题 sol:AC自动机,还是要解决跳fail边产生的重复访问,但 ...

  2. The website is API(2)

    一.Beautifu Soup库 from bs4 import BeautifulSoup soup = BeautifulSoup(demo,"html.parser") Ta ...

  3. Python数据分析与展示第0&1周学习笔记(北理工 嵩天)

    一前奏 1..Python语言开发工具选择 IDLE:自带默认常用入门级 PyCharm:简单.集成度高 Anaconda:awesome IDE较为简单,不做详细记录. 二.表示 1.numpy库入 ...

  4. 二十九、rsync+inotity实时监控同步工具

    一.场景应用:                                    客户通过url访问资源(查询,下载等),并发量是非常高的,所以运用负载均衡分担web服务器的压力,在后端连接不同的 ...

  5. Codeforces Round #525 (Div. 2)后俩题

    E:https://codeforces.com/contest/1088/problem/E dp+贪心 题目大意:选择一个k并且选择k个连通块,要求sigma a[i]/k最大,k尽量大,对于给定 ...

  6. day39-进程-队列

    #队列Queue:进程之间数据是隔离的,不共享的,但是通过multiprocessing的Queue可以实现进程之间的通信. #1.先进先出:把1 2 3放到队列里,按1 2 3的顺序拿出来. fro ...

  7. 简单标签 SimpleTagSupport示例

    最近处理JSP页面,需要把数据库查到的原始值,根据数据字典转换成更加直观的值.比如查到的结果是 01,jsp页面展示‘身份证’. 如果值比较少,就直接用c:if标签处理了,无奈接触的值比较多,只想到了 ...

  8. [LC] 76. Minimum Window Substring

    Given a string S and a string T, find the minimum window in S which will contain all the characters ...

  9. MS激活

    亲测可用 国内博客  https://msguides.com/ 他是翻译的这个哥们的网页: https://msguides.com/

  10. python往mysql数据库中写入数据和更新插入数据

    本文链接:https://blog.csdn.net/Mr__lqy/article/details/85719603 1. 连接mysql import pymysql db = pymysql.c ...