3.3 变量

3.3 变量

可以保存任意类型值

var, const, let 声明变量

const与let只能在es6以及更新的版本才能用

3.3.1 var

variable 的缩写

var message;

不进行初始化时默认保存undefined

var message;
console.log(message);    // undefined

声明时进行初始化

var message = "hello world";

作用域:
函数作用域, 非块作用域

function myFunc(){
    var message = "hello world";
}
myFunc();
console.log(message);       // 报错

省略关键字直接定义变量, 会导致这个变量成为一个全局变量

function myFunc() {
    message = "hello world";    // 全局变量
}

myFunc();   // 只要调用一次这个函数就可以在函数外访问到
console.log(message);   // hello world 

不推荐这样使用, 很难维护, 突然冒出来的一个变量都不知道是干啥的
严格模式下这样会报错

定义多个变量

var message1 = "hi",
    message2 = "hello world",
    message3;

换行与空格缩进非必须, 但是如同上面的形式便于阅读

var声明提升问题:
var声明的变量会提升为在函数作用域的顶部声明(不包括赋值, 也就说如果有赋值则在原来位置上赋值)

console.log(message);   // undefined
var message = "hi";
console.log(message);   // hi

上面这段代码相当于下面:

var message;
console.log(message);   // undefined
message = "hi";
console.log(message);   // hi

提升会将所有变量声明都提升到函数作用域的顶部

使用var反复声明同一个变量不会出错

function test() {
    var message = 1;
    var message = "hi";
    var message;
    var message = true;
    console.log(message);
}

test(); // true

如上段代码可以理解为先提升, 然后有四个message变量的var声明, 合并为一个, 所以不会报错

在全局作用域声明的变量会成为window对象的属性

var message = 'hi';
console.log(window.message);    // hi

3.3.2 let

let声明的变量是块作用域

{
    let message = "hi";
    console.log(message);   // hi
}
console.log(message);   // 报错

块作用域是函数作用域的子集, 所以let声明的变量离开他所在函数时也无法使用

function test() {
    let message = "hi";
}
console.log(message);   // 报错, message is not defined

let不允许在同一作用域出现冗余声明

{
    let message;
    let message;    // 报错
}

// 嵌套不会受影响
{
    let message;
    {
        let message;    // 可以
    }
}

// 不受关键词影响
{
    var message;
    let message;    // 报错
}

暂时性死区

let 声明的变量不会在作用域中提升

{
    console.log(message);   // 报错
    let message = "hi"; 
}

但是js引擎会注意到出现在块后面的let声明, 不过在let声明执行之前, 都无法引用未声明的变量, 这称为暂时性死区

可以理解为实际上有提升(目前大多数都这样认为), 但是由于无法在原let声明出现前引用, 没有形式上如同var一样的提升, 所以看作没有提升


全局声明

let在全局作用域中声明的变量不会成为window对象的属性

var message1 = "hi";
let message2 = "hello world";
console.log(window.message1);   // hi
console.log(window.message2);   // undefined

不过,let声明仍然是在全局作用域中发生的,
相应变量会在页面的生命周期内存续。
因此,为了避免SyntaxError,必须确保页面不会重复声明同一个变量。


** 条件声明 **
同一作用域用多次用var声明同一个变量不会出问题,
但是对于let你无法检查前面是否已经使用let声明过同名变量

不信你试试

所以let无法依赖于条件声明模式

for循环中的let声明

let解决了循环定义的迭代变量渗透到循环体外部的问题

// var
for(var i = 1; i <= 5; i++) {
    // 循环逻辑
}
console.log(i); // 5, 循环中定义的迭代变量i渗透到循环体外

// let
for(let j = 1; j <= 5; j++){
    // 循环逻辑
}
console.log(j); // 报错, j没有定义, 解决了var的渗透问题

var迭代变量问题:

for(var i = 0; i< 5; i++){
    setTimeout(()=>console.log(i), 0);
}
// 实际输出的是5,5,5,5,5

