js中的变量

js中的变量定义是松散型的。定义变量时并不要求确定变量的数据类型,只有在使用的时候才去确认变量是什么类型。出现这个问题的本质原因是,变量只是保存某一个值的名字,这个值是可以随时改变的,而且可以随便改变数据类型。

数据类型

  • 基本数据类型

    Boolean、Number、String、Undefined、Null

    储存的都是简单的数据,访问数据时都是按照值进行访问的。

  • 引用类型

    值是存在内存中的,访问时要按照内存的地址去访问。

变量

  • 创建变量

    使用关键字 var 后面跟上变量名字

    创建过程:(js解释器中)

    1. js在整个解释器中创建一个名字来保存后面要使用的变量。
    2. 同时设置这个变量的作用范围(作用域)即创建变量时所在的函数。
    3. 创建变量的过程都会在预处理阶段被提升到函数的最顶部
  • 变量赋值

    创建变量并且给变量赋值的时候,解释器首先要看下你赋的值是什么类型的。

    基本类型:解释器就会把你所要赋的值保存到变量当中。

    引用类型:解释器会把值保存到内存当中,而把内存的地址 (引用)保存到变量中。

  • 变量的操作

    1. 属性和方法的增删

      只有对象才有属性和方法

      基础类型:增加属性和方法不报错,也没有效果。

      引用类型:可以对属性和方法增删

    2. 变量的修改

      基础类型:相当于把整个变量销毁掉,然后再给变量重新的赋值。

      引用类型:对这个变量本身进行赋值的操作,相当于重新创建一个变量。

      对这个变量上某一个属性进行修改时,解释器会顺着变量所保存的内存地址,找到计算机当中的这块内存,把里面的值修改掉。

    3. 变量的复制

      基础类型:相当于创建一个新的变量,和被复制的变量之间是相互独立的。

      引用类型:只是复制了指向内存中数据的地址。指向的是同一个对象,修改属性会相互影响。

变量在函数参数中的使用

函数

js中函数的用途是封装多条语句,使其可以在多处调用。

创建函数:function name (参数) { 函数体}

函数返回值:所有函数都有返回值。函数运行时碰到return时,返回return后面表达式的值。没有return就返回undefined。

函数中的参数

作用:方便使用

  1. js的解释器并不关心函数传进来的参数是什么类型,而只是在使用的时候动态的验证参数的类型。

    // 参数可以是数字相加、字符串拼接、数字和字符串混合运算
    function add(a,b){
        return a+b
    }
    
  2. 也不关心参数数量。

    add(1,2) // 3
    add(1,2,3) // 3 只计算前两个
    add(1) // 1+ undefined = NaN
    // 同上面的函数
    function add(){
        return arguments[0] + arguments[1] 
        // 第一个参数和第二个参数相加
    }
    

    函数的参数在js的解释器中是一个数组对象,当你去访问参数的时候实际上是访问参数数组对象的值。当访问的参数数组对象的值不存在的时候就是会返回undefined

变量的访问:

基础数据类型:按值进行访问

引用数据类型:按引用进行访问

传递参数

ECMAScript 中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。

1.函数外面作为参数传递给函数的变量是基础数据类型,值是保存在变量当中的。相当于把这个变量的值复制一份出来,交给函数参数。函数的参数是局部变量,作用范围是这个函数。此时函数内外两个变量是相互独立互不影响的。

2.函数外面作为参数传递给函数的变量是引用数据类型

首先函数外面的变量是全局变量(或作用范围是函数外层),复制出来的参数是局部变量,作用范围是函数内部。在这个过程和复制引用类型是完全相同的。虽然说函数外面的变量和复制出来的参数他们两个相互独立。但他们两个当中都保存着内存当中同一个位置的引用。在这个时候如果对参数进行修改,内存当中的对象也会被修改,而当你再次通过函数外面的变量进行访问的时候,访问的还是内存当中的对象,这时你得到的就是一个被修改的值了。

函数的参数的本质是函数里面的局部变量。当你把一个变量当作参数传递给函数的时候,这个局部变量它保存的是和外部的变量相同的内存区域,当你对局部变量进行修改的时候有两种情况:

第一种情况,是对局部变量上的某一个属性进行修改。在这个过程当中,解释器首先要访问这个局部变量,然后发现它是一个引用类型,然后根据引用找到了内存当中的值,然后把值进行修改。这个修改对外部的变量它所保存的引用没有任何影响。外部变量根据引用去访问内存中的值的到的是被修改的值。

第二种情况,直接对局部变量本身进行修改。这个时候,函数参数指向的就不是原来的内存,变成了新的局部变量,他的作用范围就是函数的作用范围。而且在这个局部对象会在函数执行完毕后被销毁掉。

