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

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

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

谁能给我一个适当的例子,一个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

其他回答

在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部分]

另一种方法是http://jsfiddle.net/nnUY4/ (我不知道这种处理对象创建和显示函数是否遵循任何特定的模式)

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"

继续鲍恩斯的回答

在es6中,你现在可以创建一个类

现在你可以这样做:

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

    toString() {
        return `Shape at ${this.x}, ${this.y}`;
    }
}

所以延伸到一个圆(就像在另一个答案中一样)你可以这样做:

class Circle extends Shape {
    constructor(x, y, r) {
        super(x, y);
        this.r = r;
    }

    toString() {
        let shapeString = super.toString();
        return `Circular ${shapeString} with radius ${this.r}`;
    }
}

最终在es6中更干净,更容易阅读。


下面是一个很好的例子:

类形状{ 构造函数(x, y) { 这一点。X = X; 这一点。Y = Y; } toString () { 返回' Shape at ${this。x} $ {this.y} '; } } 类Circle扩展形状{ 构造函数(x, y, r) { 超级(x, y); 这一点。R = R; } toString () { let shapeString = super.toString(); 返回半径为${this.r}的圆形${shapeString}; } } let c = new Circle(1,2,4); Console.log (" + c, c);

我想提一下,我们可以使用标题或字符串来声明对象。 每种类型都有不同的调用方法。见下文:

Var检验= { useTitle: "这里我们使用'a Title'来声明一个对象", 'useString': "这里我们使用'a String'来声明一个对象", onTitle:函数(){ 返回this.useTitle; }, onString:函数类型{ 返回(类型); } } console.log (test.onTitle ()); console.log (test.onString (' useString '));

基本上在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