封装/数据隐藏在Javascript中

Encapsulation / Data Hiding in Javascript?

本文关键字:Javascript 隐藏 数据 封装      更新时间:2023-09-26

我想了解JavaScript中封装的概念,以及如何使我的属性和方法公开或私有。

我正在玩这个例子:

    var person = function(newName, newAge) {
    // Private variables / properties
    var name = newName;
    var age = newAge;
    // Public methods
    this.getName = function() {
        return name;
    }
    // Private methods
    var getAge = function() {
        return age;
    }
    // Public method, has acces to private methods
    this.giveAge = function() {
        return getAge();
    }
}
var jack = new person("Jack", 30);
console.log(jack.name); // undefined
console.log(jack.getName); // Jack
console.log(jack.getAge()); // TypeError: jack.getAge is not a function
console.log(jack.getAge); // undefined
console.log(jack.giveAge()); // 30

因此,变量var namevar age是私有的。为了访问它们,我通过使用.this引用来使用公共方法。因此,在我的函数中var的任何内容都是私有的,而在我的对象内部.this的任何内容在外部都是可见的。

我想这是因为人是可见的,所以它暴露了它的所有属性。

我走在正确的轨道上吗?这是隐藏或公开属性/方法的正确方法吗?

还有一个问题,为什么console.log(jack.getAge());会抛出错误?当引用我"存储"在变量中的函数时,我应该在该函数的末尾加上 ((,它是双向的,所以我不知道有什么用?

谢谢!

我想这是因为人是可见的,所以它暴露了它的所有属性。

正确。

我走在正确的轨道上吗?这是隐藏或公开属性/方法的正确方法吗?

如果你想这样做,那么是的,这是一种相当标准的方法。从ES2015开始,至少有一种其他方式,但(可能(有更多的开销。

还有一个问题,为什么 console.log(jack.getAge(((; 会抛出错误?

因为jack对象没有getAge属性,所以jack.getAge会产生undefined,这不是函数。上下文中有一个getAge变量giveAge闭包可以访问该变量(以及agename(,但jack没有getAge属性。

当引用我"存储"在变量中的函数时,我应该在该函数的末尾加上 ((,它是双向的,所以我不知道有什么用?

不,它不能双向工作。 jack.getName获取对该函数的引用。 jack.getName()调用函数并获取其返回值。

我应该指出,getAge函数没有意义。它只能由 person 函数中定义的闭包访问,就像 agename 一样。因此,任何使用 getAge 的东西都只会使用 age 来代替并避免函数调用。


为了完整起见,我会注意到许多人并不担心 JavaScript 中真正的私有属性,而是选择"按惯例私有" ——例如,他们使用命名约定(例如以 _ 开头的名称(来表示"不要碰这些,它们是私有的。当然,这并不能阻止人们使用它们,它只是表明它们不应该使用它们。提倡这一点的人通常指出,在许多具有"真正的"私有属性/字段的语言(Java,C#(中,这些属性/字段只需通过反射进行少量努力即可访问。因此,这种观点认为,仅使用命名约定是"私有的"。

我不同意(也不是特别不同意(这一点,它确实需要更多的 Java 或 C# 工作来访问私有属性而不是公共属性。我只是指出,"按照惯例私有"的方法很常见,而且经常"足够好"。

我想这是因为人是可见的,所以它暴露了它的所有属性。

不完全是。首先,人是一个常规功能。可以在没有 new-关键字的情况下完美地调用它,但结果会炸毁您的整个应用程序。

要了解原因,您应该首先了解 new-关键字在幕后的作用。这将是一个js实现:

function fakeNew(constructor, ...args){
    if(typeof constructor !== "function"){
        throw new TypeError(constructor + " is not a constructor");
    }
    //create a new Instance of the constructors prototype-property
    var instance = Object.create(constructor.prototype);
    //invoke the constructor with the scope set to the instance and the provided arguments
    var result = constructor.apply(instance, args);
    //check wether the returned value is an Object (and functions are considered as Objects)
    if(result === Object(result)){
        //then return the result-value in favor to the instance
        return result;
    }
    //otherwise return the instance
    return instance;
}

另一方面,任何函数也可以是构造函数;没有特殊需求,这一切都取决于你。

所以回到杰克

var jack = person("Jack", 30);  //would result in the following desaster:
console.log(jack);      //undefined, since person doesn't return anthing
console.log(jack.getName());    
//will throw, since jack is still undefined, and therefore doesn't have any properties
//BUT:
console.log(window.getName())   //will return "Jack" now
console.log(window.getAge);     //undefined, but this is fine 
//global scope has not been polluted with this one, cause getAge was a local variable inside the function-call
console.log(window.giveAge())   //can still call the enclosed (private) function getAge()

然后

var jill = person("Jill", 28);
//will overwrite the global functions and expose new values now
console.log(window.getName(), window.giveAge()) //"Jill", 28    
//and Jack is kind of gone, well the variable is left but the variable contained undefined, so...

接下来是范围界定。假设你做对了

//first let's add a function that executes on the scope
//inside the constructor
this.getNameAndAge = function(){
    return this.getName() + ": " + getAge();
}

.

var andy = new person("Andy", 45);
var joe = new person("Joe", 32);
//let's make Andy a little younger
andy.getNameAndAge = joe.getNameAndAge;
console.log(andy.getNameAndAge(), andy.getName() + ": " + andy.giveAge());
//will result in "Andy: 32", "Andy": 45

哇?

好吧,你已经覆盖了公共方法getNameAndAge。
通过在当前对象上调用(也是公共的(方法 getName(( 来访问该名称。
但是 giveAge(( 仍然是声明这个特定"getNameAndAge-函数实例"的作用域中的封闭变量,因此它来自 Joe 的函数调用。

为了理解这样做的影响,让我们让范围界定更加奇怪。

funciton Animal(species, _name){
    //species is likewise a local variable and can be enclosed, modified, or whatever
    //we don't need to write it to some different variable
    //but we want to expose the name of this animal, since it should be possible to change it later
    //without the need to create a getter and a setter just to change the property of _name
    this.name = _name;
    this.whoAreYou = function(){
        //so we concat the enclosed value from species with the name-argument on this object
        //in the hope that everything will be alright.
        return species + " " + this.name;
    }
}
var jack = new Animal("dog", "Jack");
var jill = new Animal("cat", "Jill");
var joe = new Animal("fish", "Joe");
console.log(jack.whoAreYou());  //"dog Jack"
console.log(jill.whoAreYou());  //"cat Jill"
console.log(joe.whoAreYou());   //"fish Joe"
//as far so good; till now ...
//since these properties are still writable someone will start and move them around
//maybe like a callback
function someFunction(someArg, callback){
    console.log(someArg, callback());
}
someFunction("Who are you?", jack.whoAreYou);
//or sth. like this:
//you may not believe that someone would ever do that, but it will happen!
jack.whoAreYou = jill.whoAreYou;
console.log(jack.whoAreYou());
//and now the poor dog has an Identity-crisis.
//the first one will result in:
"Who are you?", "dog undefined"
//the latter will log "cat Jack"
or even more fummy if sth. like this happens:
var fn = joe.whoAreYou;
console.log(fn.call(jack), fn.call(jill), fn.call(joe), fn.call(Animal));
//cause now they are all fishes, even the Animal-constuctor

我不想说这很糟糕,或者你应该避免它,但它的工作方式,应该考虑。

因为这种方式为我们提供了原型继承,以及编写mixin的好方法,而无需一直编写包装方法。

你可以把它看作是"我需要保护我的私人国家",或者"我在你提供给我的任何环境中工作"。

还有一个问题,为什么 console.log(jack.getAge(((; 会抛出错误?

因为jack.getAge是未定义的,未定义是没有函数的

var getAge = function() {
    return age;
}

对这一行的另一条评论

在 JS 中,函数和变量声明是提升的,因此从函数的开头就可以使用。表达式不是。

var person = function(){
    //...
    foo();
    bar();
    baz();
    function foo(){ console.log("this will work"); }
    var bar = function(){ console.log("this will fail"); }
    //because to this point, bar was delared and assigned with undefined, 
    //and we remember? undefined can't be invoked
    return whatever;
    function baz(){ console.log("event this would work"); }
    //unless some preprocessor decided (falsely), that this function can be removed 
    //since it is after the return-statement, and is therefore unreachable
}