Function类型
函数定义
首先函数是对象,函数名是指针,每个函数都是Function类型实例,具备引用类型的属性和方法,实际上函数名是一个指向函数对象的指针,不会与某个函数绑定
定义方式:函数声明/函数表达式/构造函数
函数声明 => 代码执行之前,解析器会率先通过名为函数声明提升的过程,读取并将函数声明添加到执行环境中,使其在执行任何代码之前可用
函数表达式 => 必须等到解析器执行到这行代码时才会真正被解释执行,在这之前这个函数名不会保存对函数的引用,除此之外与函数声明语法是等价的
构造函数 => 不推荐用,性能不好,会有两次解析代码行为(第一次是常规解析ECMAScript,第二次是解析传入构造函数中的字符串),仅用来理解下函数名是指针1
2
3
4
5
6
7
8
9
10<!-- 函数声明 -->
function sum(num1,num2){
return num1+num2
}
<!-- 函数表达式 -->
var sum = function(num1,num2){
return num1+num2
}
<!-- 构造函数 -->
var sum = new Function('num1','num2','return num1+num2')
再来感受下函数是对象
⚠️函数是对象,函数名是指针,所以函数没有重载,声明两个同名函数,后面的函数覆盖了前一个函数,实际上就是覆盖了引用第一个函数的变量指针指向
函数声明提升与变量提升测试demo,函数声明提升会提升到最顶部,var变量提升会紧跟其后,但是赋值还是在原位置
⚠️var声明紧跟函数声明后面,重复声明会被忽略的并不会导致变量变成undefined,但是赋值会执行



作为值的函数
函数名本身是变量,所以可以作为值使用:作为函数参数/作为函数返回值
作为参数传入函数:1
2
3
4
5
6
7
8<!-- 此函数不关心传进来的是什么函数 -->
function callSomeFunction(someFunction,someArgument){
someFunction(someArgument)
}
function add(num){
return num + 10
}
callSomeFunction(add,1)
作为函数返回值:
🌰排序数组,但数组的每一项是对象,希望按照对象的某个属性值进行排序1
2
3
4
5
6
7
8
9
10
11
12
13function createComparisonFunction(property){
return function(obj1,obj2){
var value1 = obj1[property]
value2 = obj2[property]
if(value1 > value2){
return 1
}else if(value1 < value2){
return -1
}else{
return 0
}
}
}

函数内部属性
arguments
包含参数的类数组,主要用途保存函数参数arguments.callee 属性是指针,指向拥有此arguments对象的函数
递归中解除函数执行与函数名紧密耦合的现象可以用,但是严格模式下会报错1
2
3
4
5
6
7
8function factorial(num){
if(num <= 1){
return 1
}else{
return num * arguments.callee(num-1)
}
}
<!-- 函数执行中解除函数名耦合,其他变量引用该函数对象执行也不会出错 -->
arguments.caller ES5新增,为了区分函数的caller属性,非严格模式下始终是undefined,严格模式报错
this
函数据以执行的环境对象(当在网页全局环境下调用,this指window),执行时确定this非定义时确定

