一篇文中告诉你JS中的"值传递"和"引用传递"

目录
  • 前言
  • 初步了解堆栈
    • 堆栈和类型的关系
    • 特点
  • 变量赋值
  • 参数传递
    • 小结
  • 面试题
  • 两者的区别就是:
  • 总结

前言

现代的前端开发,不再是刀耕火种的 JQ 时代,而是 MVVM ,组件化,工程化,承载着日益复杂的业务逻辑。内存消耗和性能问题,成为当代开发者必须要考虑的问题。

本文从堆栈内存讲起,让大家理解JS中变量的内存使用以及变动情况 。

初步了解堆栈

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

如图所示

堆栈和类型的关系

基本的数据类型(String,Number,Boolean,Null,Undefined, Symbol)都会分配栈区。它的值是存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配;按值存放,所以可以按值访问

引用数据类型 Object (对象)的变量都放到堆区。它在栈内存中保存的实际上是对象在堆内存中的引用地址, 通过这个引用地址可以快速查找到保存在堆内存中的对象。存放在堆内存中的对象,每个空间大小不一样,要根据情况进行特定的配置。

如下代码示例:

var a = 12;
var b = false;
var c = 'string'
var obj =  { name: 'sunshine' }

特点

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

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

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

变量赋值

下面来看一组基本类型的变量传递的例子:

let a = 100
let b = a
a = 200
console.log(b) // 100

初始栈中 a 的值为100;其次栈区中添加 b,并且将a复制了一份给b;最后 a保存了另外一个值 200,而b的值不会改变。

再来看一组引用类型传递的例子:

let obj1 = { name: 'a' }
let obj2 = obj1
obj2.name = 'b'
console.log(obj1.name) // b

以上代码中,obj1 和 obj2 指向了同一个堆内存,obj1 赋值给 obj2,实际上这个堆内存对象在栈内存的引用地址复制了一份给了 obj2,所以 obj1 和 obj2 指针都指向堆内存中的同一个。

图解如下:

综合案例:

var a = [1, 2, 3, 4]
var c = a[0]
// 这时变量c是基本数据类型,存储在栈内存中;改变栈中的数据不会影响堆中的数据
c = 5
console.log(c) // 5
console.log(a[0]) // 1

let b = a
// b是引用数据类型,栈内存指针和 a一样都指向同一个堆内存,改变数值后,会影响堆中的数据
b[2] = 6
console.log(a[2]) // 6

划重点:在JS的变量传递中,本质上都可以看成是值传递,只是这个值可能是基础数据类型,也可能是一个引用地址,如果是引用地址,我们通常就说为引用传递。JS中比较特殊,不能直接操作对象的内存空间,必须通过指针(所谓的引用)来访问。

所以,即使是所有复杂数据类型(对象)的赋值操作,本质上也是值传递。在往下看一下不同的值在参数中是如何传递的。

参数传递

由上可知,ECMAScript中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量赋值到另一个变量一样。在按值传递参数时,值会被复制到一个局部变量(arguments对象中的一个槽位)。在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部。

下面看一个例子:在 bar 函数中,当参数为基本数据类型时,函数体内会赋值一份参数值,而不会影响原参数的实际值。

let foo = 1
const bar = value => {
  // var value = foo
  value = 2
  console.log(value)
}
bar(foo) // 2
console.log(foo) // 1

如果将函数参改为引用类型,结果就不一样了:

let foo = { bar: 1}
const func = obj => {
  // var obj = foo
  obj.bar = 2
  console.log(obj.bar)
}
func(foo) // 2
console.log(foo.bar) // 2

从以上代码中可以看出,如果函数参数是一个引用类型的数据,那么当在函数体内修改这个引用类型参数的某个属性时,也将对原来的参数进行修改,因为此时函数体内的引用地址指向了原来的参数。

但是,如果在函数体内直接修改对参数的引用,则情况又会不一样:

let foo = { bar: 1}
const func = obj => {
  // var obj = 2
  obj = 2
  console.log(obj)
}
func(foo) // 2
console.log(foo) // { bar: 1 }

这是因为如果我们将一个已经赋值的变量重新赋值,那么它将包含新的数据或引用地址。这时函数体内新创建了一个引用,任何操作都不会影响原参数的实际值。

如果一个对象没有被任何变量指向,JavaScript引擎的垃圾回收机制会将该对象销毁并释放内存。

小结

  • 函数参数为基本数据类型时,函数体内赋值了一份参数值,任何操作都不会影响原参数的实际值
  • 函数参数是引用类型时,当函数体内修改这个值的某个属性时,将会对原来的参数进行修改
  • 函数参数是引用类型时,如果直接修改这个值的引用地址,则相当于在函数体内新创建了一个新的引用,任何操作都不会影响原参数的实际值。

面试题

  • 参数多次赋值问题
