javascript变量、作用域和内存问题

基本类型和引用类型的值

ECMAScript变量可能包含两种不同数据类型的值:

1.基本类型值:指简单的数据段。(5种基本数据类型:Undefined、Null、Boolean、Number和String,这5种基本数据类型是按值访问的,可以直接操作保存在变量中的实际的值)

2.引用类型值:指那些可能由多个值构成的对象。(引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript不允许直接访问内存中的位置,不能直接操作对象的内存空间)

动态属性

定义基本类型值和引用类型值的方式都是创建一个变量并为该变量赋值。

对于引用变量的值,可以添加其属性和方法,也可以改变和删除其属性和方法:

var person = new Object(); // 创建一个对象并保存在变量person中
person.name = "benjamin"; // 给person对象添加一个name的属性,并将字符串值"benjamin"赋值给这个属性
console.log(person.name); // 通过点运算符访问这个属性,结果输出"benjamin"

如果对象不被销毁或这个属性不被删除,这个属性将会一直存在。

而对于基本类型的值,则不能添加属性,虽然不会导致任何错误:

var person = "susan"; // 创建一个变量person赋值基本类型字符串值"susan"
person.age = 27; // 假装能加属性age并赋值数值类型27
console.log(person.age); // 结果输出undefined(属性丢失,添加失败,但不会报错)

以上说明了只能给引用类型值动态添加、改变和删除属性(person.age = undefined)。

复制变量值

1.基本类型值的复制:

从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。

var num1 = 666; // num1中保存了666
var num2 = num1; // 用num1的值来初始化num2,num2中也保存了值666
console.log(num2); // 结果输出666

这里num2中的666和num1中的666是完全独立的,对这两个变量进行任何操作都不会相互影响。

2.引用类型值的复制:

从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针(原来的变量存储的值也是指针),而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此如果改变其中一个变量,就会影响另外一个变量。

var obj1 = new Object();
var obj2 = obj1;
obj1.name = "benjamin";
console.log(obj2.name); // 结果输出"benjamin"

传递参数

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

基本类型值的传递:

function addOne(num) { // 参数num实际上是函数的局部变量
    num += 1;
    return num;
}

var count = 1;
var result = addOne(count); // count的值(数值1)被复制给参数num,变量count和参数num互相不认识,仅仅具有相同的值

console.log(count); // 1,没有变化(函数内部参数num变化不影响外部count变量),说明是传参num是按值传递,不是按引用传递
console.log(result); // 2

引用类型值的传递:

function setName(obj) {
    obj.name = "kevin"; // obj和person引用的是同一个对象,对obj的任何操作都直接影响person
    
    obj = new Object(); // 改变了obj的值(指针),接下来的任何操作都和person变量指向的对象无关
    obj.name = "mark"; // 不影响原来的person
}

var person = new Object(); // 变量person中存储的值实际上是指针,该指针指向一个对象
setName(person); // 在函数内部,obj和person引用的是同一个对象(按值传递,传递的是指针)

console.log(person.name); // 结果输出"kevin"

如果person是按引用传递的,那么在执行obj = new Object()的时候,person指向的name属性值为"kevin"的对象就会被覆盖为新的、name属性值为"mark"的对象(事实上没有)。事实上,当在函数内部重写obj的时候,这个局部变量引用的就是一个局部对象了,这个局部对象会在函数执行完毕之后立即被销毁。

检测类型

1.typeof操作符

主要用于检测基本数据类型,确定一个变量是否字符串、数值、布尔值、还是undefined。如果变量的值是一个对象或null,则返回"object"。

var s = "james";
var b = true;
var i = 25;
var u;
var n = null;
var o = new Object();

console.log(typeof s); // "string"
console.log(typeof i); // "number"
console.log(typeof b); // "boolean"
console.log(typeof u); // "undefined"
console.log(typeof n); // "object"
console.log(typeof o); // "object"

typeof操作符检测基本数据类型很有用,但是对于检测引用类型的值用处不大,因为通常我们需要知道的是某个值是什么类型的对象,而不是只知道它是对象。

2.instanceof操作符

用于检测引用数据类型。如果变量给定引用类型的实例(根据原型链识别),那么instanceof操作符就会返回true,否则返回false。

var people = new Array();
var person = "ben";

console.log(people instanceof Object); // true
console.log(people instanceof Array); // true
console.log(people instanceof RegExp); // false 
console.log(person instanceof Object); // false

ECMAScript规定所有引用类型的值都是Object的实例。使用instanceof操作符检测基本类型的值,该操作符始终会返回false,因为基本类型不是对象。  

执行环境及作用域(难)

几个概念:

1.执行环境(execution context)

执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。

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

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈会将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着。

当某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数也随之销毁(全局执行环境直到应用程序退出,例如关闭网页或浏览器的时候才会被销毁)。

2.变量对象(variable object)

每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们在编写代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

3.作用域链(scope chain)

当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。

作用域链的前端始终是当前执行的代码所在环境的变量对象。

如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象。作用域链的下一个变量对象来自包含环境,而下一个变量对象则来自下一个包含环境,这样一直延续到全局执行环境,全局执行环境的变量始终都是作用域链中的最后一个对象。

4.活动对象(activation object)

当函数被调用时,一个特殊的对象,即活动对象将会被创建,这个对象中包含形参和arguments对象。活动对象之后会作为函数上下文的变量对象来使用。

这样理解:活动对象除了变量和函数声明之外,它还存储了形参和arguments对象。

猜你喜欢

转载自www.cnblogs.com/yanggb/p/9039022.html