讲清楚之 javascript 参数传值
讲清楚之 javascript 参数传值
参数传值是指函数调用时,给函数传递配置或运行参数的行为,包括通过call、apply 进行传值。
在实际开发中,我们总结javascript参数传值分为基本数据类型按值传递(String、Numbe、Boolean、Null、undefind),引用数据类型按引用传递(Object, 包括Array、Function、Data)。这篇文章将要纠正这一误解: 实质上引用类型是按共享传递的。
在探索传值问题前,我们先看一下 javascript 在声明变量时是怎样分配内存的
- 原始值:存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。这是因为这些原始类型占据的空间是固定的,所以可将他们存储在较小的内存区域 – 栈中。这样存储便于迅速查寻变量的值。
 - 引用值:存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存地址。这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。
 
基本类型是将原始值保存在栈中。引用类型是将数据保存在堆中,然后在栈中建立堆地址的引用。在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问。而原始类型的值则是可以直接访问到的。不同的内存分配机制也带来了不同的访问机制。
而基本类型和引用类型在变量复制的时候也存在区别:
- 原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的 value 而已。
 - 引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。复制是不会产生新的堆内存消耗。
 
所以我们总结 javascript 中所有函数参数都是按值传递,都是把形参复制给实参,只是基本数据类型复制的是原始值,而引用类型复制的是堆内存的地址。
引用类型的这种求值策略就是 按共享传递(call by sharing).
理论知识梳理完了,下面举几个例子分析一下具体的情形.
基本数据类型传值
let a = 1
function foo(x) {
    console.log(x)
}
foo(a)
console.log(a)
// 2
// 1
变量 a 的值直接复制到了foo函数的实参x上,此时变量x是变量 a 的一个副本。它们独立的在各自的上下文栈中保存了值‘1’,且相互之间互不影响,我们对 a、x的读写操作,操作的是他们各自的值。
示例图中,在全局上下文和foo的上下文中各自保存了值1.
引用类型传值
let a = {
    abc: 1
}
function foo(x) {
    x.abc = 2
    console.log(x.abc)
}
foo(a)
console.log(a.abc)
// 2
// 2
根据编码经验我们会很错误的得出,上面的栗子是按引用传递。对象a的引用被传递到函数foo内部, 函数内部变量x指向全局变量a,从而实现了引用的传递,所以变量x和变量a读写的是同一个对象。我们用一张图来揭示:
但其实是按共享传递。按引用传递是我们对 javascript 求值策略的误解,如果是按引用传递那下面这个例子就懵比了:
let a = {
    abc: 1
}
function foo(x) {
    console.log(x) // {abc: 1}
    x = 2
    console.log(x) // 2
}
foo(a)
console.log(a.abc) // 1
foo 函数执行时第一个打印输出 ‘{abc: 1}’, 第二个打印输出 ‘2’, 调用 foo 函数后的console.log(a.abc)一句打印输出‘1’。
按引用传递的话。表达式console.log(a.abc)是在 foo 函数执行后执行的,此时的a应该已经被赋值为 2, a.abc是不存在的应该打印输出 ‘undefind’。 但是却打印输出了 1,说明在 foo 函数的内部执行x = 2是并没有修改外层对象 a 的值。
为什么会出现a、x在指向同一个对象后,对x赋值又没有改变原对象的值呢?
因为这里对象传递给实参是按共享传递(call by sharing)的,根据引用类型变量复制的特点(上面描述过):
foo 函数执行时, 形参 x 的值是传进去的对象 a 的内存地址引用,即在变量对象创建阶段x保存的是一个对象的堆内存地址。此时 a、x 都指向同一对象。 接着在函数的执行阶段,代码的第二行将原始数据类型 2 赋值给 x,导致 x 不再保存原先的堆内存地址转而保存一个原始值,再次访问 x 的时候是访问对 x 最后一次赋值的原始值。
所以对 x 的赋值会改变上下文栈中标识符 x 保存的具体值
此时如果使用的是按引用传递 ,则变量 a 所指向的对象因该也被赋值为 2。但其实对 x 赋值为一个基本数据类型并没有使原对象为一个字面量值,这就说明引用类型并不是按引用传值,不是遵从按引用规则来处理值的写入。
需要区分给对象属性赋值与直接给对象赋值的区别:
let a = {
    abc: 1
}
function foo(x) {
    x.abc = 99
    console.log(x) // {abc: 99}
    x = 2
    console.log(x) // 2
}
foo(a)
console.log(a) // {abc: 99}
在 foo 函数内部修改对象 x 的属性,会导致 x、a 指向的对象被修改,因为它们指向同一个堆地址。
参考:
《javascript高级程序设计》
讲清楚之 javascript 参数传值的更多相关文章
- C# asp.net页面通过URL参数传值中文乱码问题解决办法
		
