You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何禁止全局访问模块变量但允许其他模块访问?(JavaScript)

井字棋项目模块封装问题

我是JavaScript新手,正在开发一个井字棋项目。根据项目规范,我采用了模块模式:单一实例用模块实现,多实例用工厂创建。

目前我在gameBoard模块内创建了gameBoardArr数组存储棋局配置,但全局作用域能直接访问到这个数组。我需要禁止全局访问gameBoardArr,只允许displayController模块访问并修改它。


现有代码

gameBoard模块代码

let gameBoard = (function(){
    'use strict';
    var gameBoardArr = ["","","","","","","","",""];


    var gameBoardDisplay = () => {

        let gameboxes = [...document.querySelectorAll(".td")];
        //console.log(gameboxes)
        gameboxes.forEach(Element=> {
            let index = Element.id;

            //console.log(gameBoard.gameBoardArr);
            if (gameBoard.gameBoardArr[index] == "X"){
                Element.textContent = "⨉"
            } else if (gameBoard.gameBoardArr[index] == "O") {
                Element.textContent = "◯"
            } else {
                Element.textContent = gameBoard.gameBoardArr[index];
            }
            
        })

    }

    return {gameBoardDisplay, gameBoardArr};
})();

player工厂代码

let gamePlayer = (playerSymbol) => {
    let player = playerSymbol;

    return {player}
}

displayController模块代码

let displayController = (function () {


    gameBoard.gameBoardDisplay();

    let _count = 0;
    
    let _gameDiv = document.querySelector(".game-board");

    let _welcomePanel = document.querySelector(".welcome");

    let _selectionBtn1 = document.querySelector(".btn-1");
    let _selectionBtn2 = document.querySelector(".btn-2");

    var _resetBoard = () => {
        gameBoard.gameBoardArr = ["","","","","","","","",""];
        gameBoard.gameBoardDisplay();
        _count = 0;
    }

    var _congratulateWinner = (winnerName) => {


        
        playNextRoundBtn = document.createElement("button");
        playNextRoundBtn.classList.add("play-next-round")
        playNextRoundBtn.textContent = "Play Next Round"

        //console.log("winnerbox");
        winnerBox = document.querySelector(".congratulations");
        winnerBox.textContent = "";

        fullscreenbox = document.querySelector(".full-screenbox");
        fullscreenbox.classList.remove("dont-show");
        congratulationMsg = document.createElement("h1");

        if (winnerName == "DRAW") {
            congratulationMsg.textContent = `Game Drawn`;
        } else {
            congratulationMsg.textContent = `congratulations ${winnerName}!`
        };
        
    
        //console.log(referenceNode);
        winnerBox.appendChild(congratulationMsg);
        winnerBox.appendChild(playNextRoundBtn);

        setTimeout(function(){
            playNextRoundBtn.addEventListener("click", () => {
                setTimeout(5000);
                fullscreenbox.classList.add("dont-show");
            });
        }, 50);
        
        

    };

    var _checkWinner = (whatMark) => {
    
        //console.log(arr);
        let winningConfigs = [
            [2, 5, 8],
            [0, 4, 8],
            [0, 1, 2],
            [0, 3, 6],
            [1, 4, 7],
            [2, 4, 6],
            [3, 4, 5],
            [6, 7, 8]
        ]

        for (i = 0; i < winningConfigs.length; i ++) {
            //console.log(winningConfigs[i][0])

            //console.log(arr[winningConfigs[i][0]]);
            //console.log(whatMark);
            //console.log(arr[winningConfigs[i][0]] == whatMark);
            if (gameBoard.gameBoardArr[winningConfigs[i][0]] == whatMark && gameBoard.gameBoardArr[winningConfigs[i][1]] == whatMark && gameBoard.gameBoardArr[winningConfigs[i][2]] == whatMark) {

                return "win"
            } ;

            
        };
        

    };
    function _nextStep() {
        _gameDiv.classList.remove("dont-show");
        _welcomePanel.classList.add("dont-show");
    }

    function _HumanVHuman() {
        player1 = gamePlayer("X");
        player2 = gamePlayer("O");

        chanceFlag = true;

        cells = [...document.querySelectorAll(".td")];
        
        cells.forEach(cell =>{
            cell.addEventListener("click", ()=>{

                if (chanceFlag && gameBoard.gameBoardArr[cell.id] == "") {
                    gameBoard.gameBoardArr[cell.id] = "X";
                    _count = _count + 1
                    //console.log(gameBoard.gameBoardArr);
                    chanceFlag = false;
                } else if (!chanceFlag && gameBoard.gameBoardArr[cell.id] == ""){
                    gameBoard.gameBoardArr[cell.id] = "O";
                    _count = _count + 1
                    chanceFlag = true;
                }
                
                gameBoard.gameBoardDisplay();
                //console.log(chanceFlag);
                //console.log(gameBoard.gameBoardArr)
                setTimeout(function(){
                    let winStatus = _checkWinner(chanceFlag?"O":"X");

                    if (winStatus == "win" && chanceFlag == false) {
                        _congratulateWinner("Player 1");

                        _resetBoard();
                    } else if (winStatus == "win" && chanceFlag == true) {
                        _congratulateWinner("Player 2");
                        _resetBoard();
                        
                    } else if (_count == 9) {
                        console.log("first here");
                        _congratulateWinner("DRAW");
                        _resetBoard();
                    };
                }, 50);

                
                //console.log(_count);
                
                

            });
        });
    };

    function startGame() {
        _selectionBtn1.addEventListener("click", ()=>{
            setTimeout(function(){
                let gameMode = "HVH";
                _nextStep();
                _HumanVHuman();
                //console.log(gameMode);
                }, 50);   
        });
        
        _selectionBtn2.addEventListener("click", ()=>{
            let gameMode = "HVA";

            //console.log(gameMode);

        });
    };
    
    return {startGame};

})();

