1

前言
JavaScript的变量类型是弱类型的,在特定的时间内保存一个特定的值,变量的值和数据类型可以在脚本的生命周期内随意改变。

1. 基本类型和引用类型的值

JavaScript包含两种不同类型的值:基本类型和引用类型。基本类型指的是简单的数据段,而引用类型指的是那些由多个值构成的对象。
基本数据类型Number,String,Boolean,Null,Undefined这五种基本类型数据是按值访问的,因为可以操作在变量中实际的值。

引用类型是保存到内存中的对象,不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象的时候,实际上操作的是对象的引用。所以,引用类型是按引用访问的。(但是在为对象添加属性的时候,实际上操作的是实际的对象)

  • 动态添加属性
    定义基本类型和引用类型值得方式是类似的,都是创建一个变量然后为该变量赋值。但是当值存入变量后,不同类型的值执行的操作则有些区别。先看下对引用类型的值得操作:
var person = new Object()
person.name = 'nicholas'
alert(person.name) //'nicholas'

如上,创建一个对象并保存到变量person中,然后为该对象添加一个name属性且赋值为“nicholas”,最后可以访问这个新属性。

假如我们给基本类型的值添加属性,如下:

var person = 'good job'
person.name = 'Nike'
alert(person.name) //'undefined'

虽然我们看到没有出现任何报错,但是这是不符合规范的。

  • 复制变量值
    上面说的是给基本类型和引用类型分别添加属性的区别。接下来,我们从一个变量向另外一个变量复制基本类型值和引用类型值看看有什么区别。

先看基本类型的,从一个变量向另外一个变量复制:

var a = 'China NO.1'
var b = a
alert(b) //'China NO.1'

如上,输出b的时候的值和a是一模一样的,但是两者是完全独立的。这是因为从a复制到b的时候,会在变量对象上创建一个新值,然后把这个值赋值给b

在看引用类型,当从一个变量向另一个变量复制引用类型值得时候,同样会将存在变量a中的值复制一份放到b中,但是不同的是,这个值得副本是一个指针,这个指针指向存在堆内存中的一个对象。当结束复制后,这两个变量实际上引用的是一个对象。

var a = new Object()
var b = a
a.name = 'nike'
alert(b.name) //'nike'

如上,因为a和b都是引用同一个堆内存中的地址,所以当给a添加一个name属性后,b也可以访问到这个属性。

  • 传递参数
    JavaScript中所有函数的参数都是按值传递的。也就是说把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。

向参数传递基本类型值得时候,被传递的值会被复制给一个局部变量。在向参数传递引用类型值得时候,会将这个值在内存中的引用地址复制给这个局部变量,因此这个局部变量的变化会反映在函数的外部,

先看一个基本类型值传递给函数参数的例子:

function add(num){
    num += 10
    return num
}

var count = 20
var result = add(count)
alert(count) //20
alert(result) //30

接下来用引用类型的值举个例子:

function setName(obj){
    obj.name = 'nike'
}
var person = new Object()
setName(person)
alert(person.name) //'nike'

上面的例子中,首先创建一个对象保存到person中,然后将person传到setName中,person的值会被复制给obj。所以obj现在和person引用的是同一个对象(即这个变量是按值传递的),obj也会按引用访问同一个对象。在来看一个例子:

function setName(obj){
    obj.name = 'nike'
    obj = new Object()
    obj.name = 'green'
}
var person = new Object()
setName(person)
alert(person.name)//'nike'

这个例子和上面的区别就是为obj又重新定义了一个对象,然后为这个新对象定义了一个不同值得name属性。如果person是按引用传递的,nameperson就会被修改为指向name属性值为’green‘的新对象。但是并没有这样,实际上当函数在内部重写obj的时候,这个比那里引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后销毁。

  • 检测类型
    typeof操作符是一个确定变量是String,Number,Boolean,Null,Undefined的最佳工具。
var s = 'abc' 
var b = true
var n = 22
var nu = null
var u = 'undefined'
typeof s //string
typeof b //boolean
typeof n //number
typeof nu //object
typeof u //undefined

还有一个instanceof操作符,通过它我们可以判断是什么类型的对象:

//格式
变量 instanceof constructor

如果constructor在该对象的原型链上,那么就会返回true。

2.执行环境和作用域

执行环境定义了变量或者函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之相关的变量对象变量对象中保存该执行环境中的所有变量和函数。在代码层面,我们是无法访问到这个变量对象,但是JS引擎在处理的时候会用到它。

全局执行环境是最外层的一个执行环境,在浏览器中,全局执行环境被认为是window对象,因为所有全局变量和函数都是作为window对象的属性和方法创建的。

注意:某个执行环境中所有代码执行完毕后,该执行环境会被销毁,保存在其中的所有变量和函数定义也会被销毁。全局环境是在应用退出后才被销毁。

每个函数都会有自己的执行环境,当执行流进入一个函数时,该执行环境被推入到一个环境栈中,在执行完毕后会弹出,将控制权返回给之前的执行环境,JavaScript中的执行流正是由这个机制控制。

当代码在一个执行环境中运行时,会创建变量对象的一个作用域链作用域链是保证对执行环境有权访问的所有变量和函数的有序访问。(注意这里说的有权访问)作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个执行环境是一个函数,则将其活动对象作为变量对象。活动对象在最开始的时候只包含一个arguments对象。作用域链的下一个变量对象是来自包含(外部)的执行环境。再下一个变量对象则来自于下一个包含环境,以此类推,最外层的变量对象来自于全局执行环境。

标识符的解析过程也就是沿着作用域链一级一级的搜索,直到找到标识符为止,否则就会发生错误。

var color = 'blue'

function changeColor() {
    if(color == 'blue'){
        color = 'red'
    }else {
        color = 'blue'
    }
}

changeColor()

如上,changeColor包含的作用域链包含它自己的变量对象和全局环境的变量对象,为什么能在函数内访问color,这就是通过作用域链找的。

3. JavaScript中的垃圾收集

在JavaScript中,执行环境会负责管理代码执行过程中使用的内存。当不再使用的变量,就会释放其占用的内存,垃圾收集器会按照固定的时间间隔去执行这个操作。

JavaScript中函数的局部变量只有在函数执行的过程中存在,在执行的过程中会为局部变量在栈或者堆内存中分配响应的空间,以便存储它们的值,然后在函数中使用这些变量,直至函数执行结束。

关于垃圾回收的方法有标记清除,引用计数这两个方法,对于其原理有兴趣的同学可以去研究研究~

总结:今天主要讲了基本数据类型和引用数据类型的区别;关于函数参数是值传递的证明;函数的执行环境,变量对象以及作用域链的形成。


fsrookie
2.9k 声望256 粉丝

目前很多文章都是摘抄记录其他教程。见谅。