JavaScript是脚本语言
计算机语言可以分为三类,机器语言、汇编语言、高级语言。高级语言又可以简单分为解释类和编译类。这个知道就够了。
机器语言: 计算机所能识别的二进制语言,一般也不会直接拿来用于编程,无法理解且难以记忆
汇编语言: 底层程序可以直接理解的指令,一般是英文缩写,一般简短、简单(功能简单),只能做一些非常细微的操作,复杂的操作往往伴随着大量的指令,我等一般接触不多
高级语言: 简单理解,我们平常接触到的听说过的基本都是高级语言。高级语言又分为解释类和编译类。解释类语言和编译类语言都需要转换成机器语言才能被机器执行,区别在于转换的时间,解释类语言也就是一般通称的脚本语言,依赖于解释器,边解释边执行,编译类是全部转换(编译)好了,打包拿出去执行。JavaScript不需要编译,但是需要在自己的解释器上执行,比如我们常用的浏览器。
内存、地址、指针
有过C语言基础的人一定听说过这么一句话,指针是C语言的灵魂。但是似乎在JavaScript中,并没有接触过所谓的指针的概念,但JavaScript的解释器(JavaScript引擎)不管用什么编写,有的是C,有的是C++,也或者是Java,甚至是JavaScript自举实现的JavaScript解释器(元循环虚拟机),都逃不脱内存和指针,在JaVaScript中也有体现。所以了解下这些概念,对于学习JavaScript,总归是有好处的。
计算机内存最小单位是位,8位一个字节(byte),实际计算机中,内存最小单位是字节(byte),通俗来讲,可以把内存条当做内存,计算机运行时临时存储数据的地方。也可以把它看做一个大的图书馆,有无数个格子,每个格子都有一个编号,这个编号就是地址,每个地址对应唯一一段内存。我们通常所说的系统为数据分配一段内存
,也就是给这个数据分配格子,一个格子放不下,那就给分三五个格子来放。而指针则是固定占用4个格子(4byte),里面放的不是真实的数据,而是放的另外一段内存的地址,只是个地址。
原始数据类型
Undefined、Null、Boolean、Number、String和Symbol(ES6)
原始数据类型实在太简单,所以只简单划几个重点:
原始数据类型不可改变 原始数据类型一个非常重要的特征就是
不可改变
。原始数据类型是保存在栈中的,一旦声明一个变量var a = "老李";
解释器会申请一块内存来存储"老李"
,a是标识符(标识符存储的是内存地址,但在经过解释/编译后会就不需要了),如果此时其他操作尝试修改a,比如a += ",你好!";
,此时打印a,虽然会输出"老李,你好!";
但是实际这其中改变的是a的地址指向,解释器会为"老李,你好!"
申请一片新的内存来存储,a中存储的指向改变了,原先的"老李"
所占用的内存会被回收但本身并未被改变。原始数据类型是按值传递 原始数据类型的赋值,也是新开辟一片内存地址来存放新的指针和值,比如:
var b = a;
虽然看起来b和a一样,但实际上b和a一点点关系都没有,各自有各自的标识符和占用的内存,只是刚好值相同。
引用数据类型
Object
这里的Object不是狭义的Object,包含JavaScript的预定义引用类型:Object、Array、Date、Boolean、Number、String
,对,这些都是引用数据类型。也就是一个数字/日期/布尔值/字符串他们的字面量声明方式和构造函数声明方式会造成完全不一样的结果。也就是上面划的两个重点,这里需要重新划一下。
引用数据类型可以改变 我想说的不是这句废话,引用数据类型和原始数据类型存储在 栈和堆中,栈中存储的是
标识符和指针
,堆中存储的才是实际地址。画个表(表也懒得画了,下面的凑活看)吧:
var a = 1;
var a1 = a;
var b = {value: 1};
var b1 = b;
var c = new Object(1);
var c1 = c;
var d = new Number(1);
var d1 = d;
栈 堆
a 1 无
a1 1(和a的1占用的不同的内存) 无
b 指针,{value: 1}所占的内存地址 {value: 1}
b1 指针,指向和b相同,但指针本身存储在新的内存中 同上,且是同一块内存中的相同的值
c 指针,1所占的内存地址 1(object)
c1 指针,1所占的内存地址,但指针本身存储在新的内存中 同上,且是同一块内存中的相同的值
d 指针,1所占的内存地址 1(number)
d1 指针,1所占的内存地址,但指针本身存储在新的内存中 同上,且是同一块内存中的相同的值
简单讲,原始数据类型,永远是一个标识符对应一个值(一段内存)且都存放在栈里;引用数据类型永远是一个标识符对应一个指针指向同一个值(同一段内存),前二者存储在栈中,最后一个在堆中。来几个例子帮助理解下:
var a = 1;
var a1 = a;
a1 = 2;
console.log(a, a1); //1 2
这个很好理解大家各自有各自的标识符和内存地址,你变你的,不影响我。下一个例子:
var b = {value: 1};
var b1 = b;
b1.value = 2;
console.log(b.value, b1.value) //2 2
也好理解,b和b1指向的是同一个东西,简单理解就是b和b1共用同一个东西。b1改完了b再来访问,拿到的肯定是改后的。下一个例子:
var c = new Object(1);
var c1 = c;
c1 = 2;
console.log(c, c1) //{[[PrimitiveValue]]: 1} 2 PrimitiveValue就是指的原始值,这里其实只是将原始值1包装成Object,而这个原始值1依然不可更改
也很简单,c1 = 2;这句话重新声明了c1,代表着指针的c1被回收了,c1被重新声明为一个原始数据类型,这时候二者没关系了,如果是换一个方法,比如:
var c = new Object(1);
var c1 = c;
c1.value = 1;
console.log(c, c1) //{value: 1,[[PrimitiveValue]]: 1} {value: 1,[[PrimitiveValue]]: 1}
这样就没问题了。
参数中的原始数据和引用数据
原始数据类型是按值传递的,引用数据类型是按引用传递的,看例子:
var a = 1;
var b = {value: 1};
function foo(v){
v = "new Value";
}
foo(a);
在函数调用时,其实进行了如下操作:
将a赋值给v,也就是v = a;
,创建了a的副本,此时v得到了自己的内存,其中存放着1这个值,然后拿"new Value"去覆盖1,然而这一切发生在a的副本上,和a完全没有关系,所以此时打印出a,还是1。接着:
foo(b);
这个结果也很好猜,b也没有变,同样创建一个副本,副本有标识符、指针,指针指向的也是b的指针指向的值,但是v = ”new Value“;
执行时,这个副本就被重新赋值位一个string了,原先那个指针被回收。接着:
var a = 1;
var b = {value: 1};
var c = new Number(1);
function foo(v){
v.value = "new Value";
}
foo(a);
foo(b);
foo(c);
a依然是1,且不说原始数据不能被添加属性,即便被添加也只是a的副本的事,和a没一点关系;
b在函数执行中同样创建了副本,副本同样有指针,指向了
{value: 1}
所在的实际地址,此时通过指针改变了存储`{value:
1}`的内存中的value值,所以此时再通过b去访问,一样是输出改变后的值;c和b原理一样,只不过c本来没有value属性,所以会被添加一个value属性,所以上面的abc结果分别是:
console.log(a); // 1
console.log(b); // {value: "new Value"}
console.log(c); // Number {value: "new Value", [[PrimitiveValue]]: 1}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。