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

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

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

图 话题入口

今天的技术晨报,就来谈谈JS中变量的,值传递和引用传递的问题。现在,对于很多的JSer来讲,基本不关心堆和栈的问题,代码照样666。

但是,现在的前端,不再是传统的JQ时代,而是MVVM,组件化,工程化。前端的承载着复杂业务逻辑。为此,内存问题,成为JSer必须要考虑的问题。 本文从堆栈讲起,让大家理解JS中变量的内存使用以及变动情况 。

一、初步了解堆栈 

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

 图 1

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

如下代码示例

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

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

图 2 

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

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

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

二、变量传递 

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

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中比较特殊,不能直接操作对象的内存空间,必须通过指针(所谓的引用)来访问。

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

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


看如下代码。

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的内存问题,更深的干货,更精彩的内容,请加入本期的分享话题,点击传送门

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

 

原文地址:https://www.cnblogs.com/rajan/p/12198911.html

时间: 2024-10-07 23:41:12

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

分分搞懂c#中的委托

分分搞懂c#中的委托: 不说废话,不来虚的概念,不管代码是否有意义,看我的优化之路,你会理解委托了: 源代码1 public class test { //我们不管代码是否有意义,我们直接看代码重构和一步步优化的过程 int flage = 1; public void show(int a) { if (flage == 1) { do1(a); } else if (flage == 2) { do2(a); } else if (flage == 3) { do3(a); } else i

轻松搞懂Java中的自旋锁

前言 在之前的文章<一文彻底搞懂面试中常问的各种"锁">中介绍了Java中的各种"锁",可能对于不是很了解这些概念的同学来说会觉得有点绕,所以我决定拆分出来,逐步详细的介绍一下这些锁的来龙去脉,那么这篇文章就先来会一会"自旋锁". 正文 出现原因 在我们的程序中,如果存在着大量的互斥同步代码,当出现高并发的时候,系统内核态就需要不断的去挂起线程和恢复线程,频繁的此类操作会对我们系统的并发性能有一定影响.同时聪明的JVM开发团队也发现,

一文彻底搞懂面试中常问的各种“锁”

前言 锁,顾名思义就是锁住一些资源,当只有我们拿到钥匙的时候,才能操作锁住的资源.在我们的Java,数据库,还有一些分布式的环境中,总是充斥着各种各样的锁让人头疼,例如“公平锁”.“自旋锁”.“读写锁”.“分布式锁”等等. 其实真实的情况是,锁并没有那么多,很多概念只是从不同的功能特性,设计,以及锁的状态这些不同的侧重点来说明的,因此我们可以根据不同的分类来搞明白为什么会有这些“锁”?坐稳扶好了,准备开车. 正文 “公平锁”与“非公平锁” 公平锁:指线程在等待获取同一个锁的时候,是严格按照申请锁

你真的搞懂什么是线框图,什么是原型图了吗?

设计师朋友们都知道,在网站开发或者手机应用开发的前期阶段,需要创建线框图和原型图给客户展示.但有时候客户会提出略过线框图和原型图设计的阶段,直接过渡到网站的界面设计甚至是开发阶段.这种想法,往往是源于客户不明白什么是线框图,什么是原型图,以及二者在整个项目阶段中的重要性. 首先阐明一个误区,线框图不等于原型图.明确了这个概念之后,我们再来区分二者. 1. 什么是线框图? 线框图是一个网站图形化的骨架,引导一个页面的内容及概念,能够帮助设计师和客户讨论具体的网站层次和导向.一个简单的线框图只需要使

flex布局你真的搞懂了吗?通俗简洁,小白勿入~

flex布局 用以代替浮动的布局手段: 必须先把一个元素设置为弹性容器://display:flex: 一个元素可以同时是弹性容器和弹性元素; 设为flex布局以后,子元素的float.clear和vertical-align属性将失效. 主轴:弹性元素排列的方向: 弹性容器的属性 1.flex-direction 决定主轴的方向 row:默认值,从左到右: row-reverse:从右到左: column:从上到下: column-reverse:从下到上: 2.flex-wrap 决定是否换

大白话讲解Promise(三)搞懂jquery中的Promise

http://www.cnblogs.com/lvdabao/p/jquery-deferred.html @吕大豹 前两篇我们讲了ES6中的Promise以及Promise/A+规范,在Promise的知识体系中,jquery当然是必不可少的一环,所以本篇就来讲讲jquery中的Promise,也就是我们所知道的Deferred对象. 事实上,在此之前网上有很多文章在讲jquery Deferred对象了,但是总喜欢把ajax和Deferred混在一起讲,容易把人搞混.when.done.pr

一文带你搞懂 MySQL 中的分区!

作者:GrimMjx https://www.cnblogs.com/GrimMjx/p/10526821.html 一.InnoDB逻辑存储结构 首先要先介绍一下InnoDB逻辑存储结构和区的概念,它的所有数据都被逻辑地存放在表空间,表空间又由段,区,页组成. 段 段就是上图的segment区域,常见的段有数据段.索引段.回滚段等,在InnoDB存储引擎中,对段的管理都是由引擎自身所完成的. 区 区就是上图的extent区域,区是由连续的页组成的空间,无论页的大小怎么变,区的大小默认总是为1M

一次性搞懂C#中的==、Equals()和ReferenceEquals()的区别

首先看CLR中基本值类型之间的比较,先看代码: int age1 = 30; int age2 = 30; Console.WriteLine("int == int: {0}", age1 == age2); Console.WriteLine("int == int: {0}", age2 == age1); Console.WriteLine("int Equals int: {0}", age1.Equals(age2)); Consol

彻底搞懂OC中GCD导致死锁的原因和解决方案

GCD提供了功能强大的任务和队列控制功能,相比于NSOperationQueue更加底层,因此如果不注意也会导致死锁. 所谓死锁,通常指有两个线程A和B都卡住了,并等待对方完成某些操作.A不能完成是因为它在等待B完成.但B也不能完成,因为它在等待A完成.于是大家都完不成,就导致了死锁(DeadLock). 有一定GCD使用经验的新手通常认为,死锁是很高端的操作系统层面的问题,离我很远,一般不会遇上.其实这种想法是非常错误的,因为只要简单三行代码(如果愿意,甚至写在一行就可以)就可以人为创造出死锁