var let const 的区别

  1. var:声明函数变量。函数外用var声明的变量会挂载在window下,不能跨函数访问,可以跨块访问。可以重复赋值但无法删除

    javascript
    1
    2
    3
    4
    5
    6
    7
    8
    function foo() {
    var a = 1; // 局部变量
    b = 2; // 全部变量
    }
    console.log(a); // 报错

    console.log(c) //undefined 预编译时变量声明被提升到之前所以不报错
    var c = 3;
  1. let:代码块({})内声明的变量只能在块内访问且不能跨块,跨函数访问。不能在同一个代码块中重复赋值

    javascript
    1
    2
    3
    4
    5
    6
    7
    8
    9
    if(1){
    var a = 1;
    }
    console.log(a) // 1

    if(1){
    let a = 1;
    }
    console.log(a) // 报错
  2. const:声明常量。声明时必须有赋值,不能修改值

    javascript
    1
    2
    3
    4
    5
    6
    7
    const obj = {
    a : 1,
    b : 2
    }
    obj.a = 2; // 不会报错
    console.log(a) // 2
    console.log(obj) // obj{a:1,b:2} obj不会发生改变

作用域

什么是作用域?

作用域的本质是运行环境:作用域对象

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
var a = 1;
var b = 2;
}

scope = {
a: 1,
b: 2
}

// scope 就是foo函数的作用域对象

foo() //执行完成 scope就会被销毁

垃圾回收机制

意义:防止内存溢出(泄露)

清除的核心时间: 离开作用域的时候

如何判断函数作用域需要清楚呢?

  1. 标记清除:变量进入环境的时候标记为进入, 代码运行离开这个环境的时候: 标记环境离开. 垃圾回收机制启动发现这个环境已经没人了, 就开始收拾, 释放环境(内存空间释放出来)
  2. 引用计数:对变量引用情况统计, 如果引用次数为0, 作用域的消失直接伴随着变量文档消失,每一个变量相当于一个灯塔, 如果外界有船还需要用到这个灯塔, 灯塔会继续存在, 直到所有船都回来,这个灯塔就会消失

全局作用域在页面关闭之后才会消失

闭包

先来看下面的代码思考main函数的作用域消失没有?

javascript
1
2
3
4
5
6
7
8
9
10
function main(){
let data = "data from main"
function private(){
console.log(data)
}
return private
}
// main函数执行会返回来自内部的private的函数
let privateFromMain = main()
privateFromMain()

上面的代码实际上将private传给了privateFromMain,所以在main执行完成后作用域还被保存了下来。

在上面的代码中在main执行完成后本该被清理的作用域被保留了下来了,所以闭包就是通过函数拥有作用域链的方式保留了一段本来应该销毁的作用域链。

作用

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 简单加法器的实现
function createAdd() {
var sum = 0
function add() {
return sum += 1
}
return add
}
let add1 = createAdd()
let add2 = createAdd()
// 代码核心: add1 和 add2 都有
// 自己单独的作用域链 能够用保存私有变量
add1() // 1
add1() // 2
add2() // 1
add2() // 2
add1() // 3

再来看下面这个场景

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
创建一个对象 o
o.getNumber() 一开始返回0
o.add() 执行之后 o.getNumber()会增加1
没有办法用其他方式修改 内部的值
*/

/*闭包之前的版本*/

var o = {
count: 0,
getNumber: function(){
return o.count
},
add: function(){
o.count++
}
}
// 这里可以通过o.count修改它的值

/*闭包版本*/

function createO() {
var count = 0
var o = {
add: function () {
count++
},
getNumber: function () {
return count
}
}
return o
}
let o = createO()

好处:将全局变量变成一个私有变量 => function,单独保留了访问, 限制了修改

坏处:内存溢出

循环绑定问题

javascript
1
2
3
4
5
6
7
8
// 对元素点击打印序数
let liList = document.querySelectorAll(".list>li")
for(var i =0;i<liList.length;i++){
liList[i].onclick = () => {
console.log(m) // 点击任何元素都打印 3
})()
}

这里如果不用let解决,那么还有什么其他解决方法来实现呢

解决问题的核心:将全局的i转化成局部的(传参的方式),然后将函数立即执行

javascript
1
2
3
4
5
6
7
8
for(var i = 0; i < liList; i++){
(function (current){
liList[current].onclick = function(){
console.log(current)
}
})(i)
}
// 这里传参的方式其实是将实参赋值给了current 形成了函数内的局部变量,从而将i保留了下来

目前这个问题一律用let声明i来解决了

闭包思考题

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Q1

function F1() {
var a = 100;
return function () {
console.log(a);
}
}
var f1 = F1()
function F2(fn) {
var foo = fn
var a = 200;
foo();
}

F2(f1) // ?
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Q2
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m, n);
}
}
}
var a = fun(0); // ?
a.fun(1); // ?
a.fun(2); // ?
a.fun(3); // ?
var b = fun(0).fun(1).fun(2).fun(3); // ?
var c = fun(0).fun(1) //?
c.fun(2); // ?
c.fun(3); // ?

闭包在开发环境中的使用

需要将商品列表中的数据按不同的字段升序降序排列该怎样做?

javascript
1
2
3
4
5
6
7
8
9
let order = (mode,type = "asc") => {
return (a,b) => {
if(type == "asc") return a[mode] - b[mode]
if(type == "desc") return b[mode] - a[mode]
}
}
let newData = data.sort(order("price","asc"))

// 这里得到的newData就是我们所需要的数据