第4章-函数(2)

递归 Recursion

递归函数就是会直接或间接地调用自身的一种函数

 1         var walk_the_DOM = function(node,func){
 2             func(node);
 3             node = node.firstChild;
 4             while(node){
 5                 walk_the_DOM(node,func);
 6                 node = node.nextSibling;
 7             }
 8         };
 9
10         //定义getElementsByAttributes函数,它以一个属性名字符串和一个可选的匹配值作为参数
11         //它调用walk_the_DOM并传递一个用来查找节点属性名的函数作为参数,匹配的节点会累加到一个数组中
12
13         var getElementsByAttributes = function(att,value){
14             var result = [];
15             walk_the_DOM(document.body,function(node){
16                 var actual = node.nodeType === 1 && node.getAttribute(att);
17                 if(typeof actual === ‘string‘ && (actual === value || typeof value !== ‘string‘)){
18                     result.push(node);
19                 }
20             });
21             return result;
22         }

作用域 Scope

在编程语言中,作用域控制着变量的与参数的可见性与生命周期

 1         var foo = function(){
 2             var a = 3,b = 5;
 3             var bar = function(){
 4                 var b = 7; c = 11;
 5                 //此时 a=3,b=7,c=11
 6
 7                 a += b + c;
 8                 //此时 a=21,b=7,c=11
 9             };
10
11             //此时 a=3,b=5,c还没有定义
12
13             bar();
14             //此时 a=21,b=5
15         }
16             

JavaScript确实有函数作用域,意味着定义在函数中的参数和变量在外部式不可见的,而在一个函数内部任何位置定义的变量,在该函数内部任何地方都是可见的。

闭包 Closure

作用域的好处是内部函数可以访问定义它们的外部函数和参数和变量(除了this和arguments)

一个更有趣的情形是内部函数拥有比它的外部函数更长的生命周期

和以前以对象字面量的形式去初始化myObject的方式不同,这次通过调用一个函数的形式去初始化myObject,该函数会返回一个对象字面量。函数里定义了一个value变量,该变量对increment和getValue方法总是可用的,但函数的作用域使得它对其他的程序来说是不可见的

 1         var myObject = (function(){
 2             var value = 0;
 3             return {
 4                 increment: function(){
 5                     value += typeof inc === ‘number‘ ? inc : 1;
 6                 },
 7                 getValue: function(){
 8                     return value;
 9                 }
10             };
11         }());

我们并没有把一个函数复制给myObject。我们是把调用该函数狗返回的结果赋值给它。主语最后一行的()。该函数返回一个包含两个方法的对象,并且这些方法继续享有访问value的特权。

本章之前的Quo构造器产生一个带有status属性和get_status方法的对象,但那看起来并不是十分有趣,为什么要用一个getter方法去访问你本可以直接访问到的那个属性,如果status是私有属性,它才更有意义的。

 1         var quo = function(status){
 2             return {
 3                 get_status: function(){
 4                     return status;
 5                 }
 6             };
 7         };
 8
 9         var myQuo = quo(‘amazed‘);
10         console.log(myQuo.get_status());

即使quo已经返回了,但get_status方法任然享有访问quo对象的status属性的特权。get_status方法并不是访问改参数的一个副本,它访问的就是该参数本身。

 1         //定义一个函数,它设置一个DOM节点为黄色,然后把它渐变为白色
 2
 3         var fade  = function(node){
 4             var level = 1;
 5             var step = function(){
 6                 var hex = level.toString(16);
 7                 node.style.backgroundColor = ‘#FFFF‘ + hex + hex;
 8                 if(level<15){
 9                     level += 1;
10                     setTimeout(step,100);
11                 }
12             };
13             setTime(step,100);
14         };
15         fade(document.body);

为了避免下面的问题,理解内部函数能访问外部函数的实际变量而无需复制是很重要的:

 1         //糟糕的例子
 2
 3         var add_the_handlers = function (nodes){
 4             var i;
 5             for(i=0;i<nodes.length;i++){
 6                 nodes[i].onclick = function(e){
 7                     alert(i);
 8                 };
 9             }
10         };
11
12         //结束糟糕的例子

