1.函数的隐式属性
- [[scope]]:就是常说的作用域,其中存储了运行期上下文的集合
- 作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,将这种链式链接叫做作用域链。
每个JavaScript函数都是一个对象,对象中有些属性属性可以访问,但是有些不可以,这些属性仅供JavaScript引擎存取,[[scope]]就是其中一个。
运行期上下文:函数每次执行时都会产生一个上下文,这个上下文AO为即时的,因此执行完就被销毁。
查找变量:从作用域链的顶端依次向下查找。
2.作用域链
function a() {
}
var glob = 100;
a();
//a被定义时 a. [[scope]] --> 0:GO{}
//a被执行时 a. [[scope]] --> 0:AO{}
-->1:GO{}
function a() {
function b() {
var b = 234;
}
var a = 123;
b();
}
var glob = 100;
a();
//a被定义时 a. [[scope]] --> 0:GO{}
//a被执行时 a. [[scope]] --> 0:a.AO{}
-->1:GO{}
//b被定义时 b. [[scope]] --> 0:a.AO{}
-->1:GO{}
//b被执行时 b. [[scope]] -->0:b.AO{}
--> 1:a.AO{}
-->2:GO{}
3.闭包的产生
闭包:当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄漏。
闭包不是只有外部函数最后加一个return内部函数语句才能产生
也可以通过设置全局变量等于内部函数产生
总而言之,只要内部函数活得比外部函数长,就可以产生闭包
//全局变量等于内部函数的方法
var demo;
function test() {
var abc = 100;
function a() {
console.log(abc);
}
demo = a;
}
test(); //若没有这个语句则会报错没有定义demo函数
//只有test执行,才能将a函数赋值给全局变量
demo();
//输出 100
4.闭包的优点:
- 实现公有变量
eg:函数累加器(不依赖于外部变量,且能反复执行,即每次执行结果都不一样)
独立的功能不能依赖于外部变量
function add() {
var num = 0;
function a() {
console.log(++num);
}
return a;
}
var myAdd = add();
myAdd();
myAdd();
myAdd();
//输出 1 2 3
- 可以做缓存
function test() {
var food = "apple";
var obj = {
eatFood : function() {
if(food != "") {
console.log("I am eating" + food);
food = "";
} else {
console.log("There is nothing!");
}
},
pushFood : function (myFood) {
food = myFood;
}
}
return obj;
}
var person = test();
person.eatFood();
person.eatFood();
person.pushFood("banana");
person.eatFood();
//输出
I am eating apple
There is nothing!
I am eating banana
- 变量私有化
function Deng(name, wife) {
//这个变量在函数执行时会在函数的执行上下文中
var prepareWife = "xiaozhang";
this.name = name;
this.wife = wife;
this.divorce = function() {
this.wife = prepareWife;
//注意这个prepareWife前没有加this
}
}
var deng = new Deng('deng', 'xiaoliu');
//控制台输入 deng.divorce
//再输入 deng.wife
//输出 xiaozhang
//输入 deng.prepareWife
//返回 undefined
//因为这个在构造函数中定义的变量不是对象的方法,因此不能访问
//这样就形成了私有化变量
在对象方法中能够直接使用在对象中定义的变量是因为 new构造函数后,最后一步会返回构造函数对象,因此会形成闭包,就可以使用函数中定义的变量。
- 模块化开发,防治污染全局变量(参见学习JS第七节命名空间)
5.立即执行函数
定义:多用于只执行一次的函数,因为执行一次后立即销毁,避免占用过多内存(初始化功能的函数)
//括号的两种立即执行函数
(function () {
}) ()
//多用这种
(function () {
} ())
//括号优先级最高,因此先将其转换成函数表达式,因此两种方式都对
只有表达式才能被执行符号执行
能被执行符号执行的表达式,会放弃名字
!+ - && ()操作符可以将函数声明变成表达式
function test() {
var a = 123;
}()
//报错,不能执行
//因为只是函数声明,不是表达式
var test = function () {
}()
//可以执行
//执行完之后再输 test()会报错
//因为此时立即执行函数已经销毁,此时test是个空的变量
var test = function() {
}();
//输入:test
//返回:undefined
//因为 被执行符号执行的表达式,会放弃自己的名字
+ var test = function() {
}();
//可以执行
真题解析
var x = 1;
if(function f() {}) {
x += typeof f;
}
console.log(x);
//输出 1undefined
//括号会将f变成表达式,就不是函数定义了,因此f就没有被定义
6.立即执行函数与闭包的联系
function test() {
var arr = [];
for(var i = 0; i < 10; i++) {
arr[i] = function() {
console.log(i);
//arr里面保存的函数的 [[scope]]包括
//函数test的AO:{ i=10 }
//全局的GO
}
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
myArr[j]();
}
//输出 10 10 10 10 10 10 10 10 10 10 10
//加上立即执行函数后
function test() {
var arr = [];
for(var i = 0; i < 10; i++) {
(function (j) {
arr[j] = function() {
console.log(j);
}
//arr里面保存的函数的 [[scope]]包括
//外层立即执行函数的AO:{ 形参j=实参i }
//函数test的AO:{ i=10 }
//全局的GO
}(i));
}
return arr;
}
var myArr = test();
for(var k = 0; k < 10; k++) {
myArr[k]();
}
//输出 0 1 2 3 4 5 6 7 8 9
7.逗号表达式:
var a = (表达式1,表达式2);
//a被赋值为表达式2