我想知道创建具有属性和方法的JavaScript对象的最佳方法是什么。

我见过有人用var self = this然后用self的例子。在所有函数中确保作用域总是正确的。

然后我看到了使用.prototype来添加属性的例子,而其他人则是内联的。

谁能给我一个适当的例子,一个JavaScript对象的一些属性和方法?


当前回答

基本上在JS中没有类的概念,所以我们使用函数作为与现有设计模式相关的类构造函数。

//Constructor Pattern
function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.doSomething = function(){
    alert('I am Happy');
}
}

到目前为止,JS还不知道你想要创建一个对象,所以这里有一个新的关键字。

var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv

参考:专业JS网页开发人员- Nik Z

其他回答

在JavaScript中实现类和实例有两种模型:原型方法和闭包方法。两者都有优点和缺点,并且有很多扩展的变体。许多程序员和库都有不同的方法和类处理实用函数来掩盖语言中较难看的部分。

结果是,在混合公司中,您将拥有一堆元类,它们的行为都略有不同。更糟糕的是,大多数JavaScript教程材料都很糟糕,提供了一些介于两者之间的折衷方案,覆盖了所有基础,让您非常困惑。(可能作者也很困惑。JavaScript的对象模型与大多数编程语言非常不同,而且在很多地方设计得很糟糕。)

让我们从原型开始。这是您能得到的最原生的javascript:开销代码最少,instanceof将处理这类对象的实例。

function Shape(x, y) {
    this.x= x;
    this.y= y;
}

我们可以向new Shape创建的实例中添加方法,方法是将它们写入这个构造函数的原型查找:

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

现在要子类化它,尽可能多地调用JavaScript子类化。我们通过完全替换奇怪的神奇原型属性来做到这一点:

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

在添加方法之前:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

这个示例可以工作,您将在许多教程中看到类似的代码。但是,这个新的Shape()是丑陋的:我们正在实例化基类,即使没有实际的Shape被创建。它恰好在这个简单的情况下工作,因为JavaScript是如此草率:它允许传入零参数,在这种情况下x和y成为未定义的,并被分配给原型的this。X和y。如果构造函数要做更复杂的事情,它就会彻底失败。

因此,我们需要找到一种方法来创建一个原型对象,其中包含我们在类级别上需要的方法和其他成员,而不调用基类的构造函数。要做到这一点,我们必须开始编写helper代码。这是我所知道的最简单的方法:

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

这将基类原型中的成员转移到不执行任何操作的新构造函数,然后使用该构造函数。现在我们可以简单地写:

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

而不是新的Shape()错误。我们现在有了一组可接受的用于构建类的原语。

在这个模型下,我们可以考虑一些改进和扩展。例如,这里有一个语法糖版本:

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

这两个版本都有一个缺点,即构造函数不能被继承,就像在许多语言中一样。因此,即使你的子类没有向构造过程中添加任何东西,它也必须记得用基类需要的任何参数调用基类构造函数。使用apply可以稍微自动化一些,但是你仍然需要写出:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

因此,一个常见的扩展是将初始化的东西分解到它自己的函数中,而不是构造函数本身。这个函数可以很好地继承基类:

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

现在我们为每个类都得到了相同的构造函数样板。也许我们可以把它移到它自己的辅助函数中这样我们就不需要一直输入它,比如代替function。prototype。子类,把它反过来,让基类的函数吐出子类:

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

...它开始看起来有点像其他语言,尽管语法略显笨拙。如果你喜欢,你可以添加一些额外的功能。也许你想让makeSubclass接受并记住一个类名,并使用它提供一个默认的toString。也许你想让构造函数在不使用new操作符的情况下检测它是否被意外调用(否则通常会导致非常恼人的调试):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

也许你想要传入所有的新成员,并让makeSubclass将它们添加到原型中,以节省你必须编写Class.prototype…相当多。很多职业系统都是这样的,例如:

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