add_the_handlers函数的本意是想传递给每个事件处理器一个唯一的(i)值。但它未能达到目的,因为事件处理器函数绑定了变量i本身,而不是函数在构造时的变量i的值。

 1         //改良后的例子
 2
 3         var add_the_handlers = function (nodes){
 4             var helper = function(i){
 5                 return function(e){
 6                     alert(i);
 7                 }
 8             };
 9             var i;
10             for(i=0;i<nodes.length;i++){
11                 nodes[i].onclick = helper(i);
12             }
13         };

避免在循环中创建函数,它可能只会带来无畏的计算,还会引起混淆,正如上面那个糟糕的例子。我们可以先在循环之外创建一个辅助函数,让这个辅助函数在返回一个绑定了当前i的值的函数,这样就不会导致混淆。

模块 Module

我们可以使用函数和闭包来构造模块,模块是一个提供接口却隐藏状态与实现的函数或对象。通过使用函数产生模块,我们几乎可以完全摒弃全局变量的使用。

 1         String.method(‘deentityify‘,function(){
 2             //字符实体表,它映射字符实体的名字到对应的字符
 3             //这个对象最好保存在闭包中,保存在全局变量中会有很多问题,保存在函数内部,但是会带来运行时的损耗
 4
 5             var entity = {
 6                 quot:‘"‘,
 7                 lt: ‘<‘,
 8                 gt: ‘>‘
 9             };
10
11             //返回deentityify方法
12
13             return function(){
14                 return this.replace(/&([^&;]+);/g
15                     function (a,b){
16                         var r = entity[b];
17                         return typeof r === ‘string‘ ? r : a;
18                     }
19                 );
20             };
21         }());

请注意最后一行,我们用()运算法立刻调用我们刚刚构造出来的函数。这个调用所创建并返回的函数才是deentityify方法。
模块模式利用了函数作用域和闭包来创建被绑定对象与私有成员的关联,在这个例子中,只有deentityify方法有权访问字符实体表这个数据对象。
模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者把他们保存到一个可访问到的地方

使用模块模式就可以摒弃全局变量的使用,它促进了信息隐藏和其他优秀的设计实践。对于应用程序的封装,或者构造其他单例对象,模块模式非常有效

模块模式也可以用来产生安全的对象。假定我们想要构造一个用来产生序列号的对象:

 1         var serial_maker = function(){
 2             var perfix = ‘‘;
 3             var seq = 0;
 4             return {
 5                 set_prefix:function(p){
 6                     prefix = String(p);
 7                 },
 8                 set_seq:function(s){
 9                     seq = s;
10                 },
11                 gensym:function(){
12                     var result = prefix + seq;
13                     seq += 1;
14                     return result;
15                 }
16             };
17         };
18
19         var seqer = serial_maker();
20         seqer.set_prefix(‘Q‘);
21         seqer.set_seq(1000);
22         var unique = seqer.gensym();    //"Q1000"
23
24         seqer.set_prefix = function(){
25             prefix = ‘QQQ‘;        // Uncaught ReferenceError: prefix is not definedd
26         }

除非调用对应的方法,否则没法改变prefix或seq的值,seqer对象是可变的,所以它的方法可能会被替换掉,但替换后的方法依然不能访问私有成员。

柯里化(局部套用) Curry

函数也是值,从而我们可以用有趣的方式去操作函数。柯里化允许我们把函数与传递给它的参数相结合,产生出一个新的函数。

 1         Function.method(‘curry‘,function(){
 2             //arguments数组并非一个真正的数组,所以它并没有concat方法,要解决这个问题就必须在两个arguments数组上都应用数组的slice方法。
 3             var slice = Arry.prototype.slice,
 4             args = slice.apply(arguments),
 5             that = this;
 6             return function(){
 7                 return that.apply(null,args.concat(slice.apply(arguments)));
 8             };
 9         });
10
11         var add1 = add.curry(1);
12         console.log(add1(6));     //7

记忆 Memoization

函数可以将先前的操作的结果记录在某个对象里,从而避免无谓的重复运算。这种优化被称为记忆

