我们都知道在 ECMAScript 中,数据类型分为原始类型(又称值类型/基本类型)和引用类型(又称对象类型);这里我将按照这两种类型分别对函数进行传参,看一下到底发生了什么。

参数的理解

首先,我们要对函数的参数有一个了解:

形参就是函数内部定义的局部变量;

实参向形参传递值的时候,就是一个赋值操作,把实参的值直接复制一份给形参。

原始类型参数传递

示例1

  var a = 1;
function f(b) {
a = 3;
}
f(a);
console.info(a); // 3

示例1中的代码比较简单,解析如下:

  1. 首先,我们定义了一个变量 a,给它赋值为 1;又定义了一个函数 f,函数 f 的形参是 b(此时,相当于在函数 f 里定义了一个变量 var b; 它的值现在是 undefined);
  2. 调用函数 f(a)a 作为实参传入。这里可以理解为,给 b 进行了一次赋值操作 b = 1
  3. 接下来继续执行代码 a = 3;注意:在函数体里边,出现了一个变量 a ,但是它没有用 var 关键字定义,所以,它是一个全局作用域的变量,不是这个函数的局部变量,而在一开始,我们就定义了这样一个变量 a,所以在这里就是对之前的 a 进行的又一次赋值操作,把 a 从之前的 1 变成了 3
  4. 执行完 f(a) 之后,把全局变量 a 的值改变了,所以,当我们输出 a 查看它的值时,就得到了 3 ,而对于函数的形参 b 根本没有进行任何操作而已。

示例2

  var a = 1;
function f(a) {
a = 3;
}
f(a);
console.info(a); // 1

解析:

示例2与示例1的区别,从表面上看,就是形参 b 变成了 a 。但是,这样的变化结果就是,对于函数 f 来说,参数起到了作用,当我们对函数 f 进行传参操作的时候,我们传入的实参在函数内部就会得到引用。

相比较示例1的第一步,函数 f 内部定义了一个变量 a 它的值是 undefined ,注意:在未执行函数的时候,只是进行了预解析,代码没有执行,在调用函数的时候才会开始执行代码。

执行 f(a) 后,就是传入实参 1 ,函数内部的变量 a 赋值为 1 ,然后再进行 a = 3 的操作 ,此时,在函数的局部作用域的栈内存中有一个变量 a 它的值是 3 ;而在全局作用域的栈内存中,也存在一个变量 a ,它的值是 1 ,这两个变量是两个不同的变量,只是它们的名字都是 a 而已。

过程如图:

示例3

  var a = 1;
function f(b) {
a = 3;
b = 10;
}
f(a);
console.info(a); // 3
console.info(b); // 报错

解析:

下面我们再来看一下示例3,先看变量 a ,完全和示例1一样,这里就不再详细说明了;再看变量 b ,其实也是和示例2没任何不一样的地方的,只是在这里把形参的名字改变了一下而已,在最后我们输出 a 的时候,没有任何问题,结果是 3 ,但是,访问变量 b 的时候,输出会报错,这里涉及到的就是作用域问题了:在函数内部定义的变量,在函数内部可以访问,而在函数的外部是无法访问的。

具体过程可看下图:

示例4

  var a = 1;
function f(a) {
a = 3;
b = 10;
}
f(a);
console.info(a); // 1
console.info(b); // 10

解析:

综合前三个示例,示例4我想大家都应该没有什么问题了,我们直接来看图吧:

引用类型参数传递

下面我们再来两个引用类型参数的示例;以下示例仅为说明引用类型传参之后,函数内部的赋值变化,所以用的都是简单数组进行说明。

其实,引用类型参数的传递需要考虑的就是引用类型和原始类型之间的区别:

  1. 引用类型在预解析时候,和原始类型一样都会在栈区里分配到空间,生成变量;但是赋值的时候,原始类型的赋值依然保存在栈内存当中,而引用类型就会在堆内存里占用一定空间存放它的数据,而在栈区里生成一个指向堆区的地址。
  2. 对变量进行复制的时候,原始类型的复制是在栈内存当中生成一份一样的数据,可以理解为“完全复制”;而引用类型的复制,只是在栈内存中进行复制,两个变量的地址同时指向堆内存中的那一份数据。

示例5

var a = [1];
function f(a){
a[100] = 3;
}
f(a);
console.info(a); // [1,100:3]

解析:

第一步:全局栈区中生成变量 a ,赋值后,在全局堆区里生成数组 [1]

第二步:传参,调用 f(a) ,首先在函数栈区中生成变量 a ,此时值为 undefined ,然后把全局变量 a 的值赋给局部函数里的变量 a ,因为是引用类型的数组,所以,函数里的 a 在栈区里也生成一个指向全局堆里的相同 地址1

第三步:执行函数里的代码 a[100] = 3 ,找到函数栈中的 a ,再对它进行赋值,改变了全局堆中的 [1] ,得到了一个新的数组 [1,100:3]

第四步:访问全局作用域中的变量 a ,得到指向堆内存中的数组 [1,100:3]

示例6

var a = [1];
function f(b){
b[100] = 3;
b = [1,2,3];
console.info(b); // [1,2,3]
}
f(a);
console.info(a); // [1,100:3]

解析:

该示例的关键点在第四步,执行到代码 b = [1,2,3] 的时候,会对函数里面的变量 b 重新进行一次赋值,这样会在函数的堆内存中生成一个新的 数组,全局的 a 和函数中的 b 的指向地址就不一样了,所以,它俩的输出就不一样了。

