var 与 let
JS中使用 var 来声明一个变量时, 变量的作用域主要是和函数的定义有关。
针对于其他块定义来说是没有作用域的,比如 if、for等,这在我们开发中往往会引起一些问题。
而 let 是具有块级作用域的。
1、{}块级区域
function varTest() {
var x = 1;
{
var x = 2; // 同样的变量 此时x的1被覆盖为2
console.log(x); // 2
}
console.log(x); // 2
// 无块级作用域
}
function letTest() {
let x = 1;
{
let x = 2; // 不同的变量
console.log(x); // 2
}
console.log(x); // 1
}
2、if 模块
var func;
if (true) {
var name = "zzz"; // var中if没有作用域
func = function() {
console.log(name);
}
func(); // zzz
}
name = "tttt"; // name被覆盖为tttt
func(); // tttt
console.log(name); // tttt
var func;
if (true) {
let name = "zzz"; // let中if有作用域
func = function() {
console.log(name);
}
func(); // zzz
}
name = "tttt"; // 与函数中的name是不同的变量,此处的name为tttt,而函数内部的name依旧为zzz
func(); // zzz 调用func()时,if有自己的作用域,因此name='zzz'
console.log(name); // tttt 外部,因此使用全局变量,获得name='tttt'
3、for 模块
// 正常情况
for (var i = 0; i < 5; i++) {
console.log(i);
} // 0 1 2 3 4
// 但在有回调函数等情况下
// 高频笔试面试题
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 0);
} // 5,5,5,5,5; var没有块级作用域,共享同一个i
// i=4之后还有一个i++ 等到执行log(i)时,i=5
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 0);
} // 0,1,2,3,4
// 闭包写法
for (var i = 0; i < 5; i++) {
setTimeout((function() {
console.log(i);
})(i), 0);
} // 0,1,2,3,4; 函数自执行写法形成闭包,模拟块级作用域
const
使用const修饰的标识符为常量, 不可以再次赋值,来保证数据的安全性。
建议: 在ES6开发中,优先使用 const , 只有需要改变某一个标识符的时候才使用 let 。
注意点:
①const 修饰的标识符必须赋值。
②const 修饰的标识符不能重新赋值。
③const 修饰的是一个对象的话,指向对象不能改动,但是可以改变对象内部的属性。
const name; // 报错,const 必须赋值
const name = 'abc';
name = '123'; // 报错,不能再次赋值
const obj = {
name:'kevin',
age:18,
height:180
}
obj = {...}; // 报错,不能再次指向其他对象
obj.name = 'durant'; // 可以
obj.age = 20; // 可以
对象增强
属性增强:
// ES6之前的写法
let name = 'kevin';
let age = 18;
let obj = {
name:name,
age:age
}
// ES6写法
let name = 'kevin';
let age = 18;
let obj = {
name,
age
}
方法增强:
// ES6之前的写法
let obj = {
test:function(){
...
}
}
// ES6写法
let obj = {
test(){
...
}
}
闭包
1、变量作用域
变量作用域两种:全局变量、局部变量。
js中函数内部可以读取全局变量,函数外部不能读取函数内部的局部变量。
js链式作用域:子对象会一级一级向上寻找所有父对象的变量,反之不行。
2、闭包概念
能够读取其他函数内部变量的函数。
简单来说,就是在一个函数内部的函数,内部函数持有外部函数内变量的引用。
function outFun() {
var name = "javascript"; // 创建局部变量name和局部函数outName
function outName() { // outName()是函数内部方法,是一个闭包
console.log(name);
}
return outName; // outName被外部函数作为返回值返回了,返回的是一个闭包
}
var myFun = outFun(); // myFun = function outName(){}
myFun(); // javascript
3、闭包特点
(1)让外部访问函数内部变量成为可能;
(2)局部变量会常驻在内存中;
(3)可以避免使用全局变量,防止全局变量污染;
(4)闭包就是可以创建一个独立的环境,互不干扰
function add(x){
return function(y){
return x + y;
};
}
var addFun1 = add(1); // addFun1 = function(y){ return 1 + y; }
var addFun2 = add(2); // addFun2 = function(y){ return 2 + y; }
addFun1(2); // 1 + 2 = 3
addFun2(2); // 2 + 2 = 4
//add接受一个参数x,返回一个函数,它的参数是y,返回x+y
//addFun1和addFun2都是闭包。
缓存
假如有一个处理过程很耗时的函数对象,每次调用都会花费时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。
闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<!-- 错误写法 -->
<script>
var btns = document.querySelectorAll("button");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener('click',function () {
console.log("第" + i + "个按钮被点击了"); // 第5个按钮被点击了
})
}
</script>
<!-- 正确写法 -->
<script>
var btns = document.querySelectorAll("button");
for (var i = 0; i < btns.length; i++) {
(function (j) {
btns[j].addEventListener('click',function () {
console.log("第" + j + "个按钮被点击了");
})
})(i)
}
</script>
错误写法分析:
因为给每个按钮注册点击事件,所以事件未触发时,已经注册完毕,此时i=5,(i=4后,i++)。所以等到点击按钮时,传入console.log(i) 中的 i 是5。( var 没有 for 的块级作用域)
以前都是这样写的:给btn的父级注册事件,利用冒泡(即事件委派)来触发事件,不需要用到for。
正确写法分析:
(function(j){})(i) 其实是一个闭包,它利用函数的作用域形成一个闭包,又由于闭包具有缓存的特性。即将需要计算值存储起来,当调用这个函数的时候,先在缓存中查找,直接返回查找到的值。所以能找到0,1,2,3,4
4、闭包误解
闭包会造成内存泄露?
错。内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
为什么会有这个误解?
是因为 IE浏览器(IE6)垃圾回收机制有问题。导致我们使用完闭包之后,依然回收不了闭包里面引用的变量。所以是 IE浏览器 的bug,不是闭包的问题。
使用闭包的注意点:
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。