Затенение(перекрытие) переменой в различных языках программирования (variable shadowing)

Лекция



В программировании перекрытие переменных происходит , когда переменная, объявленная в определенной области видимости (блоке принятия решений, методе или внутреннем классе ), имеет то же имя, что и переменная, объявленная во внешней области видимости. На уровне идентификаторов (имен, а не переменных) это называется маскированием имен . Говорят, что эта внешняя переменная перекрывается внутренней переменной, а внутренний идентификатор маскирует внешний идентификатор. Это может привести к путанице, поскольку может быть неясно, к какой переменной относятся последующие использования перекрываемого имени переменной, что зависит от правил разрешения имен в языке.

Одним из первых языков, представивших затенение переменных, был ALGOL , который впервые ввел блоки для установления областей видимости. Это также допускалось многими производными языками программирования, включая C , C++ и Java .

Язык C# нарушает эту традицию, допуская переопределение переменных между внутренним и внешним классами, а также между методом и содержащим его классом, но не между блоком if и содержащим его методом, или между операторами case в блоке switch .

Некоторые языки допускают переопределение переменных в большем количестве случаев, чем другие. Например, Kotlin позволяет внутренней переменной в функции переопределять переданный аргумент, а переменной во внутреннем блоке — другую переменную во внешнем блоке, в то время как Java этого не допускает (см. пример ниже ). Оба языка позволяют переданному аргументу в функцию/метод переопределять поле класса.

В некоторых языках полностью запрещено затенение переменных, например, в CoffeeScript и V (Vlang) .

Пример

Луа

Приведенный ниже код на Lua демонстрирует пример переопределения переменных в нескольких блоках.

v = 1 -- a global variable

do
  local v = v + 1 -- a new local that shadows global v
  print(v) -- prints 2

  do
    local v = v * 2 -- another local that shadows outer local v
    print(v) -- prints 4
  end

  print(v) -- prints 2
end

print(v) -- prints 1

Python

Следующий код на Python демонстрирует еще один пример переопределения переменных:

x = 0

def outer():
    x = 1

    def inner():
        x = 2
        print("inner:", x)

    inner()
    print("outer:", x)

outer()
print("global:", x)

# prints
# inner: 2
# outer: 1
# global: 0

Поскольку в Python нет объявления переменных, а есть только присваивание, ключевое слово `continuous`, nonlocalвведенное в Python 3, используется для предотвращения переопределения переменных и присваивания значений нелокальным переменным:

x = 0

def outer():
    x = 1

    def inner():
        nonlocal x
        x = 2
        print("inner:", x)

    inner()
    print("outer:", x)

outer()
print("global:", x)

# prints
# inner: 2
# outer: 2
# global: 0

Это ключевое слово global используется для предотвращения переопределения переменных и присваивания значений глобальным переменным:

x = 0

def outer():
    x = 1

    def inner():
        global x
        x = 2
        print("inner:", x)

    inner()
    print("outer:", x)

outer()
print("global:", x)

# prints
# inner: 2
# outer: 1
# global: 2

Rust

fn main() {
    let x = 0;
    
    {
        // Shadow
        let x = 1;
        println!("Inner x: {}", x); // prints 1
    }
    
    println!("Outer x: {}", x); // prints 0
    
    let x = "Rust";
    println!("Outer x: {}", x);  // prints 'Rust'
}

//# Inner x: 1
//# Outer x: 0
//# Outer x: Rust

C++

#include 

int main()
{
  int x = 42;
  int sum = 0;

  for (int i = 0; i < 10; i++) {
    int x = i;
    std::cout << "x: " << x << '\n'; // prints values of i from 0 to 9
    sum += x;
  }

  std::cout << "sum: " << sum << '\n'; // prints 45
  std::cout << "x:   " << x   << '\n'; // prints 42

  return 0;
}

Java

public class Shadow {
    private int myIntVar = 0;

    public void shadowTheVar() {
        // Since it has the same name as above object instance field, it shadows above 
        // field inside this method.
        int myIntVar = 5;

        // If we simply refer to 'myIntVar' the one of this method is found 
        // (shadowing a second one with the same name)
        System.out.println(myIntVar); // prints 5

        // If we want to refer to the shadowed myIntVar from this class we need to 
        // refer to it like this:
        System.out.println(this.myIntVar); // prints 0
    }

