看高程的时候,看到这么一个概念:“ECMAScript 中所有函数的参数都是按值传递” 。对于基本数据类型都好理解,但是对于引用数据类型,当时觉得就是按照引用类型传递的,没有搞懂说的按值传递是什么意思。
直到看技术博客的时候看到这么一句话让我豁然开朗:函数的参数传递,传递的都是值,参数是 Object 类型的也一样,也是值,只不过这个值是地址值。
针对于这个知识点,做了一个扩散,比较详细的分析了这个概念问题,在自己深入理解的同时也希望能帮到同样不是很理解的开发人员
js数据类型
首先,我们知道js存在两大类型的值:基本数据类型,和引用数据类型。
基本数据类型的值包括:String、Number、Boolean、Null、undefined
引用数据类型的值包括:Object、Array、Function
我们可以通过 typeof 来判断当前的值是什么数据类型
var num = 1;
console.log(typeof num);//返回的是number
var str = 'jack';
console.log(typeof str);//返回的是string
var boo =true;
console.log(typeof boo);//返回的是boolean
var und ;
console.log(typeof und);//返回的是undefined
var nul =null;
console.log(typeof null); //返回的是object
可以得出:基本数据类型在调用 typeof 判断类型的时候,除了 null 这个类型会返回 object 类型,其他的都是返回的是自己对应的类型
// 通过构造函数实例化的数组
var arr = new Array();
console.log(typeof arr); //返回的是object
// 通过字面亮创建的数组
var arr2 = [1,32];
console.log(typeof arr2);//返回的是object
var fn = function(){};
console.log(typeof fn); //返回的是function
var obj = new Object();
console.log(typeof obj); //返回的是object
var obj2 = {a:1};
console.log(typeof obj2); //返回的是object
可以得出:复杂数据类型,除了函数 function 调用typeof 的时候,会返回 function ,其他的都会返回 object
变量复制
js规定了,基本数据类型的值,是可以直接通过变量操作这个值的。但是引用数据类型的值,是不能被直接操作的。这是因为,引用数据类型的值,是保存在内存中的,js不能直接访问内存中的某个位置。我们改变这个对象的时候,都是通过引用这个对象在内存中的地址去改变它的,
先声明一个变量,赋值一个 Number 类型
var num1 = 5
接着声明另一个变量,复制 num1 的值给它
var num2 = num1
然后改写 num1 的值
num1 = 6
分别打印 num1、num2
console.log( num1 ) // 6
console.log( num2 ) // 5
可以得出:在接收基本类型的值的变量被当作数据赋值给另外的变量的时候,只是单纯复制了这个变量所携带的值,并不是复制了这个变量。这两个变量的值是完全不相关的。
基本类型的值可能比较好理解,但是引用数据类型的值可能比较麻烦一点
先声明一个变量,通过字面量方式赋值一个对象
var obj1 = {"a":"a"}
接着声明一个变量,复制 obj1 的值给它
var obj2 = obj1
给 obj1新增一个属性
obj1.b = "b"
分别打印 obj1、obj2
console.log( obj1 ) // {a: "a", b: "b"}
console.log( obj2 ) // {a: "a", b: "b"}
可以得出:在对引用数据类型进行复制然后赋值的过程中,不论是对原来的变量进行修改,还是对赋值的变量进行修改,最后会发现,两个变量都是被修改之后的状态,这说明了,引用数据类型在被复制的过程中,和基本类型数据不一样,并不是单纯的复制了值,而是复制了这个数据的引用地址,指向的都是这个引用数据的内存空间,只要一个变量改变了,那么所有指向这个引用数据的地址的变量都会发生改变
函数形参
在函数中我们肯定知道,一个函数的形参,就相当于在函数顶部声明了一个未赋值的变量,可以举个例子
function setName (obj) {
console.log("obj:" + obj)
}
setName() // 打印 obj:undefined
明显的,我们这个函数内部只有一个 console ,并没有定义obj 这个变量,但是我打印的时候,并没有报错,而是undefined,这就说明了,obj 这个变量其实是存在的。
往上追溯,函数体内部不存在 obj 这个变量,但是函数的形参内部有一个 obj 的参数,其实就很明显了,这个函数就相当于在函数体内部最顶层的位置,隐式的声明了一个 obj 变量,但是没有赋值,所以打印的时候 obj 变量存在,但是是 undefined
function setName (obj) {
let obj; // 隐式的声明,但是不赋值,默认就是undefined
console.log("obj:" + obj)
}
setName() // 打印 obj:undefined
函数传参-传值
函数的参数传递,传递的都是值,参数是 Object 类型的也一样,也是值,只不过这个值是地址值。
根据上面的情况,下面的传值就好理解一点了,还是先用基本类型说明
通过字面量创建一个 值 为 基本数据类型的字符串 aaaa 的变量
var personal = "aaaa"
然后声明一个函数,且在函数内部重新给 obj 赋值
function setName (obj) {
obj = "1234"
}
将 变量当作参数传入 到函数中
setName(personal)
打印 personal
alert(personal) // aaaa
按照上面说的函数参数问题,我们可以把这个函数看作是这样的
function setName (obj) {
var obj;
obj = "1234"
}
当我传入定义的 字面量 personal 的时候,其实是复制了 personal 的值,然后传递到函数的时候,形参 obj 就相当于也复制了 personal 的值,然后 继续 赋值给 函数体内部 已经隐式定义但还未赋值 的 obj 变量,此时 obj = "aaaa",当我打印 personal 的时候,personal 的值是肯定没有改变的,但是当我在函数内部打印 obj 的时候,可以看到 函数内部的 obj 是变化了的
var personal = "aaaa"
function setName (obj) {
var obj ; // 形参 传递进来之后 obj = “aaaa”
obj = "1234"; //给 变量 obj 重新赋值
console.log(obj) // "1234" obj是变化了的
}
setName(personal)
console.log( personal ) // "aaaa"
可以得出:当接收基础数据类型的值的变量,作为参数传递到函数中的时候,传递的是这个变量的值,并不是这个变量,这个参数一旦作为形参 ,传递到了函数体内部,就和 外面的 变量毫无关系。
将引用数据类型,作为参数 传递到函数中,这一类情况可能比较难理解,我们先看第一个例子
var person = {
name : "leaf"
};
function obj(o){
o.name = "kafu";
return o;
}
var result = obj(person);
console.log(result.name);// kafu
console.log(person.name);// kafu
很起怪的是,这个情况怎么看都是传递的引用啊,personal 的属性 都随之改变了,这说不过去啊。接下来我们看第二个例子,或许你就明白了
var person = {
name : "leaf"
};
function obj(o){
o = {
name : "kafu"
};
return o;
}
var result = obj(person);
console.log(result.name);// kafu
console.log(person.name);// leaf
可以看到,personal 的值 并没有随之改变。这么对比,区别就出来了。
第一个例子中,传递了一个对象,然后函数体顶部 声明了 变量 o ,函数执行的时候,o 被赋值为 personal,此时 根据上面的变量复制的结论,这个 personal 实际上还是指向的是 内存中的 唯一地址。将 personal 赋值 给 o 之后,相当于 o 也指向了内存中的 personal 的地址,所以,o 改变了之后,personal 也会改变,看起来就是 按引用传递的, 但是实际上传递的是地址值。
但是在第二个例子中,我将 变量 o 重新赋值了,其实就相当于,o 的指向地址已经是一个新的了,不是 personal 的地址,所以改变 o 不会对 personal 造成影响。
其实对于基本类型,参数传递的就是变量的值,对于引用类型,参数的传递是传递对象的地址值。这拷贝地址其实也可以理解为按值传递了。