这里注意, js是单线程的, 先执行i=0并判断了他比5小后, 开始执行第一次循环体中的内容, 但是这里发现是setTimeout, 而setTimeout是异步的, 于是将他放入一个任务队列, 等到当前的同步任务执行完后在执行这个队列里的任务, 这样就导致了将循环语句执行完了但是把五个setTimeout全加到队列里没有执行, 而最后i=5时循环体不再继续, 这时这个队列里有五个setTimeout函数, 但此时都是公用一个i=5, 就导致了输出了5个5, 这里虽然setTimeout设置的时间为0, 事件本身没有延迟,但是改变了任务执行的先后顺序,执行的先后顺序又导致了微小的延迟(执行其他语句的延迟而非专门等了多少毫秒才执行setTimeout的设置的延迟)
具体可以看看下面这个例子

// 1
alert(1);//第一个输出

// 2
setTimeout(function() {
    alert(2);   //待同步任务执行完毕之后才输出
},0);   //虽然设置的是0

// 3 
alert(3);//第二个输出

// 2本身没有延迟但是由于顺序改变了, 要先执行完3才能执行2, 这里有一段微小延迟, 是执行顺序引起的而非函数中延迟参数设置引起的

而let可以解决上述问题

使用let时, js引擎在后台会为每个迭代循环声明一个新的迭代变量(虽然都叫i但是相当于在不同的块中)
每个setTimeout引用的都是不同的变量实例

for(let i = 0; i < 5; i++){
    setTimeout(()=>console.log(i), 0);
}

这种每次迭代声明一个独立变量实例的行为适用于所有风格的for循环,
包括for-in和for-of循环

3.3.3 const

const与let基本相同

区别是: 声明时必须同时初始化, 且后续再尝试修改const声明的变量会导致错误

const message1 = 'hi';
console.log(message1);  // hi

// const声明的变量的作用域也是块
{
    const message2 = 'hello';
    console.log(message2);  // hello
}

try{
    console.log(message2);  
}
catch(err){
    console.log('something wrong about message2')
}
// something wrong about message2

// 必须声明时初始化
const message3; // 报错

const声明不可修改的限制只限定于你指定的变量的引用, 也就是你不可以再更改去引用另一个对象, 但是你引用的对象的引用其实是可以更改的

let person1 = {
    name = "zhangsan",
    age = 18
}
let person2 = {
    name = "lisi",
    age = 20
}

const student1 = person1;
student1 = person2; // wrong
person1.age+=1; // ok

注意你给你声明的const变量赋的是一个变量还是一个值

let person = {
    name: 'zhangsan',
    age: 18
}

const student1_age = person.age;
// 这里person.age其实是一个值而非一个变量, 相当于const student1_age = 18, 与person.age没有引用关系
// 所以修改person.age不会引起因为是const声明的变量所以无法修改的错误

person.age = 19;    // ok
console.log(student1_age);  // 18
console.log(person.age);    // 19

使用const声明一个不会被修改的for循环变量, 也就是说每次迭代只是创建一个新的变量, 这对for-in和for-of循环特别有意义

// 1
for(const key in {a:1, b:2}) {
    console.log(key);
}
// a, b


// 2
for(const value of [1,2,3,4,5]) {
    console.log(value);
}
// 1,2,3,4,5


// 3
for(const i = 0; i < 5; i++){
    //something
}
// 报错

其实看到这里疑惑大大的, 但是想了会, 这里说说我的看法
对于上述代码片段3, 执行了5次循环体, 这五次执行使用的作用域应该是一个块, 而对于每次执行, 都再使用另一个块执行逻辑体并传入当时i的值, 也就是一个()对应五个{},对于使用let的循环体参数i, 自然没有影响, 但是使用const会导致i自增, 而const声明的变量不可以再修改, 而报错
但是对于for-of与for-in我还是解释不通, 除非再将()分作几个作用域, 对片段1来说{a:1, b:2}相当于将()再分为两个块, 那么这俩虽然名字都叫i但是也不会引起冲突

3.3.4 声明风格及最佳实践

  1. 不使用var

使用let与const代替var

  1. const优先, let次之
    使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。

因此,很多开发者认为应该优先使用const来声明变量,只在提前知道未来会有修改时,再使用let。

这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为

当然typescript更好的解决了这些问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BetterChinglish

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值