从概念上讲,函数接受输入,进行计算,而后产生输出。下图是一个函数黑盒示意图,它计算一个帐户在t
年以后的余额,其初始余额为p
,年利率为r
,每一年取n
次复利。
要使用这个函数,只需向函数发送四个数值,并在其回应信息中获取计算所得的余额。函数的用户看不到其“内在工做”,因此咱们把函数想象成黑盒子。javascript
这里书本给出一个关于什么是抽象的说明:在平常生活中到处能够看到相似的状况。咱们开车,但并不了解内燃机或者氢燃料电池;咱们用微波炉加热事食物,却不明白深层的物理学知识;咱们发送即便消息、推文、打电话,却对文字、声音的编码与传输方式一无所知。咱们把这种只看事物的主体部分而不关心细节的理念叫作抽象。函数则是对计算的抽象。html
在JavaScript中,函数类型值包含一个可执行的代码块,成为函数体,以及零个或多个输入,成为形参(parameter)。下面这个函数只有一个参数,它会计算此参数的三次方。前端
function (x) {return x*x*x;}
函数也是值,和数字、真值、字符串、数组及普通对象同样。所以,能够把函数类型值赋给变量。java
var cube = function (x) {return x*x*x;}
argument
)。在被调用事,函数首先把每一个实参值赋给对应的形参,而后执行函数体。若是存在return
语句,它会将计算结果传回给调用者。下面的脚本展现一个函数定义以及对它的三次调用。程序员
// 定义函数 —— 这时不会运行函数体 var cube = function (x) { return x*x*x; }; // 进三次调用,将函数体运行三次 alert(cube(-2)); alert(cube(10)); alert(("在一个魔方中有" + (cube(3) - 1) + "个立方体 "))
在第一次调用中,咱们向函数cube
传递了-2,cube
会把-2赋值给x
,而后计算-2-2-2,并把结果值(-8)返回给调用处。这个值随后又被传给alert
函数的调用。
函数也能够有名字。函数有了名字,在调用时,就不必定要将它赋值给变量了。web
function cube (x) { return x*x*x; }; alert(cube(-2)); // -8
在JavaScript中,这种定义方式成为函数声明,相似于(但又不彻底等同于)把函数赋值给一个同名变量。尽管不少程序员喜欢函数声明的方式,但咱们更喜欢使用变量声明方式。咱们会在章尾讨论。编程
var diceRoll = function () { return 1+Math.floor(6*Math.random()); };
diceRoll()
,而不能写成diceRoll
。前一个表达式会调用函数,然后一个就是函数自身。var diceRoll = function () { return 1+Math.floor(6*Math.random()); }; alert( diceRoll() ); alert( diceRoll );
这里都没什么问题,我想了想试了下以下函数:segmentfault
function test() { return "fn-test"; }; alert(test);
若是一个函数完成了某主体的执行,却没有执行任何return
语句,它会返回undefined
值。这个undefined
值真的只是一个技术术语,由于在调用一个没有return
语句的函数时,主要是为了它产生的效果,而不是为了它产生的任何值。api
我对这就句话理解:若是函数定义时没有要求返回最终值,则默认返回undefined
。调用一个没有返回值的函数后面还不是太理解...效果?何种效果?数组
var echo = function (message) { alert(message + "."); alert("I said: "+message+"!"); }; echo("Sanibonani"); // 调用这个函数最天然的方式 var x =echo("Hello"); // 为x赋值undefined,但在实际中不会发生 console.log(x); // undefined
若是没有为函数传递足够了实参值,则额外的形参变量会被初始化为undefined。
var show = function (x,y) { alert(x+" "+y); }; show(1); // "1 undefined"
// 返回半径为r的圆的面积 var circleArea = function (r) { return Math.PI*r*r; }; // 返回y可否被x整除 var divides = function (x,y) { return y % x === 0; };
咱们能够利用本身编写的函数来构建其余函数。
...都是基本的例子略过。
函数语句内隐式类型转换和优先级与结合性问题!
略.......
将对象引用做为参数传送
看一下向函数传递对象的状况
// 返回一个数组中全部元素之和 var sum = function (a) { var result = 0; for (var i=0;i<a.length;i+=1) { result += a[i]; } return result; }; alert(sum([])); alert(sum([10,-3,8]));
再看另外一个例子,它使用了一种彻底不一样的风格。
// 把一个数组中全部字符串都转换成大写 var uppercaseAll = function (a) { for (var i=0;i<a.length;i+=1) { a[i]=a[i].toUpperCase(); } };
区别在于,函数sum返回一个值,而uppercaseAll根本没有包含return语句!相反,它修改了传递给它
// 把一个数组中全部字符串都转换成大写 var uppercaseAll = function (a) { for (var i=0;i<a.length;i+=1) { a[i]=a[i].toUpperCase(); } }; var result = uppercaseAll(["a","b","c"]); alert(result); // undefined alert(uppercaseAll(["a","b","c"])); // undefined
自我理解:调用函数后,传参,计算,由于没有return
返回值,因此只是计算而已,则uppercaseAll(["a","b","c"])
就会像章开头说的那样,默认返回undefined
,而后赋值给变量result
。(最后alert
调用函数证实这一点)。
var uppercaseAll = function (a) { for (var i=0;i<a.length;i++) { a[i]=a[i].toUpperCase(); } }; var dogs = ["spike","spot","rex"]; alert(uppercaseAll(dogs)) // undefined,此值并不表明函数没有执行,而是执行了未指定返回值,则返回默认值。 alert(dogs); // ["SPIKE","SPOT","REX"],修改了传入对象的属性
alert
调用,结果是undefined
。由于调用函数并无返回结果,只会返回undefined
(虽然函数内部的确执行了大写转换操做,可是没有返回值然并卵
),因此最后显示undefined
。dogs
引用的数组对象已经被修改,即以前说的,它修改了传递给它的对象的属性。由于没有设置返回值,默认返回的undefined
被上一个alert
函数调用。而dogs
引用的数组被调用结束后因为没有返回值,避免了成为新数组被返回出去。因此大写字母保留下来。另外一个:
var uppercaseAll = function (a) { var result = []; for (var i=0;i<a.length;i+=1) { result.push(a[i].toUpperCase()) } return result; // 返回的是一个新数组! }; var dogs = ["spike","spot","rex"]; alert(uppercaseAll(dogs)); // ["SPIKE","SPOT","REX"],这里alert调用的对象,是函数返回的新数组!不是dogs alert(dogs); // ["spike","spot","rex"]
alert
函数接收,显示处理结果:["SPIKE","SPOT","REX"]
dogs
引用的数组仍是小写呢?由于调用函数返回的最终值没有从新赋值给dogs
。换句话说,alert
函数调用的dogs
数组,和uppercaseAll
函数没有关系。uppercaseAll
执行结束已经返回了一个新数组
。alert(uppercaseAll(dogs));
这段语句的结果,是大写字母
仍是undefined
,取决因而否对函数设置返回值!!!确保你理解了最后这两个函数的区别,第一个函数修改了其实参的属性,第二个函数没有改动实参,而是
返回一个新的数组
。
// 返回数组中的最大元素 var max = function (a) { var largest = a[0]; for (var i=0;i<a.lengt;ai++) { if (a[i]>largest) { largest = a[i]; } } return largest; }; max([7,19,-22,0]); max(["dog","rat","cat"]);
它能正常工做吗?
这个函数依靠>
操做符一次比较数组中的连续值,跟踪当前找到的最大值(从第一个元素a[0]
开始)。如今>
知道如何比较数字与数字、字符串与字符串,但奇怪的是,除非>两边的值都是字符串,不然JavaScript会把这两个值都看做数字(隐式转换),而后进行相应比较。有时,这种作法是没问题的。
但若是有一个值被转换成NaN
,那么状况就不妙了。若是x
或y
为NaN
,表达式x>y
会得出false
。3>NaN
是false
,NaN>3
也是false
!这就表示:
alert(max([3,"dog"])); // 3 alert(max(["dog",3])); // "dog"
3
和"dog"
实际上是不可比较的,因此计算这种数组的最大值基本上没有什么意义。那在这种状况下难道不该当抛出一个异常吗?不少语言都会这么作。其余语言甚至会拒绝运行包含这种比较的程序!而后,JavaScript很愉快地运行了这种比较,而后给出了没什么意义的结果,若是愿意的话,能够尝试在代码里探测这些问题。
/*返回数组中的最大元素。若是数组包含了不可比较的元素,函数会返回一个不肯定的任意值*/
函数在这个注释中承诺:只要调用者仅传递有意义的参数,那它就返回最大值;不然契约失效。函数对实参提出了这些约数条件称为先决条件
。函数自身不会检查先决条件,没有知足先决条件只是会致使未指明的行为。先决条件是编程圈子很是熟悉并且深入理解的一个术语,因此咱们将为引入先决条件的注释采用一种约定。
// 返回数组中的最大元素。先决条件:数组中的全部元素必须是能够互相比较的
重构
。重构
:重构就是对代码作结构性的调整,让其变得更好,通常(但不必定)是将大而混乱的代码分解成较小的组成部分。在这个案例中咱们要将用户交互与主要计算区分开
来,将主要计算部分包装成一个漂亮的函数。// 返回n是否为质数。先决条件:n是一个大于或等于2的整数,在JavaScript可表示的整数范围以内。 var isPrime = function (n) { for (var k=2,last=Math.sqrt(n);k<=last;k+=1) { if (n%k===0) { return false; } } return true; };
请务必注意:这个函数只会返回它的实参是否是质数,并不会弹出一条说明判断结果的消息!其他脚本负责提示输入、检查错误、报告结果。
var SMALLEST = 2,BIGGEST = 9E15; var n = prompt("输入一个数组,我会检查它是否是质数"); if (isNaN(n)) { alert("这不是个数字"); } else if (n<SMALLEST) { alert("我不能检测这么小的数字"); } else if (n>BIGGEST) { alert("这个数字对我来讲太大了,没法检测"); } else if (n%1!==0) { alert("我只能测试整数"); } else { alert(n+"是"+(isPrime(n)? "质数" : "合数")); // 注意这里若是去掉三目运算符的括号,则会先计算字符串链接符,永远弹出:"质数" }
分离关注点可让复杂系统变得容易理解。
对于像航天飞机或金融服务系统这样额大型系统,要理解或诊断其中的某个问题,必须可以肯定一些具备明确行为的子系统。若是只是把一个大型系统当作一系列语句的集合,那就永远没法真正理解它。
将质数计算放到它本身的函数中,就能生成一段能够重复使用的代码,能够将它放到咱们未来编写的任意脚本中。咱们已经体验过函数的复用性
了:咱们已经调用过alert
和Math.sqrt
,却不须要本身去编写其中的细节。
但咱们这个质数函数的复用性到底如何呢?调用这个函数的脚本作了不少错误检查。若是真的但愿这个函数只需编写一次,却能被数百个、数千个脚本调用,那期待这些“调用者”来作一样的错误检查是否公平呢?固然不公平了。咱们能够在函数中检查错误。
// 返回实参是否为2到9e15之间的质数。 // 若是实参不是整数或者超出2到9e15的范围,则会抛出异常。 var isPrime = function (n) { if (n%1!==0 || n<2 || n>9e15) { throw "这个数组不是整数或者超出范围"; }; for (var k=2,last=Math.sqrt(n);k<last;k++) { if (n%k===0) { return true; } } return false; };
注意,这个函数在遇到问题时会抛出异常,而不是弹出错误提示!这是很关键的。要使函数真正实现可复用,它永远都不该接管用户交流的责任。(我理解为错误不与交互模块混用
)
函数的不一样用户对错误报告可能会有不一样的要求。有些人会把错误写到网页的某个位置,有些人可能会把错误收集到一个数组中,有些人可能想用别的某种语言博报告错误,预测用户可能使用的每种语言不是这个函数的任务。
当编写为调用者计算数值的函数时,应当经过抛出异常来指示错误。
本节最后一个例子是一个生成斐波那契数列的函数。斐波那契数列是一个很是值得注意的数列,在天然、音和金融市场中都会出现它的属性。这个数列的开头以下:
0,1,1,2,3,5,8,13,21,34,55,89,144,...
数列中每一个值(前两个值除外)都是前两个值之和。
f(n)=f(n-1)+f(n-2)
咱们的函数会构造一个数组f,从[0,1]开始,而后不停地把最后一个元素(f[f.length-1])和倒数第二个元素(f[f.length-2])相加。由于函数只能处理整数,因此咱们必须确保结果只不会超过JavaScript能够连续表达的整数范围,大约是9e15。就目前来讲,咱们先作个弊,只生成其中的前75的数字,由于我知道这些数字是安全的。
// 返回一个数组,其中包含斐波那契数列的前75个数字。即f.length = 75 var fibonacciSequence = function () { var f = [0,1]; for (var i=0;i<=75;i++) { f.push(f[f.length-1]+f[f.length-2]); } alert(f); }; fibonacciSequence();
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> /*计算*/ function fbnqFn(n) { var fbnqArr = [0,1]; for (var i=0;i<n;i++) { fbnqArr.push(fbnqArr[fbnqArr.length-2]+fbnqArr[fbnqArr.length-1]); } fbnqArr.length = n; return fbnqArr; }; /*交互*/ function client() { var Max = prompt("须要生成多少个斐波那契数?(不能超过150)"); if (isNaN(Max)===true || Max%1!==0 || Max<0 || Max>150) { throw "输入数字不能是非数字、整数、负数且不能超过150"; } var test = fbnqFn(Max); console.log(test+" | "+test.length); }; // start client(); </script> </body> </html>
利用函数,能够将其任意的计算进行打包,在调用者看来,就是一条单独地简单命令。请看如下计算阶乘的函数:
// 返回n的阶乘。先决条件:n是一个介于0到21之间的整数(包含0和21)。超过21,返回近似值 var factorial = function (n) { var result = 1; for (var i=1;i<=n;i++) { result *= i; } return result; };
这个函数声明了一个形参n,以及它本身的两个变量:i和result。在函数内部声明的变量成为局部变量,和形参同样,属于函数本身,与脚本其余位置的同名变量彻底无关。这点很是好,请看:
var result = 100; alert(factorial(5)); // 120 alert(result); // 100
咱们不会希全局变量result
仅仅由于咱们计算了一次阶乘就发生改变。脚本的不一样部分每每是由不一样人编写的。编写函数调用部分的做者彻底不知道在函数中会用到哪些变量。若是你调用了alert
函数,而它改变了你的某些变量,你确定会不高兴。
在JavaScript中,在函数内部声明的变量以及函数的形参均拥有函数做用域,而在函数以外声明的变量则拥有全局做用域,成为全局变量。拥有函数做用域的变量只在声明它们的函数中可见,与外部世界隔离,就像咱们前面看到的那样。下面这段很是简短的脚本更清地代表了这一点。
var message = "冥王星只是一个矮行星"; var warn = function () { var message = "你立刻要看到一些争议性的东西"; alert(message); }; warn(); // "你立刻要看到一些争议性的东西" alert(message); // "冥王星只是一个矮行星"
这里有两个恰巧同名的不一样变量。全局变量的做用域开始于它的声明位置,一直延伸到脚本结束,而局部变量的做用域则是声明它的函数体内部。在这种状况下,局部变量和全局变量的名字相同(message),其做用域重叠。在重叠区域中,最内层的声明优先。
var warning = "不要双击提交按钮"; var warn = function () { alert(warning); // 这里能够看到全局变量 }; warn(); // "不要双击提交按钮" alert(warning); // "不要双击提交按钮"
能在函数访问全局变量并无什么使人惊讶的。实际上,咱们已经用过了不少全局变量:alert
、prompt
、isNaN
、Math
等等。若是不容许在函数中用它们,要完成任何事情都会面临巨大的阻碍。可是,这也意味着一个潜在的问题。
var message = "新游戏的时间"; var play = function () { message = "正在玩"; // 没有声明 alert(message); }; alert(message); play(); alert(message); play();
上面脚本定义了一个message变量,它的值由函数更新。在函数中修改全局变量几乎总被认为是很是差的编程实践:脚本中的函数进行"相互交流"的正确作法是经过函数实参和返回值,而不是经过全局变量。程序应当尽可能少的使用全局变量:
尽可能减小全局变量的使用。具体来讲,函数应该经过参数和返回值进行"交流",而不是经过更新全局变量。
JavaScript中局部变量的做用域包含了声明它们的整个函数体,这一事实又会致使另外一种可能状况:全局变量是在声明以后才会出现,而局部变量则是在其函数开始执行时就立刻存在的,即使变量是在函数体中间声明的。考虑如下代码:
var x = 1; // 在此处,全局变量x已经存在,而全局变量y则还没有存在 // 在此处使用y则会抛出一个ReferenceError引用错误 var y = 2; // 此时全局变量y已经存在 var f = function () { alert(z); // 没有错误,显示undefined var z = 3; alert(y+3); // 5 }; f();
其实上面例子有一个变量提高
的问题,根据变量提高机制,var
会提高到当前做用域的顶端,z
的做用域是f
所包含的区块,因此你的代码等价于
var x = 1; var y = 2; var f = function () { var z; // 会把 var 声明提高到最高的位置 这种特性叫作 变量提高 此时声明了 z 可是为定义值 因此z的值是 undefined alert(z); z = 3; alert(y+3); }; f();
当调用函数时,JavaScript引擎会在该处建立一个对象,用以保存函数的形参和局部变量。形参会被当即初始化,得到调用时所传实参值的副本,全部局部变量会被马上初始化为undefined
(这里不是先初始化再赋值的?)。上面例子里,在z声明前就引用了它,但并无抛出ReferenceError
,其缘由就在于此。可是,尽管你知道局部变量在声明以前便可调用,但这并不意味着就应该使用处于未定义状态的局部变量。事实上,故意在定义变量以前就使用它们,几乎可让全部阅读你代码的人产生混淆,因此这被认为是很是差的风格。不少JavaScript风格指南甚至直接认定这是一种错误;JSLint甚至包含了一项设置,专门用于检查这一状况。
练习(包含变量声明提高和函数声明提高问题)
函数内部定义的变量只对函数内部可见,对外部不可见
或,函数内部定义的变量、对象。使其能被外部发现,使用的范围。
var x = 1; var f = function (y) { alert(x+y); }; f(2); // 3
那么按照变量提高,其实是:
var x = 1; var f = function (y) { var y; // 声明提高 y = 2; // 得到调用函数传入的实参 alert(x+y); // 这里的y引用的是全局变量y }; f(2);
若是把形参y
更名为x
,脚本会提示什么?
var x = 1; var f = function (x) { alert(x+y); }; f(2);
实际上会报错,由于变量y
没有定义
var x = 1; var f = function (x) { var x; x = 2; alert(x+y); };
JavaScript是每个值,只要它不是undefined、null、布尔值、数字和字符串,那它就是一个对象。所以,函数值也是对象,并且跟全部对象同样,也能够有属性。它们还能够像其余值同样,其自己是其余对象的属性。
知道函数是对象以后,天然会问,函数有那些属性?
函数属性的其余用途包括:计算生成特定结果的次数、记住函数在给定实参下的返回值,以及定义与特定对象集合相关的数据。
当建立了函数对象以后,JavaScript会其初始化两个属性。第一个是length
,初始值为函数的形参
个数。
var average = function (x,y) { return (x+y)/2; }; alert(average.length); // 2,一个用于x,一个用于y
第二个预约义属性是prototype
,以后在讨论
因为函数也是值,因此能够做为对象的属性。把函数放在对象内部有两个主要理由,第一个理由是把许多相关函数放在一组。例如:
var geometry = { circleArea:function (radius) { return Math.PI*radius*radius; }, circleCircumference:function (radius) { return 2*Math.PI*radius; }, sphereSurfaceArea:function (radius) { return 4*Math.PI*radius*radius; }, boxVolume:function (length,width,depth) { return length*width*depth; } };
把许多函数组合到单个对象中,有助于组织和理解大型程序。人类不但愿去尝试理解一个拥有数百个甚至数千个函数的系统,若是一个系统只有数十个软件组成部分,那咱们理解起来会容易不少。例如,在一个游戏程序中,咱们会很天然地为玩家、地貌、物理属性、消息传递、装备、图像等分别建立出子系统,每一个都是一个很大的对象。
面向过程
转向面向对象
。例如,咱们不必定要将函数看做对形状执行操做,将函数存储为形状的属性。将函数放在对象的内部,可让人们专一于这些函数,让函数扮演对象行为的角色。 var circle = { radius:5, area:function () { return Math.PI*this.radius*this.radius; }, circumference:function () { return 2*Math.PI*this.radius; } }; alert(circle.area()); // 78.53981633974483 circle.radius = 1.5; alert(circle.circumference()); // 9.42477796076938
this
表达式,这是一个至关强大的表达式,能够根据上下文表达出不一样含义。当一个调用中引入了包含函数的对象时(就如上面的circle.area()
),this
指的就是这个包含函数的对象。使用
this
表达式的函数属性称为方法
。所以,咱们说circle
有一个area
方法和一个circumference
方法。
谈谈本身理解,有大神有别的建议欢迎评论
更好的运用面向对象编程思惟
对象在建立时自带操做函数(属性),存储在对象内部,做为对象的一部分存在。
var x = 2; var p = { x:1, y:1, z:function () { return x + this.x; // x引用的是全局变量,this.x指向的是p.x }, }; alert(p.z()); // 3
在上一节,咱们仅定义了一个circle
圆对象。但若是须要不少个圆,怎么办?
// 错误的示范 var Circle = function (r) { return { radius:r, area:function () { return Math.PI*this.radius*this.radius; }, circumference:function () { return 2*Math.PI*this.radius; } }; }; var c1 = Circle(2); // 建立一个半径为2的圆 var c2 = Circle(10); // 建立一个半径为10的圆 alert(c1.area()) // "314.1592653589793"
这段代码表面上看没问题,但有一个缺陷。每次建立一个圆,也另行建立了额外的面积和周长方法。
在建立多个圆时,会浪费大量的内存来保存面积和周长函数的冗余副本——这是很糟糕的事情,由于内存资源是有限的。当脚本耗尽内存就会崩溃。型号,JavaScript的原型prototype
提供了一种解决方案。
// 一个圆的原型,其设计目的是做为下面用Circle函数建立的全部圆的原型 var protoCircle = { radius:1, area:function () {return Math.PI*this.radius*this.radius;}, circumference:function () {return 2*Math.PI*this.radius;} }; // 建立具备给定半径的圆 var Circle = function (r) { var c= Object.create(protoCircle); // 将protoCircle原型建立到变量c中 c.radius = r; // c的_proto_指向protoCircle对象 return c; };
Circle
建立的圆都有本身的radius
属性和一个隐藏连接,指向一个惟一的共享原型
,其中包含了area
和circumference
函数(分别只有一个)。这是极好的,不过还只是有小小缺陷。咱们使用了两个全局变量Circle
和protoCircle
。若是只有一个就更好了,这样可让咱们的原型圆做为Circle
函数的一个属性。咱们如今就有了一模式,用于很方便的定义一系列同种"类型"
的对象。/* 一个圆数据类型。概要: * * var c = Circle(5); * c.radius => 5 * c.area() => 25pi * c.circumference() => 10pi */ var Circle = function (r) { var circle = Object.create(Circle.prototype); circle.radius = r; return circle; }; Circle.prototype = { area:function () {return Math.PI*this.radius*this.radius}, circumference:function () {return 2*Math.PI*this.radius}, };
咱们能够应用这一模式,生成一个用于建立矩形的函数。
/* 矩形数据类型。概要: * * var r = Rectangle(5,4); * r.width => 5 * r.height => 4 * r.area() => 20 * r.perimeter() => 18 */ var Rectangle = function (w,h) { var rectangle = Object.create(Rectangle.prototype); rectangle.width = w; rectangle.height = h; return rectangle; }; Rectangle.prototype = { area:function () {return this.width*this.height}; perimeter:function () {return 2*(this.width+this.height)} };
全新方式:JavaScript中的每一个函数对象都自动包含一个prototype
属性,prototype
是函数两个预约义属性中的第二个,第一个length
。只要函数一经定义,它的prototype
属性就会被初始化为一个全新对象。(这个全新对象有本身的一个属性,叫作constructor
)。
下图展现了一个新鲜出炉的函数,用于算两个值的平均值。
其次在使用函数建立对象时,只要是用来魔法操做符new,就无需明确链接原型,也无需返回新建立对象。当你在函数调用以前加上了new时,会发生三件事情。
JavaScript会建立一个全新的空对象,而后使用引用这个新对象的表达式this来调用此函数。
该构造对象的原型被设定为函数的prototype属性。
该函数会自动返回新的对象(除非你明确要求函数返回其余东西)
这些规则看上去很复杂,但看一个例子就清楚了。
产生一个圆的函数,如何使用new操做符来调用该函数,建立的圆的实例
/*一个圆数据类型。概要: * var c = new Circle(5); * c.radius => 5 * c.area() => 25pi * c.circumference() => 10pi */ var Circle = function (r) { this.radius = r; }; Circle.prototype.area = function () { return Math.PI*this.radius*this.radius; }; Circle.prototype.circumference = function () { return 2*Math.PI*this.radius; }; var c1 = new Circle(2); // 建立半径为2的圆 var c2 = new Circle(10); // 建立半径为10的圆 alert(c2.area()); // "314.1592653589793"
此脚本先建立一个函数对象,咱们将用变量Circle
引用它。和全部函数同样,建立它时,拥有一个第二对象,这个对象被prototye
属性引用。随后,咱们向这个原型对象添加area
和circumference
函数。接下来咱们调用new Circle
建立一对圆对象。操做符new
建立新的对象,这个对象其原型为Circle.prototype
。
根据设计,诸如Circle
这样的函数就是要用new
调用的,这种函数称为构造器。根据约定,咱们用大写首字母命名,并省略return
语句,优先使用JavaScript的自动功能返回新建立的对象。之因此要约定使用大写首字母,缘由在下一节给出。
没有return
语句的构造器调用将返回对象,而不是返回一般的undefined
,新建立对象的原型将被神奇地指定给一个历来不会显式建立的对象。
Object.create
的缘由之一。一些JavaScript程序员建议对于新脚本仅使用Object.create
,由于这样可让对象与其原型之间的连接更为明确。明确的代码更易读易懂易于处理。坚持使用Object.create
的另外一个缘由多是出于哲学考虑:咱们能够直接用对象来考虑问题,而不用另行引用“类型”的概念。可是,咱们不能放弃构造器和操做符new
。JavaScript从一开始就在使用它们,数以千计的现有脚本中都使用了它们,JavaScript的许多内置对象都是经过这些方式构建的,因此咱们须要真正理解它们。经过一些练习能够熟悉它们,对目前来讲,请复习如下步骤。
new
建立和使用一种自定义数据类型,好比圆: this.radius = r
这样的赋值语句,为每一个圆初始化一个独有的属性;Circle.prototype
;new Circle()
来建立特定圆。对于如此建立的每一个圆,其原型将自动变为Circle.prototype
apply
和call
)前面的在JavaScript——this、全局变量和局部变量混谈中已经给出前两种规则(全局做用域和函数做用域下的this引用),接下来要说一个注意点。
3
:当用一个以new
操做符调用的函数中时,this
引用指的是新建立的对象。var Point = function (x,y ) { this.x = x; this.y = y; }; var p = new Point(4,-5); // 新的实例 var q = Point(3,8); // 这里修改了全局变量x和y!
上面的最后一行代表,咱们必定要很是注意,老是以new
来调用构造器,以避免修改了已有的全局变量,致使脚本运行失控。为减小发生这种意外的可能性,JavaScript程序员用大写字母书写构造器的名字。可使用一些工具(JSLint)来扫描代码,不要调用函数而不使用new前缀,很危险!
4
:利用函数方法apply
和call
,能够专门定义一个但愿用做this
值的对象。var f = function (a,b,c) { this.x += a+b+c; }; var a = {x:1,y:2}; f.apply(a,[10,20,5]); // 调用f(10,20,5),以"a"为this f.call(a,3,4,15); // 调用f(3,4,15),以"a"为this alert(a.x); // 58 var Point = function (x,y) { this.x = x; this.y = y; }; var p = {z:3}; Point.apply(p,[2,9]); // 如今p为{x:2,y:9,z:3} Point.call(p,10,4); // 如今p为{x:10,y:4,z:3}
这些方法容许借用(或劫持)现有的方法和构造器,将它们用于一些原本没打算为其使用的对象。这些方法稍有不一样:call
会传送其实参,而apply
会将实参打包放在一个数组中。
this
引用有哪四种应用?分别对应不一样的做用域和上下文中,全局做用域、被当作方法调用、new
构造函数调用、apply
与call
。
var p = { x:1, f:function (y) { this.x += y; return this.x; } }; var q = {x:5}; alert(p.f(1)); alert(p.f.call(q,3));
先分析第一个alert
:既然是p.f
,接收方对象是p
,则this
引用指向p
。且本来的p
对象中,局部变量x
值为1,执行函数传参后,1 += 1;因此最后p.x
的值为2。
第二个alert
一样是p.f
,只是此次用了call
方法,这个方法能够借用现有的方法和构造器,也就是说,p.f
这个方法被借用了,给谁呢?对了括号内的q
对象,并传参3,此时this
引用指向了q
对象(注意当调用的一瞬间,this
已经指向了q
对象),且q
对象已经有q.x=5
,传参相加,最终结果q.x
值为8。
考虑下面两个函数:
var squareAll = function (a) { var result = []; for (var i=0;i<a.length;i+=1) { result[i]=a[i]*a[i]; } return result; }; var capitalizeAll = function (a) { var result = []; for (var i=0;i<a.length;i+=1) { result[i]=a[i].toUpperCase(); } return result; };
这两个函数只有很小的一点不一样,他们都是向一个数组中的每一个元素应用一个函数,并收集结果;可是,第一个函数是计算这些元素的平方,而第二个函数则是将这些元素变为大写。咱们能不能仅为共同结构编写一次代码,而后用参数来实现它们之间的小小区别?
var collect = function (a,f) { var result = []; for (var i=0;i<a.length;i+=1) { result[i]=f(a[i]); } return result; };
对每一个数组元素实际执行的函数(好比求平方或转换为大写)如今做为实参传送。
var square = function (x) {return x*x}; var capitalize = function (x) {return x.toUpperCase();}; var squareAll = function (a) {return collect(a,square);}; var capitalizeAll = function (a) {return collect(a,capitalize)};
对于这些小小的square和capitalize函数,咱们甚至能够不为其声明变量。
var squareAll = function (a) { return collect(a,function (x) {return x*x};) }; var capitalizeAll = function (a) { return collect(a,function (x) {return x.toUpperCase();}); };
好,来看看它们如何工做的。
var arr1 = [-2,5,0]; var arr2 = ["hi","ho"]; alert(squareAll(arr1)); alert(capitalizeAll(arr2));
函数f接受一个另外一个函数g做为其实参(并在本身体内调用g),这种函数f称为高阶函数。函数collect称为高阶函数,内置的sort也是如此。咱们能够向sort传送一个比较函数,使它采用不一样的排序方式。比比较函数就是咱们本身编写的一个两实参函数,当第一个实参小于第二个时返回一个负值,当两个实参相等时返回0,当第一个实参较大时则返回一个正值。
var a = [3,6,10,1,40,25,8,73]; alert(a.sort()); // 按字母排序 alert(a.sort(function (x,y) {return x-y;})); // 按数值递增排序 alert(a.sort(function (x,y) {return y-x;})); // 按数值递减排序
由于咱们能够告诉sort
函数,按照咱们喜欢的任意方式来比较元素,因此能够编写一些代码,用几种不一样方式对一组对象进行排序。
在web页上设置定时器、与用户操做进行交流时,常常会传送函数。它也是人工智能编程中最为重要的程序设计范例之一。且有助于构建很是大的分布式应用程序。
高阶函数一词不只适用于以函数为实参的函数,还适用于返回函数的函数。
var withParentheses = function (s) {return "("+s+")";}; var withBrackets = function (s) {return "["+s+"]";}; var withBraces = function (s) {return "{"+s+"}";};
这三个函数很是相似。能够怎样进行重构呢?这三个函数中的每个均可以由另外一函数构造而成,只需告诉构造者要使用那种分隔符便可。
var delimitWith = function (prefix,suffix) { return function (s) {return prefix+s+suffix;} }; var withParentheses = delimitWith("("+s+")"); var withBrackets = delimitWith("[","]"); var withBraces = delimitWith("{","}");
withParentheses、withBrackets、withBraces
这三个函数都成为闭包。粗略的说,JavaScript闭包是一种函数,它的函数体使用了来自外围(enclosing)函数的变量。闭包在一些很是高级复杂的java结构中扮演着不可或缺的角色。
function circleArea(x) { return Math.PI*Math.pow(x,2) };
这种形式的函数官方名称为函数声明,它的工做方式与前者很是类似,可是这两种定义形式是不一样的。
具体来讲,函数声明只能出如今脚本中的全局位置,或者出如今一个函数体的"顶级",不容许只出如今语句内部。根据官方的EA规范,下面代码出现移一处语法错误:
if (true) { function successor() {return x+1;} // 不容许 }
这里的戒律是:即使浏览器容许,也绝对不要将函数声明放在一条语句内。不管是否选择使用函数声明,它们的存在都会影响咱们编写特定表达式的方式,由于函数声明一以单词function开头,因此JavaScript的设计者决定任何语句都不能以这个单词开头,以避免读者混淆。