九.参数:

当函数被调用的时候,会得到一个 “免费” 奉送的参数,那就是 arguments 数组。通过它函数可以访问所有它被调用时传递给它的参数列表,包括那些没有被分配给函数声明时定义形式参数的多参数。这使得编写一个无需指定参数个数的函数成为可能:

<script>//构造一个讲很多个值相加的函数//注意函数内部定义的变量 sum 不会与函数外部定义的 sum 产生冲突。//该函数只会看到函数内部的那个变量var sum = function(){var  i,sum = 0;for(i =0;i<arguments.length;i +=1){sum += arguments[i];}return sum;};document.writeln(sum(4,8,15,16,23,42)); //108
</script>

这不是一个特别有用的模式。在第6章中,我们将会看到如何给数组添加一个相似的方法来达到同样的效果。

因为语言的一个设计错误,arguments 并不是一个真正的数组。它只是一个“类似数组(array-like)”的对象。arguments拥有一个length 属性,但它缺少所有的数组方法。我们将在本章结尾看到这个设计错误导致的后果。

十.返回

当一个函数被调用时,它从第一个语句开始执行,并在遇到关闭函数体的)时结束。那使得函数把控制权交还给调用该函数的程序部分。

return语句可用来使函数提前返回。当return被执行时,函数立即返回而不再执行余下的语句。
        一个函数总是会返回一个值。如果没有指定返回值,则返回undefined .

如果函数以在前面加上 new前缀的方式来调用,且返回值不是一个对象,则返回this (该新对象)。

十一.异常

JavaScript 提供了一套异常处理机制。异常是干扰程序正常流程的非正常(但是并非完全是出乎意料)的事故。当查出这样的事故时,你的程序就应该抛出一个异常:

var add = function(a,b){if(typeof a !== 'number' || typeof b !=='number'){throw{name: "TypeError",message: "add needs number"};}return a+b;
}

throw 语句中断函数的执行。它应该抛出一个 exception 对象。该对象包含可识别的异常类型的 name 属性和一个描述性的 message 属性。你也可以添加其他的属性。

该 exception 对象将被传递到一个 try 语句的 catch 从句:

<script>var try_it = function(){try{add("sevent");}catch(e){document.writeln(e.name + ": "+ e.message);}}try_it()//ReferenceError: add is not defined
</script>

如果在 try 代码块内抛出一个异常,控制权就会跳转到他的 catch 从句。

一个 try 语句只会有一个将捕捉所有异常的 catch代码块。如果你的处理手段取决于异常的类型,那么异常处理器必须检查异常对象的 name 属性以确定异常的类型。

注:其实 try catch 语句也可以嵌套使用,还可以和 finally 一起使用,本文就不加以讲解,要想了解的同学可以百度以下,或者私信我。

十二.给类型增加方法

JavaScript 允许给语言的基本类型增添方法。在上一次对象的博客中,我们可以清晰地看到,通过给Object.prototype 添加方法来使得该方法所有对象可用。这样的方式对所有函数可用:

<script>Function.prototype.medthod = function(name,func){this.prototype[name] = func;return this;}
</script>

通过给 Function.prototype 增加一个 method 方法,我们就不必键入 prototype 这个属性名了。这个缺点也就被掩盖了

JavaScript 并没有单独的整数类型,因此有时候只提取数值中的整数部分是必要的。JavaScript 本身提供的取整方法有些丑陋,我们可以通过给 Number.prototype 添加一个 integer 方法来改善它。他会根据数字的正负来判断是使用 Math.ceiling 还是 Math.floor。

<script>Number.method ('integer' , function (){return Math[this< 0 ? 'ceiling' : 'floor' ](this);});document.writeln ( (-10 / 3) .integer ()) // -3
</script>

JavaScript缺少一个移除字符串末端空白的方法。那是一个很容易修复的疏忽:

<script>string.method ( 'trim', function (){return this.replace (/^\S+|\s+$/g, "") ;});document.writeln ( '"'+" neat ".trim() + '"');
</script>

我们的 trim方法使用了一个正则表达式。我们将在后面看到更多关于正则表达式的内容。