    public static void main(String[] args){
        new Shadow().shadowTheVar();
    }
}

Однако следующий код не скомпилируется:

public class Shadow {
    public static void main(String[] args){
        int a = 1;

        for(int i = 0; i < 10; i++) {
            // This causes a compilation error since redefining a variable
            // inside a nested block in the same function is not allowed.
            int a = i;
            System.out.println(a);
        }
    }
}

JavaScript

letВ ECMAScript 6 введена возможность использования constблочной области видимости, что позволяет перекрывать переменные.

function myFunc() {
    let my_var = 'test';
    if (true) {
        let my_var = 'new test';
        console.log(my_var); // new test
    }
    console.log(my_var); // test
}
myFunc();

Затенение(перекрытие) переменой в различных языках программирования (variable shadowing)

1. Области видимости в JavaScript

Чтобы понять shadowing, нужно помнить про scope.

Function scope

var имеет область видимости функции:

var x = 1;

function test() {
  var x = 2;
  console.log(x);
}

test(); // 2
console.log(x); // 1

Block scope

let и const имеют область видимости блока:

let value = 10;

if (true) {
  let value = 20;
  console.log(value); // 20
}

console.log(value); // 10

Блок — это обычно { ... }.

2. Простые примеры variable shadowing

Shadowing в функции

let count = 5;

function increment() {
  let count = 0;
  count++;
  console.log(count);
}

increment(); // 1
console.log(count); // 5

Может быть ошибкой, если разработчик хотел изменить внешний count, но случайно создал новый.

Правильный вариант, если нужно менять внешнюю переменную:

let count = 5;

function increment() {
  count++;
}

increment();
console.log(count); // 6

Shadowing в блоке

const status = "idle";

{
  const status = "loading";
  console.log(status); // loading
}

console.log(status); // idle

Это допустимо и иногда полезно, но может ухудшать читаемость.Shadowing параметров функции

const id = 100;

function getUser(id) {
  console.log(id);
}

getUser(42); // 42

Параметр id затеняет внешний id.

Обычно это нормально, но если во внешнем scope тоже есть важный id, код может стать неочевидным.

3. Главная проблема: путаница между “изменить” и “создать новую”

Частая ошибка:

let isLoggedIn = false;

function login() {
  let isLoggedIn = true;
}

login();

console.log(isLoggedIn); // false

Разработчик мог ожидать true, но внутри функции была создана новая переменная.

Исправление:

let isLoggedIn = false;

function login() {
  isLoggedIn = true;
}

login();

console.log(isLoggedIn); // true

4. Shadowing с var, let и const

var ведет себя опаснее

var x = 1;

if (true) {
  var x = 2;
}

console.log(x); // 2

Здесь нет блочной области видимости, потому что var ограничен функцией, а не блоком.

С let поведение другое:

let x = 1;

if (true) {
  let x = 2;
}

console.log(x); // 1

Поэтому в современном JavaScript почти всегда лучше использовать let и const, а не var.

5. Illegal shadowing

В JavaScript есть случаи, когда затенение запрещено.

Например:

let x = 10;

{
  var x = 20; // SyntaxError
}

Почему ошибка? Потому что var не ограничивается блоком и фактически пытается объявить x в той же или конфликтующей области видимости, где уже есть let.

Но такой код допустим:

var x = 10;

{
  let x = 20;
  console.log(x); // 20
}

console.log(x); // 10

6. Shadowing и Temporal Dead Zone

let и const имеют Temporal Dead Zone — период от начала области видимости до фактического объявления переменной.

Пример:

let name = "Alice";

{
  console.log(name); // ReferenceError
  let name = "Bob";
}

Может показаться, что console.log(name) должен взять внешний name, но нет. Внутри блока уже существует локальная переменная name, просто она еще недоступна до строки объявления.

Правильно:

let name = "Alice";

{
  console.log(name); // Alice
}

или:

let name = "Alice";

{
  let localName = "Bob";
  console.log(localName); // Bob
}

7. Shadowing в циклах

let i = 100;

for (let i = 0; i < 3; i++) {
  console.log(i);
}

console.log(i);

Результат:

0
1
2
100

Это нормальный и часто приемлемый shadowing. Но в сложном коде одинаковые имена могут мешать понимать, о каком i идет речь.

8. Shadowing в callback-функциях

const user = { name: "Alice" };

users.map(user => {
  console.log(user.name);
});

