晨叔技术晨报: 你真的搞懂JS中的“值传递”和“引用传递”吗?

 晨叔周刊,每周一话题,技术每天涨。 前端

 

  本周的话题是JS的内存问题(加入本周话题,请点击传送门)。git

 

图 话题入口微信

 

  今天的技术晨报来,就来谈谈JS中变量的,值传递和引用传递的问题。如今,对于不少的JSer来说,基本不关心堆和栈的问题,代码照样666。函数

可是,如今的前端,再也不是传统的JQ时代,而是MVVM,组件化,工程化。前端的承载着复杂业务逻辑。为此,内存问题,成为JSer必需要考虑的问题。 本文从堆栈讲起,让你们理解JS中变量的内存使用以及变更状况 。组件化

 

1、初步了解堆栈 spa

先初步了解JS中的堆和栈,首先,内存空间分为 堆和栈两个区域,js 代码运行时,js解析器会先判断变量类型,根据变量类型,将变量放到不一样的内存空间中(堆和栈)。指针

 图 1code

 

 基本的数据类型(String,Number,Boolean,Null,Undefined)都会分配栈区。而Object (对象)类型的变量都放到堆区。对象

 以下代码示例blog

1 var a = 12;
2 var b = false;
3 var c  = "string"
4 
5 var chenshu =  {name:"晨叔周刊",desc:"每周一话题,技术每天涨" }

 

对应的内存分配图以下图2

 

图 2 

 

栈区的特色:空间小,数据类型简单,读写速度快,通常由JS引擎自动释放

堆区的特色:空间大,数据类型复杂,读写速度稍逊,当对象不在被引用时,才会被周期性的回收。

 

了解了内存的栈区和堆区后, 接下来,来看看变量如何在栈区和堆区“愉快的玩耍”。

 

2、变量传递 

 进入今天的重点,先看看下面的代码。

var a = 12;
var b= a; 
b = 13;

 上面代码的运行结果,a 变量的值没变,由于 第二行“b = a” ,把a的值赋值个b时, 执行的是“复制”的操做,a 和 b 没有关系。(so easy ,不在多BB),再往下看

1 var chenshu =  {name:"晨叔周刊",desc:"每周一话题,技术每天涨" };
2 var xiaoming = chenshu;
chenshu 不是基础类型变量, 而是一个对象。

第二行中,“xiaoming = chenshu”,进行也是“复制的操做”, 为何? 且看下图分解。

根据代码,首先, 申明了变量“chenshu” ,以下图3。

 

图 3 

 

接下来, “xiaoming = chenshu”(复制操做),内存空间图以下图4

 

 

 图4

 

在上图4 中,xiaoming 变量的存储空间(栈区)复制了 chenshu变量的值,可是这个变量的值,并非一个基础的数据类型,而是一个堆区的内存地址(指针)。因此操做“xiaoming”变量和操做“chenshu”变量效果都同样。

划重点:在JS的变量传递中,本质上均可以当作是值传递,只是这个值多是基础数据类型,也多是一个指针,若是是指针,咱们一般就说为引用传递。 JS中比较特殊,不能直接操做对象的内存空间,必须经过指针(所谓的引用)来访问。

 

因此,即便是因此复杂数据类型(对象)的赋值操做,本质上也是值传递。在往下看看一个例子。 

 

3、函数的形参、实参的值传递 


看以下代码。

function setName(user)
{
      user.name = "new name";// 从新设置name 这个属性
}

var chenshu = { name:"晨叔" };

setName(chenshu);

console.log(chenshu.name);

 讲解: 

  函数 setName 有一个 形参 “user”,没毛病。

  将“chenshu”对象传给 “setName ” 函数,chenshu 为实参,也没毛病。

重点:传参的过程当中,js引擎将实参chenshu的值(值是对象{name:"晨叔"}的指针)“复制”给形参。即,形参user 和 chenshu变量指向同一个堆内存对象。 没毛病。 但问题来了,实参和形参在是一个变量?仍是两个变量? 不少开发者的误区:认为 在 setName 函数中改变了形参user的属性,实参 chenshu的属性也发生变更,就认为同一个变量,但真实的状况是:实际上实参和形参 是两个变量,只是实参和形参指向同一个堆区的变量而已,见以下图5

 图5 

 

形参user只是存了对象 { name:"晨叔" }的地址,但在栈区中,user是单独存在的,并且函数运行完,user 当即被释放了,为了更加直观的说明这个问题。咱们再来分析一段代码。

 1  function setName(user)
 2 {
 3       user.name = "new name";// 从新设置name 这个属性
 4       user = { name:"西门吹雪" };
 5  }
 6 
 7  var chenshu = { name:"晨叔" };
 8  setName(chenshu);
 9 
10  console.log(chenshu.name);//输出  new name

 

上面这段代码就能很能说明问题,在“setName”函数中,形参user首先修改实参chenshu对象的name属性以后, 又从新指向了一个新对象“{ name:"西门吹雪" }”, 但由于 形参user和实参chenshu是两个对象,形参user指向新对象后,对实参chenshu并无影响,由于实参和形参进行的是值传递(复制),实参和形参是两个独立在栈区的变量。 

 

本文的分享就到这里,咱们来作一下总结。

  1.  咱们初步理解了js中的堆区和栈区。
  2.  理解了变量传递的方式——值传递,所谓的引用传递,本质上也是值传递,只是传递的这个值是一个指针, 值传递带来的效果就是在内存栈区建立了一个新的空间。
  3.  经过函数的实参和形参传递的分析,咱们进一步的理解js的值传递。 

本文的内容只是初步的探索JS的内存问题,更深的干货,更精彩的内容,请加入本期的分享话题,点击传送门

 请关注“晨叔周刊”微信公众号, 便可每周与晨叔深刻研究一个话题,每周一发布本周话题,周二,周六 早上九点更新周刊内容。晨叔口号: 晨叔周刊,每周一话题,技术每天涨。

相关文章
相关标签/搜索