全局作用域代码

displayController.startGame();

解决方案:封装棋盘操作接口

核心思路是不直接暴露gameBoardArr,而是提供专用的操作接口,让displayController通过接口完成棋盘读写、重置、胜负判断等操作,同时完全隔离全局作用域的访问权限。

修改后的gameBoard模块

let gameBoard = (function(){
    'use strict';
    // 完全封装在模块内部,外部无法直接访问
    var gameBoardArr = ["","","","","","","","",""];

    var gameBoardDisplay = () => {
        let gameboxes = [...document.querySelectorAll(".td")];
        gameboxes.forEach(Element=> {
            let index = Element.id;
            if (gameBoardArr[index] == "X"){
                Element.textContent = "⨉"
            } else if (gameBoardArr[index] == "O") {
                Element.textContent = "◯"
            } else {
                Element.textContent = gameBoardArr[index];
            }
        })
    }

    // 获取指定位置的棋盘值
    const getCell = (index) => {
        return gameBoardArr[index];
    }

    // 设置指定位置的棋盘值,带合法性校验
    const setCell = (index, value) => {
        if (index >= 0 && index < 9 && gameBoardArr[index] === "") {
            gameBoardArr[index] = value;
            gameBoardDisplay(); // 设置后自动更新显示
            return true; // 设置成功返回true
        }
        return false; // 设置失败返回false
    }

    // 重置棋盘
    const resetBoard = () => {
        gameBoardArr = ["","","","","","","","",""];
        gameBoardDisplay();
    }

    // 校验指定标记是否获胜
    const checkWinner = (mark) => {
        const winningConfigs = [
            [2, 5, 8],
            [0, 4, 8],
            [0, 1, 2],
            [0, 3, 6],
            [1, 4, 7],
            [2, 4, 6],
            [3, 4, 5],
            [6, 7, 8]
        ];
        return winningConfigs.some(config => {
            return config.every(index => gameBoardArr[index] === mark);
        });
    }

    // 只暴露操作接口,不暴露原始数组
    return { gameBoardDisplay, getCell, setCell, resetBoard, checkWinner };
})();

修改后的displayController关键部分

// 重置棋盘改为调用接口
var _resetBoard = () => {
    gameBoard.resetBoard();
    _count = 0;
}

// 移除原有的_checkWinner函数,改用gameBoard的接口

function _HumanVHuman() {
    player1 = gamePlayer("X");
    player2 = gamePlayer("O");

    chanceFlag = true;

    cells = [...document.querySelectorAll(".td")];
    
    cells.forEach(cell =>{
        cell.addEventListener("click", ()=>{
            let success = false;
            // 通过getCell判断位置是否为空
            if (chanceFlag && gameBoard.getCell(cell.id) === "") {
                success = gameBoard.setCell(cell.id, "X");
                if (success) {
                    _count++;
                    chanceFlag = false;
                }
            } else if (!chanceFlag && gameBoard.getCell(cell.id) === ""){
                success = gameBoard.setCell(cell.id, "O");
                if (success) {
                    _count++;
                    chanceFlag = true;
                }
            }
            
            if (success) {
                setTimeout(function(){
                    let currentMark = chanceFlag ? "O" : "X";
                    // 调用gameBoard的checkWinner接口
                    let winStatus = gameBoard.checkWinner(currentMark);

                    if (winStatus && chanceFlag === false) {
                        _congratulateWinner("Player 1");
                        _resetBoard();
                    } else if (winStatus && chanceFlag === true) {
                        _congratulateWinner("Player 2");
                        _resetBoard();
                    } else if (_count === 9) {
                        _congratulateWinner("DRAW");
                        _resetBoard();
                    };
                }, 50);
            }
        });
    });
};

修改后,gameBoardArr完全被封装在模块内部,全局作用域无法直接访问,同时displayController可以通过暴露的接口正常完成所有游戏逻辑。


内容的提问来源于stack exchange,提问作者Shiva Kharbanda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 06:10:58