总结

在 js 中,原始类型是按值传递的;引用类型是按共享传递的。

按值传递 - call by value

一个外部变量传递给一个函数时,函数实参获取到的实际上是这个外部变量的副本,在函数内部我们对实参进行的修改并不会反应到这个外部变量上。

按引用传递 - call by reference

一个外部变量传递给一个函数时,函数实参实际上是这个外部变量的一个引用,我们在函数内部对这个实参的任何修改,都会反应到这个外部变量。

按共享传递 - call by sharing

js学习之函数的参数传递的更多相关文章

  1. js学习之函数表达式及闭包

    来自<javascript高级程序设计 第三版:作者Nicholas C. Zakas>的学习笔记(七) 直接切入主题~ 定义函数的方式有两种: 函数声明 function functio ...

  2. js学习之函数声明与函数表达式区别[原创]

    作为一名js初学者,与大家分享下.Javascript中有函数声明提升的功能,会优先编译函数声明部分.比如, ff(); function ff(){ alert("hello world. ...

  3. js学习之函数

    1/.js中函数就是对象. 2/以表达式方式定义的函数一般不要函数名以使代码紧凑. 3/js中函数声明的方式会被默认提到外部脚本或最前面,所以在其定义前的代码也可调用. 然而以表达式方式的则不行. 4 ...

  4. js学习笔记 -- 函数

    js函数有类似javaMethod用法 Math.max.apply( Math.max.call( Array map,reduce,filter,sort , , , , , , , , ]; v ...

  5. JS学习之函数内部属性和方法

    知识点:arguments和this对象.caller属性.apply()和call()方法     arguments对象:函数内部对象,传入函数中所有参数的集合,类数组对象 属性:callee 指 ...

  6. js学习-自定义函数、对象的字面量、json对象学习小结

    一.自定义对象的构造: var student=new Object(); //object是顶级对象,使用构造函数的方法创建一个对象,此处的意思是创建了一个学生的空对象 student.name=& ...

  7. js学习:函数

    概述 函数的声明 JavaScript 有三种声明函数的方法 function 命令 function命令声明的代码区块,就是一个函数.function命令后面是函数名,函数名后面是一对圆括号,里面是 ...

  8. node.js学习(2)函数

    1 简答函数 2 匿名函数 3 回调函数

  9. JS学习之函数的属性和方法

随机推荐

  1. IE6、7下html标签间存在空白符,导致渲染后占用多余空白位置的原因及解决方法

    直接上图:原因:该div包含的内容是靠后台进行print操作,输出的.如果没有输出任何内容,浏览器会默认给该空白区域添加空白符.在IE6.7下,浏览器解析渲染时,会认为空白符也是占位置的,默认其具有字 ...

  2. 踩石行动:ViewPager无限轮播的坑

    2016-6-19 前言 View轮播效果在app中很常见,一想到左右滑动的效果就很容易想到使用ViewPager来实现.对于像我们常说的banner这样的效果,具备无限滑动的功能是可以用ViewPa ...

  3. 探索ASP.NET MVC5系列之~~~6.Session篇(进程外Session)

    其实任何资料里面的任何知识点都无所谓,都是不重要的,重要的是学习方法,自行摸索的过程(不妥之处欢迎指正) 汇总:http://www.cnblogs.com/dunitian/p/4822808.ht ...

  4. iOS开发之Alamofire源码深度解析

    今天博客中的Alamofire源码的版本是以现在最新的3.4版本为例.上篇博客系统的对NSURLSession相关的东西进行了详细的解析,详情请看<详解NSURLSession>,为了就是 ...

  5. 代码的坏味道(14)——重复代码(Duplicate Code)

    坏味道--重复代码(Duplicate Code) 重复代码堪称为代码坏味道之首.消除重复代码总是有利无害的. 特征 两个代码片段看上去几乎一样. 问题原因 重复代码通常发生在多个程序员同时在同一程序 ...

  6. BPM配置故事之案例5-必填与水印文本

    物资申请表改好了,但是没过两天老李又找来了. 老李:这个表格每次都是各个部门发给我们,再由我们采购部来填,太影响效率了,以后要让他们自己填. 小明:那就让他们填呗,他们有权限啊. 老李:可是他们说不会 ...

  7. 亡命之徒aaaaaa.......chao

    前端是一个看似入门门槛不高,但要学好很难的领域.前端的知识体系庞杂又松散,技术演进快,如果摸不清脉络的话很容易陷入盲人摸象的困境甚至跑偏.其实只要掌握了正确的方法,学习前端和学好前端就只是个时间问题. ...

  8. 【初探Spring】------Spring IOC(二):初始化过程---简介

    首先我们先来看看如下一段代码 ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultList ...

  9. 利用AOP写2PC框架(二)

    AOP的底层已经封装好了以后,我们就要开始针对应用层写具体的业务逻辑了. 也就是说我们需要有个类继承于AopProxyBase,并且重写其After,Bofore以达到我们的拦截记录的功能.代码如下: ...

  10. 埃尔米特插值问题——用Python进行数值计算

    当插值的要求涉及到对插值函数导数的要求时,普通插值问题就变为埃尔米特插值问题.拉格朗日插值和牛顿插值的要求较低,只需要插值函数的函数值在插值点与被插函数的值相等,以此来使得在其它非插值节点插值函数的值 ...