理解JavaScript变量和类型

3次阅读

共计 2472 个字符,预计需要花费 7 分钟才能阅读完成。

转载自 ConardLi:《【JS 进阶】你真的掌握变量和类型了吗》公众号:code 秘密花园

1. JavaScript 数据类型


ECMAScript 标准规定了 7 种数据类型,这些数据类型分为 原始类型 对象类型

1.1 原始类型局有不可变性:
如下:

var str = ‘abc’;
str.slice(1);
str.substr(1);
str.trim(1);
str.toLowerCase(1);
str[0] = 1;
console.log(str); //abc

以上这些方法都在原字符串的基础上产生一个新字符串,而非直接去改变 str,这印证了字符串的不可变形。

当执行以下代码时:

str += ‘6’;
console.log(str); //abc6

可见 str 的值被改变了,这貌似与字符串的不可变性不符合呀,这个要从内存上来理解。

在 JavaScript 中,每一个变量在内存中都需要一个空间来存储。
内存空间有分为栈内存和堆内存。

栈内存:

  • 存储的值大小固定
  • 空间较小
  • 可以直接操作其保存的变量,运行效率高
  • 由系统自动分配存储空间

JavaScript 中的原始类型的值被直接存储在占中,在变量定义时,栈就为其分配好了内存空间。

由于栈中的内存空间的大小是固定的,所以存储在栈中的变量就是不可变的。
而当执行了 str += ‘6’ 的操作时,实际上是在栈中又开辟了一块内存空间用于存储‘abc6’, 然后将变量 str 指向这块空间。


1.2 引用类型

堆内存:

  • 存储的值大小不定,可动态调整
  • 空间较大,运行效率低
  • 无法直接操作其内部存储,使用引用地址读取
  • 通过代码进行分配空间

引用类型的值实际存储在堆内存中,它在栈中只存储了一个固定长度的地址,这个地址指向堆内存中的值。

如下:

var obj1 = {name: ‘abc’};
var obj2 = {age: 18};
var obj3 = function() {…};
var obj4 = [1, 2, 3, 4, 5];

引用类型不具有不可变性,可以改变它们:

obj1.name = ‘abc6’;
obj2.age = 19;
obj4.length = 0;
console.log(obj1); //{name: ‘abc6’}
console.log(obj2); //{age: 19}
console.log(obj4); //[]

以数组为例,它的很多方法都可以改变它自身:

  • pop()
  • push()
  • shift()
  • unshift()
  • reverse()
  • sort()
  • splice()

1.3 复制

原始类型的复制:

var name = ‘abc’;
var name2 = name;
name2 = ‘code’;
console.log(name); //abc

内存中有一个变量 name,值为 abc。从变量 name 复制出一个变量 name2,此时在内存中创建了一个块新的空间用于存储 abc,虽然两者的值是相同的,但是两者指向的内存空间完全不同,这两个变量参与任何操作都互不影响。

引用类型的复制:

var obj = {name: ‘abc’};
var obj2 = obj;
obj2.name = ‘code’;
console.log(obj.name); //code

当复制引用类型的变量时,实际上复制的是堆中存储的地址,所以复制出来的 obj2 实际上和 obj 指向的堆中同一个对象。因此,改变其中任何一个变量的值,两一个变量都会受到影响。


1.4 比较
当对两个变量进行比较时,不同类型的变量的表现是不同的:

var name = ‘abc’;
var name2 = ‘abc’;
console.log(name === name2); //true
var obj = {name: ‘abc’};
var obj2 = {name: ‘abc’};
console.log(obj === obj2); //false

对于原始类型,比较时会直接比较它们的值,如果值相等,即返回 true。
对于引用类型,比较时会比较它们的引用地址,虽然两个变量在堆中存储的对象具有属性值都是相等的,但是它们被存储在了不同的存储空间,因此比较值为 false。


1.5 值传递和引用传递
如下:

let name = ‘abc’;
function changeValue(name) {
name = ‘code’;
}
changeValue(name);
console.log(name);

执行上面的代码,如果最终打印出来的 name 是‘abc’,没有改变,说明函数参数传递的是变量的值,即值传递。如果最终打印出来的是‘code’,函数内部的操作可以改变传入的变量,那么说明函数参数传递的是引用,即引用传递。

上面的例子执行结果是‘abc’,即函数参数仅仅是被传入变量复制给了的一个局部变量。改变这个局部变量不会对外部变量产生影响。

let obj = {name: ‘abc’};
function changeValue(obj) {
obj.name = ‘code’;
}
changeValue(obj);
console.log(obj.name); //code

ECMAScript 中的所有的函数的参数都是按值传递的。

当函数参数是引用类型时,同样将参数复制了一个副本到局部变量,只不过复制的这个副本是指向堆内存中的地址而已,在函数内部对对象的属性进行操作,实际上和外部变量指向堆内存中的值相同,但是这并不代表这引用传递。

接着下一个例子:

let obj = {};
function changeValue(obj) {
obj.name = ‘abc’;
obj = {name: ‘code’}; // 赋予副本新的地址
}
changeValue(obj);
console.log(obj.name); //abc

可见,函数参数传递的并不是变量的引用,而是变量拷贝的副本,当变量是原始类型时,这个副本就是值本身,当变量是引用类型是,这个副本是指向堆内存的地址。

重要的事情说三遍:

  1. ECMAScript 中的所有的函数的参数都是按值传递的。
  2. ECMAScript 中的所有的函数的参数都是按值传递的。
  3. ECMAScript 中的所有的函数的参数都是按值传递的。

转载自 ConardLi:《【JS 进阶】你真的掌握变量和类型了吗》公众号:code 秘密花园

正文完
 0