欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  web前端

JavaScript中变量的存储方式

程序员文章站 2022-05-29 17:02:06
...

基本原理

在js中变量包括5中基本类型以及一个复杂数据类型Object,当然常用的函数和数组都是对象。对于基本类型和复杂类型,对应着两种不同的存储方式–栈存储和堆存储。为什么要实现两种存储方式的理由很简单,就是基本类型一旦初始化则内存大小固定,访问变量就是访问变量的内存上实际的数据,称之为按值访问。而对象类型说不定什么时候就会增加自身的大小,内存大小不固定。比如动态添加对象的属性、动态增加数组的大小等等都会使变量大小增加,无法在栈中维护。所以js就把对象类型的变量放到堆中,让解释器为其按需分配内存,而通过对象的引用指针对其进行访问,因为对象在堆中的内存地址大小是固定的,因此可以将内存地址保存在栈内存的引用中。这种方式称之为按引用访问。 嗯,理解这一点很重要,在以后的编程中可以避免很多问题。 我们来看下如下的代码:

var a = 'I am a string.';   //a,b,c的变量中保存的都是实际的值,因为他们是基本类型的变量
var b = 1010;
var c = false;
var d = a;    //d中保存着和“a值一样的副本,它们互不影响”
a = 'I am different from d';
alert(d);    //输出'I am a string'

以上代码很好理解,就是说按值访问的变量复制“你的就是你的,我的就是我的,咱们都有副本,互不影响。”而对于按引用访问则稍有不同:

var e = {
name : 'I am an object',
setName : function(name){
this.name = name;
}
};
var f = e;    //赋值操作,实际上的结果是e,f都是指向那个对象的引用指针
f.setName('I am different from e,I am object f.');
alert(e.name);    //对f进行操作,e的值也改变了!

对于引用类型的赋值,说白了,就是把那个对象的指针复制了过去,两个指针指向的都是同一个实体对象,不存在副本,原本的对象还是只有一个!好。以上就是基本类型和引用类型的最大最根本的差别!我用一张图来形象的表示下:

JavaScript中变量的存储方式

*栈内存中存放基本类型变量,以及对象的指针;堆中存放对象实体

JavaScript中变量的存储方式

*复制前后栈和堆中的情况

引用类型引发的问题

1.使用原型模型创建对象的问题

我们都知道,在JavaScript OO(Object Oriented)中,使用原型模式创建对象的最大的好处就是可以让对象实例共享原型(prototype)所包含的属性和方法。这样就避免了构造函数模式的缺陷,即每个对象都会有每个方法的副本,每个方法都会在每个实例上重新创建一遍,方法重用无意义的问题。

嗯,使用原型模式是为所有实例共享了方法,但是当原型中有引用类型值的属性的时候,问题就来了:

var Person = function(){
};
Person.prototype = {
constructor : Person,
name : 'Hanzongze',
hobby : ['basketable', 'swiming', 'running'],    //注意,这里包含着一个引用类型的属性
sayName : function(){
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.hobby.push('music');
alert(person2.hobby);    //输出为'basketable', 'swiming', 'running','music'
alert(person1.hobby === person2.hobby);    //true

由于hobby属性是引用类型的值,所以由Person构造函数创建出来的实例的hobby属性,都会指向这一个引用实体,实例对象间的属性互相干扰。这不是我们想要的结果,为避免这类问题,解决方案就是组合使用构造函数模型和原型模型:

var Person = function(){
this.name = 'Hanzongze';
this.hobby = ['basketable', 'swiming', 'running'];    //对引用类型的值使用构造函数模式
};
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.hobby.push('music');
alert(person2.hobby);   //输出 'basketable', 'swiming', 'running',说明对person1的修改没有影响到person2
alert(person1.hobby === person2.hobby);    //false

2.原型继承中的问题

这个问题与上一个的本质其实是一样的,只不过是发生在了原型继承的背景中。看一个原型链继承的问题:

var Person = function(){
this.name = 'Hanzongze';
this.hobby = ['basketable', 'swiming', 'running'];
};
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
};
//子类型Student
function Student(){
}
Student.prototype = new Person();    //Student继承了Person
var student1 = new Student();
var student2 = new Student();
student1.hobby.push('music');    //对子类实例student1的引用属性做了改动
var student3 = new Student();
alert(student2.hobby);    //输出'basketable', 'swiming', 'running', 'music'
alert(student3.hobby);    //输出'basketable', 'swiming', 'running', 'music'

在这段代码中,可以看到,子类Student继承自父类Person。但由于使用的是原型继承,也就是说父类的实例作为了子类的原型,那么实例中的引用类型属性也就继承在了子类的原型prototype中去了。则子类的实例共享该引用属性,相互影响。

解决方案,那就是使用借用构造函数方案(但是也不是理想的方案,理想的方案是组合使用原型链和借用构造函数。涉及到了比较多的继承模式,这里简单描述,以后会写一篇详细的文章):

var Person = function(){
this.name = 'Hanzongze';
this.hobby = ['basketable', 'swiming', 'running'];
};
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
};
function Student(){
//借用构造函数,继承了Person
Person.call(this);
}
var student1 = new Student();
var student2 = new Student();
student1.hobby.push('music');
alert(student2.hobby);    //输出'basketable', 'swiming', 'running', 'music'
相关标签: Javascript 变量