1

基础概念

  • 变量是存储信息的容器,这里需要区分一下:变量不是指存储的信息本身,而是指这个用于存储信息的容器,可以把变量想象成一个个用来装东西的纸箱子
  • 变量需要声明,并且建议在声明的同时进行初始化,如下所示:

    var aa = '1';
    let bb = 2;
    const CC = 3;
  • 如果重新声明变量,该变量的值不会丢失

    var aa = 'cc';
    var aa; //此时的值还是cc
  • 变量名(标志符)命名要求:

    • 适用于变量/属性/函数/函数参数的名字等
    • 可由字母、数字、下划线_及美元符$组成
    • 不可以使用关键字、保留字以及truefalsenull、(evalarguments严格模式下)
    • 使用undefined虽然不会报错,但是并不能声明成功,无论给它初始化为什么,它的值仍然为undefined
    • 最好使用字母开头(还可以使用_$

值类型

  • 变量可能包含两种不同数据类型的值:基本类型值(简单的数据段)和引用类型值(可能由多个值构成的对象)
  • 因为所有引用类型的值都是Object的实例,所以可以使用 xx instanceof Object 来判断是否为引用类型的值,如果是则会返回true,否则返回false
  • 存储方式

    • 基本类型值在内存中占据固定大小空间,因此被保存在栈内存中(栈由操作系统自动分配释放);
    • 引用类型值是对象,保存在堆内存中;(堆:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收)
  • 访问方式

    • 对于基本类型,变量存储的是具体的值,可以按值访问,直接操作存储的值
    • 对于引用类型,变量存储的是对象引用地址(指针),是按引用访问,当查询时,我们需要先从栈中读取存储的内存地址,然后再顺藤摸瓜地找到保存在堆内存中的值
  • 复制变量值

    • 复制时,基本类型拷贝的是具体的值,而引用类型拷贝的是引用地址,所以引用类型赋值时要格外注意!!!
    //基本类型赋值
    var num1 = 10;
    var num2 = num1;
    console.log(num1,num2);// 10, 10
    num2 = 100;
    console.log(num1,num2);// 10, 100
    
    //引用类型赋值
    //person2拷贝了person1的引用地址,所以person1和person2实际上指向的是同一个对象
    //所以改变person2会影响到person1
    var person1 = {
        name:'cc'
    };
    var person2 = person1;
    console.log(person1.name, person2.name);// cc, cc
    person2.name = 'cshine';
    console.log(person1.name, person2.name);// cshine, cshine
    
    var arr1 = [1,2,3];
    var arr2 = arr1;
    console.log(arr1, arr2);// [1, 2, 3], [1, 2, 3]
    arr2[1]= 'string';
    console.log(arr1, arr2);// [1, "string", 3], [1, "string", 3]
  • 传递参数

    • ECMAScript中所有的函数的参数都是按值传递,即使是引用类型值的传递也是按值传递
    • 传递基本类型值时,被传递的值会被复制给一个局部变量(即命名参数或者arguments中的一个元素)
    • 传递引用类型值时,引用类型值是一个引用地址(指针),它也会复制给一个局部变量,这个局部变量再根据这个地址去按引用访问对象,因此这个局部变量的变化会反映在函数的外部。所以还是按值传递,而不是因为它是按引用传递才导致局部变量的变化会反映在函数的外部

注意点

  • es6新增了letconst命令用来声明变量

    • let命令,它的用法类似于var,但是所声明的变量,只在let命令所在的块级作用域内有效,且在该块级作用域内不可重复声明
    • const声明一个只读的常量,一旦声明,常量的值就不能改变,与let一样,只在声明所在的块级作用域内有效,且在该块级作用域内不可重复声明
    • const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于引用类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个引用地址(指针),const只能保证这个引用地址(指针)是固定的,至于它指向的堆内存中的存储的值是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
  • 未使用var/let/const进行声明,此时创建的变量为全局变量,尽量不要这样做!
  • var命令和function命令声明的全局变量,是顶层对象的属性,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性(浏览器中为windownode中为global
  • 对于var/let来说,只声明而未初始化,此时变量存储的信息是“空”的,也就是undefined;但是对于const来说,只声明而不进行初始化是不允许的,会抛出Missing initializer in const declaration的错误,因为const定义的是常量,一旦定义便不可更改。

声明提升(hoisting

  • var命令声明的变量会发生”声明提升“现象,即变量可以在声明之前使用,值为undefined

    console.log(aa);// undefined
    var aa = 'string';
    console.log(aa);// string
  • function声明的函数也会发生”声明提升“现象,函数在声明之前可以使用,值为指向该函数的指针;但是如果是在支持ES6的环境中,ES6允许在块级作用域内声明函数,此时在块级作用域中声明的函数,会提升到所在的块级作用域的头部,值为整个函数块,同时还会提升到块级作用域外,但是值为undefined。(环境导致块级作用域内声明函数的行为差异非常大,所以应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句)

    console.log(fn);// ƒ fn(){}
    function fn(){}
    console.log(fn);// ƒ fn(){}
    
    //支持es6的环境中
    console.log(fb);// undefined
    if(true){
        console.log(fb); // ƒ fb(){}
        function fb(){}
    }
    console.log(fb);// ƒ fb(){}
    
    console.log(fc);// undefined
    if(false){
        console.log(fc); // 未执行
        function fc(){}
    }
    console.log(fc);// undefined
    fc() //Uncaught TypeError: fc is not a function
  • 函数和变量都会声明提升,此时若函数名和变量名同名,函数名的优先级要高;但是正式执行代码时,同名函数会覆盖只声明却未赋值的变量,但是它不能覆盖声明且赋值的变量(原因分析可见另一篇文章JavaScript基础系列---执行环境与作用域链)

    console.log(bb);// ƒ bb(){}
    var bb;
    function bb(){}
    console.log(bb);// ƒ bb(){}
    
    
    console.log(cc);// ƒ cc(){}
    var cc = 'string';
    function cc(){}
    console.log(c)c;// string
  • 局部变量/函数(函数作用域function块里面的变量/函数)也会声明提升,可以先使用后声明,不影响外部同名变量/函数
  • letconst命令改变了语法行为,它们所声明的变量一定要在声明后使用,否则报错。由于letconst命令在当前块级作用域内不可重复声明所以当有同名函数时,会直接报错Identifier xx has already been declared

参考资料


Cshine
169 声望5 粉丝

前端魔法修炼ing