1. 函数定义
2. 函数保存
3. 函数创建
4. JS预解析,函数参数变量提升申明
5. 函数执行环境和作用域
6. 函数内部属性 arguments 和 this
7. 闭包
1. 函数定义:函数也是一种对象,内置的Function()函数用于创建函数对象实例。
2. 函数保存 函数名:是指向函数对象的引用类型变量,函数名变量保存在栈内存,函数对象保存在堆内存。
3. 函数创建: new构造函数 和 关键字function(函数声明和函数表达式)
3.1 new构造函数
var add = new Function('a','b','return a + b;'); //可以接受任意数量的参数,最后一个参数作为函数体, //而前面的所有参数都作为函数的形式参数,前面的形式参数还可以使用逗号隔开作为一个参数传入, console.info(add(1,2)); //3 //new构造函数创建函数,会解析两次代码,一次正常解析,一次解析函数体, //效率会影响,但是比较适合函数体需要动态编译的情况。
3.2 关键字function(函数声明和函数表达式)
function add(a, b){ //函数申明 return a + b; } console.info(add(1,2)); //3 var subtract = function(a, b){ //函数表达式 return a - b; }; console.info(subtract(2,1)); //1
4. JS预解析,函数参数变量提升申明
4.1 函数申明和函数表达式两者如何区分:如果没有函数名,则一定是函数表达式。其它的主要根据两者上下文区分。
4.2 函数申明和函数表达式两者区别:在JS引擎解析的时候,函数申明会提升,而函数表达式不会。
JS预解析,函数变量声明过程:
1、引擎在解析时,首先会解析函数(function关键字)声明,然后再解析变量(var 关键字)声明并赋值为undefined。
遇到重名函数和变量,无论先后顺序,都只保存函数。相同变量或函数会保留后面的。所有的函数在正式运行代码之前,都只是函数块保存在内存代码区中。
2、当逐行执行代码时,表达式会改变内存中预解析变量的值。
3、当逐行执行代码,调用执行函数时,则会开辟新的函数域,又会执行预解析和逐行执行命令过程。
首先内部函数声明提升,并将函数名的类型设置为函数类型,然后解析函数参数,将传入的实际参数值赋给形式参数,最后再内部变量声明提升,只提升声明,不初始化,如果有重名,同优先级的后面覆盖前面的,不同优先级的不覆盖(已经解析了优先级高的,就不再解析优先级低的).
4、在全局作用域里,页面里有多对<script>代码块,会先解析和执行完前面的<script>后才会预解析和执行后面的<script>,但是前面预解析保存的变量和函数,后面的script中也可以使用。
全局作用域
//先执行1-4的JS预解析,再返回从5-14逐行执行代码 console.info(typeof fn); //5.function,有声明提升,同名情况下以函数为准 var fn = ''; //3.解析var声明同名保留函数 //6.表达式改变内存中的值,fn等于空字符串 function fn(){ //1.解析函数声明,全局作用域中fn指向代码区该代码块 } console.info(typeof fn); //7.string,由于第6步表达式改变了内存中变量的值 try{ fn(); //8. 已经是string类型,不能调用了,抛出类型异常 }catch(e){ console.info(e); //9.TypeError } fn = function(){console.info('fn');}; //10. 表达式,改变内存中fn值,指向函数 fn(); //11. fn,可以调用 console.info(typeof gn); //12. function 内存中同名变量与函数以函数为准 function gn(){ //2.解析函数声明,全局作用域中gn指向代码区该代码块, } var gn = ''; //4.解析var声明,但全局作用域中有同名函数,则会保留函数 //13.表达式,gn赋值为空字符串 console.info(typeof gn); //14.string 因为13中已经赋值改变为空字符串了
函数作用域:函数作用域在函数定义时不存在的,只有在函数实际调用才有函数作用域。
//申明优先级: 内部函数声明 > 函数参数 > 内部变量声明。 //已经解析了优先级高的,就不再解析优先级低的 //解析步骤1-3,然后4-8逐行执行代码 function fn(inner){ //2.因为有同名内部函数,则不会解析同名参数,如果没有,则解析并将传入的实际参数赋给形式参数 console.info(inner); //4. 输出inner函数, console.info(inner()); //5. 调用inner函数,查找该域中,other为undefined,如果没有则向外级作用域查找 function inner(){ //1.解析内部函数,函数作用域中inner指向该函数 return other; } ; var other = 'other'; //3. 内部变量提升,other=undefined //6. 赋值表达式,更改other为other. console.info(inner); //7. 输出inner函数, console.info(other); //8. 在6中other更改为other,则输出other } fn('param'); //0. 调用执行函数,开辟局部作用域,预解析与逐步执行代码
无块级作用域:ECMAScript中只有全局执行环境和函数执行环境,只有全局作用域和函数作用域——虽然有块语句。
function fn(){ var fnScope = 'a'; { var blockScope = 'b'; blockScope += fnScope; } console.info(blockScope); //输出 ‘ba' 没有块作用域,所以可以在整个函数作用域内访问blockScope console.info(fnScope); //输出 ‘a' 没有块作用域,所以可以在整个函数作用域内访问fnScope } fn(); //ba,a console.info(blockScope); //ReferenceError,函数作用域外,不能访问内部定义的变量 console.info(fnScope); //ReferenceError
4.3 作为值的函数
在一般的编程语言中,如果要将函数作为值来使用,需要使用类似函数指针或者代理的方式来实现,但是在ECMAScript中,函数是一种对象,拥有一般对象具有的所有特征,可以做为一个引用类型的值去使用,比如函数也可以作为另一个函数的参数或者返回值,异步处理中的回调函数就是一个典型的用法。
4.4 函数预解析到相同名字函数时:重载
函数是对象,函数名是指向函数对象的引用类型变量,不能实现重载。
//函数名只是一个变量而已,同名函数声明会依次解析覆盖 function fn(a){ // 1. 函数解析,fn指向该函数(1) return a; } function fn(a,b){ //2. 函数解析,fn覆盖上面,执行该函数(2) return a + b; } console.info(fn(1)); //调用函数(2),只传递一个参数,则b为undefined,1+undefined就为NaN console.info(fn(1,2)); // 3
在ECMAScript中,怎么模拟重载呢?
简单数据类型包装对象(Boolean、Number、String),既可以作为构造函数创建对象,也可以作为转换函数转换数据类型,这是一个典型的重载。
//1.根据函数的作用来重载 function fn(){ if(this instanceof fn) { // 功能模块1 }else { // 功能模块2 } } //2. 根据函数内部属性来重载, 根据判断参数个数来执行不同代码块 function fn(){ var length = arguments.length; if( length == 0 ) { return 0; }else if( length == 1 ) { return +arguments[0]; //转换为number型 }else{ return (+arguments[0])+(+arguments[1]); } } console.info(fn()); //0 console.info(fn(1); //1 console.info(fn(true)); //1 console.info(fn(1,2)); //3 console.info(fn('1','2')); //3
5. 函数执行环境和作用域
5.1 执行环境(execution context):所有的JS代码都运行在一个执行环境中。活动的执行环境从逻辑上形成了一个栈,全局执行环境永远是这个栈的栈底元素,栈顶元素就是当前正在运行的执行环境。每一个函数都有自己的执行环境,当执行流进入一个函数时,会将这个函数的执行环境压入栈顶,函数执行完之后再将这个执行环境弹出,控制权返回给之前的执行环境。
5.2 变量对象(variable object):每一个执行环境都有一个与之对应的变量对象,执行环境中定义的所有变量和函数就是保存在这个变量对象中。
5.3 作用域链(scope chain):当代码在一个执行环境中运行时,会创建由变量对象组成的一个作用域链。这个链的前端,就是当前代码所在环境的变量对象,链的最末端,就是全局环境的变量对象。在一个执行环境中解析标识符时,会在当前执行环境相应的变量对象中搜索,找到就返回,没有找到就沿着作用域链一级一级往上搜索直至全局环境的变量对象,如果一直未找到,就抛出引用异常。
5.4 活动对象(activation object):如果一个执行环境是函数执行环境,也将变量对象称为活动对象。活动对象在最开始只包含一个变量,即arguments对象(这个对象在全局环境的变量对象中不存在)。
当前执行环境被弹出栈就会被销毁,当 当前执行环境恢复成全局环境时整个处理过程结束,全局环境直至页面退出再销毁。
对于作用域链,还可以使用with、try-catch语句的catch块来延长:
- 使用with(obj){}语句时,将obj对象添加到当前作用域链的最前端。
- 使用try{}catch(error){}语句时,将error对象添加到当前作用域链的最前端。
函数在内部递归调用自己的实现原理:就是作用域链。
函数名是函数定义所在执行环境相应变量对象的一个属性,然后在函数内部执行环境中,就可以沿着作用域链向外上溯一层访问函数名指向的函数对象了。
如果在函数内部将函数名指向了一个新函数,递归调用时就会出问题。
function fn(num){ if(1 == num){ return 1; }else{ fn = function(){ return 0; }; return num * fn(num - 1); //这里调用fn函数,如果在当前执行环境里定义fn,就不是递归了 } } console.info(fn(5)); //0
6. 函数内部属性 arguments 和 this
函数内部属性:可以理解为函数相应的活动对象的属性,是只能从函数体内部访问的属性,函数每一次被调用,都会被重新指定,具有动态性。this和arguments。
6.1 arguments对象(这个对象在全局环境的变量对象中不存在)。
1_ECMAScript 里函数形参与实际传入的实参两者之间数量没有任何限制。
2_形式参数甚至可以取相同的名称,只是在实际传入时会取后面的值作为形式参数的值(这种情况下可以使用arguments来访问前面的实际参数)。
function gn(a,a){ console.info(a); console.info(arguments[0]); console.info(arguments[1]); } gn(1,2); //2,1,2 gn(1); //undefined,1,undefined 同优先级的后面的覆盖前面的,这样安全性就很成问题了,因此在ES5的严格模式下,重名的形式参数被禁止了。
3_实际接收的参数组成arguments对象.类数组对象。 访问传入参数, 第一个参数arguments[0], 第二个参数arguments[1],
4_arguments属性:length、callee和caller
1、length属性表示实际接收到的参数个数
2、callee属性指向函数对象本身,即有: fn.arguments.callee === fn
3、caller属性主要和函数的caller相区分,值永远都是undefined
5_arguments保存实际参数值,而形式参数也保存实际参数值,这两者之间有一个同步关系,修改一个,另一个也会随之修改。
6_arguments和形式参数之间的同步,只有当形式参数实际接收了实际参数时才存在,对于没有接收实际参数的形式参数,不存在这种同步关系。
7_arguments对象虽然很强大,但是从性能上来说也存有一定的损耗,所以如果不是必要,就不要使用,建议还是优先使用形式参数。
fn(0,-1); function fn(para1,para2,para3,para4){ console.info(fn.length); //4,形式参数个数 console.info(arguments.length); //2,实际参数个数 console.info(arguments.callee === fn); //true,callee对象指向fn本身 console.info(arguments.caller); //undefined console.info(arguments.constructor); //Object(),而不是Array() try{ arguments.sort(); //类数组毕竟不是数组,不能直接调用数组方法,抛出异常 }catch(e){ console.info(e); //TypeError } var arr = Array.prototype.slice.call(arguments); //先转换为数组 console.info(arr.sort()); //[-1,0],已经排好序了 console.info(para1); //0 arguments[0] = 1; console.info(para1); //1,修改arguments[0],会同步修改形式参数para1 console.info(arguments[1]); //-1 para2 = 2; console.info(arguments[1]); //2,修改形式参数para2,会同步修改arguments[1] console.info(para3); //undefined,未传入实际参数的形式参数为undefined arguments[2] = 3; console.info(arguments[2]); //3 console.info(para3); //undefined,未接受实际参数的形式参数没有同步关系 console.info(arguments[3]); //undefined,未传入实际参数,值为undefined para4 = 4; console.info(para4); //4 console.info(arguments[3]); //undefined,为传入实际参数,不会同步 }
函数内部调用自身时与函数名解耦 (可以使用arguments.callee(在ES5的严格模式下被禁止使用) 和 匿名的函数表达式)
//求阶乘,递归调用 function factorial(num){ if(num <= 1) { return 1; }else{ return num * factorial(num - 1); //递归调用时,仍然只是调用自己的函数名 } } var fn = factorial; //函数赋给了另一个变量, factorial = null; //而函数名另外被赋值 try{ fn(2); //由于函数内部递归调用了factorial,而factorial已经赋值为null了,所以抛出异常 }catch(e){ console.info(e); //TypeError } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //斐波那契数列,使用arguments.callee,实现了函数对象和函数名的解耦 function fibonacci(num){ if(1 == num || 2 == num){ return 1; }else{ return arguments.callee(num - 1) + arguments.callee(num - 2); //利用arguments.callee指向当前函数对象进行调用 } } var gn = fibonacci; fibonacci = null; console.info(gn(9)); //34,使用arguments.callee,实现了函数对象和函数名的解耦,可以正常执行 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //斐波那契数列,使用命名函数表达式 var fibonacci = (function f(num){ return num <= 2 ? 1 : (f(num - 1) + f(num - 2)); }); var gn = fibonacci; fibonacci = null; console.info(gn(9)); //34,使用命名函数表达式实现了函数对象和函数名的解耦,可以正常执行
6.2 this
1_全局环境中的this
在全局环境中,this指向全局对象本身(浏览器中也就是window),这里也可以把全局环境中的this理解为全局执行环境相应的变量对象,在全局环境中定义的变量和函数都是这个变量对象的属性:
//全局环境中,this指向全局对象本身,浏览器中就是window var vo = 'a'; vo2 = 'b'; function fn(){ return 'fn'; } console.info(this === window); //true console.info(this.vo); //a console.info(this.vo2); //b console.info(this.fn()); //fn ///////////////////////////////////////////////////////////////////////////// //在自定义函数中要引用全局对象,最好的方式是将全局对象(this)作为参数传入函数 //这种方式兼容性更好(ECMAScript的实现中全局对象未必都是window) (function(global){ //用global接收,在压缩时,也可以将global简化为g, console.info(global === window); //在内部可以使用global代替window了 })(this); //这里将this传入
2 _函数内部属性的this
在函数环境中,this是一个内部属性对象,可以理解成函数对应的活动对象的一个属性,而这个内部属性的值是动态的。
//this变化常见情况 //1._使用new调用时,函数也称为构造函数,这个时候函数内部的this被指定为新创建的对象. //2_作为一般函数调用时,this指向全局对象. //3_作为对象的方法调用时,this指向调用这个方法的对象。 //4_使用apply()、call()或bind()调用函数时,this指向第一个参数对象。 // 如果没有传入参数或传入的是null和undefined,this指向全局对象(在ES5的严格模式下会设为null)。 // 如果传入的第一个参数是一个简单类型,会将this设置为相应的简单类型包装对象。
具体情况:
//1._使用new调用时,函数也称为构造函数,这个时候函数内部的this被指定为新创建的对象. function fn(){ var name = 'privatei'; //函数对应的活动对象的属性,也可成为类的私有属性,类外无法直接调用 this.name = 'public'; //当使用new调用函数时,将this指定为新创建对象,也就是给新创建对象添加属性 } var person = new fn(); console.info(person.name); //public /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //2_作为一般函数调用时,this指向全局对象. //3_作为对象的方法调用时,this指向调用这个方法的对象。 var name = 'The Window'; var object = { name : 'My Object', getName:function(){ return this.name; }, getNameFunc:function(){ return function(){ return this.name; } } }; console.info(object.getName()); //My Object 正常调用对象方法,this指向调用这个方法的对象 console.info((object.getName)()); //My Object和上面情况一样 console.info((object.getName = object.getName)); //function ,这里最终返回的是函数对象本身,输出function(){ return this.name; } console.info((object.getName = object.getName)()); //The Window,这里最终返回的是函数对象本身,如果直接调用会作为一般函数来调用 console.info((object.getName)()); //如果再次直接调用则会输出My Object var getName = object.getName; console.info(getName()); //The Window,当把这个函数赋给另一个函数然后作为一般函数调用的,this指向了全局对象,就是函数作为一般函数调用时内部属性this指向全局对象 console.info(object.getNameFunc()()); //The Window 先是调用getNameFunc这个方法,返回一个函数,然后再调用这个函数,也是作为一般函数来调用。 //因为函数作为对象的方法调用时this指向这个调用对象,所以在函数内部返回this时才能够延续调用对象的下一个方法——也就是链式操作(jQuery的一大特色)。 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //4_使用apply()、call()或bind()调用函数时,this指向第一个参数对象。 // 如果没有传入参数或传入的是null和undefined,this指向全局对象(在ES5的严格模式下会设为null)。 // 如果传入的第一个参数是一个简单类型,会将this设置为相应的简单类型包装对象。 var name = 'The Window'; function fn(){ return this.name; } var person = { name:'personi', getName:fn }; var person2 = { name:'person2' }; var person3 = { name:'person3' }; console.info(fn()); //The Window,作为一般函数调用,内部属性this指向全局对象 console.info(person.getName()); //person,作为对象方法调用,this指向这个对象 console.info(fn.apply(person2)); //person2, 使用apply、call或bind调用函数,执行传入的第一个参数对象,apply会立即执行 console.info(fn.call(person2)); //person2, 使用apply、call或bind调用函数,执行传入的第一个参数对象,call会立即执行 var newFn = fn.bind(person3); //ES5中新增方法,会创建一个新函数实例返回,内部this值被指定为传入的参数对象,bind不会立即执行 console.info(newFn()); //person3
7. 函数属性和方法 函数内部属性
函数是一个对象,因此也可以有自己的属性和方法。不过函数属性和方法与函数内部属性很容易混淆,对比学习。
(1)函数内部属性:可以理解为函数相应的活动对象的属性,是只能从函数体内部访问的属性,函数每一次被调用,都会被重新指定,具有动态性。this和arguments。
(2)函数属性和方法:这是函数作为对象所具有的特性,只要函数一定义,函数对象就被创建,相应的属性和方法就可以访问,并且除非你在代码中明确赋为另一个值,否则它们的值不会改变,因而具有静态性。有一个例外属性caller,表示调用当前函数的函数,也是在函数被调用时动态指定。在ES5的严格模式下,不能对具有动态特性的函数属性caller赋值。
类别 | 名称 | 说明 | 是否来自Object继承 |
函数内部属性 | arguments | 函数实际参数的类数组对象 | 不谈继承性 |
this | 函数据以执行的环境对象 | 不谈继承性 | |
函数属性 | caller | 调用当前函数的函数 | 否 |
length | 函数形式参数的长度 | 否 | |
prototype | 函数原型对象 | 否 | |
constructor | 表示创建函数实例的函数即Function() | 继承了Object方法 | |
函数方法 | call | 动态绑定函数内部属性this, 以列举方式接收参数,会立刻执行 | 否 |
apply | 动态绑定函数内部属性this, 以数组方式接收参数,会立刻执行 | 否 | |
bind | 动态绑定函数内部属性this, 以列举方式接收参数,需要的时候调用执行 | 否 ES5中新增 | |
hasOwnProperty(propertyName) |
检查给定的属性是否在当前对象实例中 |
继承了Object方法 | |
propertyIsEnumerable(propertyName) |
检查给定的属性是否能够是使用for-in语句来枚举 |
继承了Object方法 | |
isPrototype(object) | 检查传入的对象是否是另一个对象的原型 | 继承了Object方法 | |
toString() | 返回对象的字符串表示 | 覆盖了Object类型方法 | |
valueOf() | 返回对象的字符串、数值或布尔值表示,通常与toString()方法返回值相同 | 覆盖了Object类型方法 | |
toLocalString() | 返回对象的字符串表示,该字符串与执行环境的地区相对应 | 覆盖了Object类型方法 |
使用最多的是函数本身特有的方法:call(),apply()和bind(). 设置函数内部属性this从而扩展函数作用域。
7. 闭包
闭包是指有权访问另一个函数作用域中的变量的函数。对象是带函数的数据,而闭包是带数据的函数。
实例入门:普通情况下全局作用域下无法直接访问局部作用域变量。
function fn1() { var i = 10; } fn1(); //函数执行完毕,函数作用域被弹出JS作用域,再加上垃圾回收机制,i会被回收 alert ( i ); //提示错误,i未定义 i是局部变量,也无法在外部直接调用。
简单闭包:
function fn1(){ var i = 10; function fn2(){ alert(i); //fn2函数内引用外部fn1函数的变量i,所以变量i也被引用着,也不会被垃圾回收机制回收。 } return fn2; //fn1函数内部定义的fn2函数,并作为返回值 } var test = fn1(); //fn1调用执行返回fn2,并且被全局变量test引用,所以,fn2函数不会被垃圾回收机制回收。 test(); //test保存了fn2的函数地址,所以test()可以直接被执行,而且作用域链也可以访问到其所调用的变量,所以最后可以输出10.
作用域链从内到外,闭包从外到内
闭包主要用途:
//1. 保存临时数据 //2. 修改this指向 //3. 进行缓存 //4. 模仿块级作用域 //5. 模仿私有变量和私有函数、模块模式等
1.利用闭包保存数据
function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(){ return i; }; } return result; } var funcs = createFunctions(); for (var i=0,l=funcs.length; i < l; i++){ console.info(funcs[i]()); //会输出10个10, } //由于闭包带有的数据是createFunctions相应的活动对象的最终状态,而在createFunctions()代码执行完成之后,活动对象的属性 i 已经变成10, //因此在下面的调用每一个函数,都去寻找内存中的i,i最后状态都是10,所以都输出10了 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //要处理这种问题,可以采用匿名函数作用域来保存状态: function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = (function(num){ //每一个状态都使用一个立即调用的匿名函数来保存(保存在匿名函数相应的活动对象中), return function(){ //然后在最终返回的函数被调用时,就可以通过闭包带有的数据(相应的匿名函数活动对象中的数据)来正确访问了 return num; }; })(i); } return result; } var funcs = createFunctions(); for (var i=0,l=funcs.length; i < l; i++){ console.info(funcs[i]()); //输出0,1,2,3,4,5,6,7,8,9 } //尽管闭包存在效率和内存的隐患 //闭包在性能上会有较大影响,因此建议不要滥用, //由于闭包会保存其它执行环境的活动对象作为自身作用域链中的一环,这也可能会造成内存泄露。
2.利用闭包改变this指向:<button id='my-btn' title='Button'>Hello</button> <script type="text/javascript"> var handler = { title:'Event', handleClick:function(event){ console.info(this.title); } }; var btn = document.getElementById('my-btn'); //获取页面按钮 btn.onclick = handler.handleClick; //给页面按钮添加事件处理函数 </script> // 点击“Hello”按钮,控制台打印的是Button,而不是期望中的Event, // 原因就是这里在点击按钮的时候,处理函数内部属性this指向了按钮对象。 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //可以使用闭包来解决这个问题: <button id='my-btn' title='Button'>Hello</button> <script type="text/javascript"> var handler = { title:'Event', handleClick:function(event){ console.info(this.title); } }; var btn = document.getElementById('my-btn'); //获取页面按钮 btn.onclick = function(event){ handler.handleClick(event); //给页面按钮添加事件处理函数, 这里形成一个闭包,调用函数的就是对象handler了,函数内部属性this指向handler对象,因此会输出Event }; </script> //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //利用函数绑定方法bind来改变this指向问题 <button id='my-btn' title='Button'>Hello</button> <script type="text/javascript"> var handler = { title:'Event', handleClick:function(event){ console.info(this.title); } }; var btn = document.getElementById('my-btn'); //获取页面按钮 //bind为ES5新增的方法,为了保证运行正常,在不支持的浏览器上添加该方法 if( !Function.prototype.bind){ Function.prototype.bind = function(scope){ var that = this; //调用保存bind()方法的函数对象,保证下方return可以正常返回 return function(){ that.apply(scope,arguments); //使用apply方法,指定that函数对象的内部属性this }; }; } btn.onlick = handler.handleClick.bind(handler); //使用bind()方法,绑定onlick执行函数handleClick内部函数的this指向handler ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //上面对函数使用bind()方法时,只使用了第一个参数,如果调用bind()时传入多个参数并且将第2个参数开始作为函数实际调用时的参数,就要给函数绑定默认参数 if(!Function.prototype.bind){ Function.prototype.bind = function(scope){ var that = this;//调用bind()方法的函数对象 var args = Array.prototype.slice.call(arguments,1); //从第2个参数开始组成的参数数组,arguments不是对象,需要利用Array的原型方法转换为数组 return function(){ var innerArgs = Array.prototype.slice.apply(arguments); that.apply(scope, args.concat(innerArgs)); //使用apply方法,指定that函数对象的内部属性this,并且填充绑定时传入的参数 }; }; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //柯里化:在上面绑定时,第一个参数都是用来设置函数调用时的内部属性this,如果把所有绑定时的参数都作为预填的参数,则称之为函数柯里化。 if(!Function.prototype.curry){ Function.prototype.curry = function(){ var that = this; //调用curry()方法的函数对象 var args = Array.prototype.slice.call(arguments); //预填参数数组 return function(){ var innerArgs = Array.prototype.slice.apply(arguments); //实际调用时参数数组 that.apply(this, args.concat(innerArgs)); //使用apply方法,并且加入预填的参数 }; }; }
3.利用闭包缓存
var fibonacci = (function(){ //使用闭包缓存,递归 var cache = []; function f(n){ if(1 == n || 2 == n){ return 1; }else{ cache[n] = cache[n] || (f(n-1) + f(n-2)); //使用闭包缓存,递归 ,如有缓存直接使用 return cache[n]; } } return f; })(); var f2 = function(n){ //不使用闭包缓存,直接递归 if(1 == n || 2 == n){ return 1; }else{ return f2(n-1) + f2(n-2); //不使用闭包缓存,每次都会计算 } }; //测试两者的计算时间 var test = function(n){ var start = new Date().getTime(); console.info(fibonacci(n)); console.info(new Date().getTime() - start); start = new Date().getTime(); console.info(f2(n)); console.info(new Date().getTime() - start); }; test(10);//55,2 ,55,2 test(20);//6765,1 ,6765,7 test(30);//832040,2, 832040,643 //n值越大,使用缓存计算的优势越明显
4.利用闭包模仿块级作用域
(function(){ //这里是块语句 //这种模式也称为 立即调用的函数表达式, //特别是由于jQuery源码使用这种方式而大规模普及起来。 })();