1.编码string state=Server.UrlEncode(stateName.Text.Trim());Response.Redirect("aaa.aspx?state=&quo ...
 - JavaScript URL传值过程中遇到的问题及知识点总结
		
JavaScript URL传值过程中遇到的问题及知识点总结 Web系统开发过程中经常用到URL进行传值,刚刚接触时不太会解析,会出现中文乱码问题等. 1.父子页面之间的传值(在一个页面中以加载ifr ...
 - 讲清楚之 javascript中的this
		
讲清楚之 javascript中的this 这一节来探讨this. 在 javascript 中 this 也是一个神的存在,相对于 java 等语言在编译阶段确定,而在 javascript 中, ...
 - struts(二) ---中参数传值
		
struts中参数传值的方式有 种: 第一种:直接通过属性来传值 第二种: 第三种:
 - delphi数组作为参数传值
		
在函数中如果数组的个数不定,可以使用开放数组参数 实参可以接受静态数组和动态数组 procedure p1(a:array of Byte); begin ShowMessage( IntToHex( ...
 - Get和Post的参数传值
		
1. get是从服务器上获取数据,post是向服务器传送数据. 2. get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到.post是通过 ...
 - html的URL参数传值问题
		
在URL中的参数传值时,例如:www.nihao.com?id=001 ,= 两边不能有空格,不然PHP在通过$_GET['id']获取时会出现传值为空.
 - C++ 参数传值 与 传引用
		
参数传值 在 C++ 中,函数参数的传递有两种方式:传值和传引用.在函数的形参不是引用的情况下,参数传递方式是传值的.传引用的方式要求函数的形参是引用.“传值”是指,函数的形参是实参的一个拷贝,在函数 ...
 - C# oracle to_date 日期型 参数传值
		
C#操作oracle,date字段,使用参数传值 例子一,获取三小时前的记录 public static DataTable Query() { const string sSql = &qu ...
 
随机推荐
- 任意文件夹打开CMD命令窗口
			
1:打开任意文件夹 2:按住Shift键,鼠标右键单击 3:该文件夹下的命令窗口已打开,方便快捷
 - oracel数据库ORA-28001: the password has expired
			
调试c#项目时登录用户不成功ORA-28001: the password has expired错误 密码过期失效 网上查了一下,是Oracle11g密码过期的原因 Oracle提示错误消息ORA- ...
 - Python3中__repr__和__str__区别
			
示例: class Test(object): def __init__(self, value='hello, world!'): self.data = value >>> t ...
 - TensorFlow入门示例教程
			
本部分的代码目前都是基于GitHub大佬非常详细的TensorFlow的教程上,首先给出链接: https://github.com/aymericdamien/TensorFlow-Examples ...
 - mysql总结:索引,存储引擎,大批量数据插入,事务,锁
			
mysql总结 索引概述: 索引是高效获取数据的数据结构 索引结构: B+Tree() Hash(不支持范围查询,精准匹配效率极高) 存储引擎: 常见存储引擎: Myisam:5.5之前默认引擎,支持 ...
 - HTML的怎么使用,开发工具以及常用标签。
			
前端学习:学习地址:黑马程序员pink老师前端入门教程,零基础必看的h5(html5)+css3+移动,下面这些都是一些学习笔记.临渊羡鱼,不如退而结网!!愿我自己学有所成,也愿每个前端爱好者学有所成 ...
 - LGP3281口胡
			
当你看到一个东西的时候,GF 有可能比 DP 更方便.处理贡献也有可能比 DP 更方便. 这个题意明显是让我们计算 \(S(r)-S(l-1)\) 之类的东西( 所以直接考虑前缀的答案就好了( 考虑将 ...
 - ArcMap进行天空开阔度(SVF)分析
			
这里的SVF并不是生物学或医学的(Stromal Vascular Fraction),而是指GIS中的(Sky View Factor,SVF),即为(城市)天空开阔度. 城市天空开阔度(Sky V ...
 - Spring Boot 进行优雅的字段校验
			
Controller层 VS Service层 一般推荐与业务无关的放在Controller层中进行校验,而与业务有关的放在Service层中进行校验. 那么如何将参数校验写的优雅美观呢,如果都是if ...
 - 爬虫系列之Scrapy框架
			
一 scrapy框架简介 1 介绍 (1) 什么是Scrapy? Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍.所谓的框架就是一个已经被集成了各种功能(高性能 ...