caller
ES5新增属性
该属性保存调用当前函数的函数的引用,如果是全局作用域调用,该值是null
⚠️严格模式下不能为caller属性赋值,会报错
函数属性和方法
属性
length 属性保存函数希望接收的命名参数个数,即定义函数时写的参数个数
prototype 指向原型,对于引用类型,该属性是保存它们所有实例方法的真正所在(toString() valueOf()等都保存在proptotype下),只不过是通过各自的对象实例访问
ES5开始prototype不可枚举
方法
apply和call 在特定的作用域中调用函数,即定义了函数体内this对象的值,两者功能相同,差异在于接收参数方式不同apply()=>第一个参数是运行函数的作用域,第二个参数是函数要接收的参数数组(可以是Array实例/arguments对象)call()=>第一个参数同样是运行函数的作用域,第二个函数接收参数的形式是直接传递并非数组
⚠️两者选取取决于哪种给函数传递参数更方便,如果直接打算传递arguments,或者包含函数中先接收到的也是一个数组,那就用apply,否则就用call更合适,不传递函数参数的情况下哪种都一样
⚠️两者强大的地方在于扩充函数赖以运行的作用域,最大好处是对象不需要与方法有任何耦合关系
bind 创建一个函数实例,其this值会被绑定到传给bind()的值,在任何执行环境中执行此函数实例其this都会是绑定的值不变,IE9+
toString()和toLocaleString()返回函数代码,返回格式因浏览器而异valueOf()返回函数本身
函数表达式
匿名函数/拉姆达函数:function关键字后面没有跟名字,函数的name属性为空
不建议用法:1
2
3
4
5if(condition){
function sayHi(){...};
}else{
function sayHi(){...};
}
⚠️这种用法浏览器表现不一致,多数浏览器会忽略conditon返回第二个声明,但是也有condition为true返回第一个声明的,所以不要这么用,反而写成表达式方法可以
1 | var sayHi; |
递归
前面说arguments.callee在严格模式下会报错,为了解藕,可以利用命名函数表达式来解决
1 | var fun1 = (function f(num){ |

闭包
概念:有权访问另一个函数作用域中变量的函数,创建闭包的常用方法是在一个函数内部创建另一个函数
函数从创建到执行:创建函数时,会创建一个预先包含外层函数活动对象直至全局变量对象的作用域链,此作用域链保存在[[Scope]]属性中,当调用函数时,为函数创建一个执行环境,然后复制[[Scope]]属性的对象构建起当前执行环境中的作用域链,而后,当前活动对象被创建被推入作用域链最前端
⚠️作用域链实质是指向变量对象的指针列表,而不实际包含变量对象
闭包做为返回值返回现象:闭包被返回其会包含初始作用域链,即能访问外层函数变量对象和全局变量,so外层函数虽被执行完成,执行环境作用域链被销毁,但活动对象不会被销毁,因为在被返回的闭包引用,直至闭包不再被引用才会销毁,so闭包变量执行完毕解除引用赋值为null以待垃圾回收例程将其清除释放内存
1 | function wrap(){ |
闭包变量
由于闭包作用域链引用外层函数的活动对象,所保存的是整个活动对象,而不是某个状态特殊值,所以表现的是最后一个值状态定格,会出现经典的for循环返回闭包i值一致
1 | function createFunctions(){ |

解决方案:创建立即执行闭包,将i每个状态作为参数传递进去(利用的是函数参数按值传递的特性)
1 | function createFunctions(){ |

闭包this
普通函数(非箭头函数)this是在运行时基于函数的执行环境绑定的,全局函数this指window,作为对象方法调用this指此对象,而闭包的执行环境具有全局性,通常指向window(使用call或apply除外)

⚠️解释1:this属性以及arguments属性属于函数的内部属性,这两个属性的搜索只会在当前活动对象,永远不会搜索到外层函数
⚠️解释2:this是基于函数的执行环境绑定的,上面的调用方式最终的闭包是在全局环境下执行的,this指向了window
其他示例:
闭包防止内存泄漏
除了避免循环引用外,要注意闭包初始作用域链会包含外层的活动对象的,不管有没有使用都会存在作用域链里,所以及时的解除
1 | function assignHandler(){ |
优化成:1
2
3
4
5
6
7
8function assignHandler(){
var element = document.getElementById('someElement');
var id = element.id
element.onclick = function(){
alert(id);
};
element = null;
}
模仿块级作用域
不存在块级作用域
模拟块级作用域(私有作用域),采用立即执行匿名函数方式

意义:1常用在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数;2减少闭包占用,没有指向匿名函数的引用,执行完毕立即销毁作用域链了
私有变量
没有私有成员的概念,所有对象属性都是公有的
私有变量:函数内定义的变量都是私有变量,在函数外部无法访问,包括函数的参数/变量/方法
而通过闭包可以创建能够访问私有变量的公有方法(特权方法)
两种在对象上创建特权方法的方式:
方式一:自定义类型的特权方法
-函数模式:构造函数中创建私有变量和函数,构造函数外部不能访问,通过实例方法(闭包)–特权方法访问
1 | function MyObject(){ |

利用私有和特权成员,可以隐藏不应该被直接修改的数据
1 | function MyObject(name){ |

⚠️构造函数的缺点就是每次创建实例都会重新创建一组相同的特权方法,并且私有变量的属性也不是共享的,每个实例有独立的一份私有变量
-原型模式:静态私有变量,通过在私有作用域中创建变量和方法,在构造函数的原型上创建所有实例共享的特权方法
1 | (function(){ |

⚠️静态私有变量的方式,使私有变量和方法被所有实例共享,特权方法是原型方法也被共享
构造函数方法实例变量/私有作用域方法静态私有变量的择取,根据是否共享视需求而定
方式二:单例的特权方法
单例:只有一个实例的对象
-模块模式:为单例创建私有变量和特权方法,通过在私有作用域中创建私有变量和方法,返回包含有权访问私有变量和方法的闭包(特权方法)的单例对象
1 | var obj = function(){ |
⚠️本质:对象字面量定义的是单例的公共接口,此模式在需要对单例进行某些初始化,同时又需要维护私有变量时非常有用,这种模式创建的单例都是Object实例
单例管理应用程序级的信息案例
1 | var application = function(){ |
-增强的模块模式
其实就是不再是字面量形式返回对象,而规定必须是哪种数据类型
1 | var obj = function(){ |
⚠️最后的说明:创建闭包必须维护额外的作用域,过度使用可能会占用大量内存