讲清楚之 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 ...
随机推荐
- 关于UI自动化IOS元素定位方法说明
1. 元素属性介绍 下图是通过weditor定位的微博的"我的钱包",各属性如下图: className:元素类型,如:XCUIElementTypeButton isEnable ...
- 矩池云上安装yolov4 darknet教程
这里我是用PyTorch 1.8.1来安装的 拉取仓库 官方仓库 git clone https://github.com/AlexeyAB/darknet 镜像仓库 git clone https: ...
- 字符集编码(四):UTF
在前面文章<字符集编码(中):Unicode>中我们聊了 Unicode 标准并提到其有三种实现形式:UTF-16.UTF-8 和 UTF-32,本篇我们就具体聊聊这三种 UTF 是怎么实 ...
- yum 安装的历史与撤销(yum history undo )
我们可以使用yum history 来查看yum的历史记录 想撤销安装则输入 yum history undo <ID号> 即可撤销yum的安装/升级
- NET经典书籍必读
C#与.NET框架,入门 + 进阶 + 精通,外加并发编程实例,10本C#图书,一本都不能少. 1.<Learning hard C#学习笔记> 作者:李志 书号:978-7-115-3 ...
- 简悦+Logseq 搭建本地化个人知识库
最近在少数派上看到了 简悦 +Logseq 个人知识库搭建 | 从零开始完全指南 - 少数派, 一时间感觉打开了新世界,其实我很早就买了简悦 2.0,但由于一直没有很好的使用场景,外加配置实在过于复杂 ...
- pycharm远程调试、开发(详细操作)
如果仅是远程开发,新建 ssh Interpreter 并 apply tools -> deployment -> browser remote host 即可 1.服务器侧准备 准备调 ...
- logging 日志配用
第一步,创建一个logger: 第二步,创建一个handler,用于写入日志文件: 第三步,再创建一个handler,用于输出到控制台: 第四步,定义handler的输出格式: 第五步,将logger ...
- Java的浅克隆和深克隆
如何实现对象的克隆 (1)实现 Cloneable 接口并重写 Object 类中的 clone() 方法: (2)实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真 ...
- 如何解析EML(邮件)格式的文件以及一款小巧的EML邮件阅读工具
在理解EML格式的时候,先回顾一下历史,这样有助于理解邮件的格式,比如邮件传输时为何会有多种编码方式.此外,理解EML格式也有助于理解HTTP协议. 历史溯源 由于历史原因,我们目前看到的大部分的网络 ...