函数 - 77797963/review-notes GitHub Wiki

函数

函数实际上是对象,每个函数都是Function类型的实例 ,而且都与其他引用类型一样具有属性和方法。由于函数是一个对象,因此函数名实际上也是一个指向函数的对象的指针,不会与某个函数绑定,所以函数可以有多个名字。

函数定义方式

函数申明 :一个重要的特征就是函数声明提升,在执行代码之前会先读取函数声明,这就意味着可以把函数声明放在调用它的语句后面。

function sum(num1,num2){ return num1+num2; }

函数表达式

var sum=function(num1,num2){ return num1+num2; };//函数末尾有个分号,就像声明其他变量时一样 这种形式像是变量赋值,即创建一个函数并将它赋值给变量sum。这种情况创建的函数叫匿名函数,因为function关键字后边没有标识符。匿名函数的name是空字符串。 (function(num1,num2){ alert(arguments.callee.name);//空字符串 })();

函数声明与函数表达式的区别:解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用,至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。 alert(sum(10,10));//10 function sum(num1,num2){ return num1+num2; } alert(sum(10,10));//sum is not a function var sum=function(num1,num2){ return num1+num2; };

name属性:是一个非标准的属性(但在ES6中,已将其写入了标准),这个属性值永远等于跟在function关键字后面的标识符,IE不生效,但IE Edge生效 var fun=function add(num1,num2,num3){ return num1+num2+num3; }; alert(fun.name);//add var fun=function(num1,num2,num3){ return num1+num2+num3; }; alert(fun.name);//‘’ ES5中会返回空字符串 var fun=function(num1,num2,num3){ return num1+num2+num3; }; alert(fun.name);//fun ES6中会返回实际的函数名

function add(num1,num2,num3){ return num1+num2+num3; }; alert(add.name);//add Function构造函数返回的函数实例,name属性的值为anonymous。 (new Function).name // "anonymous" bind返回的函数,name属性值会加上bound前缀。 function foo() {}; foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "

js没有函数重载

函数重载是允许在同一范围中声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,也就是说用同一个运算符完成不同的运算功能。这就是重载函数。重载函数常用来实现功能类似而所处理的数据类型不同的问题。

function addSomeNumber(num){ return num+100; } function addSomeNumber(num){ return num+200; } var result=addSomeNumber(100);//300 创建第二个函数时,实际上是覆盖了引用第一个函数的变量addSomeNumber

函数内部对象

在函数内部,有两个特殊的对象:arguments和this。 arguments是一个类数组,包含着传入函数中的所有参数,这个对象有一个叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数 function factorial(num){ if(num<=1){ return 1; }else{ return numfactorial(num-1) } } 定义阶乘函数一般都要用到递归算法,这个函数的执行与函数名紧紧耦合在一起,如果名字变了以后,内容就无法执行,可以像下边这样使用arguments.callee function factorial(num){ if(num<=1){ return 1; }else{ return numarguments.callee(num-1) } } 在严格模式下,不能通过脚本访问arguments.callee,访问这个属性会导致错误,不过可以使用命名函数表达式来达成相同的结果。如下: var factorial=(function f(num){ if(num<=1){ return 1; }else{ return num*f(num-1) } });