通过给基本类型增加方法,我们可以大大提高语言的表现力。因为JavaScript 原型继承的动态本质,新的方法立刻被赋予到所有的值(对象实例)上,哪怕值(对象实例)是在方法被创建之前就创建好了。

基本类型的原型是公共的结构,所以在类库混用时务必小心。一个保险的做法就是只在确定没有该方法时才添加它。

<script>
//有条件地增加一个方法
Function.prototype.method = function (name,func){if( !this.prototype [name] ){this.prototype [name] =func;}
}
</script>

另一个要注意的就是for in语句用在原型上时表现很糟糕。我们在讲述对象的时候已经看到了几个减轻这个问题的影响办法:我们可以使用 hasOwnProperty 方法去筛选出继承而来的属性,或者我们可以查找特定的类型。

十三.递归

递归函数会直接或间接地调用自身的一种函数。递归是一种强大的编程技术,它将一个问题分解为一组相似的子问题,每一个都用一个寻常解去解决。一般来说,一个递归函数调用自身去解决它的子问题。

“汉诺塔”是一个著名的难题。塔的设备包括三根柱子和一套直径各不相同的空心圆盘。开始时源柱子上的所有圆盘都按照较小的圆盘放在较大的圆盘之上的顺序堆叠。目标是通过每次移动一个圆盘到另一根柱子,最终将一堆圆盘移动到目标柱子上,过程中不可以将大的圆盘放置在较小的圆盘之上。这个难题有一个寻常解:

<script>var hanoi = function (disc, src, aux, dst){if(disc > 0){hanoi (disc - 1,src, dst, aux);document.writeln ( 'Move disc ' + disc +' from ' +src + ' to ' +dst) ;hanoi (disc - 1,aux,src, dst);}}hanoi(3, 'Src','Aux' , 'Dst' );
</script>

圆盘数量为3时它返回这样的解法:

Move disc 1 from src to Dst

Move disc 2 from src to Aux

Move disc 1 from Dst to Aux

Move disc 3 from Src to Dst

Move disc 1 from Aux to Src

Move disc 2 from Aux to Dst

Move disc 1 from src to Dst

hanoi函数把一堆圆盘从--根柱子移到另一根柱子,必要时使用辅助的柱子。它把该问题分解成三个子问题。首先,它移动一对圆盘中较小的圆盘到辅助柱子上,从而露出底下较大的圆盘。然后它就移动底下的圆盘到目标柱子上。最后,它将刚才较小的圆盘从辅助柱子上再移动到目标柱子上。通过递归地调用自身去处理-一对圆盘的移动,从而解决那些子问题。

传递给hanoi函数的参数包括当前移动的圆盘编号和它将要用到的三根桂子。当它调用自身的时候,它去处理当前正在处理的圆盘之上的圆盘。最终,它会以一个不存在的圆盘编号去调用。在那样的情况下,它不执行任何操作。由于该函数对非法值不予理会,我们也就不必担心它会导致死循环。

递归函数可以非常高效地操作树形结构,比如浏览器端的文档对象模型(DOM)。每次递归调用时处理给定树的一小段。

十四.作用域

在编程语言中,作用域控制着变量与参数的可见性及生命周期。对于程序员来说这是一个重要的帮助,因为他减少了名称冲突,并且提供了自动内存管理。

