一、函数对象隐含属性
函数声明,就相当于创建函数对象。函数对象中有以下隐含属性:
1、arguments
1) 在调用函数时,对函数数据预处理阶段进行初始化。
arguments赋值为实参列表,并将其添加为执行上下文对象的属性。
2) 函数调用时以及调用完毕,arguments会自动初始化为undefined。
2、this
1) this从广义上来说是函数对象隐藏的属性,
但是实质上this是执行上下文对象的属性,而不是函数的属性。
2) 调用函数时,对函数数据预处理阶段初始化。
this属性赋值为调用函数的对象。
3、__proto__(显示原型)
1) 默认指向构造函数隐式原型执行的原型对象,即Function.prototype
4、prototype(隐式原型)
1) 默认指向构造函数隐式原型执行的原型对象,即Function.prototype
5、 [[Scopes]](作用域链属性)
1) 外部不能访问,只供JS引擎访问。
2) 作用域链在函数编码时就已经确定。
作用域链是抽象的概念,而作用域链属性是作用域链具体的代码实现。
3) 作用域链属性是个数组。从索引值0开始查找。
默认情况下,在函数声明,即函数对象创建时,索引值0 指向window对象。
在闭环函数中,
1.1 函数对象隐含属性
1)arguments、proto、prototype、[[Scopes]] 是函数对象隐含的属性
function s(){
var h="!23";
}
console.dir(s)
2)[[Scopes]] 也是是函数对象隐含的属性。但是只给JS引擎访问。
function sss(){
var s=12;
console.log("123")
}
console.log("arguments" in sss)
console.log("prototype" in sss)
console.log("__proto__" in sss)
console.log("[[Scopes]]" in sss)
3)this从广义上来说是函数对象隐含的属性,但是实质上this是函数对象对应的执行上下文对象的属性
function sss(){
var s=12;
console.log("123")
}
console.log("arguments" in sss) //true
console.log("prototype" in sss) //true
console.log("__proto__" in sss) //true
console.log("this" in sss) //false
1.2 arguments属性
1)arguments属性在函数调用时,执行函数体代码前进行初始化的
2)arguments属性在函数调用完毕会自动初始化为null
function sss(){
console.log(arguments)
}
console.log(sss.arguments)
sss("123");
console.log(sss.arguments)
1.3 [[Scopes]] 属性( 闭包之 [[Scopes]] 属性 )
作用域链是一个抽象的概念,在函数硬编码阶段,作用域链就已经确定。
作用域链属性是作用域链这个抽象概念的代码实现。
二、闭包
+++ 闭包的由来
1、函数执行完毕,函数内部的变量就会被释放。
但是由于闭包,延长了闭包函数引用外部函数的变量的存储时间。
2、函数作用域是独立的、封闭的,外部的执行环境是访问不了的。
但是由于闭包,函数可以访问另外一个函数作用域的变量。
+++ 闭包
1) 从狭义上来说,闭包就是能够读取其他函数内部变量的函数。
函数嵌套,内部函数引用了外部函数的变量,则该内部函数叫做闭包函数。
2) 从广义上来说。闭包就是闭包作用域对象。它只保存闭包函数引用外部函数的变量。
该对象被闭包函数的作用域链属性所引用。
+++ 产生闭包的条件
1) 函数嵌套
2) 内部函数引用了外部函数中的数据。
3) 执行外部函数。
即闭包函数被声明,闭包函数对象被创建。
+++ 闭包的声明周期
闭包函数定义执行,就会创建闭包。
闭包创建后,闭包函数不被外部引用,就会死亡。
+++ 闭包的作用
1、延长了函数局部变量的生命周期。
使函数内部的变量在函数执行完毕后,仍然存活在内存中。
2、在函数外部可以直接操作到函数内部的数据。
1.1 闭包
1)创建闭包的条件
>>>>>> 内部函数必须引用外部函数的变量,才会创建闭包作用域对象
内部函数没有引用外部函数的变量则不会创建闭包作用域对象
function s(){
var a=12;
function ss(){
console.log("123")
}
return ss;
}
var h=s();
h();
内部函数引用了外部函数的变量才会创建闭包作用域对象
function s(){
var a=12;
function ss(){
a++;
console.log("123")
}
return ss;
}
var h=s();
h();
>>>>>> 闭包函数定义执行,才会创建闭包
2)闭包的生命周期
闭包的生命周期:
产生: 闭包函数定义执行,就会创建闭包。
死亡: 闭包函数对象不被外部引用就会死亡。
>>>>>> 闭包函数定义执行时,才会创建闭包
在调用ff函数后,首先会初始化ff函数数据,
由于变量提升与函数提升。此时闭包函数f定义执行,就会创建闭包。
function ff(){
//在执行该行代码时,就已经产生了闭包。
var a=1;
function f(){
a++;
console.log(a)
}
return f;
}
var f=ff();
在调用ff函数后,首先会初始化ff函数数据,
由于变量提升与函数提升。变量a与变量f会被提升,但是f的初始化没有被提升。
所以在执行f的初始化时,才会创建闭包。
function ff(){
var a=1;
//在执行该行代码时,才会产生闭包。
var f=function (){
a++;
console.log(a)
}
return f;
}
var f=ff();
>>>>>> 闭包函数对象不会外部引用,就会死亡
function ff(){
//在执行该行代码时,就已经产生了闭包。
var a=1;
function f(){
a++;
console.log(a)
}
return f;
}
var f=ff();
f();
//设置f为null。此时闭包函数不被外界访问,成为垃圾对象
//此时闭包就会死亡。
f=null;
3)闭包的作用
闭包的作用:
1、延长了函数局部变量的生命周期。
使函数内部的变量在函数执行完毕后,仍然存活在内存中。
2、在函数外部可以直接操作到函数内部的数据。
>>>>>> 闭包的由来
a、函数在调用完后,其内部定义的变量就会被释放。
b、外部不能访问函数内部定义的变量。
function s(){
var a=12;
console.log(a++)
}
//函数在调用后,其内部定义的变量就会被释放
s();
//外部不能访问函数内部变量
console.log(a);//报错
>>>>>> 闭包的作用
1)延长了函数局部变量的生命周期
2)在函数外部可以直接操作
function s(){
var a=12;
function ss(){
a++;
console.log(a);
}
return ss;
}
var h=s();
h();
h();
1.2 闭包的应用
1)利用闭包定义JS模块
1、JS模块是具有特定功能的JS文件。
2、所有的数据和函数封装到一个函数中。
3、只向外暴露一个具有包含n个方法的对象。
>>>>>> 方式一
index.js
function myModel(){
var msg="abc"
function doSomething(){
console.log(msg.toUpperCase())
}
function doSomething2(){
console.log(msg.toLocaleLowerCase())
}
return {
"doSomething":doSomething,
"doSomething2":doSomething2
}
}
index.html
<script type="text/javascript" src="a.js" ></script>
<script>
var model=myModel();
model.doSomething();
model.doSomething2();
</script>
>>>>>> 方式二(典型封装)
index.js
(function(){
var msg="abc"
function doSomething(){
console.log(msg.toUpperCase())
}
function doSomething2(){
console.log(msg.toLocaleLowerCase())
}
//向外暴露
window.myModel={
"doSomething":doSomething,
"doSomething2":doSomething2
}
})();
index.html
<script type="text/javascript" src="a.js" ></script>
<script>
myModel.doSomething();
myModel.doSomething2();
</script>
3)利用闭包循环添加监听事件
>>>>>> 方式一:普通添加
var divs=document.getElementsByTagName("div");
for(var i=0;i<divs.length;i++){
divs[i].index=i;
divs[i].onclick=function(){
console.log("这是第"+this.index+"个按钮")
}
}
>>>>>> 方式二:利用闭包来实现
var divs=document.getElementsByTagName("div");
for(var i=0;i<divs.length;i++){
(function(i){
divs[i].onclick=function(){
console.log("这是第"+i+"个按钮")
}
})(i);
}
1.3 作用域链属性 – [[Scopes]] 属性 【闭包的本质】
1) 作用域链是一个抽象的概念,在函数硬编码阶段,作用域链就已经确定。
作用域链属性是作用域链的代码实现。在函数定义执行时确定。
2) 作用域链属性是内部属性,外部无法访问。只供JS内部调用。
3) 作用域链属性一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
a、普通函数中作用域链属性只有一个全局执行上下文对象,即window对象。
b、闭包函数中作用域链属性从索引值0开始依次是闭包作用域对象、全局执行上下文。
1) 闭包作用域对象是我自己定义的概念。
闭包作用域对象存储的是闭包函数引用上一级函数的变量。
2) 闭包作用域对象只有在闭包函数引用外部函数变量时才会形成,否则不会。
4) 闭包作用域对象
a、闭包作用域对象是我自己定义的概念。
它只保存闭包函数引用的外部函数的变量。
b、闭包作用域对象只有在内部函数引用外部函数变量时才会形成。
5) 作用域链属性的作用:
查找变量时,首先在自身作用域对应的执行上下文对象中查找变量,
如果找不到,就到作用域链属性中从索引值0开始依次向上查找。
1)作用域链属性是函数的内部函数,只给JS引擎访问
function s(){
var h=12;
}
console.dir(s)
2)作用域链属性一个指向变量对象的指针列表
>>>>>> 普通函数的作用域链属性中只有一个window对象
function s(){
var h=12;
}
console.dir(s)
>>>>>> 闭包函数的作用域链属性中从索引值0开始依次是闭包作用域对象、全局执行上下文。
function s(){
var h=12;
function s2(){
h++;
}
return s2;
}
var m=s();
console.dir(m)
function s(){
var h=12;
function s2(){
var a=12;
function s3(){
a++;
h++;
}
return s3;
}
return s2();
}
var m=s();
console.dir(m)
3)闭包作用域对象
>>>>>> 闭包作用域对象只保存闭包函数引用外部函数的变量,外部函数的其他变量不会保存。
function s(){
var h=12;
var s=122;
function s2(){
h++;
}
return s2;
}
var m=s();
console.dir(m)
>>>>>> 闭包作用域对象只在内部函数引用外部时才会创建。
function s(){
var h=12;
var s=122;
function s2(){
console.log("!23")
}
return s2;
}
var m=s();
console.dir(m)
4)作用域链属性的作用
作用域链是在编码时就已经确定的,而作用域链属性是作用域链的代码实现。
查找变量:
1、沿着作用域链查找变量
查找变量时,首先在自身作用域对象的执行上下文对象查找,
找不到,就到函数的作用链属性中查找,从0开始依次查找。
2、沿着原型链查找对象的属性
1.5 常见的闭包
>>>>>> 将函数对象作为另外一个函数的返回值
function ff(){
var a=1;
function f(){
a++;
console.log(a)
}
return f;
}
//创建了一个闭包函数,即产生一个闭包作用域对象
var f=ff();
f(); //2
f();//3
//又创建了一个闭包函数,即产生一个闭包作用域对象
var f2=ff();
f2();//2
f2();//3
>>>>>> 将函数作为实参传递给另一个函数调用
function showDelay(msg,time){
setTimeout(function(){
alert(msg)
},time)
}
showDelay("213",2000)
1.6 闭包的缺点
闭包的缺点:
闭包的作用就是延长了局部变量的生命周期。
但是如果闭包函数执行完,如果没有释放闭包函数,容易造成内存泄露。
function s(){
var a=new Array(10000);
function ss(){
console.log(a.length)
}
return ss;
}
var h=s();
h();
h=null; //让闭包函数成为垃圾对象,被回收
三、内存泄露与内存溢出
+++ 内存溢出与内存泄漏
内存溢出:
内存溢出是一种程序运行的错误。
当程序的运行内存超过剩余内存时,就会抛出内存溢出的报错。
内存泄露:
内存泄露是指占用的内存没有及时释放
内存泄露积累多了容易造成内存溢出。
内存泄露是量变,内存溢出是质变。
+++ 常见的内存泄露:
1) 意外的全局变量
2) 没有及时清理的定时器
3) 没有及时清理闭包函数
3.1 直视JS运行占用的内存
var obj={};
for(var i=0;i<1000;i++){
obj[i]=new Array(1000000)
}
console.log(obj.length)
运行上述代码,查看内存,会发现内存一直向上飙升
3.2 内存泄露与内存溢出
1) 内存泄露是一种程序运行的错误
2) 内存溢出是指占用的内存没有及时释放
闭包函数在运行完后,没有及时释放,就会造成内存溢出
function f(){
var a=1;
function f2(){
a++;
}
return f2;
}
var h=f();
h();
3) 内存溢出与内存泄露的关系
内存溢出是量变,内存泄露是质变。
内存溢出积累多了,就会造成内存泄露。
3.3 常见的内存溢出(解决方案)
1)意外的全局变量
//1.意外的全局变量
function s(){
a=new Array(10000)
console.log(a.length)
}
//解决方案
// a=null;
2)没有及时清理定时器
//2.没有及时清理定时函数
var id=setInterval(function s(){
console.log("!23")
},3000)
//解决方案
// clearInterval(id)
3)没有及时清理闭包函数
//3.闭包
function f(){
var a=1;
function f2(){
a++;
}
return f2;
}
var h=f();
h();
//解决方案
// h=null;