引言
在写业务代码的同时,我觉得还是很需要把基础知识全部理清楚的,闭包和作用域呢我一直觉得我懂了,那么试试看能不能说清楚?当然我也会参考一下别人说法,所以会在参考资料里面写上啦嘻嘻~~
作用域和作用域链
作用域是一个语言无关的概念,当然作用域分为词法作用域和动态作用域
作用域:通常来说,一段程序代码中所用的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
词法作用域:也叫做静态作用域,它的作用域是指在词法分析阶段就确定了,是不会改变的。
动态作用域:是在运行的时候根据程序的流程信息来动态确定的,而不是写代码是进行静态确定的。
var a = 2;
function foo() {
console.log(a); // 会输出2还是3?
}
function bar() {
var a = 3;
foo();
}
bar();
如果是词法作用域
foo()
函数引用到全局作用域中的a,因此会输出2。因为词法作用域是写代码的时候就静态确定下来的,JavaScript的作用域就是词法作用域(大部分语言也都是基于词法作用域的),所以这段大妈输出应该是2
.
如果是动态作用域
动态作用域不会关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,作用域链就是基于调用栈的,而不是代码中的作用域嵌套,因此,如果是JavaScript具有动态作用域,理论上输出结果应该是3
.
总结
JavaScript并不具有动态作用域,它只有词法作用域。eval()
、with
、this
机制在某种程度上很像动态作用域,使用上要特别注意。
词法作用域关注函数在何处声明,而动态作用域是在运行时确定的(javascript中的this也是)。
闭包
闭包的创造条件
- 存在内、外两层函数
- 内层函数对外层函数的局部变量进行了引用
闭包是对作用域的眼神,也是在实际开发中经常使用的一个特性。
一个例子:
var scope = {};
if (scope instanceof Object) {
var j = 1;
for (var i = 0; i < 10; i++) {
//console.log(i);
}
console.log(i); //输出10
}
console.log(j);//输出1
}
无论用过何种手段将内部函数传递到所在的所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包
一个难一点的例子:
function wait(message) {
setTimeout( function timer() {
console.log( message );
}, 1000 );
}
wait( "Hello, closure!" );
这也是闭包。传入的timer
函数一定会被执行,知识内部引擎调用执行,深入到引擎的内部,内置的工具函数setTimeout(..)
持有对一个参数的引用,引擎会调用这个函数!!!
IIFE(立即执行函数)是闭包吗
它创建了闭包,在内存中创建了一块区域,这块区域保存着作用域链上的作用域引用。等同创建了闭包
// 它需要有自己的变量,用来在每个迭代中储存i 的值:
for (var i=1; i<=5; i++) {
(function() {
var j = i;
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})();
}
// 行了!它能正常工作了!。
// 可以对这段代码进行一些改进:
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})( i );
}
//当然你也可以这样写
for (var i=1; i<=5; i++) {
(function(i) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
})( i );
}
//你也可以这样
function timeManage() {
for (var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
}
//timeManager();输出1,2,3,4,5
function createClosure() {
var result = [];
for (var i = 0; i < 5; i++) {
result[i] = function(num) {
return function() {
console.log(num);
}
}(i);
}
return result;
}
//createClosure()[1]()输出1;createClosure()[2]()输出2
可以利用闭包创建私有变量:
function makeEmployee(name) {
return {
getName() {
return name;
},
};
}
const employee = makeEmployee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;//外部没办法操纵
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
由于闭包会额外的附带函数的作用域(内部匿名函数携带外部函数的作用域),因此,闭包会比其它函数多占用些内存空间,过度的使用可能会导致内存占用的增加。
总的来说注意三个问题:
- 变量
- this
- 内存
闭包中的this问题:
闭包中的this之前讲过,是特例,跟动态作用域差不多:
var object = {
scope:"local",
getScope:function(){
return function(){
return this.scope;
}
}
}
//object.getScope()()返回值为global
匿名函数的this永远指向windows
预编译
JavaScript脚本的运行其实是由两个阶段组成的:
- 预编译阶段:在内存中开辟出一块空间,用来存放变量和函数,为使用
var
和function
关键字开辟出一片空间,用来存放这二者声明的变量和函数。
-预编译的时候function
的优先级高于var
(也说明了函数是一等公民~~~)
预编译时不会对变量进行赋值(不会进行初始化),赋值是在执行阶段进行的。 - 执行阶段
结语
嘤嘤嘤终于写完了,今天一天特别不在状态,晕晕乎乎的,所以写出来的东西可能有点乱,我原来的目的是为了让自己方便学习使用,如果大家看到了之后觉得有问题,或者想喷我嘤嘤嘤?评论区是你们的舞台呜呜呜呜呜~~我这么可爱,别喷我,可以留言我很可爱呀~对了,如果觉得我讲的不清楚,都可以看参考资料的,我觉得讲得很到位欸
参考资料
词法作用域 VS 动态作用域
深入javascript——作用域和闭包
你不知道的JS(2)深入了解闭包
JavaScript - 预编译