引用类型、块级作用域、var-let-const、词法作用域、作用域链

77 阅读4分钟

引用类型

  • 在JS中有八种数据类型,除Object外,其它都属于基本数据类型 primitive type

基本数据类型有 : String、Number、bigint、null、undefined、Symbol、Boolean

  • 原始类型存储的是值,复制的是值
  • 引用类型存储的是地址,复制的是地址,修改的是地址指向的内容

上面代码,a、b均为原始类型,因此当赋值给b的a值发生变动时,b并不改变

arr1、arr2是引用类型,当 arr2 = arr1,是将arr1的地址赋值给arr2,因此当arr1的地址引用的数组的值发生改变时,arr2同时发生改变

参数传递

        function num(n){			//等价于 let n = a
            n++
            return n
        }
        let a = 1
        num(a)
        console.log(a); 			//打印出1

上面代码中,num(a) 相当于把 a 赋值给 n,把a的值赋给n,因为 a 是 Number原始类型,所以a的值并没有变

        function incArr(arr){			//等价于 let arr = arr1
            arr[0]++
            return arr[0]
        }
        let arr1 = [0, 2, 5, 34]
        console.log(incArr(arr1));			//打印出1
        console.log(arr1);							//打印出[1, 2, 5, 34]

上面函数,把arr1 的地址赋值给 arr,因此当函数改变了该地址的值,arr1也发生了改变

  • 函数的形参如果是引用类型(Objet、Array、Function...),操作形参的内容就是操作实参的内容
  • 函数的形参如果是原始类型,修改后需return出去

块级作用域

  • let、const存在块级作用域
  • var 没有块级作用域
        let a = 3
        var b = 4
        {
            let a = 33				//在{}块级作用域内更改变了值,因为let有块级作用域,所以更改只在作用域中生效
            var b = 44				//var没有块级作用域,所以更改作用于全局
        }
        console.log(`a为${a},b为${b}`);			//输出a3b44

let、var 和 const 的区别

  • var

1.var是ES3定义变量的方式

2.在作用域内声明前置

        a = 3
        var a 
        console.log(a);				//打印出a的值

3.可重复声明

        a = 3
        var a 				//等价于var a; a = 3;
        a = 4
        var a = 44
        console.log(a);			//打印44

4.没有块级作用域

  • let

1.let 和 const 是ES6定义变量的方式

2.先声明再使用(无前置声明)

        c = 3					
        let c					//报错,在声明前使用,报错信息:Uncaught ReferenceError: can't access lexical declaration 'c' before initialization
        let c = 4			//报错,重复声明,报错信息:Uncaught SyntaxError: redeclaration of let c
				console.log(c);

3.不可重复声明

4.有块级作用域

  • const

1.同let

2.声明时需要立即初始化

3.初始化后不能修改

const a;				//报错,声明必须同时初始化; Uncaught SyntaxError: missing = in const declaration
const b = 6;
b = 7;					//报错,初始化后不能再修改; Uncaught TypeError: invalid assignment to const 'b'

4.对于引用类型,存储的是地址,只要地址没变,就不会报错

        const a = 3
        a = 2						//报错,报错信息:Uncaught TypeError: invalid assignment to const 'a'
        const b = []
        b.z = 2
        b[1] = 22				//不报错,嘻嘻
  • 建议用let或const,不建议用var

function声明前置

        fun(12,34)						//运行输出函数结果
        function fun(a, b){
            console.log(`${a}哈哈${b}`);
        }

上面代码说明函数也有声明前置

        fun(12,34)			//报错,报错信息为Uncaught ReferenceError: can't access lexical declaration 'fun' before initialization
        const fun = function(a, b){
            console.log(`${a}哈哈${b}`);
        }

上面代码用const声明了一个变量,赋值函数,const需要在声明同时初始化,因此报错

作用域

  • 作用域是值和表达式能被访问的执行上下文
  • 如果值和表达式不在当前作用域,就无法访问
  • 作用域有层级,子作用域可访问祖先作用域变量

作用域种类有:

  • 全局作用域
  • 函数作用域
  • 块级作用域 {}
  • 模块作用域 :模块模式中运行代码的作用域

词法作用域:创建函数所在的作用域

作用域链

  • 当需要访问某个变量的时候,先从自己作用域查找
  • 如果找不到,再从创建当前函数所在的作用域(词法作用域)去找,以此往上,直到全局作用域
        let a = 1
        function fn(){
            let a = 0
            log()
        }
        function log(){
            console.log(a);
        }
        fn()			//这里,bar函数所在的作用域是全局作用域,因此输出a为1
        let a = 1
        f()						//函数会声明前置,因此可以先调用,再声明
        function f(){
            let a = 11
            bar()
            function bar(){
                console.log(a);				//这里,bar函数所在的作用域是f作用域,因此输出a为11
            }
        }
  • TDZ:const 和 let 变量在当前作用域的开头到当前声明该变量之间的区域属于暂时性死区,不可访问该变量(如果出现var 需关注 var 声明前置)
        function fn(){
            console.log(a);
        }
        fn()
        var a = 1
它的执行顺序是这样的:
        var a 
        function fn(){
            console.log(a);
        }
        fn()
        a = 1
因为 var 有声明前置,当执行 fn 的时候尚未给a赋值,因此打印出undefined
        let a = 0
        function fn(){
            bar()
            let a = 1
            function bar(){
                console.log(a);
            }
        }
        fn()
因为 let 有块级作用域,因此log只能在函数内找 a ,但因为bar的执行在let a之前,所以会报错
报错信息:Uncaught ReferenceError: can't access lexical declaration 'a' before initialization