前言
JS中变量是弱类型,也就是说它的值和类型可以在脚本的生命周期内改变,这时一个强大的特性,但是使用起来也非常容易出现问题。JS中的数据分为基本类型和引用类型,Undefined、Null、Boolean、Number、String这五种就是基本类型,它们都是按值访问的变量,可以操作保存在变量中的实际值;引用类型的值保存在堆内存中的对象里,引用类型在实际操作的是对象引用。
动态属性
对于引用类型的值可以为其添加属性和方法,也可以改变和删除其属性和方法。
var hello = new Object()
hello.id = 10
hello.name = "zhangsan"
hello.print = function() {
alert(this.id + " " + this.name)
}
hello.print() // 10 zhangsan
delete hello.id
alert(hello.id) // undefined
对于值类型如果也添加属性和方法,这样做并不会出现错误,但是真正操作的时候无法访问到添加的属性和方法。
var hello = "Hello"
hello.id = 10
hello.name = "zhangsan"
hello.print = function() {
alert(this.id + " " + this.name)
}
alert(hello.print) // undefined
alert(hello.id) // undefined
typeof操作符能够监测变量的类型,对于任意的Object引用对象都会返回object,JS为了判定引用对象的具体类型使用instanceof操作符,如果用它来测试基本类型直接返回false,对于引用类型会遍历原型链来做识别。
执行上下文和作用域
每个函数都有自己的执行上下文(Execution Context)对象,每个执行上下文对象又包含一个变量对象VO(Variable Object),这个对象专门用来存储执行上下文中的定义的变量、函数和参数,用户编写的代码无法访问这个对象,但解析器在执行过程中会使用它。当执行流执行到一个函数的时候,会创建变量对象的一个作用域链(Scope Chain);如果执行上下文对象是函数,那么函数的活动对象(Active Object)作为变量对象;作用域链下一个变量对象来自包含它的执行上下文变量对象,下一个来自于下一个的执行上下文变量对象,一直延续到最后一个执行上下文的变量对象。
var color = "red"
function print() {
alert(color)
var color = "green"
alert(color)
num = 100
alert(num)
}
print() // undefined green 100
alert(num) // 100
使用var声明的变量会自动加入到最接近的环境中去,如果声明时没有var关键字会自动被添加到全局上下文中。var声明变量并且赋值其实是两个步骤,一个是声明另外一个是赋值,声明会被提前到函数开始的地方,赋值操作依然保持在原来的位置,print函数执行的时候它的变量对象VO里包含color的声明,但是还未赋值,这时alert结果就是undefined,之后做了赋值操作结果就是green了,num未使用var声明也就不会加入到print函数的执行环境中而是直接加入到了全局执行环境的变量对象中,后面在全局访问的时候依然能够获取。
从上面的例子可以看出函数的变量对象VO是在函数执行之前初始化的,这也就是为什么在函数中声明的变量和函数会被提前。其实它的声明过程还是分步骤的,主要有三个步骤:声明参数、声明函数、声明变量,其中函数的声明会自动覆盖变量的声明。
function print(num) {
alert(num) // undefined
alert(count) // function count() { return 200 }
var count = 100 // 这个变量声明和函数count声明交换位置结果也不会变化
function count() {
return 200;
}
alert(count) // 100
alert(count()) // 什么都没展示,表明count是一个数值而不是函数
alert(add) // undefined
var add = function(x, y) {
return x + y;
}
alert(add) // fuction(x, y) { return x + y; }
}
print()
上面的print函数在执行之前先初始化变量对象VO,VO中声明了参数num,函数count和变量count,但是函数count覆盖了变量count的声明,所以最开始查看count的值是函数声明,接着count=100执行了赋值操作将count从function类型变成了number类型,后面再调用count因为它不是函数无法被调用。特别需要注意的是函数变量和函数声明是不同的,上面的add是一个变量而不是函数声明,最开始的时候它的值就是undefined。
没有块级作用域
前面提到过除了函数有执行上下文,其他的if、switch、for、while语句块都没有执行上下文,这些语句块中声明的变量都会被加入到最近的执行上下变量对象中,这样它们之后的代码块依然能够访问之前的变量,也就说块级作用域并不存在。
for (var i = 0; i < 10; i++) {
}
alert(i) // 10
if (i == 10) {
var hello = "Hello World"
}
alert(hello) // Hello World
内存管理
JS中常用的内存垃圾回收主要包括标记清理和引用计数管理,不过引用计数管理会因为循环引用导致对象无法被回收,为了避免这种情况需要在不需要对象的时候将它的引用赋值为空,这样垃圾收集器就能够正确的回收它们了。