var foo = function(){var a=3,b=5;var bar = function(){var b=7,c=11;//此时,a 为 3, b 为 7 ,c 为 11a += b+c;//此时,a 为 21, b 为 7 ,c 为 11};//此时,a 为 3, b 为 5 ,c 还没有定义bar();//此时,a 为 21, b 为 5
};

大多数使用C语言语法的语言都拥有块级作用域。在一个代码块中(括在一对花括号中的语句集)定义的所有变量在代码块的外部是不可见的。定义在代码块中的变量在代码块执行结束后会被释放掉。这是件好事。

糟糕的是,尽管代码块的语法似乎表现出它支持块级作用域,但实际上JavaScript 并不支持。这个混淆之处可能成为错误之源。

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

很多现代语言都推荐尽可能迟地声明变量。而用在JavaScript上的话却会成为糟糕的建议,因为它缺少块级作用域。所以,最好的做法是在函数体的顶部声明函数中可能用到的所有变量。

十五.闭包:

作用域的好处是内部函数可以访问定义他们的外部函数的参数和变量(除了 this 和 arguments)。这是一件非常好的事情。

我们的 getElementsByAttribute 函数可以工作是因为它声明了一个 results 变量,且传递给 walk_the_DOM 的内部函数也可以访问 results 变量。

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

之前,我们构造了一个myobject对象,它拥有一个value属性和一个increment方法。假定我们希望保护该值不会被非法更改。

和以对象字面量形式去初始化 my0bject 不同,我们通过调用一个函数的形式去初始化myobject ,该函数将返回一个对象字面量。此函数定义了一个 value 变量。该变量对 increment 和 getvalue 方法总是可用的,但函数的作用域使得它对其他的程序来说是不可见的。

var myObject = function(){var value = 0;return {increment:function(inc){value += typeof inc ==="number"? inc:1;},getValue:function(){return value;}}
}();

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

之前构造器函数里面定义的的 Quo 构造器产生出带有 status 属性和 get_status 方法的一个对象。但那看起来并不是十分有趣。为什么要用一个 getter 方法去访问你本可以直接访问到的属性呢?如果 status 是私有属性时,它才是更有意义的。所以,让我们定义另一种形式的 quo 函数来做此事:

<script>//创建一个名为 que 的构造函数。//他构造出带有 get_status 方法和 status 私有属性的一个对象。var quo = function(stuats){return {get_status:function(){return status;}};};//构造一个 que 实例var myQue = que("amazed");document.writeln(myQue.get_status());
</script>

这个 quo 函数被设计成无须在前面加上 new 来使用,所以名字也没有首字母大写。当我们调用 quo 时,它返回包含 get_status 方法的一个新对象。该对象的一个引用保存在 myQuo 中。即使 quo 已经返回了,但是 get_status 方法仍然享有访问 quo 对象的 status 属性的特权。get_status 方法并不是访问该参数的一个拷贝,他访问的就是该参数的本身。这是可能的,因为该函数可以访问它被创建时所处的上下文环境。这被称为闭包。闭包是一个能减少全局变量污染的很好用的方式,我在上面对对象解释的文章里面做了相关的介绍,感兴趣的同学可以去看一下。

十六.回调

函数可以让不连续事件的处理变得更容易。例如:假定有这么一个序列,由用户交互开始,向服务器发送请求,最终显示服务器的响应。最纯朴的写法可能会是这样的:

request = prepare_the_request() ;
send_request_synchronously(request,function(response){display (response) ;
});

这种方式的问题在于网络上的同步请求将会导致客户端进入假死状态。如果网络传输或服务器很慢,响应性的降低将是不可接受的。

更好的方式是发起异步的请求,提供一个当服务器的响应到达时将被调用的回调函数。异步的函数立即返回,这样客户端不会被阻塞。

request =prepare__the_request ();
send_request_asynchronously(request,function (response){display (response);
});

我们传递了一个函数作为参数给send_request_asynchronously 函数,它将在收到响应时被调用。

17.模块

我们可以使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。通过使用函数去产生模块,我们几乎可以完全摒弃全局变量的使用,从而缓解这个JavaScript的最为档糕的特性之一所带来的影响。

举例来说,假定我们想要给 String 增加一个 deentityify 方法。它的任务是寻找字符串中的HTML字符实体并替换为它们对应的字符。在一个对象中保存字符实体的名字和它们对应的字符是有意义的。但我们该在哪里保存该对象呢?我们可以把它放到一个全局变量中,但全局变量是魔鬼。我们可以把它定义在该函数本身,但是那有运行时的损耗,因为该函数在每次被执行的时候该字面量都会被求值--次。理想的方式是将其放入一个闭包,而且也许还能提供一个增加更多字符实体的扩展方法:

String.method('deentityify',function(){// 字符实体表。他映射字符实体的名字到对应的字符var entity = {quot:'"',lt:'<',gt:'>'};//返回deentityify 方法return function(){// 这才是 deentityify 方法。他调用字符串的 replace 方法。// 查找'&'开头和';'结束的之字符串。如果这些字符可以在字符实体表中找到。// 那么就将该字符实体替换为映射表中的值,他用到一个正则表达式return this.replace(/&([^&;]+);/g,function (a,b){var r = entity[b];return typeof r === 'string' ? r: a;});};
}());

注意一下最后一行。我们用() 运算法立即调用我们刚刚构造出来的函数。这个调用所创建并返回的函数才是 deentityify 方法.

document.writeln('<">'.deentityify()); // <">

模块模式利用了函数作用域和闭包来创建绑定对象与私有成员的关联,在这个例子中,只有deentityify方法有权访问字符实体表这个数据对象。 模块模式的一般形式是:

一个定义了私有变量和函数的函数; 利用闭包创建可以访问私有变量和函数的特权函数; 最后返回这个特权函数,或者把它们保存到一个可以访问的地方。 使用模块模式就可以摒弃全局变量的使用。它促进了信息隐藏和其他优秀的设计实践。对于应用程序的封装,或者构造其他单例对象,模块模式非常有效。

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

var serial_maker = function () {
// 返回一个用来产生唯一字符串的对象。
// 唯一字符串由两部分组成: 前缀 + 序列号。
// 该对象包含一个设置前缀的方法,一个设置序列号的方法。
// 和一个产生唯一序列号的gensym方法 var prefix = ''; var seq = 0; return { set_prefix: function (p) { prefix = String(p); }, set_seq: function (s) { seq = s; }, gensym: function () { var result = prefix + seq; seq += 1; return result; } };
};
var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym(); // unique is "Q1000"

seqer包含的方法都没有用到this或that。因此没有办法损害seqer。除非调用对应的方法,否则没法改变prefix或seq的值。seqer对象是可变的,所以它的方法可能会被替换掉,但替换后的方法依然不能访问私有成员。seqer就是一组函数的集合,而且那些函数被授予特权,拥有使用或修改私有状态的能力。

如果我们把seqer.gensym作为一个值传递给第三方函数,那个函数能用它产生唯一字符串,但却不能通过它来改变prefix或seq的值。

18.级联

有一些方法没有返回值。例如,一些设置或修改对象的某个状态却不返回任何值得方法就是典型的例子。如果我们让这些方法返回this而不是undefined,就可以启用级联。在一个级联中,我们可以再单独一条的语句中一次调用同一个对象的很多方法。一个启用级联的Ajax类库可能允许我们一这样的形式去编码:

getElement('myBoxDiv').move(250, 150).width(100).height(100).color('red').border('10px outset').padding('4px').appendText("Please stand by").on('mousedown', function (m) {this.startDrag(m, this.getNinth(m));}).on('mousemove', 'drag').on('mouseup', 'stopDrag').later(2000, function () {this.color('yellow').setHTML("What hath God Wraught?").slide(400, 40, 200, 200);}).tip("This box is resizeable");

在这个例子中,getElement函数产生一个对应于id="myBoxDiv"的DOM元素并提供了其他功能的对象。该方法允许我们移动元素,修改它的尺寸和样式,并添加行为。这些方法每一个都返回该对象,所以调用返回的结果可以被下一次调用所用。
级联可以产生出具备很强表现力的接口。它也能帮助控制那种构造试图一次做太多事情的接口的趋势。

19.套用

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

var addl = add.curry(1);
document.writeln(addl(6)); // 7

add1是吧1传递给add函数的curry方法后创建的一个函数。addl函数把传递给它的参数的值加1。JavaScript并没有curry方法,但我们可以通过给Function.prototype添加功能来实现:

Function.method('curry', function () {var args = arguments, that = this;return function () {return that.apply(null, args.concat(arguments));};
}); // 有些不太对头

curry方法通过创建一个保存着原始函数和被套用的参数的闭包来工作。它返回另一个函数,该函数被调用时,会返回调用原始函数的结果,并传递调用curry时的参数加上当前调用的参数的所有参数,它使用Array的concat方法去连接两个参数数组。
糟糕的是,就像我们先前看到的那样,arguments数组并非一个真正的数组,所以它并没有concat方法。要避开这个问题,我们必须在两个arguments数组上都应用数组的slice方法。这样产生出拥有concat方法的常规数组。

Function.method('curry', function () {var slice = Array.prototype.slice,args = slice.apply(arguments),that = this;return function () {return that.apply(null, args.concat(slice.apply(arguments)));};
});
说明:
// concat方法
var a = [1,2,3];
document.write(a.concat(4,5));
// slice方法
var arr = new Array(3);
arr[0] = "George";
arr[1] = "John";
arr[2] = "Thomas";
document.write(arr.slice(1)); // John,Thomas

20.记忆

函数可以用对象去记住先前操作的结果,从而能避免无谓的运算。这种优化被称为记忆。JavaScript的对象和数组要实现这种优化是非常方便的。

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

var fibonacci = function (n) {return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
};
for (var i = 0; i <= 10; i++) {document.writeln('// ' + i + ': ' + fibonacci(i));
}
// 0: 0
// 1: 1
// 2: 1
// 3: 2
// 4: 3
// 5: 5
// 6: 8
// 7: 13
// 8: 21
// 9: 34
// 10: 55

这样是可以工作的,但它做了很多无谓的工作。fibonacci函数被调用的453次。我们调用了11次,而它自身调用了442次去计算可能已被刚计算过的值。如果我们让该函数具备记忆功能,就可以显著地减少它的运算量。
我们在一个名为memo的数组里保存我们的存储结果,存储结果可以隐藏在闭包中。当我们的函数被调用时,这个函数首先看是否已经知道存储结果,如果已经知道,就立即返回这个存储结果。

var fibonacci = function () {var memo = [0, 1];var fib = function (n) {var result = memo[n];if (typeof result !== 'number') {result = fib(n - 1) + fib(n - 2);memo[n] = result;}return result;};return fib;
};

这个函数返回同样的结果,但它只被调用了29次。我们调用了它11次。它自身调用了18次去取得之前存储的记过。
我们可以把这种形式一般化,编写一个函数来帮助我们构造带记忆功能的函数。memoizer函数将取得一个初始化的memo数组和fundamental函数。它返回一个管理memo存储和在需要时调用的fundamental函数的shell函数。我们传递这个shell函数和该函数的参数给fundamental函数:

var memoizer = function (memo, fundamental) {var shell = function (n) {var result = memo[n];if (typeof result !== 'number') {result = fundamental(shell, n);memo[n] = result;}return result;};return shell;
};

现在,我们可以使用memoizer来定义fibonacci函数,提供其初始的memo数组和fundamental函数:

var fibnoacci = memoizer([0, 1], function (shell , n) {return shell(n - 1) + shell(n - 2);
});

通过设计能产生出其他函数的函数。可以极大减少我们必须要做的工作。例如:要产生一个可记忆阶乘函数,我们只需提供基本的阶乘公式即可:

var factorial = memoizer([1, 1], function (shell, n) {return n * shell (n - 1);
});

注:上述讲述的就是JavaScript语言精粹一书的函数的第二部分,感兴趣的同学可以点击下方链接访问第一部分的内容 https://blog.csdn.net/qq_51227484/article/details/118768098

JavaScript语言精粹——函数(第二部分)相关推荐

  1. 《javascript语言精粹》读书笔记——函数

    这几天发现了一本好书,又薄又精辟,<JavaScript语言精粹> 看了对象.函数这两节,发现书如其名,确实是精粹. 函数的调用 函数调用的方式有四种: - 方法调用模式 - 函数调用模式 ...

  2. 《JavaScript语言精粹》学习笔记(函数(2))

    <JavaScript语言精粹>学习笔记(函数(2)) 函数(Functions) 参数(Arguments) 当参数被调用时,会得到一个"免费"的参数数组argume ...

  3. 精通javascript、javascript语言精粹读书笔记

    于是选择两本书做最后的冲刺: 精通javascript(jquery之父著作),javascript语言精粹.希望我也能成为一个javascript的好手. Dom Elements的属性 精通jav ...

  4. 《JavaScript语言精粹 修订版》 读书笔记

    之前看到这篇文章, 前端网老姚浅谈:怎么学JavaScript?,说到怎么学习JavaScript,那就是 看书.分析源码. 10本书读2遍的好处,应该大于一本书读20遍. 看书主动学习,看视频是被动 ...

  5. 读阮一峰对《javascript语言精粹》的笔记,我有疑问。

    <javascript语言精粹>是一本很棒的书籍,其中作者在附录列出了12种他所认为的javascript语言中的糟粕. 我最近开始跟读前端前辈的博客,其中读到了阮一峰的<12种不宜 ...

  6. Javascript语言精粹之Array常用方法分析

    Javascript语言精粹之Array常用方法分析 1.Array常用方法分析 1.1 Array.prototype.sort() Javascript的默认比较函数假定被排序元素都是字符串,所以 ...

  7. javascript语言精粹 微盘_JavaScript语言精粹(修订版)pdf

    摘要 "学习.理解.实践大师的思想,我们才有可能站在巨人的肩上,才有机会赶超大师,这本书就是开始." "这是一本介绍JavaScript语言本质的重要书籍,值得任何正在或 ...

  8. JavaScript 语言精粹读书笔记

    最近在看 赵泽欣 / 鄢学鹍 翻译的 蝴蝶书, 把一些读后感言记录在这里. 主要是把作者的建议跟 ES5/ES5.1/ES6 新添加的功能进行了对比 涉及到的一些定义 IIFE: Immediatel ...

  9. JavaScript语言精粹-读书笔记(1)

    JS 语言精粹 蝴蝶书 点击链接下载电子书 这本书需要基本的JS基础和一定的经验.第一次看感觉很普通,工作一段时间后发现这本书很多细节很重要.这本书基于 ECMA3版本写的,现在已经普遍使用ECMA6 ...

最新文章

  1. nojy 105 九的余数
  2. linux光驱驱动目录,linux下挂载光驱
  3. 怎样使用python替代shell?
  4. linux 命令行 ctrl z,Linux操作系统下运行命令时CTRL+Z的作用
  5. android jni 字符串拼接,JNI 字符串拼接方法
  6. 表达式树练习实践:入门基础
  7. 【C语言进阶深度学习记录】二十六 C语言中的字符串与字符数组的详细分析
  8. Ubuntu 12.04 安装配置 Apache2
  9. 关于二叉树,你该了解这些!
  10. JavaScript开发者应懂的33个概念js-33-concepts
  11. ps2021没法用神经元滤镜,ps2021神经滤镜不能下载
  12. 计算机bios更改usb端口,联想电脑bios怎么设置usb接口
  13. Mybatis之分页插件PageHelper工作原理
  14. Activity生命周期走向分析
  15. 批处理文件rd \s\q **是什么意思?
  16. SpringBoot集成Minio搭建自己的分布式文件服务器(Minio集成篇)
  17. 什么是Teardrop攻击?我们要如何防御Teardrop攻击?
  18. 如何将局域网IP映射为公网IP
  19. VLClclc Plugin Object的方法
  20. 数据结构-树与深度优先遍历

热门文章

  1. textrank 算法
  2. 信息系统项目管理师-五大过程组与十大管理49个过程之间的对应关系总览
  3. 2022年无线蓝牙耳机推荐?口碑最好的国产无线蓝牙耳机推荐
  4. 正则验证密码格式(密码必须包含字母大小写、数字、特殊字符,且不能少于8位)
  5. java exchanger 应用场景_java多线程系列:Semaphore和Exchanger
  6. 基于泰尔森回归的股票预测研究
  7. 有两种歌声可以打动我
  8. 手把手教你如何下载各种文具用品的商品图
  9. Android判断直播结束,Android 直播打断事件处理
  10. 关于计算机游戏的英语读法,电脑游戏,computer game,音标,读音,翻译,英文例句,英语词典...