zoukankan      html  css  js  c++  java
  • 【转】Javascript基本类型和引用类型的区别

    根据【转贴】进一步补充

    今天明白了一个困扰很久的问题:引用类型和基本类型的区别与联系
    要明白这个问题,首先需要理解堆栈的概念。那什么又是堆栈,有什么区别和联系呢?
    堆:首先堆是动态分配的,JVM并不会自动释放这部分内存。只用等待系统的gc来进行内存回收。
    栈:是在类加载中有系统静态分配的,而且分配时按照内存的高低地址分配。这部分内存系统会自动进行释放。
    string是一个特殊类型,它存储的机制是引用类型。

    堆(Heap)栈(Stack)

    1、内存分配方面:

    • 堆:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式是类似于链表。可能用到的关键字如下:new、malloc、delete、free等等。
    • 栈:由编译器(Compiler)自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

    2、申请方式方面:

    • 堆:需要程序员自己申请,并指明大小。在c中malloc函数如p1 = (char *)malloc(10);在C++中用new运算符,但是注意p1、p2本身是在栈中的。因为他们还是可以认为是局部变量。
    • 栈:由系统自动分配。 例如,声明在函数中一个局部变量 int b;系统自动在栈中为b开辟空间。

    3、系统响应方面:

    • 堆:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的delete语句才能正确的释放本内存空间。另外由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
    • 栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

    4、大小限制方面:

    • 堆:是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
    • 栈:在Windows下, 栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是固定的(是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

    5、效率方面:

    • 堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便,另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。
    • 栈:由系统自动分配,速度较快。但程序员是无法控制的。

    6、存放内容方面:

    • 堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
    • 栈:在函数调用时第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈,然后是函数中的局部变量。 注意: 静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

    7、存取效率方面:

    • 堆:char *s1 = "Hellow Word";是在编译时就确定的;
    • 栈:char s1[] = "Hellow Word"; 是在运行时赋值的;用数组比用指针速度要快一些,因为指针在底层汇编中需要用edx寄存器中转一下,而数组在栈上直接读取

    按照CEMA-262第3版的定义,JS变量松散类型的本质,决定了它只是在特定时间用于保存特定值的一个名字而已。由于不存在必须定义变量数据类型的规则,变量的值及其数据类型可以在脚本生命周期内改变。尽管从某种角度看,这可能是一个既有趣又强大,同时又容易出问题的特性,但js变量实际的复杂程度还远不止如此。

    ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。
    基本类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。
    而引用类型值则是指那些保存在堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。

    在将一个值赋给变量时,解析器必须确定这个值是基本类型值,还是引用类型值。(Undefined、Null、Boolean、Number、String)这五种基本数据类型的值在内存中分别占有固定大小的空间,因此可以把他们的值保存在栈内存中。对于保存基本数据类型的变量,我们可以说他们是按值访问的,因为我们操作的是它们实际保存的值。

    如果赋给变量的是一个引用类型的值,则必须在堆内存中去为这个值分配空间。由于这种值的大小不固定,因此不能把它们保存到栈内存中。但内存地址的大小是固定的,因此可以将内存地址保存在栈内存中。这样,当查询引用类型的变量时,就可以首先从栈中读取内存地址,然后在“顺藤摸瓜”找到保存在堆中的值。对于这种查询方式,我们把它叫做按引用访问,因为我们操作的不是实际的值,而是被那个值所引用的对象。

    1 动态属性

    定义基本类型值和引用类型值的的方式是类型的。但是对不同类型值可以执行的操作则是大相径庭。对于引用类型的值 ,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。

    var person = new Object();
    person.name = "Nicholas";
    alert(person.name); //"Nicholas"

    以上代码创建了一个对象并将其保存在了变量person中。
    然后,为该对象添加了一个名为name的属性,并将“Nicholas”赋给这个属性。
    紧接着又通过alert()访问了这个新属性,如果对象不被销毁或这个属性不被删除,则这个属性将一直存在。

    //补充:基本类型也可以赋值属性,不会报错,但结果是未定义:
    //例:
    var person="luotian";
    person.age=27;
    alert(person.age); //undefined

    2 复制变量值

    除了保存方式不同之外,在一个变量向另一个变量复制基本类型值和引用类型值时,也存在不同。
    如果从一个变量向另一个变量复制基本类型的值,会在栈中创建一个新值,然后把该值复制到为新变量分配的位置上。

    var num1 = 5;
    var num2 = num1;

    这2个“5”是完全独立的,它们可以参与任何操作而不会相互影响。


    当一个变量向另一个变量复制引用类型的值时,同样也会将存储在栈中的值复制一份到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制后,两个变量实际上将引用同一个对象。因此改变其中一个变量,就会影响到另一个变量:

    var obj1 = new Object();
    var obj2 = obj1;
    obj1.name = "Nicholas";
    alert(obj2.name); // "Nicholas"

    3 传递参数

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

    在向参数传递基本类型的值时,被传递的值会被复制给另一个局部变量(即命名参数,或用ECMAScript的概念来说,就是arguments对象中的一个元素)。

    1 function addTen(num){
    2 num += 10;
    3 return num;
    4 }
    5 var count = 20;
    6 var result = addTen(count);
    7 alert(count); //20
    8 alert(result); //30

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

    function setName(obj){
    obj.name = "Nichoas";
    }
    var person = new Object();
    setName(person);
    alert(person.name); //“Nichoas” 

    为了证明对象是按值传递的,我们看看下面这个修改过的例子:

    function setName(obj){
    obj.name = "Nichoas";
    obj = new Object();
    obj.name = "Greg";
    }
    var person = new Object();
    setName(person);
    alert(person.name); //“Nichoas”

    这个例子与前面唯一的区别,就是在setName()中重新定义了一个对象并为该对象定义了一个带有不同值的name属性。
    如果 person 是按引用传递的,那么person 就会自动被修改为指向其name属性值为“Greg”的对象。但是,当接下来在访问person.name时,显示的值仍然是“Nichoas”。
    这说明即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。
    可以把CEMAScript函数的参数想象成局部变量。

    4 检测类型

    在检测基本数据类型时typeof是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大。通常,我们并不是想知道某个值是对象,而是想知道他是什么类型的对象。为此,ECMAScript提供了instanceof操作符result = variable instanceof constructor
    如果 变量是给定引用类型(由构造函数表示)的实例,那么instanceof操作符就会返回true:

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

    根据 规定,所有引用类型的值都是Object的实例。因此,在检测一个引用类型值和Object构造函数时,instanceof 操作符始终会返回true。
    当然,如果使用instenceof操作符检测基本类型值,返回false.因为基本类型不是对象

    使用typeof检测函数时,该操作会返回"function".在Safari 和 Chrome中使用 typeof检测正则时,会错误地返回"function"

  • 相关阅读:
    27 Spring Cloud Feign整合Hystrix实现容错处理
    26 Spring Cloud使用Hystrix实现容错处理
    25 Spring Cloud Hystrix缓存与合并请求
    24 Spring Cloud Hystrix资源隔离策略(线程、信号量)
    23 Spring Cloud Hystrix(熔断器)介绍及使用
    22 Spring Cloud Feign的自定义配置及使用
    21 Spring Cloud使用Feign调用服务接口
    20 Spring Cloud Ribbon配置详解
    19 Spring Cloud Ribbon自定义负载均衡策略
    18 Spring Cloud Ribbon负载均衡策略介绍
  • 原文地址:https://www.cnblogs.com/tinaluo/p/6275748.html
Copyright © 2011-2022 走看看