变量的类型检测

  • typeof 操作符

    typeof 操作符是用来区分变量是基本类型还是对象的一种方式。

    一共返回6种数据类型:string、 布尔、number、 undefined 、object 和function

    无法区分是那种对象。

  • instanceof操作符 (二元操作符)

    左面是要检测的对象,右面这个对象要检测的数据类型,返回布尔值。

    alert(person instanceof Object); // 变量 person 是 Object 吗?
    alert(colors instanceof Array); // 变量 colors 是 Array 吗?
    alert(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗?
    

    对基础类型检测永远返回false

可以先用typeof检测如果是对象后用 instanceof 得到是什么对象。

作用域

编程语言最基本的功能 存储 运算是主要功能。

计算机需要按一定规则对值进行存储,进行值的操作时也要按一定规则进行读取。计算机对值进行存储和读取的这套规则就叫作用域。

当你需要把值进行保存的时候,这个值的保存肯定需要一定的范围。如果这个范围是以函数为单位的话,那么这个作用域就叫函数作用域。如果说这个范围是以模块为单位的话,就叫块级作用域。单位越小重复的概率越小,当然活动范围越小。

客人住酒店,酒店》楼层》房间》卫生间

作用域是可以进行相互嵌套的。一个作用域可以包含多个作用域。

js这门编程语言的本质是编译语言,编译语言需要两个过程:

  • 编译

    创建作用域生成执行代码

    • 词法语法分析

      js语言是由6种数据类型,20条流程控制语句以及一堆运算符组成,此过程识别这些

    • 代码生成

    js的编译是在js解释器中进行的。js的编译时间是非常有限的。js的解释器在编译的过程当中使用了大量的技巧来进行优化提升编译速度。

    var a = 123
    //编译过程:
    /*
    1.打开浏览器加载js,创建全局作用域(建立酒店)
    2.词法语法分析阶段,识别语句、运算符,数据类型
    识别到一个声明语句 var a 需要声明一个变量a 这个时候解释器回去询问当前作用域是否有a这个变量存在,如果有忽略这个声明,如果没有创建变量a并且分配一定存储空间。(接到一个客人让客人入住酒店)这个就是编译的过程。
    编译完会生成要运行的代码。
    变量声明会提前的原因就是因为有了编译过程。
    */
    function a(){
        console.log('a')
    }
    /*
    如果编译过程中发现申明的是函数,同声明变量类似把函数声明提到最顶,同时还会创建一个新的作用域(在酒店里建了一个房间)
    */
    // 当同一个作用域下声明了相同名字的函数和变量,函数会覆盖变量。
    // 两个独立作用域可以嵌套不能重叠。
    

    with语句 和event函数可以自定义作用域,影响效率,因它破坏了解释器去创建和管理作用域的规则。自定义的作用域解释器为避免错误在编译的过程中不会去做优化。

  • 执行

    解释器在编译阶段会创建作用域并且生产可执行代码,生成的可执行代码在运行阶段会创建作用域链。

    作用域是按照一定的规则对变量或者函数进行存储,作用域链的作用是对作用域中的变量和函数进行访问。**作用域是存储的规则,作用域链是访问的规则。**代码在运行时每次进去到函数当中都会创建一个作用域链。这个作用域链,链接了它和它上层的作用域。当代码执行过程当中,如果遇到了变量它会在它当前的作用域中查找,如果找到了就进行相关的操作。如果没找到就会顺着作用域链向上进行查找一直找到全局作用域。全局作用域上也没有就在全局作用域上创建一个属性。只能在父级查找,不能在同级查找。

    卫生间》房间》楼层》酒店》

    作用域是js解释器在编译阶段进行创建的。它的作用是对当前的变量和函数进行保存,而且限定了函数和变量的使用范围

    作用域链 是在解释器在编译完成后在函数运行过程当中去创建的,作用1是把作用域链接起来。作用2当你访问函数或者变量的时候按照作用域链依此进行访问。js每进入到一个函数当中就会触发一次编译,编译完成后立即执行。js只有函数作用域没有块级作用域。

    想使用块级作用域: 把块做成函数后让他立即执行或者使用ES6

内存

把一个变量设置为null,js垃圾回收机制回自动清理掉变量释放内存。

总结:

作用域:计算机对值进行保存和读取的这套规则

js的程序在运行的时候需要经历两个步骤:

  • 编译

    • 词法语法分析

      创建作用域,把整个代码中需要的函数和变量按照作用域进行保存限定他们的使用范围

    • 代码生成

  • 执行

    创建作用域链,对之前函数和变量进行访问的一个链式的规则。作用域链接了在编译过程中创建的作用域,而且提供了对作用域当中各种函数和变量的访问规则。当你对一个变量进行操作的时候,它首先会在当前的作用域下查找这个变量,如果找到就进行对应的操作,如果没找到就顺着作用链去查找它上层的作用域。

js的解释器为了保证内存的合理使用,提供了一个垃圾回收机制。他会一段时间运行一次,当他运行时如果发现你有哪些函数或者变量不需要使用了就会把这些东西清除掉。也可手动去管理这些变量,把变量的值设置为null