我们想要一个递归函数来计算Fibonacci数列。一个Fibonacci数字是之前两个Fiboacci数字之和。最前面的两个数字是0和1。

 1         var fibonacci = function(n){
 2             return n < 2 ? n : fibonacci(n-1) + fibonacci(n-2);
 3         };
 4
 5         for(var i=0;i<=10;i++){
 6             document.writeln(‘//‘ + i + ‘: ‘ + fibonacci(i));
 7         }
 8
 9         //0: 0
10         //1: 1
11         //2: 1
12         //3: 2
13         //4: 3
14         //5: 5
15         //6: 8
16         //7: 13
17         //8: 21
18         //9: 34
19         //10: 55

这个程序做了很多无谓的工作,fibonacci函数被调用了453次

如果我们让该函数具备记忆功能,就可以显著减少运算量。我们在一个名为memo的数组里保存我们的存储结果,存储结果可以隐藏在闭包中,当函数被调用时,这个函数首先检查结果是否存在,如果存在就立即返回这个结果。

 1         var fibonacci = function(){
 2             var memo = [0,1];
 3             var fib = function(n){
 4                 var result = memo[n];
 5                 if(typeof result !== ‘number‘){
 6                     result = fib(n-1) + fib(n-2);
 7                     memo[n] = result;
 8                 }
 9                 return result;
10             };
11             return fib;
12         }();

这样这个函数只被调用了29次。

我们可以把这种技术推而广之,编写一个函数来帮助我们构造记忆功能的函数

 1         var memoizer = function(memo,formula){
 2             var recur = function(n){
 3                 var result = memo[n];
 4                 if(typeof result !== ‘number‘){
 5                     result = formula(recur,n);
 6                     memo[n] = result;
 7                 }
 8                 return result;
 9             };
10             return recur;
11         };

现在我们可以使用memoizer函数来定义fibonacci函数

1         var fibonacci = memoizer([0,1],function(recur,n){
2             return recur(n-1) + recur(n-2);
3         });

要产生一个可记忆的阶乘函数:

1         var factorial = memeoizer([1,1],function (recur,n){
2             return n * recur(n-1);
3         });
时间: 2024-07-28 22:44:42

第4章-函数(2)的相关文章

离散数学--第5章 函数

第5章 函数• 5.1 函数定义及其性质• 5.2 函数的复合与反函数 5.1 函数定义及其性质• 5.1.1 函数的定义– 函数定义– 从A到B的函数• 5.1.2 函数的像与完全原像• 5.1.3 函数的性质– 函数的单射.满射.双射性– 构造双射函数 满射:就是Y全用了. 单射:一对一.说明是单调的. 双射:即使单射又是满射. 1 5.2 函数的复合与反函数• 5.2.1 函数的复合– 函数复合的基本定理及其推论– 函数复合的性质• 5.2.2 反函数– 反函数存在的条件– 反函数的性质

第七章 函数

第七章  函数 7.1  函数的基础知识 要使用函数,必须完成如下工作: Ø  提供函数定义 Ø  提供函数原型 Ø  调用函数 7.1.1  函数的定义 函数总体来说可以分为两类,一类是没有返回值的,另一类是具有返回值的,两类的函数定义的格式如下 void functionName(parameterList) { statement(s) return; //可以有也可以没有 } typeName functionName(parameterList) { statement(s) retu

紫书第4章 函数和递归

1  序 系统的整理下第四章的学习笔记.同上次一样,尽量在不依赖书本的情况下自己先把例题做出来.这次有许多道题代码量都比较大,在例题中我都用纯C语言编写,但由于习题的挑战性和复杂度,我最终还是决定在第五章开始前,就用C++来完成习题.不过所有的代码都是能在C++提交下AC的. 在习题中,我都习惯性的构造一个类来求解问题,从我个人角度讲,这会让我的思路清晰不少,希望自己的一些代码风格不会影响读者对解题思路的理解. 其实在第四章前,我就顾虑着是不是真的打算把题目全做了,这些题目代码量这么大,要耗费很

Welcome to Swift (苹果官方Swift文档初译与注解三十五)---248~253页(第五章-- 函数 完)

Function Types as Return Types (函数类型作为返回值类型) 一个函数的类型可以作为另一个函数的返回值类型.可以在一个函数的返回值箭头后面写上一个完整的函数类型. 例如: 下面的例子定义了两个简单的函数,分别为stepForward 和 stepBackward.其中stepForward函数返回值比它的输入值多1,stepBackward函数返回值比它输入值少1.这两个函数的 类型都是(Int) -> Int: func stepForward(input: Int

C++ Primer学习总结 第6章 函数

第6章 函数 1.    函数最外层作用域中的局部变量不能使用与函数形参一样的名字,因为它们属于同一个作用域范围. 2.    局部静态变量的生命周期: 在整个程序的执行路径第一次经过对象定义语句时初始化,并且直到整个程序终止时才被销毁,在此期间即使对象所在函数结束执行也不会对它有影响. 3.    如果重载的函数的参数只有顶层const区别,那么是错误的: 如果有底层const区别可以算作重载. 4.    如果函数的参数要使用引用(且不会改变引用对象的值),那么应该定义成常量引用. 因为使用

《javascript语言精粹》——第4章函数

函数就是对象 [1].函数字面量即(函数表达式)包括四部分: 第一部分:保留字function: 第二部分:函数名称,可有可无: 第三部分:包围在一对小括号的一组参数,参数用逗号隔开: 第四部分:包围在一对花括号的一组语句,是函数的主体: 函数字面量可以出现在任何允许表达式出现的地方. [2].调用有四种调用模式: 除了声明时定义的形参,每个函数接收附加的的参数:this和arguments  ,this的值取决于调用的模式. 第一种:方法调用模式: var aa={ value:0, incr

C++ primer plus读书笔记——第8章 函数探幽

第8章 函数探幽 1. 对于内联函数,编译器将使用相应的函数代码替换函数调用,程序无需跳到一个位置执行代码,再调回来.因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存. 2. 要使用内联函数,需要在: 函数声明前加上关键字inline; 函数定义前加上关键字inline; 3. 程序员请求将函数作为内联函数时,编译器不一定会满足这种需求.它可能认为该函数过大或注意到函数调用了自己(内联函数不能递归). 4. 必须在声明引用变量时进行初始化 int rat = 101; int &

C++ primer plus读书笔记——第7章 函数——C++的编程模块

第7章 函数——C++的编程模块 1. 函数的返回类型不能是数组,但可以是其他任何一种类型,甚至可以是结构和对象.有趣的是,C++函数不能直接返回数组,但可以将数组作为结构或对象的组成部分来返回. 2. 在C++中括号为空意味着不指出参数.在ANSI C中,括号为空意味着不指出参数.在C++中,不指定参数列表时应该用省略号. void say_bye(…); 3.  数组名数组名解释为其第一个元素的地址有一些例外,首先,对数组名使用sizeof将得到整个数组的长度:其次,将地址运算符用于数组名时

Swift 1.1语言第7章 函数和闭包

Swift 1.1语言第7章  函数和闭包 在编程中,随着处理问题的越来越复杂,代码量飞速增加.其中,大量的代码往往相互重复或者近似重复.如果不采有效方式加以解决,代码将很难维护.为 了解决这个问题,人们提出了函数这一概念.使用函数可以将特定功能的代码封装,然后在很多的地方进行使用.本章将会讲解函数和闭包的相关内容.本文选自<Swift 1.1语言快速入门> 7.1  函数介绍 函数是执行特定任务的代码块.使用函数会给开发这带来很多的好处.以下总结了其中两点. 1.结构鲜明,便于理解 如果在一

Welcome to Swift (苹果官方Swift文档初译与注解三十三)---235~240页(第五章-- 函数)

Default Parameter Values (参数默认值) 在定义函数的时候,你可以给任何参数定义一个默认的值,如果定义了默认值,在调用这个函数的时候,你可以忽略这个参数. 注意: 设置参数默认值的时候,需要按照函数的参数列表最后的开始,这可以确保在调用函数的时候,即使没默认值的参数也可以按顺序的对应上,也使得函数在调用的时候更加清晰. 下面的代码例子是join函数的新版本,它使用了默认参数: func join(string s1: String, toString s2: String