在一个对象系统中,你可能会认为有很多潜在的功能是可取的,但没有人真正同意一个特定的公式。


那就是结束的方式。这避免了JavaScript基于原型的继承的问题,因为它根本不使用继承。而不是:

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

现在,Shape的每个实例都有自己的toString方法副本(以及我们添加的任何其他方法或其他类成员)。

每个实例拥有每个类成员的副本的缺点是效率较低。如果要处理大量的子类实例,原型继承可能会更好。同样调用基类的方法也有点烦人:我们必须记住在子类构造函数覆盖它之前的方法是什么,否则它就会丢失。

[也因为这里没有继承,instanceof操作符将不起作用;如果需要的话,您必须提供自己的类嗅探机制。虽然你可以用类似于原型继承的方式来摆弄原型对象,但这有点棘手,仅仅为了让instanceof工作并不值得。]

每个实例都有自己的方法的好处是,该方法可以绑定到拥有它的特定实例。这很有用,因为JavaScript在方法调用中绑定This的奇怪方式,其结果是,如果你从它的所有者分离一个方法:

var ts= mycircle.toString;
alert(ts());

那么方法内部的这个将不是预期的Circle实例(它实际上是全局窗口对象,导致广泛的调试麻烦)。实际上,这通常发生在一个方法被获取并分配给setTimeout, onclick或EventListener时。

使用原型的方式,你必须为每一个这样的赋值包含一个闭包:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

或者,在未来(或者现在如果你破解了Function.prototype),你也可以用function.bind():

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

如果你的实例是通过闭包的方式完成的,绑定是通过实例变量的闭包免费完成的(通常称为self或self,尽管我个人不建议使用后者,因为self在JavaScript中已经有另一个不同的含义)。不过,您并不是免费获得上述代码片段中的参数1,1,因此如果需要这样做,仍然需要另一个闭包或bind()。

闭包方法也有很多变体。你可能更喜欢完全省略这个,创建一个新的that并返回它,而不是使用new操作符:

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

哪种方式是“合适的”?两者都有。哪个是“最好的”?那要看你的情况了。FWIW:当我在做强面向对象的东西时,我倾向于为真正的JavaScript继承创建原型,而为简单的一次性页面效果创建闭包。

但这两种方式对大多数程序员来说都是违反直觉的。两者都有许多潜在的混乱变化。如果您使用其他人的代码/库,您将同时遇到这两种情况(以及许多介于两者之间和通常不完善的方案)。没有一个普遍接受的答案。欢迎来到JavaScript对象的奇妙世界。

[这是为什么JavaScript不是我最喜欢的编程语言的第94部分]

当一个人在构造函数调用期间使用关闭“this”的技巧时,是为了编写一个函数,可以被其他不希望调用对象上的方法的对象用作回调。这与“使范围正确”无关。

这是一个普通的JavaScript对象:

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

阅读Douglas Crockford关于JavaScript的文章,您可能会学到很多东西。约翰·瑞西格也很出色。好运!

闭包是万能的。Bobince已经很好地总结了创建对象时的原型与闭包方法。但是,您可以用函数式编程的方式使用闭包来模拟OOP的某些方面。记住,函数在JavaScript中是对象;所以用另一种方式使用function作为对象。

这里有一个闭包的例子:

function outer(outerArg) {
    return inner(innerArg) {
        return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
    }
}

前段时间我看到Mozilla关于Closure的文章。下面是让我眼前一亮的:“闭包让你将一些数据(环境)与对这些数据进行操作的函数相关联。这与面向对象编程有明显的相似之处,在面向对象编程中,对象允许我们将一些数据(对象的属性)与一个或多个方法关联起来。”这是我第一次在没有参考原型的情况下看到闭包和经典OOP之间的相似之处。

How?

假设您想要计算某些项目的增值税。增值税可能在应用程序的生命周期内保持稳定。在OOP(伪代码)中实现的一种方法是:

