作用域
首先先介绍一下作用域等一些基础概念。
每个JavaScript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供JavaScript引擎存取,[[scope]]就是其中一个。
[[scope]] : 指的就是我们所说的作用域,其中存储了执行期上下文的集合
作用域链 : [[scope]] 中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链接叫做作用域链。
运行期上下文 : 当函数执行时,会创建一个称为执行期上下文的内部对象(AO)。一个执行期上下文定义了一个函数执行的环境,函数每次执行时对应的执行环境都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。
查找变量 :从作用域链的顶端依次向下查找。
下面举一些例子:
function a(){ function b(){ function c(){ } c(); } b(); } a(); a defined a.[[scope]] ----> 0 : GO //a定义的时候产生GO对象 a doing a.[[scope]] ----> 0 : aAO //a执行的时候新产生AO对象 1 : GO b defined b.[[scope]] ----> 0 : aAO //子级b定义会继承父级a运行时产生的对象 1 : GO b doing b.[[scope]] ----> 0 : bAO //子级b新产生AO对象 1 : aAO 2 : GO c defined c.[[scope]] ----> 0 : bAO //c定义时会继承b运行时产生的属性 1 : aAO 2 : GO c doing c.[[scope]] ----> 0 : cAO //c执行时同时又产生新的AO 1 ;bAO 2 : aAO 3 : GO
立即执行函数
之前学过函数的定义、函数表达式,还有一种函数叫做立即执行函数。
立即执行函数:函数执行过后立即被销毁。
立即执行函数的官方写法:
// 立即执行函数的官方写法 (function() {} ()); W3C建议此种 (function() {})();
针对初始化功能的函数,可以有参数。
var num = function (a,b){ return a + b; }(1,2); (function abc(){ var a = 123; var b = 234; console.log(a+b); }())
只有表达式才能被执行符号执行,能被执行符号执行的表达式,函数名字会被自动忽略。
function test(){ console.log("a"); }() 会出现语法解析错误,因为括号前面是函数声明 (+ function test( ){ console.log('a'); }()) -------->打印出a
下面是一道曾阿里面试题
function test(a, b, c, d){ console.log(a + b + c + d); }(1, 2, 3, 4); // 不报错也没有执行
下面是几道经典的例题,可以参考一下:
function test(){ var arr = []; for(var i = 0; i < 10; i ++){ arr[i] = function (){ console.log(i); } } return arr; } var myArr = test(); for(var j = 0; j < 10; j++){ myArr[j](); }
// 输出:10个10
那么采用立即执行函数呢?会有怎样的结果呢?
function test(){ var arr = []; for(var i = 0; i < 10; i ++){ (function(j){ arr[i] = function (){ console.log(j + " "); } }(i)) } return arr; } var myArr = test(); for(var j = 0; j < 10; j++){ myArr[j](); }
// 输出结果 0 1 2 3 4 5 6 7 8 9
大家可以自行思考一下。
闭包
闭包的现象:当内部函数保存到外部时会产生闭包。
闭包会导致原有的作用域链不释放,造成内存泄漏
(内存泄漏:内存占用(比如:手握沙子,握得越紧手里剩得就越少))
闭包触发的情况:
两个或多个函数互相嵌套,把里面的函数保存到外部,这样的情况一定会产生闭包。从外面还可以调用里面的函数。
闭包的作用:
实现公有变量
eg:函数累加器
可以做缓存(存储结构)
eg:eater
可以实现封装,属性私有化
eg:person()
模块化开发,防止污染全局变量
// 函数累加器 function add(){ var count = 0; function demo(){ count ++; console.log(count); } return demo; } var counter = add(); counter(); counter(); counter(); counter(); counter(); counter(); // eater 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();
闭包:可以实现封装,属性(变量)私有化 //属性(变量)私有化:别人访问不出来,只有自己设置方法让显式才能访问比如以下例子的prepareWife 例如: function Deng(name, wife){ var prepareWife = "xiaozhang"; this.name = name; this.wife = wife; this.divorce = function (){ this.wife = prepareWife; } this.changePrepareWife = function (target){ prepareWife = target; } this.sayPrepareWife = function(){ console.log(prepareWife); } } var deng = new Deng('deng', 'xiaoliu'); //将方法实例化对象
以上例子中我们用deng.prepareWife是访问不出来的,只能是调用sayPrepareWife方法才能实现。
这就将PrepareWife实现了私有化,实际是存在的,只是访问不出来,只能通过自身设置的方法才能访问,这就防止了污染全局变量。
附加一个逗号操作符:
先看前面的表达式,再看后面的表达式,把后面表达式的计算结构返回
例题:
var f =( function f(){ return "1"; }, function g(){ return 2; } )(); console.log(typeof(f)); // -------number var x = 1; if(function f(){}){ x += typeof f; } console.log(x); // --------> 1undefined
闭包还有一些知识有待补充,等后面学习继续补充。