JavaScript中值传递,引用传递,函数传参,你学懂了吗?
快来补习基础知识
JS类型
JavaScript中5+1种基本数据类型:
String
、Number
、Boolean
、null
、undefined
、(Symbol
:ES6新基本数据类型)
引用类型:
除了上面的六种基本类型之外,其他的类型如Array
、Function
、Object
、Date
、RegExp
等都是引用类型。他们都通过引用传递,在技术底层上都是对象。
基本数据类型的值传递
如果一个基本的数据类型绑定到某个变量,我们可以认为该变量包含这个基本数据类型的值。
var x = 10;
var y = "abc";
var z = null;
当我们使用=将这些变量赋值到另外的变量,实际上是将对应的值拷贝了一份,然后赋值给新的变量。我们把它称作值传递。
var x = 10;
var y = "abc";
var a = x;
var b = y;
console.log(x, y, a, b); // 10, 'abc', 10, 'abc'
a
和x
都包含 10,b
和y
都包含’abc’,并且它们是完全独立的拷贝,互不干涉。如果我们将a的值改变,x不会受到影响。
var x = 10;
var y = "abc";
var a = x;
var b = y;
a = 5;
b = "def";
console.log(x, y, a, b); // 10, 'abc', 5, 'def'
- 栈内存中包括了变量的标识符和变量的值
引用类型的值传递
对于引用类型来说,也是值传递,不过传递的这个“值”是引用类型实例的地址。
//创建一个数组对象,地址=[0X001]
var obj = [1];
//将obj2 = [0x001]
var obj2 = obj;
//给[0x001]代表的对象执行push()
obj.push(2);
//所以二者输出的是一样的值,因为同时引用的是同一个对象
console.log(obj);
console.log(obj2);
- 栈内存中保存了变量标识符和指向堆内存中该对象的指针
- 堆内存中保存了对象的内容
函数参数
当我们对函数进行传参时,函数会将这些数据拷贝赋值给函数的参数变量:
- 对于基本数据类型的参数,传递的是变量的值。
- 对于引用类型的参数来说,传递的是对象的地址。
纯函数
纯函数:给定一个函数,给定输入,返回唯一输出,除此之外不会对外部环境产生任何附带影响。所有函数内部定义的变量在函数返回后都被垃圾回收掉。
但是如果函数传递的是一个对象,则传入的是一个地址值,则该变量的操作会影响到原本的对象,这样的编程手法将产生附带影响,使代码逻辑复杂和可读性变低。
为此,数组函数,比如Array.map
和Array.fiter
以纯函数出现。虽然参数是一个数组变量,但在内部通过深度拷贝并赋值给一个新变量,然后在新数组上操作,以防止原始数组被更改。
来看一个例子:
function changeAgeImpure(person) {
person.age = 25;
return person;
}
var alex = {
name: "Alex",
age: 30
};
var changedAlex = changeAgeImpure(alex);
console.log(alex); // { name: 'Alex', age: 25 }
console.log(changedAlex); // { name: 'Alex', age: 25 }
在非纯函数changeAgeImpure
中,将对象person
的age
更新并返回。原始的alex
对象也被影响,age
更新为 25。
让我们来看如何实现一个纯函数:
function changeAgePure(person) {
var newPersonObj = JSON.parse(JSON.stringify(person));
newPersonObj.age = 25;
return newPersonObj;
}
var alex = {
name: "Alex",
age: 30
};
var alexChanged = changeAgePure(alex);
console.log(alex); // { name: 'Alex', age: 30 }
console.log(alexChanged); // { name: 'Alex', age: 25 }
我们通过JSON.sringify
将对象变为一个字符串,然后再通过JSON.parse
将字符串变回对象。通过该操作会生成一个新的对象。
挑战一道经典面试题
值传递和引用传递是在js中极为重要知识,下面一道面试题检测你是否真正理解这个知识:
function changeAgeAndReference(person) {
person.age = 25;
person = {
name: "John",
age: 50
};
return person;
}
var personObj1 = {
name: "Alex",
age: 30
};
var personObj2 = changeAgeAndReference(personObj1);
console.log(personObj1); // -> ?
console.log(personObj2); // -> ?
题解
如果这道题你没有正确做出来,那你就有必要补习一下上面的基础知识。
//创建一个对象,地址[0x001]
var personObj1 = {
name: 'Alex',
age: 30
};
//将地址[0x001]通过personObj1传到函数内
var personObj2 = changeAgeAndReference(personObj1);
function changeAgeAndReference(person) {
//arguments[0]:var person = [0x001];即person = personObj1的内存地址
//将[0x001].age=25;
person.age = 25;
//arguments[0]开辟了新的堆内存空间[0x002],此时person = [0x002];
person = {
name: 'John',
age: 50
};
//如果此时执行这条语句,则[0x002].age = 99;
//person.age=99;
//返回person,即[0x002];
return person;
}
//[0x001]={ name: 'Alex', age: 25 }
console.log(personObj1);
//[0x002]={ name: 'John', age: 50 }
console.log(personObj2);
总结
- 基本数据类型传递变量值,引用类型传递地址值,二者都是值传递。
- 函数传参遵循上条规则。
- 如果你是急于求成直接看总结的:学习没有捷径,请一步一个脚印!