2025-04-08【JavaScript】变量传递的本质

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 里,数据类型可分为基本类型(像 undefinednullbooleannumberstringsymbol 以及 bigint)和引用类型(例如 ObjectArrayFunction 等)。它们在赋值和传递时的表现存在明显差异:

基本类型:始终传递值的副本

  • 赋值操作:赋值时会创建一个新值,原变量和新变量之间没有关联。
  • 函数参数:传入的是值的副本,函数内部对参数的修改不会影响外部变量。

示例

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));
    
    不过,这种方法存在局限性,像函数、undefinedSymbol 以及循环引用等情况都无法处理。
  • 递归函数
    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指向的原数组对象

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容