JavaScript精华知识之函数部分

xiaoxiao2021-02-27  176

函数精华知识

函数包含一组语句,它们是JavaScript基础的模块单元。函数用于指定对象的行为。

所谓编程,就是 将需求分解为 一组函数数据结构的技能。

函数对象

JavaScript中的函数就是对象。对象是" 名/值"对的集合并拥有一个连到原型对象的隐藏连接(即原型对象ptototype),对象字面量创建的对象连接到Object.prototype。函数字面量创建的对象连接到Function.prototype(该原型对象本身连接到Object.prototype)。 每个函数在创建时会附加两个属性:函数的上下文和实现函数行为的代码。 函数是对象,所以可以像其它值一样被使用。函数可以保存在变量中、对象中和数组中。函数可以作为参数传递给其它函数,也可以作为某个函数的返回值。函数也拥有方法。

JavaScript在创建函数对象时,会给该对象设置一个“调用”属性。当JavaScript调用该函数时,可以理解为调用这个函数的“调用”属性。

函数字面量

函数对象通过函数字面量创建:将一个函数赋值一个变量 var sum = function (a, b) { return a + b; }; 其中,第一部分的function是保留字。第二部分是函数名,这里的函数名被忽略,函数可以通过函数名来递归调用自己。没有函数名的函数称为匿名函数。第三部分是圆括号中的参数,参数可以多个并用逗号隔开,参数是被定义为函数中的变量。参数在函数被调用时初始化为实参的值。 函数中可以嵌套函数,内部函数除了可以访问自己的参数和变量外,也可以访问其父函数的变量和参数。这是通过作用域链实现的。 通过函数字面量创建的函数还有另一个属性:包含一个连接上下文的连接,也就是闭包。

调用 

调用一个函数会暂停当前函数的执行,传递控制权和参数。除了声明时定义的形参外,每个函数还接收两个附加的参数:this和arguments。this的值取决于调用函数的方式,调用函数的方式有四种: 方法调用模式、函数调用模式、构造器调用模式和apply调用模式。 调用运算符就是跟在任何产生一个函数值的表达式之后的一对圆括号。圆括号中有0个或多个用逗号隔开的表达式,每个表达式产生一个参数值,参数值传递给声明函数定义的形参。当实参个数与形参个数不匹配时,不会产生错误: 当实参个数多于形参个数时,多出的部分被忽略。当实参个数少于形参个数时,缺失的值传入undefiend。 不会对参数值进行类型检测,也就是说,任何值都可以被传递给任何参数值。

方法调用模式

当一个函数被保存为对象的一个属性时,称它为一个方法。当这个一个方法(即某个函数)被调用时,this对象就被绑定到了调用这个方法的对象上,即this对象就指向了这个对象。 var myObj = { value : 0, getValue : function (inc) { this.value += typeof inc === "number" ? inc : 1; } }; myObj.getValue(); //调用该方法,没有传递参数,默认为undefined console.log(myObj.value); //1 此时myObj.value的值为1了 myObj.getValue(3); //有参数 console.log(myObj.value); //1 + 3 = 4 "this.value += typeof inc === "number" ? inc : 1;"相当于"this.value = this.value + typeof inc === "number" ? inc : 1;"这是有个运算符优先级问题:加性运算符优先于全等运算符,全等运算符优先于三元运算符,赋值运算符优先级别最低。 方法可以使this访问对象,同时可以取得对象的值或修改对象的值。当myObj对象调用this对象所在的方法getValue时,this对象就指向了myObj对象,并与其绑定。

函数调用模式

当一个函数不是作为一个对象的方法来调用时,那么它就是作为一个函数来调用。 var sum = add(3, 4); //7

apply调用模式

