作用域
JS作用域的概念:当函数被调用时,会创建一个执行环境和相应的作用域,该执行环境里包含了变量或函数有权访问的数据。
每一个执行环境都有一个变量对象[[Scope]],该对象保存着执行环境中的所有变量和函数,我们的代码无法访问这个对象,JS 解析器在处理数据时会使用到它。
1、全局执行环境
在 JS 中,根据宿主的不同,全局执行环境对象也不同,对于 WEB 而言,全局执行环境是 window 对象。
所有的全局变量和函数都是做为 window 对象的属性和方法创建的。
var name = "zhar";
function say(){
console.log(name);
}
console.log(name);//zhar
console.log(window.name);//zhar
say();//zhar
window.say();//zhar
2、局部执行环境
每个函数有自己的执行环境。
当执行流进入一个函数时,函数的环境会被推入一个环境栈中。函数执行完成后,栈将环境推出。所以在函数之外是没法访问的,这个环境已经不存在。
3、作用域链
当代码在一个环境中执行时,会创建一个作用域链对象(scope chain)。
作用域链的首位,始终是当前执行环境的变量对象。这是一个包含 arguments 和其它命名参数值的活动对象。第二位是外部函数的活动对象,第三位是外部函数的外部函数的活动对象,......,直到作为作用域终点的全局执行环境。
4、没有块级作用域(函数有局部执行环境)
在 JS 中并没有像强类型语言中的块级作用域,比如在 JAVA 中一对花括号中,是一个块级的作用域。
所以下面代码相当于在全部环境中修改了name
var name = 'zhar'
if(true){
var name = 'tom';
}
console.log(name);
//按着上面作用域链中描述的情况,name 应该输出为 zhar,但实际情况为 tom,便是因为在 JS 中没有块级作用域的概念
//另外一种块级作用域的典型情况为 for 循环
for(var i=0;i<5;i++){
//dosomething
}
console.log(i);//5
5、变量提升分为声明式和赋值式
var name = 'zhar';
function say(){
console.log(name);//undefined
var name = 'tom';
}
say();
上面的示例代码中就存在着变量提升
- 声明式
say();//运行正常
function say(){
console.log("Hello");
}
此为声明式语句,JS 解析器会将声明式语句提升至当前执行环境的顶端
1.赋值式
say();//运行错误 Uncaught TypeError: say is not a function
var say = function(){
console.log("Hello");
}
//相当于以下代码
var say = undefined;//故会弹出say is not a function
say = function(){
console.log("hello");
}
此为赋值式语句,JS 解析器会将赋值式语句提升到当前执行环境的顶端,并且赋值为 undefined
闭包
闭包:我的理解就是外部函数能够读取内部函数的变量。可以看做内部函数与外部函数的一座桥梁。
闭包可以形成独立的空间,永久的保存局部变量。保存中间值的状态
缺点是容易造成内存泄漏,因为局部变量永远不会被回收。内存压力太大
1、创建闭包
通常就是在一个函数内部创建另一个(匿名)函数
function say(){
var name = "zhar";
return function(){
return name+"-30";
}
}
//获取闭包
var hi = say();//Function
console.log(hi());//zhar-30
上面示例中,第三行代码做为 say 方法的一个内部函数,访问了外部的 name 属性,该函数被返回,在其它地方被调用时,仍然可以访问到 say 方法内的 name 属性
根据前面所学的作用域链的知识,可以得出匿名函数中 包含了外部作用域中的 name 属性的引用,当匿名函数被返回后,它的作用域链包含上级函数(say)的活动对象(name)。这样,当 say 函数执行完成后,其活动对象也并不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。
由于闭包所引用的变量不会被自动销毁的特性,在使用闭包时要非常小心,有可能会引起内存占用过多
闭包常见场景
1.循环添加事件BUG
var lis = document.querySelectorAll("li");
for(var i=0;i<lis.length;i++){
lis[i].onclick = function(){
console.log(this.innerHTML,i);
}
}
//在上面的代码中,innerHTML能够得到正确的值,但 i 并不能,这是由于在 onclick 所指定的匿名函数
中使用了外部环境中的同一个活动对象 "i",当外部环境执行完成后,i 的值是 lis 的长度,所以引用了
所有 i 的对象值都变为了 lis的长度
//修改代码如下:
for (var i = 0; i < lis.length; i++) {
//给onclick添加了一个自执行函数,执行之后会将返回值添加给
onclick事件,即将里面的函数添加到onclick事件上
lis[i].onclick = (function(index) {
return function() {
console.log(lis[index].innerHTML, index);
}
})(i);
}
//修改后的代码将 onclick 指定的匿名函数改为了立即执行,并将外部环境的 i 值做为参数传递给该函数
(参数是做为值传递),而在匿名函数内部,由一个闭包函数来引用 index
//另一种方法(这种方法只是做为一种扩展,与本知识点无关)
for (var i = 0; i < lis.length; i++) {
//在JS中,可以将lis[i]看做一个对象,毕竟万物皆对象,然后可以给这对象添加index属性index为i
lis[i].index = i;
lis[i].onclick = function() {
console.log(this.innerHTML, this.index);
};
}
2.使用闭包封装一个完整的对象操纵功能
对于一些封装,开发者并不想对外暴露一些属性或变量,此时,可通过闭包来创建
var PERSON = function() {
var obj = {
name: "zhar",
age: 30,
address: "北京"
};
return {
get: function(pName) {
return obj[pName];
},
add: function(pName, pVal) {
obj[pName] = pVal;
},
del: function(pName) {
delete obj[pName];
},
update: function(pName, pVal) {
obj[pName] = pVal;
}
}
}();
console.log(PERSON.get("name"));//zhar
PERSON.add("desc","175");
console.log(PERSON.get("desc"));//175
PERSON.del("age");
console.log(PERSON.get("age"));//undefined
作用域和闭包经典题目
function f1() {
var n = 999;
nAdd = function() {
n += 1
}
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result();//999
nAdd();//执行n += 1;
result();//1000
function show(i) {
var a = i;
function inner() {
a = 10;
console.log(1,a);//10
}
inner();
console.log(2,a);//10
}
var a = 0;
show(5);
console.log(3,a);//0
var too = "old";
if (true) {
var too = "new";
}
console.log(1,too);//new
function test() {
var too = "new";
}
test()
console.log(2,too);//new
var i = 0;
function outPut(i) {
console.log(i)
}
function outer() {
outPut(i);//0
function inner() {
outPut(i);//undefined赋值式变量提升
var i = 1;
outPut(i);//1
}
inner();
outPut(i);//0
}
outer();
var a = function(){
a=2;
console.log(1111,a);
}
function a(){
a=1;
console.log(2222,a);
}
a();//2--->因为声明式变量提升先被置顶,然后再被覆盖:即a--->第二个function--->undefined--->第一个function
a();//a is not a function