JavaScript中,变量传递的本质
- 函数参数传递是按值传递的方式
虽然对象和数组这类引用类型传递的是引用的副本,但引用本身的值其实是对象在内存中的地址。
function createObject() {
let obj = { value: 1 };
const result = obj;
obj = null;
return result;
}
const myObj = createObject();
console.log(myObj); // 输出: { value: 1 }
在这个示例中,createObject 函数内部的obj = null
操作不会影响到调用者接收的 myObj
变量,因为 myObj 持有的是对象的引用副本,而非 obj 变量本身。
另外有代码
function createSubWindow (options, routerPath) {
const defaultOptions = {
width: 400,
height: 300,
parent: BrowserWindow.getFocusedWindow(),
modal: true,
minimizable: false,
maximizable: false,
resizable: false,
atuoHideMenuBar: true,
show: false, // 先隐藏窗口,on('ready-to-show')事件后再显示
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
}
options = {...defaultOptions, ...options};
const subWindow = new BrowserWindow(options);
if(process.env['VITE_DEV_SERVER_URL']){
subWindow.loadURL(process.env['VITE_DEV_SERVER_URL'] + routerPath) // 开发环境下指定路径
}else{
subWindow.loadFile(path.resolve(__dirname,"../dist/index.html")) // 打包才有用 因为开始时没有dist文件夹
}
subWindow.once('ready-to-show', () => subWindow.show()); // 窗口准备好后再显示
subWindow.on('closed', () => {
subWindow = null;
});
return subWindow;
}
其中设置subWindow=null
没有意义,因为首先它不会影响传出的引用subWindow
(相当于只是把局部变量内存地址设置为空),其次这个局部变量在函数体执行完毕后会被自动销毁
其他的变量传递规则
在 JavaScript 里,数据类型可分为基本类型(像 undefined
、null
、boolean
、number
、string
、symbol
以及 bigint
)和引用类型(例如 Object
、Array
、Function
等)。它们在赋值和传递时的表现存在明显差异:
基本类型:始终传递值的副本
- 赋值操作:赋值时会创建一个新值,原变量和新变量之间没有关联。
- 函数参数:传入的是值的副本,函数内部对参数的修改不会影响外部变量。
示例:
let a = 5;
let b = a; // b 得到的是 a 的值的副本
b = 10; // 不会影响 a 的值
function changeValue(num) {
num = 20; // 只修改了参数的副本,不会影响外部的 x
}
let x = 15;
changeValue(x);
console.log(x); // 输出 15
引用类型:传递引用而非副本
- 赋值操作:赋值时传递的是对象的引用,而非对象本身。新变量和原变量指向同一个对象。
- 函数参数:传入的是引用的副本,函数内部对参数的修改会影响原对象。
示例:
let obj1 = { name: 'Alice' };
let obj2 = obj1; // obj2 得到的是 obj1 的引用
obj2.name = 'Bob'; // 修改了同一个对象
console.log(obj1.name); // 输出 'Bob'
function changeName(person) {
person.name = 'Charlie'; // 修改了原对象
}
changeName(obj1);
console.log(obj1.name); // 输出 'Charlie'
创建引用类型副本的方法
若想得到引用类型的副本,而非引用,可采用以下方法:
浅拷贝:仅复制一层属性
-
Object.assign()
:let original = { a: 1, b: { c: 2 } }; let copy = Object.assign({}, original); copy.b.c = 3; // 修改嵌套对象会影响原对象
-
扩展运算符(Spread):
let copy = { ...original }; // 对象 let arrCopy = [...originalArray]; // 数组
-
Array.slice()
:let arr = [1, 2, [3]]; let copy = arr.slice(); copy[2][0] = 4; // 修改嵌套数组会影响原数组
深拷贝:递归复制所有嵌套属性
-
JSON.parse(JSON.stringify()):
不过,这种方法存在局限性,像函数、let deepCopy = JSON.parse(JSON.stringify(original));
undefined
、Symbol
以及循环引用等情况都无法处理。 -
递归函数:
function deepClone(obj) { if (obj === null || typeof obj !== 'object') return obj; let clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key]); } } return clone; }
-
第三方库:
可以使用如 Lodash 的_.cloneDeep()
等工具。
总结
- 基本类型:赋值和传参时传递的是值的副本。
- 引用类型:赋值和传参时传递的是引用,除非主动创建副本。
- 浅拷贝:仅复制对象的一层属性,嵌套对象仍为引用。
- 深拷贝:递归复制所有嵌套属性,生成完全独立的对象。
理解这些规则有助于避免代码中出现意外的副作用,特别是在处理复杂数据结构时。
let a = [1, 2,3]
let b = a
b.length = 0
b = []
console.log('a:', a)
b确实是a的引用,二者指向同一个内存对象,但b = []并不能清空原数组,而是将b指向了一个空数组,b.length = 0
才能清空a b
指向的原数组对象