function func (person) {
  person.age = 25
  person = {
    age: 50
  }

  return person
}
var person1 = {
    age: 30
}
var person2 = func(person1);
console.log(person1)
console.log(person2)

答案:{ age: 25 },{ age: 50 }。因为函数内部,person 第一次修改,相当于 复制了 person1 的内存地址给person,第二次修改是创建一个新的 person 变量。所以 person1 在堆内存中的值会被修改,person 也是新的 person 变量返回的值

  • 变量干扰问题
let obj1 = { x: 100, y: 200}
let obj2 = obj1
let x1 = obj1.x
obj2.x = 101
x1 = 102
console.log(obj1)

答案:{ x: 101, y: 200 },x1是干扰项,因为obj.x是原始类型值,所以修改后不会影响原数据的引用地址。

两者的区别就是:

举个例子:

值传递:A觉得B的房子装修风格很好,于是借用了B的装修风格。但是过了段时间A给房子里面又添加了点别的风格,但是B的房子风格还是原来的。

引用传递:A喜欢B的房子风格,借用了人家的风格,过了段时间A给家里添加了新的风格,但是A觉得自己的风格比B的好,于是通过B给A的地址,去B的家硬是把人家的风格改成和自己一样的了。

总结

到此这篇关于JS中"值传递"和"引用传递"的文章就介绍到这了,更多相关JS 值传递和引用传递内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JS引用传递与值传递的区别与用法分析

    本文实例讲述了JS引用传递与值传递的区别与用法.分享给大家供大家参考,具体如下: 这里详细解释JS值传递和引用传递以及二者的区别. 我们先来解释一下这两个的基本概念吧. 函数调用中,传递是一个数值,我们称为 "值传递". 函数调用中,传递是对象,一般称为 "引用传递". 现在这里总体上说明,这二者的本质区别就在于传递的数据类型不一样,值传递传递的是一个值,而引用传递传递的是一个对象. 看一下代码以及代码中的注释. 一.引入 function func(a) { a+

  • JavaScript中的值是按值传递还是按引用传递问题探讨

    最近遇到个有趣的问题:"JS中的值是按值传递,还是按引用传递呢?"   在分析这个问题之前,我们需了解什么是按值传递(call by value),什么是按引用传递(call by reference).在计算机科学里,这个部分叫求值策略(Evaluation Strategy).它决定变量之间.函数调用时实参和形参之间值是如何传递的.   按值传递 VS. 按引用传递 按值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参的副本.修改形参的值并不会影响

  • 深入浅析JS是按值传递还是按引用传递(推荐)

    按值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参的副本.修改形参的值并不会影响实参. 按引用传递(call by reference)时,函数的形参接收实参的隐式引用,而不再是副本.这意味着函数形参的值如果被修改,实参也会被修改.同时两者指向相同的值. 按引用传递会使函数调用的追踪更加困难,有时也会引起一些微妙的BUG. 按值传递由于每次都需要克隆副本,对一些复杂类型,性能较低.两种传值方式都有各自的问题. JS的基本类型,是按值传递的. var a = 1

  • JS是按值传递还是按引用传递

    按值传递 VS. 按引用传递 按值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参的副本.修改形参的值并不会影响实参.   按引用传递(call by reference)时,函数的形参接收实参的隐式引用,而不再是副本.这意味着函数形参的值如果被修改,实参也会被修改.同时两者指向相同的值.   按引用传递会使函数调用的追踪更加困难,有时也会引起一些微妙的BUG.   按值传递由于每次都需要克隆副本,对一些复杂类型,性能较低.两种传值方式都有各自的问题.   我们

  • 深入理解JavaScript的值传递和引用传递

    JavaScript有5种基本的数据类型,分别是:布尔.null.undefined.String和Number.这些基本类型在赋值的时候是通过值传递的方式.值得注意的是还有另外三种类型: Array.Function和Object,它们通过引用来传递.从底层技术上看,它们三都是对象. 基本数据类型 如果一个基本的数据类型绑定到某个变量,我们可以认为该变量包含这个基本数据类型的值. var x = 10; var y = 'abc'; var z = null; 当我们使用=将这些变量赋值到另外

  • 浅谈JavaScript 函数参数传递到底是值传递还是引用传递

    在传统的观念里,都认为JavaScript函数传递的是引用传递(也称之为指针传递),也有人认为是值传递和引用传递都具备.那么JS的参数传递到底是怎么回事呢?事实上以下的演示也完全可以用于Java 首先来一个比较简单的,基本类型的传递: function add(num){ num+=10; return num; } num=10; alert(add(num)); aelrt(num); //输出20,10 对于这里的输出20,10,按照JS的官方解释就是在基本类型参数传递的时候,做了一件复制

  • JavaScript传递变量: 值传递?引用传递?

    当变量A赋值给变量B时,会将栈中的值复制一份到为新变量分配的空间中. 如何理解? 复制代码 代码如下: var x = y = 1; y = 2; alert(x); x的值为多少? 复制代码 代码如下: var obj = {}; var sub = {}; sub['id'] = 3; obj['sub'] = sub; sub['id'] = 4; alert(obj['sub']['id']); obj['sub']['id']的值又为多少?他们真的符合你的预期吗? 我们分别运行2段代码

  • 一篇文中告诉你JS中的"值传递"和"引用传递"

    目录 前言 初步了解堆栈 堆栈和类型的关系 特点 变量赋值 参数传递 小结 面试题 两者的区别就是: 总结 前言 现代的前端开发,不再是刀耕火种的 JQ 时代,而是 MVVM ,组件化,工程化,承载着日益复杂的业务逻辑.内存消耗和性能问题,成为当代开发者必须要考虑的问题. 本文从堆栈内存讲起,让大家理解JS中变量的内存使用以及变动情况 . 初步了解堆栈 先初步了解JS中的堆和栈,内存空间分为 堆和栈 两个区域,代码运行时,解析器会先判断变量类型,根据变量类型,将变量放到不同的内存空间中(堆和栈)

  • jQuery中each和js中forEach的区别分析

    本文实例讲述了jQuery中each和js中forEach的区别.分享给大家供大家参考,具体如下: <script> $(function(){ // 3.1遍历数组 var arr = [1, 3, 5, 7, 9]; // 3.1.1通过原生方法遍历数组 // 第一个回调函数参数是遍历到的元素 // 第二个回调函数参数是当前遍历的索引 // 返回值:没有返回值 var res = arr.forEach(function(ele, idx){ console.log(idx, ele);

  • 深入理解python中函数传递参数是值传递还是引用传递

    目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是"传对象引用"的方式.实际上,这种方式相当于传值和传引用的一种综合.如果函数收到的是一个可变对象(比如字典 或者列表)的引用,就能修改对象的原始值--相当于通过"传引用"来传递对象.如果函数收到的是一个不可变对象(比如数字.字符或者元组)的引用,就不能 直接修改原始对象--相当于通过"传值"来传递对象. 你可以在很多讨论该问题

  • Java中的值传递和引用传递实例介绍

    复制代码 代码如下: package Object.reference; public class People {     private String name;     private int age;     public People(){     }     public People(String name, int age) {         super();         this.name = name;         this.age = age;     }    

  • 探讨Java中函数是值传递还是引用传递问题

    相信有些同学跟我一样,曾经对这个问题很疑惑.在网上也看了一些别人说的观点,评论不一.有说有值传递和引用传递两种,也有说只有值传递的,这里只说下个人见解 先给大家介绍下概念 值传递:(形式参数类型是基本数据类型):方法调用时,实际参数把它的值传递给对应的形式参数,形式参数只是用实际参数的值初始化自己的存储单元内容,是两个不同的存储单元,所以方法执行中形式参数值的改变不影响实际参数的值. 引用传递:(形式参数类型是引用数据类型参数):也称为传地址.方法调用时,实际参数是对象(或数组),这时实际参数与

  • Java中值传递和引用传递的区别

    在Java中参数的传递主要有两种:值传递和参数传递: 下面是对两种传递方式在内存上的分析: 一:值传递 解释:实参传递给形参的是值  形参和实参在内存上是两个独立的变量 对形参做任何修改不会影响实参 代码示例如下: package arrayDemo; public class Demo1 { public static void main(String[] args) { int b =20; change(b);// 实参 实际上的参数 System.out.println(b); } pu

  • java及C++中传值传递、引用传递和指针方式的理解

    java的值传递理解: 代码1: public class Test { /** * @param args */ public static void main(String[] args) { StringBuffer buffer= new StringBuffer("colin"); SChange(buffer); System.out.println( buffer); } public static void SChange (StringBuffer str) { st

  • Java 值传递和引用传递详解及实例代码

     Java 值传递和引用传递 前言: 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 答:是值传递.Java 编程语言只有值传递参数.当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本.指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的. Java参数,不管是原始类型还是引用类型,传递的都是副本(有另外一种说法是传值,但是说传副本更好理解吧,传值通

  • Java值传递和引用传递详解

    当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 答:是值传递.Java 编程语言只有值传递参数.当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本.指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的. Java参数,不管是原始类型还是引用类型,传递的都是副本(有另外一种说法是传值,但是说传副本更好理解吧,传值通常是相对传址而言). 如果参数类型是原

  • 详解java的值传递、地址传递、引用传递

    详解java的值传递.地址传递.引用传递 一直来觉得对值传递和地址传递了解的很清楚,刚才在开源中国上看到一篇帖子介绍了java中的值传递和地址传递,看完后感受颇深.下边总结下以便更容易理解. 按照以前的理解,java中基本数据类型是值传递,对象是地址(引用)传递.给大家看个例子: public class ObjectTrans { public static void main(String[] args) { String name = "123"; SChange(name);

随机推荐