this引用的是函数据以执行的环境对象,或者也可以说是this值(当在页面全局作用域中调用函数时,this对象引用的就是window window.color='red'; var o={color:'blue'}; function sayColor(){ alert(this.color); } sayColor();//red o.sayColor=sayColor; o.sayColor();//blue alert( o.sayColor === sayColor);//true 一定要牢记,函数的名字仅仅是一个包含指针的变量而已,因此,即使是在不同的环境中执行,全局的sayColor()函数与o.sayColor()指向的仍然是同一个函数

ECMAScript5也规范化了另一个函数对象的属性caller.这个属性中保存着调用当前函数的函数的引用,如果在全局作用域中调用当前函数,它的值为null function outer(){ inner(); } function inner(){ alert(inner.caller);//弹出outer函数的源代码 alert(arguments.callee.caller);//弹出outer函数的源代码 } outer();

函数属性和方法

属性:length:表示函数希望接收的命名参数的个数,在ES6中会减去指定了默认值的参数个数

function sayName(name){} function sum(num1,num2){} function sayHi(){} alert(sayName.length);//1 alert(sum.length);//2 alert(sayHi.length);//0 在ES6中的例子: (function (a) {}).length // 1 (function (a = 5) {}).length // 0 (function (a, b, c = 5) {}).length // 2 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

(function (a = 0, b, c) {}).length // 0 (function (a, b = 1, c) {}).length // 1

属性:prototype:对于ECMAScript中的引用类型而言,prototype是保存它们所有实例 方法的真正所在。换句话说,诸如toString(),valueOf()等方法实际上都是保存在prototype名下,只不过是通过各自对象的实例访问罢了。

每个函数都包含两个非继承而来的方法:apply(),call().这两个方法用途都是在特定的作用域中调用函数,实际上等于设置函数体内的this对象的值。 apply()方法接收两个参数:一个是在其中运行函数的作用域 ,另一个是参数数组。其中第二个参数可以是Array的实例,也可以是arguments对象。 call():第一个参数是函数作用域 ,剩下的是逐个列举的要传递的参数。

每个函数继承的toLocaleString(),toString(),valueOf()都返回函数代码,但因浏览器差异,返回的代码存在差异,所以无法根本这些返回的结果来实现任何功能,不过,这些信息在调试代码的时候倒是很有用。

由于JS中没有块级作用域,但使用匿名函数可以块级作用域。

function outputNumber(count){ (function(){ //这里是块级作用域 for(var i=0;i<count;i++){ alert(i); } })(); alert(i);//导致错误 } 在for循环外部嵌套一个匿名函数,在函数中定义的任何变量都在执行结束时被销毁。在私有作用域中(匿名函数内)可以访问count,因为这个匿名函数是一个闭包。 这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。在一个由多人共同开发的大型项目中,过多的全局变量和函数容易导致命名冲突。能过创建私有作用域,每个开发人员即可以使用自己的变量,又不必担心搞乱全局作用域。 这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链。

使用特权方法访问私有变量

JS中其实没有私有成员的概念,所有对象属性都是公有的。不过,倒是有一个私有变量的概念。任何函数内部定义的变量,都可以认为是私有变量,因为外边访问不到。私有变量包括函数的参数,局部变量,函数内部定义的其他函数。 function MyOjbect(){ //私有变量和私有函数 var privateVairable=10; function privateFunction(){ return false; } //特权方法 this.publickMethod=function(){ privateVairable++; return privateFunction(); }; } 我们把有权访问私有变量和私有函数的公有方法称为特权方法。特权方法其实是一个闭包,能过自己的作用域链就可以访问这些私有变量和方法。 这种方法的缺点就是构造函数模式针对对每个实例都会创建同样一组新方法,使用静态私有变量来实现特权方法可以避免这个问题:如下

(function(){ //私有变量和私有函数 var privateVairable=10; function privateFunction(){ return false; } //构造方法 MyOjbect=function(){}; //公有、特权方法 MyOjbect.prototype.publickMethod=function(){ privateVairable++; return privateFunction(); }; })(); 这种方式创建一个私有作用域,并在其中封装了一个构造函数及相应的方法。构造函数没有使用函数声明定义,而是使用函数表达式。因为函数声明只能创建局部函数,出于同样的原因,也没有在声明MyOjbect时使用var关键字。初始化未经声明的变量,总是会创建一个全局变量。因此MyObject也可以在这个私有作用域之外被访问到。但要注意,严格格式下给未经专用的变量赋值会导致错误。 (function(){ var name=""; Person=function(value){ name=value; }; Person.prototype.getName=function(){ return name; } Person.prototype.setName=function(value){ name=value; }; })(); var person1=new Person("Nic"); alert(person1.getName());//Nic person1.setName('Greg'); alert(person1.getName());//Greg var person2=new Person('Mic'); alert(person1.getName());//Mic alert(person2.getName());//Mic 在本例中,Person构造函数与getName(),setName()方法一样,都可以访问私有变量name。变量name就变成了一个静态的,由所有实例共享的属性。也就是说,在一个实例中调用setName或者新建一个Person实例都会影响name的值。结果就是所有实例都返回相同的值。这种方式创建的有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量了。

多查找作用域中的一个层次,就会在一定程序上影响查找速度。而这正是使用闭包和私有变量的一个显示不足之处。

模块模式:为单例创建私有变量和特殊方法。所谓单例指的就是只且个实例的对象。JS以对象字面量的方式来创建单例对象。

数据sort()方法时候的比较函数