Здесь параметр callback-функции user затеняет внешний user.

Лучше дать более точное имя:

const currentUser = { name: "Alice" };

users.map(listUser => {
  console.log(listUser.name);
});

Особенно это важно в React, Node.js и при работе с массивами.

9. Shadowing в catch

const error = "Global error";

try {
  throw new Error("Local error");
} catch (error) {
  console.log(error.message); // Local error
}

console.log(error); // Global error

Параметр catch (error) затеняет внешний error.

Лучше:

const globalError = "Global error";

try {
  throw new Error("Local error");
} catch (caughtError) {
  console.log(caughtError.message);
}

10. Shadowing импортов

import { format } from "./utils";

function render() {
  const format = "short";
  console.log(format);
}

Здесь локальная переменная format затеняет импортированную функцию format. Это может привести к багам:

import { format } from "./utils";

function render() {
  const format = "short";

  return format(new Date()); // TypeError: format is not a function
}

Лучше:

import { format } from "./utils";

function render() {
  const dateFormat = "short";

  return format(new Date(), dateFormat);
}

11. Shadowing глобальных объектов

Очень плохая практика — называть переменные как встроенные объекты:

const Array = [1, 2, 3];

const items = new Array(5); // TypeError

Или:

const console = "debug";

console.log("Hello"); // TypeError

Также лучше не использовать имена:

Object
Array
String
Number
Boolean
Promise
Date
Math
JSON
console
window
document
undefined
NaN
Infinity
setTimeout

12. Связанные аналогичные проблемы

12.1. Hoisting

var поднимается наверх функции:

console.log(x); // undefined
var x = 10;

Фактически JavaScript воспринимает это примерно так:

var x;
console.log(x);
x = 10;

С let и const иначе:

console.log(x); // ReferenceError
let x = 10;

Связь с shadowing: из-за hoisting и одинаковых имен можно получить неожиданное поведение.

12.2. Accidental global variable

Если забыть let, const или var, можно случайно создать глобальную переменную:

function test() {
  value = 123;
}

test();

console.log(value); // 123 в нестрогом режиме

Решение:

"use strict";

function test() {
  const value = 123;
}

И всегда использовать let / const.

12.3. Reassignment вместо shadowing

Иногда проблема обратная: разработчик думает, что создает новую переменную, но на самом деле меняет внешнюю.

let config = { theme: "light" };

function setup() {
  config.theme = "dark";
}

setup();

console.log(config.theme); // dark

Это не shadowing, потому что новой переменной config нет. Это изменение объекта по ссылке.

Если нужна независимая копия:

let config = { theme: "light" };

function setup() {
  const localConfig = { ...config, theme: "dark" };
  console.log(localConfig.theme); // dark
}

console.log(config.theme); // light

12.4. Name collision

Коллизия имен — более широкая проблема, когда разные сущности получают одинаковые имена.

function user() {
  // ...
}

const user = {
  name: "Alice"
}; // SyntaxError в одной области видимости

Решение — понятные имена:

function createUser() {
  // ...
}

const currentUser = {
  name: "Alice"
};

12.5. Closure bugs

Shadowing может запутывать замыкания:

let value = "global";

function outer() {
  let value = "outer";

  return function inner() {
    let value = "inner";
    console.log(value);
  };
}

outer()(); // inner

Если нужно обратиться к outer value, локальный value внутри inner мешает.

Исправление:

let value = "global";

function outer() {
  let outerValue = "outer";

  return function inner() {
    console.log(outerValue);
  };
}

outer()(); // outer

13. Когда shadowing допустим

Shadowing не всегда ошибка. Иногда он нормален.

Например, в маленьком scope:

function normalize(user) {
  return {
    ...user,
    name: user.name.trim()
  };
}

Или в callback:

const numbers = [1, 2, 3];

const doubled = numbers.map(number => number * 2);

Если имя очевидное и область видимости маленькая, это нормально.

14. Когда shadowing опасен

Shadowing стоит избегать, если:

  1. Внешняя переменная важна.
  2. Внутренний scope большой.
  3. Имя слишком общее: data, value, result, item, user.
  4. Есть вложенные функции.
  5. Есть импорт с таким же именем.
  6. Переменная используется в React-компоненте или async-коде.
  7. Код поддерживают несколько разработчиков.

Плохой пример:

const data = await fetchUsers();

