在C ++中,我最熟悉的语言,通常会声明一个这样的对象:
class foo
{
public:
int bar;
int getBar() { return bar; }
}
调用getBar()
可以正常工作(忽略bar
可能未初始化的事实)。 bar
中的变量getBar()
位于类foo
的范围内,所以我不需要说this->bar
,除非我真的需要说清楚我是bar
引用课程“function foo()
{
this.bar = 0;
this.getBar = function() { return bar; }
}
而不是参数。
现在,我正在尝试在Javascript中开始使用OOP。所以,我查找如何定义类并尝试相同的事情:
bar is undefined
它给了我bar
。将this.bar
更改为this.bar
可以解决问题,但是为每个变量执行此操作会使我的代码变得非常混乱。这对每个变量都是必要的吗?由于我找不到与此有关的任何问题,这让我觉得我做的事情从根本上是错误的。
编辑:是的,所以,从评论我得到的是bar
,一个对象的属性,引用了不同于{{1}}的局部变量。有人可以说,为什么这就是范围和对象,以及是否有另一种方法来定义一个不需要的对象?
答案 0 :(得分:32)
JavaScript没有类基于类的对象模型。它使用了更强大的原型继承,它可以模仿类,但不适合它。一切都是对象,对象[可以]从其他对象继承。
构造函数只是一个为新创建的对象分配属性的函数。对象(由new
keyword调用创建)可以通过this
keyword(函数的本地)引用。
方法也只是一个在对象上被称为的函数 - 再次使用this
指向对象。至少在使用member operator(点,括号)调用该函数作为对象的属性时。这会给新手带来很多混淆,因为如果你传递那个函数(例如传递给事件监听器),它就会与访问它的对象“分离”。
现在继承在哪里? “类”的实例继承自同一原型对象。方法被定义为该对象上的函数属性(而不是每个实例的一个函数),您调用它们的实例只是继承该属性。
示例:
function Foo() {
this.bar = "foo"; // creating a property on the instance
}
Foo.prototype.foo = 0; // of course you also can define other values to inherit
Foo.prototype.getBar = function() {
// quite useless
return this.bar;
}
var foo = new Foo; // creates an object which inherits from Foo.prototype,
// applies the Foo constructor on it and assigns it to the var
foo.getBar(); // "foo" - the inherited function is applied on the object and
// returns its "bar" property
foo.bar; // "foo" - we could have done this easier.
foo[foo.bar]; // 0 - access the "foo" property, which is inherited
foo.foo = 1; // and now overwrite it by creating an own property of foo
foo[foo.getBar()]; // 1 - gets the overwritten property value. Notice that
(new Foo).foo; // is still 0
所以,我们只使用该对象的属性并对它感到满意。但是所有这些都是“公开的”,可以被覆盖/更改/删除!如果这与你无关,那你很幸运。您可以通过在其名称前加下划线来表示属性的“私有性”,但这只是对其他开发人员的提示,可能不会被遵守(特别是在出错时)。
因此,聪明的头脑找到了一个使用构造函数作为闭包的解决方案,允许创建私有“属性”。每次执行javascript函数都会为局部变量创建一个新的变量环境,一旦执行完成,就可能会收集垃圾。在该范围内声明的每个函数也可以访问这些变量,并且只要可以调用这些函数(例如,通过事件监听器),环境就必须保持不变。因此,通过从构造函数中导出本地定义的函数,您可以使用只能由这些函数访问的局部变量来保留该变量环境。
让我们看看它的实际效果:
function Foo() {
var bar = "foo"; // a local variable
this.getBar = function getter() {
return bar; // accesses the local variable
}; // the assignment to a property makes it available to outside
}
var foo = new Foo; // an object with one method, inheriting from a [currently] empty prototype
foo.getBar(); // "foo" - receives us the value of the "bar" variable in the constructor
此getter函数在构造函数中定义,现在称为“特权方法”,因为它可以访问“私有”(本地)“属性”(变量)。 bar
的值永远不会改变。当然,您也可以为它声明一个setter函数,并且可以添加一些验证等。
请注意,原型对象上的方法无法访问构造函数的局部变量,但它们可能使用特权方法。我们加一个:
Foo.prototype.getFooBar = function() {
return this.getBar() + "bar"; // access the "getBar" function on "this" instance
}
// the inheritance is dynamic, so we can use it on our existing foo object
foo.getFooBar(); // "foobar" - concatenated the "bar" value with a custom suffix
所以,你可以结合两种方法。请注意,特权方法需要更多内存,因为您创建了具有不同范围链的不同函数对象(但代码相同)。如果要创建大量实例,则应仅在原型上定义方法。
当你将一个“类”的继承设置为另一个时,它会变得更加复杂 - 基本上你必须让子原型对象从父类继承,并在子实例上应用父构造函数来创建“私人属性”。查看Correct javascript inheritance,Private variables in inherited prototypes,Define Private field Members and Inheritance in JAVASCRIPT module pattern和How to implement inheritance in JS Revealing prototype pattern?
答案 1 :(得分:7)
明确地说this.foo
意味着(正如您所理解的那样)您对foo
引用的当前对象的属性this
感兴趣。因此,如果您使用:this.foo = 'bar';
,您将设置foo
引用的当前对象的属性this
等于bar
。
JavaScript中的this
关键字并不总是像C ++中那样。在这里,我可以举个例子:
function Person(name) {
this.name = name;
console.log(this); //Developer {language: "js", name: "foo"} if called by Developer
}
function Developer(name, language) {
this.language = language;
Person.call(this, name);
}
var dev = new Developer('foo', 'js');
在上面的示例中,我们使用函数Person
的上下文调用函数Developer
,因此this
引用将由Developer
创建的对象。您可能会从console.log
结果中看到this
来自Developer
。使用方法call
的第一个参数,我们指定将调用函数的上下文。
如果您不使用this
,您创建的属性将是一个局部变量。您可能知道JavaScript具有功能范围,因此变量将是本地的,仅对于声明它的函数可见(当然还有它在父元素内声明的所有子函数)。这是一个例子:
function foo() {
var bar = 'foobar';
this.getBar = function () {
return bar;
}
}
var f = new foo();
console.log(f.getBar()); //'foobar'
使用var
关键字时都是如此。这意味着,如果您忘记了bar
var
,那么您将bar
定义为本地变量。
function foo() {
bar = 'foobar';
this.getBar = function () {
return bar;
}
}
var f = new foo();
console.log(window.bar); //'foobar'
本地范围确实可以帮助您实现隐私和封装,这是OOP的最大好处之一。
真实世界的例子:
function ShoppingCart() {
var items = [];
this.getPrice = function () {
var total = 0;
for (var i = 0; i < items.length; i += 1) {
total += items[i].price;
}
return total;
}
this.addItem = function (item) {
items.push(item);
}
this.checkOut = function () {
var serializedItems = JSON.strigify(items);
//send request to the server...
}
}
var cart = new ShoppingCart();
cart.addItem({ price: 10, type: 'T-shirt' });
cart.addItem({ price: 20, type: 'Pants' });
console.log(cart.getPrice()); //30
JavaScript范围的另一个好处是Module Pattern。 在模块模式中,您可以使用JavaScript的本地功能范围模拟隐私。使用此方法,您可以拥有私有属性和方法。这是一个例子:
var module = (function {
var privateProperty = 42;
function privateMethod() {
console.log('I\'m private');
}
return {
publicMethod: function () {
console.log('I\'m public!');
console.log('I\'ll call a private method!');
privateMethod();
},
publicProperty: 1.68,
getPrivateProperty: function () {
return privateProperty;
},
usePublicProperty: function () {
console.log('I\'ll get a public property...' + this.publicProperty);
}
}
}());
module.privateMethod(); //TypeError
module.publicProperty(); //1.68
module.usePublicProperty(); //I'll get a public property...1.68
module.getPrivateProperty(); //42
module.publicMethod();
/*
* I'm public!
* I'll call a private method!
* I'm private
*/
有一个奇怪的语法,无父语包装匿名函数但暂时忘记它(它只是在初始化后执行函数)。功能可以从使用示例中看出,但优点主要在于提供一个简单的公共接口,而不会使您了解所有实现细节。有关模式的更详细说明,您可以看到我上面提到的链接。
我希望通过this
:-)信息帮助您了解JavaScript的一些基本主题。
答案 2 :(得分:4)
function Foo() {
this.bar = 0;
this.getBar = function () { return this.bar };
}
当您使用new
关键字调用上述函数时 - 就像这样......
var foo = new Foo();
... - 发生了一些事情:
1)创建一个对象
2)使用引用该对象的this
关键字执行该功能
3)返回该对象。
foo
成为这个对象:
{
bar: 0,
getBar: function () { return this.bar; }
};
为什么不这样做呢:
var foo = {
bar: 0,
getBar: function () { return this.bar; }
};
如果它只是一个简单的对象,你会的。
但是创建一个带有构造函数的对象(就是它的调用方式)在创建多个“相同”对象时给了我们很大的优势。
请参阅javascript中的所有函数都是使用prototype属性[对象]创建的,并且使用该函数创建的所有对象(通过使用new关键字调用它)都链接到该原型对象。这就是为什么它如此酷 - 你可以在原型对象中存储所有常用方法(和属性,如果你想),并节省大量内存。这是它的工作原理:
function Foo( bar, bob ) {
this.bar = bar;
this.bob = bob;
}
Foo.prototype.calculate = function () {
// 'this' points not to the 'prototype' object
// as you could've expect, but to the objects
// created by calling Foo with the new keyword.
// This is what makes it work.
return this.bar - this.bob;
};
var foo1 = new Foo(9, 5);
var foo2 = new Foo(13, 3);
var result1 = foo1.calculate();
var result2 = foo2.calculate();
console.log(result1); //logs 4
console.log(result2); //logs 10
就是这样!
答案 3 :(得分:2)
为了更接近JavaScript中的OOP,您可能需要查看模块设计模式(例如,描述为here)。
基于闭包效果,此模式允许在对象中模拟私有属性。
使用“私有”属性,您可以直接通过其标识符引用它们(即,在构造函数中没有this
关键字)。
但无论如何,JS中的闭包和设计模式 - 一个高级主题。因此,熟悉基础知识(也在前面提到的书中解释过)。
答案 4 :(得分:2)
在javascript this
中始终引用该函数的所有者对象。例如,如果您在页面中定义函数foo()
,则所有者是javascript对象windows
;或者如果你在html元素foo()
上定义<body>
,那么所有者就是html元素体;同样,如果你定义元素<a>
的函数onclick,那么所有者就是锚。
在您的情况下,您在开始时将属性bar
分配给“所有者”对象并尝试返回本地变量bar
。
由于您从未定义过任何本地变量bar
,因此未定义条形码。
理想情况下,如果要将值返回零,则代码应将变量定义为var bar;
。
答案 5 :(得分:1)
这个就像是对象(变量或函数)的公共访问修饰符,而 var 是私有访问修饰符
示例
var x = {};
x.hello = function(){
var k = 'Hello World';
this.m = 'Hello JavaScript';
}
var t = new x.hello();
console.log(t.k); //undefined
console.log(t.m); //Hello JavaScript