深入学习JavaScript函数
前言:
函数对于任何一门语言来说都是核心的概念,通过函数能封装任意多条语句,而且能在任何地方、任何时候调使用执行。而JavaScript中最好的特性就是它对函数的实现。它几乎无所不可以。但是,函数在JavaScript中并非万可以的,就像《JavaScript语言精粹》中所说:函数在JavaScript里也并非万可以药。
一、函数对象
在JavaScript
中函数就是对象,对象是“名值”对的集合并拥有一个连到原型对象的隐藏连接。
我们都知道,对象字面量产生的对象连接到Object.prototype
,例如:
var obj={ name:"JoeWright"};console.log(Object.prototype.isPrototypeOf(obj)); //true
而函数对象连接到Function.prototype
(该原型对象本身连接到Object.prototype
)。例如:
var foo=new Function("a","b","return a+b");console.log(foo(2,3)); //5console.log(obj.constructor); //? Object()console.log(Function.prototype.isPrototypeOf(foo)); //true//Function对象本身连接到Object.prototypeconsole.log(Object.prototype.isPrototypeOf(Function)); //true
少量属性:
constructor:返回创立该对象的构造函数
length:返回函数定义的参数个数
caller(ECMA标准之外的属性):返回调使用当前函数的函数
argument:返回该函数执行时内置的arguments对象
例如:
function foo(a,b,c){ return foo2.caller;}function bar(){ return foo2();}console.log(foo.length); //3console.log(foo.name); //fooconsole.log(bar()); //f bar()
注意:Function
构造函数尽管使你的脚本在运行时重新定义函数的情况下具备更大的灵活性,但它也会减慢代码的执行速度。为了避免减慢脚本速度,应该尽可可以少用Function
构造函数,多用function
关键字的形式公告函数。
二、函数字面量
函数字面量包括四部分:
第一部分是保留字
function
。第二部分是函数名,它能被省略(匿名函数)。函数能使用它的名字来递归调使用自己。
第三部分是包围在圆括号中的一组参数(形参)。其中每个参数使用逗号分隔。
第四部分是包围在花括号中的一组语句
格式如下:
function functionName(parameters) { 执行的代码}
例如:
function add(a,b) { return a+b;}
三、函数的调使用
调使用一个函数将暂停当前函数的执行,传递控制权和参数给新函数。
函数除了公告时定义的形式参数,每个函数接收两个附加的参数:
this
和arguments
。参数this在面向对象编程中非常重要,它的值取决于调使用的模式。在
JavaScrip
t中一共有四种调使用模式:方法调使用模式、函数调使用模式、构造器调使用模式和apply调使用模式。这些调使用模式在如何初始化关键参数this上存在差异。
上面讲到在JavaScript中一共有四种调使用模式,在这里我们分别对这几种模式逐个详解。
方法调使用模式
当一个函数被保存为对象的一个属性时,我们称它为一个方法
当一个方法被调使用时,this被绑定到该对象(这句话对于我们了解this关键字非常重要!!!!)
假如一个调使用表达式包含一个属性存取表达式(即.表达式或者者是[subscript]下标表达式,注意:假如属性不确定,要使用[])
下面是方法调使用模式的一个例子:
var myObj={ name:"JoWright", value:0, increment:function(inc){ this.value+=typeof inc ==="number"?inc:1; }};myObj.increment(); console.log(myObj.value); //1myObj.increment(3)console.log(myObj.value); //4var key="name"; //将name属性赋给比变量key,此时属性名不确定console.log(myObj.key); //undefinedconsole.log(myObj[key]); //JoeWright
this
到对象的绑定 发生在调使用的时候,这个绑定又称为 “超级”迟绑定(very late binding)。这样的绑定使得函数能对this
高度复使用。通过this可获得它们所属对象的上下文的方法称为公共方法
函数调使用模式
当一个函数并非一个对象的属性时,那么它被当作一个函数调使用:
function add(a,b){ return a+b;}console.log(add(1,2)); //3
当函数以此模式调使用时,this被绑定到全局对象(浏览器下为Window,node下为 global)。
例如:
var myObj={ value:1, multiply:function(){ var add=function(){ return this.value*2; }; console.log(add()); }};myObj.multiply(); //NaN
为什么是NaN呢?由于add方法是一个匿名函数,此时add函数中的this绑定的是全局对象,不是myObj(实际上add方法是被Window对象调使用的,所以它没有对myObj对象的访问权)
处理方法:利使用 闭包 的特性(先使用that变量来存储this)
var myObj={ value:1, multiply:function(){ var that=this; var add=function(){ return that.value*2; }; console.log(add()); }};myObj.multiply(); //2
构造器调使用模式
JavaScript
是一门基于原型继承的语言。这意味着对象能直接从其余对象继承属性。假如在函数前面带上new关键字来调使用(当作构造函数调使用),那么将创立一个隐藏连接到改函数的prototype成员的新对象,同时this 将会绑定到那个新对象上。
例如:
function Person(name){ this.name=name;}//为Person的所有实例提供一个名为getName的公共方法Person.prototype.getName=function(){ return this.name;}var p=new Person("JoeWright");console.log(p);//Person {name: "JoeWright"}
按照商定,构造器函数保存在以大写格式命名的变量里。假如调使用构造器函数时没有在前面加上new,可可以会发生非常糟糕的事情(即没有编译时的警告,也没有运行时的警告),所以 大写商定非常重要
Apply调使用模式(相似的还有call、bind)
格式如下:
functionName.apply(thisArg[, [arg1, arg2, ...]])
thisArg:将被绑定给this的值
[arg1,arg2,..]:参数数组(可选)
例如:
function add(a,b){ return a+b;}var sum=add.apply(null,[3,4]);console.log(sum); //7
function Person(name){ this.name=name;} //为Person的所有实例提供一个名为getName的公共方法Person.prototype.getName=function(){ return this.name;}var Pet={ name:"dogg"};//Pet并没有继承自Person.prototype,但我们能在Pet上调使用getName方法,虽然Pet并没有一个名为getName的方法。var petName=Person.prototype.getName.apply(Pet);console.log(petName); //dogg
四、参数
当函数被调使用时,会得到一个“免费”奉送的参数,这个参数就是arguments
数组(arguments
对象)。通过它函数能访问所有它被调使用时传递给它的参数列表,这使得编写一个毋庸指定参数个数的函数成为可可以。
例如:
//使用arguments对象求最大值function max(){ var max=0; for(var i=0,len=arguments.length;i<len;i++){ if(arguments[i]>max){ max=arguments[i]; } } return max;}console.log(max(2,1,4,3,7)); //7
arguments对象的callee属性:引使用当前被调使用的函数对象
function bar(){ return arguments.callee; }console.log(bar()); //? bar()
典型应使用:函数的递归调使用
//匿名函数的递归调使用( function(count){ if(count<=3){ console.log(count); arguments.callee(++count); } })(0);//0 1 2 3
默认参数
实现方法一:
function add(x,y){ x=x||0; y=y||1; return x+y;}console.log(add()); //1console.log(add(3,4)); //7
实现方法二:
function add(x,y){ x=x===undefined?0:x; y=y===undefined?1:y; return x+y;}console.log(add()); //1console.log(add(3,4)); //7
五、返回
return语句能使函数提前返回。当return被执行时,函数立即返回而不再执行余下的语句。
一个函数总是会返回一个值。假如没有指定值,就返回undefined
function f(a,b){ return a+b; return "hello JoeWright"; //永远不会被执行}console.log(f(1,2)); //3
六、异常
JavaScript提供了一套异常解决机制。异常是干扰程序正常流程的非正常的事故。
例如:
var add=function(a,b){ if(typeof a!=="number"||typeof b!=="number"){ throw{ name:"TypeError", message:"add needs numbers" }; } return a+b;};var try_it=function(){ try{ add("hello"); }catch(e){ console.log(e.name+":"+e.message); }}try_it(); //TypeError add needs numbers
七、给类型增加方法
JavaScript允许给语言的基本类型增加方法。这样的方式对函数(Function)、数组(Array)、字符串(String)、正则表达式(RegExp) 和 布尔值(Boolean) 同样适使用。
例如:String
对象原来没有reverse
方法,我们能从它的原型上扩展该方法。
String.prototype.reverse=function(){ return Array.prototype.reverse.apply(this.split("")).join("");}console.log("hello".reverse()); //olleh
八、递归
递归函数是直接或者间接地调使用自身的一种函数。
例如:
function sum1(n){ return n==1?1:f3(n-1)+n;}console.log(sum1(100)); //5050//等价于var sum2=function(n){ //匿名函数的递归 return n==1?1:arguments.callee(n-1)+n;}console.log(sum2(100)); //5050
九、作使用域
在编程语言中,作使用域控制着变量与参数的可见性及生命周期。这对开发者来说是一个重要的帮助,由于它减少了名称冲突,并且提供了自动内存管理
var fun1=function(){ var a=3,b=5; var fun2=function(){ var b=7,c=11; console.log(a+" "+b+" "+c); //3 7 11 a+=b+c; console.log(a+" "+b+" "+c); //21 7 11 }; console.log(a+" "+b); //3 5 fun2(); console.log(a+" "+b);// 21 5};fun1();
作使用域的知识就写这么多,下次的文章深入学习:)
十、闭包
各种专业文献上的"闭包"(closure)定义非常笼统,很难看懂。所以这里引使用阮一峰老师的了解:
闭包就是可以够读取其余函数内部变量的函数,闭包让这些变量的值始终保持在内存中,不会在调使用结束后,被垃圾回收机制(garbage collection)回收。
闭包的例子:
function func1(){ var n=5; function func2(){ console.log(n); } return func2;}var res=func1();res(); //5
上面的例子中,func2函数就是闭包。
因为在Javascript语言中,只有函数内部的子函数才可以读取局部变量,因而能把闭包简单了解成 "定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
十一、回调
回调函数就是一个参数,将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数。这个过程就叫做回调。
看下面的例子:
function addOne(a){ return a+1;}function test(a,b,c,callback){ var array=[]; for(var i=0;i<3;i++){ array[i]=callback(arguments[i]*2); } return array;}console.log(test(10,20,30,addOne)); //21,41,61//匿名函数的写法(推荐)console.log(test(10,20,30,function(a){return a+1}));//21,41,61
十二、板块
板块就是实现特定功可以的一组方法。
原始写法:
function f1(){ //...}function f2(){ //...}
上述函数f1、f2组成了一个板块,要用时直接调使用即可以了
缺点:容易造成变量污染
对象写法:
var obj={ name:"JoeWright", f1:function(){ //... }, f2:function(){ //... }};
上面的函数f1()和f2(),都封装在obj对象里。用的时候,就是调使用这个对象的属性obj.f1()。
缺点:暴露所有板块成员,内部状态能被外部改写,例如:obj.name="Tom"
;
完善:
var obj={};Object.defineProperties(obj,{ name:{ value:"JoeWright", writable:false //设置可写为false(默认为 false) }, f1:function(){ //... }, f2:function(){ //... }});obj.name="Joe";console.log(obj.name); //JoeWright
立即执行函数写法:
var obj=(function(){ var value="JoeWright"; var f1=function(){ //... }; var f2=function(){ //... } return { f1:f1, f2:f2 }; });console.info(obj.value); //undefined
用上面的写法,外部代码无法读取内部的value变量。
十三、套使用
函数也是值,从而我们能使用有趣的方式去操作函数值。套使用允许我们将函数与传递给它的参数相结合去产生一个新函数。
例如:
function sum1(n){ return n==1?1:f3(n-1)+n;}console.log(sum1(100)); //5050var sum2=sum1; //把sum1函数赋给变量sum2,实现套使用console.log(sum2(100)); //5050
完结
以上就是我对js函数的少量总结,不足的地方希望小伙伴们可以够提出来,大家一起学习,一起进步:)
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 深入学习JavaScript函数