function process() {
  const data = getLocalData();

  return data.map(data => {
    return data.value;
  });
}

Лучше:

const usersResponse = await fetchUsers();

function process() {
  const localUsers = getLocalData();

  return localUsers.map(user => {
    return user.value;
  });
}

15. Shadowing в React

Частая проблема:

function UserCard({ user }) {
  const [selectedUser, setSelectedUser] = useState(null);

  return users.map(user => (
    
setSelectedUser(user)}> {user.name}
)); }

Здесь user из map затеняет user из props.

Лучше:

function UserCard({ user: currentUser }) {
  const [selectedUser, setSelectedUser] = useState(null);

  return users.map(listUser => (
    
setSelectedUser(listUser)}> {listUser.name}
)); }

16. Shadowing в async-коде

let result;

async function load() {
  const result = await fetchData();
}

await load();

console.log(result); // undefined

Внутри функции создан локальный result, внешний не изменился.

Исправление:

let result;

async function load() {
  result = await fetchData();
}

await load();

console.log(result);

Но еще лучше — возвращать значение:

async function load() {
  return await fetchData();
}

const result = await load();

17. Способы решения

1. Использовать разные, более точные имена

Плохо:

const data = getData();

function save(data) {
  // ...
}

Лучше:

const usersData = getData();

function save(formData) {
  // ...
}

2. Уменьшать вложенность

Плохо:

function handle(user) {
  if (user.active) {
    users.forEach(user => {
      if (user.role === "admin") {
        console.log(user);
      }
    });
  }
}

Лучше:

function handle(currentUser) {
  if (!currentUser.active) return;

  users.forEach(teamUser => {
    if (teamUser.role === "admin") {
      console.log(teamUser);
    }
  });
}

3. Избегать var

Плохо:

var value = 1;

if (true) {
  var value = 2;
}

Лучше:

let value = 1;

if (true) {
  const localValue = 2;
}

4. Использовать ESLint

Полезное правило:

{
  "rules": {
    "no-shadow": "error",
    "no-redeclare": "error",
    "no-undef": "error"
  }
}

Для TypeScript:

{
  "rules": {
    "@typescript-eslint/no-shadow": "error"
  }
}

5. Делать маленькие функции

Чем меньше функция, тем меньше риск перепутать переменные.

Плохо:

function process(data) {
  // 100 строк кода
}

Лучше:

function normalizeUsers(users) {
  return users.map(normalizeUser);
}

function normalizeUser(user) {
  return {
    ...user,
    name: user.name.trim()
  };
}

18. Практическое правило

Хороший ориентир:

Shadowing допустим, если область видимости маленькая и имя очевидное.
Shadowing опасен, если переменная из внешнего scope тоже нужна или код становится неоднозначным.

19. Мини-чеклист

Перед тем как оставить одинаковое имя, спроси себя:

  1. Не хотел ли я изменить внешнюю переменную?
  2. Не затеняю ли я импорт?
  3. Не затеняю ли я глобальный объект?
  4. Не станет ли код непонятным через месяц?
  5. Не проще ли назвать переменную точнее?

20. Краткий пример “плохо → хорошо”

Плохо:

const user = getCurrentUser();

function render() {
  users.map(user => {
    console.log(user.name);
  });
}

Хорошо:

const currentUser = getCurrentUser();

function render() {
  users.map(listUser => {
    console.log(listUser.name);
  });
}

Итог: variable shadowing — это не всегда баг, но частый источник скрытых ошибок и плохой читаемости. Лучше избегать одинаковых имен в соседних и вложенных областях видимости, особенно в больших функциях, callback-ах, React-компонентах, async-коде и при работе с импортами.

Вау!! 😲 Ты еще не читал? Это зря!

создано: 2026-05-28
обновлено: 2026-05-28
1



Помог ли вам этот ответ?
Нажмите оценку и напишите коротко почему. Так мы сможем сделать следующие ответы точнее и полезнее.
Насколько вы довольны ответом?
Ваш отзыв напрямую влияет на качество следующих подсказок и ответов.


Поделиться:
Пожаловаться

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выполнения задания
  • Возможно применение функции гаранта на сделку
  • Приоритетная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можете продать (как исполнитель) или купить (как заказчик) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно применение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей

Комментарии

Оставить комментарий

Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Лекции и учебник по "Разработка программного обеспечения и информационных систем"

Термины: Разработка программного обеспечения и информационных систем