Javascript 在函数内创建的对象的变量范围

Javascript variable scope for objects created within function

考虑这段代码,

var getPerson = function() {
    var _name = "";
    var person = {};

    person.getName = function() {
        return _name;
    }

    person.setName = function(value) {
        _name = value;
    }

    return person;
}


p1 = getPerson();
p1.setName("foo");

p2 = getPerson();
p2.setName("bar");

console.log(p1.getName()); // logs "foo"
console.log(p2.getName()); // logs "bar"

有人可以在这里解释一下 _name 的范围吗? p1._name 和 p2._name 不存在。该变量真正存储在哪里?

_name 作用域为 getPerson 函数,因为它是使用 var 关键字声明的。

嵌套在 getPerson 中的任何函数都将继承在 getPerson 中声明的变量,并且它们可以覆盖它来声明自己的变量。 This concept is called closure 并且在对 JavaScript 进行任何认真的开发之前值得理解。

在Java脚本中,范围不是由块决定的(如在Java中),而是function scoped

因此,setName 函数中的 _namegetPerson 中声明的 _name 相同。

_name 的范围是 getPerson() 函数中的任何代码。

在 Javascript 中,函数中的局部变量限定在函数的特定调用范围内,并且每次调用函数都会创建一组新的局部变量。此外,这些局部变量就像其他变量一样被垃圾收集。它们没有严格存储在函数退出时销毁的堆栈中。

由于垃圾回收,只要仍可访问的代码引用了该变量,函数内的变量就会继续存在。因此,在您的 getPerson() 函数中, _name 变量的范围是 getPerson() 函数的特定调用,但是变量的生命周期只要某些代码仍然可以达到变量,甚至在 getPerson() 的调用完成后很久。

这在 Javascript 中称为闭包,是一个非常有价值和有用的工具。

因为 person.getName()person.setName() 仍然引用 _name 变量,即使在 getPerson() 函数执行完很长时间后,它仍然存在并且不会被语言收集为垃圾。

所以,scope是由变量的声明决定的。 _name 的范围在声明它的函数内以及该函数内也存在的任何其他子范围内。 Javascript 具有 lexical 范围(这意味着范围由声明的 where/how 决定)。但是,一个范围内的变量的生命周期由垃圾收集器单独确定,并且一个范围内的变量可以在创建该范围的函数执行之后继续存在,只要其他一些可访问的代码仍然引用该变量.

我喜欢将函数作用域视为 Javascript 中的垃圾收集实体。虽然 Javascript 的某些实现甚至可能比整个作用域更细化(例如,尽可能在作用域内收集单个变量的垃圾),但垃圾收集而不是基于堆栈的作用域的概念为您提供了一个框架理解像 Javascript 这样的语言和像 C++ 这样使用显式堆栈帧来确定局部变量生命周期的语言之间的区别。

我认为您可能是从 Java 背景开始使用 JavaScript 的。

var _name = ""; 在匿名函数(分配给 getPerson 的函数)范围内定义了 _name 变量。该变量不附加任何对象,它只是一个普通变量。

如果你真的想做p1._name和p2._name,你可以做一两个变通方法。

使其成为返回对象的属性

var getPerson = function() {
    var person = {
      _name: ''
    };

    person.getName = function() {
        return this._name;
    }

    person.setName = function(value) {
        this._name = value;
    }

    return person;
}


p1 = getPerson();
p1.setName("foo");

p2 = getPerson();
p2.setName("bar");

console.log(p1.getName()); // logs "foo"
console.log(p2.getName()); // logs "bar"

console.log(p1._name); // logs "foo"
console.log(p2._name); // logs "bar"

在返回的对象上创建 getter

var getPerson = function() {
    var _name = "";

    var person = {
        get _name () {
            return _name;
        }
    };

    person.getName = function() {
        return _name;
    }

    person.setName = function(value) {
        _name = value;
    }

    return person;
}


p1 = getPerson();
p1.setName("foo");

p2 = getPerson();
p2.setName("bar");

console.log(p1.getName()); // logs "foo"
console.log(p2.getName()); // logs "bar"

console.log(p1._name); // logs "foo"
console.log(p2._name); // logs "bar"

您可以通过许多其他方式完成此操作; Java脚本是一种动态语言。

小费

你上面所说的称为闭包。通过返回 person,您公开了方法 person.getNameperson.setName。因为这些方法是在 getPerson 中定义的,所以它们也可以访问 _name 变量(因为 _name 是在同一个函数中定义的)。

getPerson 之外的代码无法访问 _name,但它们可以通过 getName 和 setName 方法访问 if(以受控方式)。这为 _name 变量创建了隐私。