public class Calculator {
    public property VAT { get; private set; }
    public Calculator(int vat) {
        this.VAT = vat;
    }
    public int Calculate(int price) {
        return price * this.VAT;
    }
}

基本上,你将一个VAT值传递给你的构造函数,你的计算方法可以通过闭包对它进行操作。 现在不再使用类/构造函数,而是将VAT作为参数传递到函数中。因为你唯一感兴趣的东西是计算本身,返回一个新函数,这是计算方法:

function calculator(vat) {
    return function(item) {
        return item * vat;
    }
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110

在您的项目中,确定顶级价值是什么是增值税计算的良好候选者。作为一个经验法则,当你传递相同的参数时,有一种方法可以使用闭包来改进它。不需要创建传统的对象。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

var Person = function (lastname, age, job){
this.name = name;
this.age = age;
this.job = job;
this.changeName = function(name){
this.lastname = name;
}
}
var myWorker = new Person('Adeola', 23, 'Web Developer');
myWorker.changeName('Timmy');

console.log("New Worker" + myWorker.lastname);

创建对象

在JavaScript中创建对象最简单的方法是使用以下语法:

Var检验= { A: 5, B: 10, F:函数(c) { 返回。A +这个。B + c; } } console.log(测试); console.log (test.f (3));

这对于以结构化的方式存储数据非常有用。

然而,对于更复杂的用例,创建函数的实例通常更好:

函数测试(a, b) { 这一点。A = A; 这一点。B = B; 这一点。F =函数(c) { 返回。A +这个。B + c; }; } var test = new test (5,10); console.log(测试); console.log (test.f (3));

这允许你创建多个对象共享相同的“蓝图”,类似于你如何使用类在eg。Java。

然而,通过使用原型,这仍然可以更有效地完成。

当一个函数的不同实例共享相同的方法或属性时,您可以将它们移动到该对象的原型中。这样,函数的每个实例都可以访问该方法或属性,但不需要为每个实例复制该方法或属性。

在我们的例子中,将方法f移动到原型中是有意义的:

函数测试(a, b) { 这一点。A = A; 这一点。B = B; } Test.prototype.f =函数(c) { 返回。A +这个。B + c; }; var test = new test (5,10); console.log(测试); console.log (test.f (3));

继承

在JavaScript中进行继承的一个简单而有效的方法是使用以下两行代码:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

这类似于这样做:

B.prototype = new A();

两者之间的主要区别是,A的构造函数在使用Object时不运行。创建,这更直观,更类似于基于类的继承。

你总是可以选择在创建B的新实例时运行A的构造函数,方法是将它添加到B的构造函数中:

function B(arg1, arg2) {
    A(arg1, arg2); // This is optional
}

如果你想将B的所有参数传递给A,你也可以使用Function.prototype.apply():

function B() {
    A.apply(this, arguments); // This is optional
}

如果你想在B的构造函数链中混合另一个对象,你可以combine object。用Object创建。分配:

B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;

Demo

function A(name) { this.name = name; } A.prototype = Object.create(Object.prototype); A.prototype.constructor = A; function B() { A.apply(this, arguments); this.street = "Downing Street 10"; } B.prototype = Object.create(A.prototype); B.prototype.constructor = B; function mixin() { } mixin.prototype = Object.create(Object.prototype); mixin.prototype.constructor = mixin; mixin.prototype.getProperties = function() { return { name: this.name, address: this.street, year: this.year }; }; function C() { B.apply(this, arguments); this.year = "2018" } C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype); C.prototype.constructor = C; var instance = new C("Frank"); console.log(instance); console.log(instance.getProperties());


Note

对象。create可以在包括IE9+在内的所有现代浏览器中安全使用。对象。assign不能在任何版本的IE和一些移动浏览器中工作。建议对对象进行填充。创建和/或对象。如果您希望使用它们并支持未实现它们的浏览器,则分配。

你可以为Object找到一个polyfill。建立在这里 还有一个是Object。分配。