apply()方法接收两个参数:一个是作为this的值,一个是参数数组。 function sum (a, b) { return a + b; } var arr = [3, 4] function sum1() { return sum.apply(sum1, arr); //this对象指向了sum1对象,使sum1拥有了sum对象的属性和方法 }; console.log(sum1());

参数

在函数被调用时,arguments对象用于"配送"传递进来的实参,它类似于数组,将传递给函数的实参保存在其中。可以通过arguments的length属性来访问传递进来的实参,并得到实参的个数。

返回

当一个函数被调用时,它从第一条语句开始执行,并在遇到结束函数符"}"结束。然后将控制权将于调用该函数的程序。 return语句可用于提前结束函数,当return被执行时,函数执行完毕,retun语句后面的语句不会执行。 可以指定要返回的值,如果没有指定返回值,则返回undefined。 如果函数调用时在其前面加上new关键字,且返回值不是一个对象,那么会返回this对象。 function sum (a, b) { return a + b; } var arr = [3, 4] function sum1() { return sum.apply(sum1, arr); //this对象指向了sum1对象,使sum1拥有了sum对象的属性和方法 }; console.log(new sum1()); //this指向的是sum1对象,所以返回的是sum1{}

异常

前面提过JavaScript提供了一个异常处理机制(try {}catch() {}语句与throw{}语句),异常指干扰程序正常流程的不寻常事故。当发生异常应该抛出一个错误: var add = function (a, b) { if (typeof a != "number" || typeof b != "number") { throw { name : "TypeError", message : "add needs numbers" } } return a + b; }; throw语句中继函数的执行。它会抛出一个exception对象,该对象有name属性和message属性。 当然也可以手动地为其添加其它的属性和属性值。exception对象被传递到try语句的catch()从句,从而可以在catch()中返回throw语句的name属性和message属性,当然也可以是手动添加的属性。 var try_it = function () { try { add(); //此时传入的值为空,或者为非数字。 } catch (e) { console.log(e.name + ":" + e.message); //得到name属性和message属性,即TypeError:add needs numbers } }; try_it(); //调用这个函数 如果在try代码中抛出了一个异常,控制权就会跳转到catch从句。当传入add()函数的实参是数字时,就不会有异常。

如果此时,我们 只有以下代码: var try_it = function () { try { add(); } catch (e) { console.log(e.name + ":" + e.message); } }; try_it();

那么就会弹出"ReferenceError:add is not defined"异常。add()函数没有定义。也可以看出name属性和message属性是exception对象默认的属性,当然我们也可以将throw语句写出来新添加其它的属性,将在catch中抛出来。 一个try语句只会有一个捕获所有异常的catch代码块。 以上就是throw语句与try/catch语句的联合使用。

递归

递归函数就是直接或间接地调用自身的函数。它的思路是将一个问题分解为一组相似的子问题,每一个子问题都用一个寻常解去解决。一般来说,递归函数调用自身去解决子问题。

作用域

在编程语言中,作用域控制着变量和函数的可见性和生命周期。这是一种很重要的服务,因为这样可以减少名称冲突,提供自动内存管理。 var foo = function () { var a = 5, b = 10; var bar = function () { var b = 4, c = 8; //此时,a为5,b为4,c为8 console.log("a:" + a + "b:" + b + "c:" + c); a += b + c; b += c; //此时,a为17,b为12, c为8 console.log("a:" + a + "b:" + b + "c:" + c); }; //此时,a为5,b为10,c未定义 console.log("a:" + a + "b:" + b); //这个语句先执行 bar(); //在此时才调用bar()函数,bar()函数里面的语句这时才执行。 //此时,a为17,b为10 console.log("a:" + a + "b:" + b); //最后显示 } foo(); //调用foo函数 变量a对于函数bar来说,可以说是“全局变量”,因此当变量a在bar函数中进行一系列操作后,变量a的值会变的,而变量b在bar函数中而言,相当于又重新定义了一个变量b,bar函数内部的变量b就是在bar函数中定义的那个变量,因为进行一系列操作后值变成12,而在bar函数外部的变量b则是foo函数中定义的变量,因此值还是10,可以说是“此变量非彼变量”,对于变量c,只在bar函数中定义过,因此在bar函数中有效,而在foo函数中是不能访问到的。 JavaScript有函数作用域,意味着在函数内部定义的变量和参数,在函数外部是不可见的,而在函数内部的任意位置定义的变量和参数,在函数内部的任何地方都是可见的。 以上的例子就可以说明这点,在bar函数内部的定义的变量b和变量c在bar函数内部是可见的,因此可以显示进行一系列操作后的结果,而这两个变量对于bar函数外部即foo函数而言是不可见的。因此调用bar()函数后,最后变量a和变量b的结果还是在foo()函数的函数体顶部定义时的值一样。 块级作用域:在一个代码块中(括在一对花括号中的一组语句)定义的变量和参数对于在代码块外部是不可见的,定义在代码块中的变量和参数在代码块执行完毕后会被释放、消除。 通过以上例子,很容易让人误解JavaScript也有这种性质。对于JavaScript而言看似也有块级作用域,其实是错的,其实,JavaScript不支持块级作用域。注意,这点很重要。 对于变量声明有一点要说的是:最好的做法是在函数体的顶部声明在函数中可能用到的所有变量。

闭包

作用域的好处在于 内部函数可以访问其外部函数中定义的变量和参数(除了this对象和arguments对象)。内部函数拥有比外部更长的生命周期,也就是说当外部函数返回后,返回的内部函数依然能够访问外部函数的变量和参数,这也是内部函数作用域链的原因。 举例,通过调用函数的形式来初始化变量myObject,该函数返回一个对象字面量。函数里定义了一个value变量。该变量对increment和getValue方法都是可用的,但函数的作用域使得它对其它的程序是不可用的。

var myObject = (function () { var value = 0; //定义的变量 return { increment : function (inc) { value += typeof inc === "number" ? inc : 1; }, getValue : function () { return value; //返回变量的值 } }; console.log(value); //这段语句不会执行。 }()); //返回的是函数执行后的结果,即是一个方法对象,这里将调用的函数的引用赋值保存在了myObject中。 myObject.increment(3); //调用increment方法后,value的值变成3 var a = myObject.getValue(); //调用getValue方法后,返回value的值 console.log(a); //3,此时value的值为3 myObject.increment(); //没有参数,所以传递给实参的是undefined var b = myObject.getValue(); console.log(b); // value = 3 + 1; //调用并返回结果了,对于其它程序来说,函数的value变量已经相当于清除了。 //console.log(value); //这段代码无论放于何处,都会报错,因为value变量对于除了increment和getValue方法对象之外的其它程序都是不可见的,为未定义的变量。

这里并没有把一个函数赋值给变量myObject,而是将调用该函数后返回的结果赋值给了它。这个函数返回一个包含两个方法的对象,并且这两个方法继续享有访问value变量的特权。(按理说,当一个函数返回后,它所定义的变量和参数都会被清除,但由于它返回的是方法的对象,且由于内部函数作用域的关系,外部函数的变量已经在内部函数的作用域链上了,内部函数将继续拥有访问外部函数的变量和参数的特权)

接下来创建一个构造器:

//创建一个构造函数quo //它构造出带有getStatus方法和私有变量status的对象 var quo = function (status) { return { getStatus : function () { return status; } }; }; //定义一个quo的实例 var myQuo = quo("myQuo"); //调用quo函数后,返回一个方法对象,这里将quo对象的指针赋值给了myQuo //这个实例调用方法 console.log(myQuo.getStatus()); //myQuo console.log(status); // 空字符 当调用quo时,返回包含getStatus方法的新对象。该对象的一个引用保存在myQuo中。即使quo返回了,但getStatus方法仍然可以访问quo对象的status属性的特权。因为getStatus函数可以访问它被创建时所处的上下文环境。 再举例,理解内部函数能访问外部函数的的实际变量而无需复制量很重要的:

var handlers = function (nodes) { var i; for (i = 0; i < nodes.length; i ++) { nodes[i].onclick = function (e) { alert(i); }; } }; 这里会弹出节点的数目,因为弹出的是i的最后的值,即节点的总数值。每个内部函数的作用域链上都保存着handlers函数的活动对象,所以它们引用的都是同一个变量i,handlers函数被调用后,也就是handlers函数返回后,i的值为nodes.length。内部函数的作用域链上连接的是外部函数最后的变量值即nodes的总数(i最后的值), 上述问题解决方法:将内部函数变成一个闭包函数,用于绑定当前值的函数。

function handers = function (nodes) { var i; var help = function (i) { return function (e) { alert(i); }; }; for (i = 0; i < nodes.length; i++) { nodes[i].onclick = help(i); } };

创建团包函数,作用是用来保存当前的变量i的值。 这个例子可以变成:

function handers = function (nodes) { var i; var help = function (i) { return function (e) { alert(i); }; }; for (i = 0; i < nodes.length; i++) { nodes[i].onclick = (function (i) { return function (e) { alert(i); }; }(i)); //将调用本身后返回的方法对象赋值给事件处理程序。 } }; 这个help(i)函数调用返回了一个绑定了当前变量i的值的函数。这个变量i的值是由help(i)函数保存的。

回调

函数使得对不连续事件的处理变得更容易。 request = request(); response = send_request(request); display(response);

这是同步请求,会使客户端处于“假死”状态。 更好的方法是发起异步请求,提供一个当服务器响应到达时随即触发的回调函数。异步函数立即返回,这样的话客户端就不会阻塞。

request = request(); response = send_request(request, function (request) { display(response); }); 当接收到服务器端的响应时,立即触发回调函数,异步立即返回。

转载请注明原文地址: https://www.6miu.com/read-9405.html

最新回复(0)