- 函数变量提升:在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。
- 函数声明:
say()
function say(){
}
- 函数表达式
fun ();//error 函数表达式不存在函数变量提升
var fun = function(){
//这个function是匿名函数
}
递归
- 使用arguments.callee()实现递归。在编写递归函数时,使用 arguments.callee 总比使用函数名更保险。
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * arguments.callee(num-1);
}
}
- 命名函数表达式的形式创建(在严格模式中arguments.callee会报错)
var factorial = (function f(num){
if (num <= 1){
return 1;
} else {
return num * f(num-1);
}
}); //即使是修改了factorial变量名函数内部也能调用到f()命名函数
闭包
闭包是指有权访问另一个函数作用域中的变量的函数
-
捋一下一般函数的一生:
- 当创建一个函数时,会生成一个预先包含全局变量对象(变量对象包括作用域链上的所有对象,全局对象永远在变量对象上,而局部环境的变量对象只在函数执行过程中存在)作用域链,存放在函数的[[Scope]]属性中
- 当调用函数执行时,函数会创建执行环境。首先他会吸收[[Scope]]属性作为执行作用域链,随后再将arguments组成的活动对象推入执行环境作用域链的前端。
-
当函数执行完毕,局部活动对象销毁,全局活动对象保存在内存中
一般函数的执行环境
-
捋一下闭包函数的一生:
首先用父函数和匿名函数来表示外层函数和返回的内层函数- 当调用执行父函数创建匿名函数后,即使父函数已经执行完毕,但是其活动对象不会被销毁,因为他被匿名函数引用
-
匿名函数执行时,会吸收父函数和全局变量对象作为执行作用域链,随后再将自己局部变量和arguments组成的活动对象推入作用域链前端
所以匿名函数的作用域链是:
argument和局部变量组成的活动对象
父函数的活动对象
全局变量对象
父函数和匿名函数执行环境
闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。
闭包与变量
闭包只能取得包含函数中任何变量的最后一个值
闭包父函数的活动对象只会保留父函数执行完毕后的一次。举个栗子:父函数执行 a=1 a=2 a=3,执行完毕后a=3,保存给内部匿名函数的活动对象的a就是等于3。所有返回的匿名函数都是指向相同作用域下的那个a
function createFunctions() {
debugger
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
};
}
i = 2 //将i重新赋值为2,则返回结果都是2
return result;
}
const a = createFunctions()
console.log(a[0]())//2
console.log(a[5]())//2
- 当createFunctions执行时,父函数创建i变量,i随着程序执行变成0,变成1,变成2。。。变成10,变成2。当createFunctions完全执行完毕后,才会将自己的活动变量交给匿名函数,此时交出去的i是2!!!,所以返回的所有匿名函数得到的i都是2!!
- 解决方法:
- 返回匿名自执行函数,匿名自执行函数再返回闭包函数,匿名自治性函数传参给闭包函数返回。
- 将var改成let,每个for循环都是不同的作用域,返回的不再是同一个i
关于this对象
- this 对象是在运行时基于函数的执行环境绑定的。
目前知道的this指向
- 全局下指向window
- 对象中函数执行对象
- 匿名闭包函数的this指向全局window(每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量)
- 获得父函数this的方法,将父函数this赋值给变量传递
闭包带来的内存泄露
- 闭包会导致父函数局部变量一直保存在内存中,因为闭包函数一直保持着对他的引用
- 怎么解决?
- 将局部变量中需要用到的值取出来引用,例如const age = obj.age
- 将父函数的局部变量变为null obj=null(为什么都没有引用obj还不会被垃圾回收呢?因为闭包函数会引用所有父函数的活动对象)
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
模仿块级作用域
- 在匿名函数中定义的任何变量,都会在执行结束时被销毁
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();
alert(i); //导致一个错误!
}
- 这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。
- 这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
私有变量(闭包还可以用在对象创建私有变量)
- 私有变量:函数中固定的变量(函数参数和函数的局部变量)
- 特权方法:能访问到私有变量的方法
- 使用闭包,匿名函数返回父函数中的局部变量
- 构造函数中,通过get(),set()返回私有变量属性
静态私有属性:在私有作用域中用原型返回私有属性
模块模式:如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。和闭包差不多,就是返回了个对象
var application = function () {
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());
//公共
return {
getComponentCount: function () {
return components.length;
},
registerComponent: function (component) {
if (typeof component == "object") {
components.push(component);
}
}
};
}();
增强的模块模式
- 单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况(就是返回了一个对象,该对象又是某个构造函数的实例)
var application = function () {
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());
//创建 application 的一个局部副本
var app = new BaseComponent();
//公共接口
app.getComponentCount = function () {
return components.length;
};
app.registerComponent = function (component) {
if (typeof component == "object") {
components.push(component);
}
};
//返回这个副本
return app;
}();