JavaScript面向对象编程的简介:对象,原型和类

by Andrea Koutifaris

由Andrea Koutifaris

JavaScript面向对象编程的简介:对象,原型和类 (An intro to object-oriented programming in JavaScript: objects, prototypes, and classes)

In many programming languages, classes are a well-defined concept. In JavaScript that is not the case. Or at least that wasn’t the case. If you search for O.O.P. and JavaScript you will run into many articles with a lot of different recipes on how you can emulate a class in JavaScript.

在许多编程语言中,类是一个定义明确的概念。 在JavaScript中并非如此。 或者至少不是这样。 如果您搜索OOP和JavaScript,则会遇到许多文章,其中有许多关于如何在JavaScript中模拟class的不同秘诀。

Is there a simple, K.I.S.S. way to define a class in JavaScript? And if so, why so many different recipes to define a class?

有没有一种简单的KISS方式在JavaScript中定义类? 如果是这样,为什么要定义这么多不同的配方呢?

Before answering to those questions, let’s understand better what a JavaScript Object is.

在回答这些问题之前,让我们更好地了解什么是JavaScript Object

JavaScript中的对象 (Objects in JavaScript)

Let’s begin with a very simple example:

让我们从一个非常简单的示例开始:

const a = {};
a.foo = 'bar';

In the above code snippet an object is created and enhanced with a property foo. The possibility of adding things to an existing object is what makes JavaScript different from classic languages like Java.

在上面的代码片段中,创建了一个对象,并使用foo属性对其进行了增强。 将事物添加到现有对象的可能性使JavaScript与Java之类的经典语言有所不同。

More in detail, the fact that an object can be enhanced makes it possible to create an instance of an “implicit” class without the need to actually create the class. Let’s clarify this concept with an example:

更详细地讲,可以增强对象的事实使创建“隐式”类的实例成为可能,而无需实际创建该类。 让我们用一个例子来阐明这个概念:

function distance(p1, p2) {
  return Math.sqrt(
    (p1.x - p2.x) ** 2 + 
    (p1.y - p2.y) ** 2
  );
}

distance({x:1,y:1},{x:2,y:2});

In the example above, I didn’t need a Point class to create a point, I just extended an instance of Object adding x and y properties. The function distance doesn’t care if the arguments are an instance of the class Point or not. Until you call distance function with two objects that have an x and y property of type Number, it will work just fine. This concept is sometimes called duck typing.

在上面的示例中,我不需要Point类来创建点,我只是扩展了添加xy属性的Object实例。 函数距离与参数是否为Point类的实例无关。 直到用两个具有Number类型的xy属性的对象调用distance函数时,它才能正常工作。 这种概念有时称为鸭子打字

Up to now, I’ve only used a data object: an object containing only data and no functions. But in JavaScript it is possible to add functions to an object:

到目前为止,我只使用了一个数据对象:一个仅包含数据而没有函数的对象。 但是在JavaScript中可以向对象添加函数:

const point1 = {
  x: 1,
  y: 1,
  toString() {
    return `(${this.x},${this.y})`;
  }
};

const point2 = {
  x: 2,
  y: 2,
  toString() {
    return `(${this.x},${this.y})`;
  }
};

This time, the objects representing a 2D point have a toString() method. In the example above, the toString code has been duplicated, and this is not good.

这次,表示2D点的对象具有toString()方法。 在上面的示例中, toString代码已被复制,这不好。

There are many ways to avoid that duplication, and, in fact, in different articles about objects and classes in JS you will find different solutions. Have you ever heard of the “Revealing module pattern”? It contains the words “pattern” and “revealing”, sounds cool, and “module” is a must. So it must be the right way to create objects… except that it isn’t. Revealing module pattern can be the right choice in some cases, but it is definitely not the default way of creating objects with behaviors.

有很多避免重复的方法,实际上,在JS中有关对象和类的不同文章中,您会找到不同的解决方案。 您是否听说过“公开模块模式”? 它包含单词“ pattern”和“ revealing”,听起来很酷,并且“模块”是必须的。 因此,它必须是创建对象的正确方法……除非并非如此。 在某些情况下,显示模块模式可能是正确的选择,但绝对不是创建具有行为的对象的默认方法。

We are now ready to introduce classes.

现在,我们准备介绍课程。

JavaScript中的类 (Classes in JavaScript)

What is a class? From a dictionary: a class is “a set or category of things having some property or attribute in common and differentiated from others by kind, type, or quality.”

什么是课程? 从字典中来看:类是“具有共同的属性或属性,并通过种类,类型或质量与其他属性或属性区分开的一组事物或类别。”

In programming languages we often say “An object is an instance of a class”. This means that, using a class, I can create many objects and they all share methods and properties.

在编程语言中,我们经常说“对象是类的实例”。 这意味着,使用一个类,我可以创建许多对象,并且它们都共享方法和属性。

Since objects can be enhanced, as we’ve seen earlier, there are may ways to create objects sharing methods and properties. But we want the simplest one.

正如我们前面所看到的,由于可以增强对象,因此可以使用多种方法来创建对象共享方法和属性。 但是我们想要最简单的一种。

Fortunately ECMAScript 6 provides the keyword class, making it very easy to create a class:

幸运的是,ECMAScript 6提供了关键字class ,使创建类非常容易:

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

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

So, in my opinion, that is the best way of declaring classes in JavaScript. Classes are often related to inheritance:

因此,我认为这是在JavaScript中声明类的最佳方法。 类通常与继承有关:

class Point extends HasXY {
  constructor(x, y) {
    super(x, y);
  }

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

As you can see in the example above, to extend another class it is enough to use the keyword extends .

如您在上面的示例中看到的,要扩展另一个类,只需使用关键字extends就足够了。

You can create an object from a class using the new operator:

您可以使用new运算符从类创建对象:

const p = new Point(1,1);
console.log(p instanceof Point); // prints true

A good object oriented way of defining classes should provide:

定义类的一种好的面向对象的方法应提供:

  • a simple syntax to declare a class

    声明类的简单语法
  • a simple way to access to the current instance, a.k.a. this

    一个简单的方法来获取当前实例,又名this

  • a simple syntax to extend a class

    扩展类的简单语法
  • a simple way to access the super class instance, a.k.a. super

    一种访问超类实例(又名super的简单方法

  • possibly, a simple way of telling if an object is an instance of a particular class. obj instanceof AClass should return true if that object is an instance of that class.

    可能的一种简单的方法来判断对象是否是特定类的实例。 如果该对象是该类的实例,则obj instanceof AClass应该返回true

The new class syntax provides all the points above.

新的class语法提供了以上所有要点。

Before the introduction of the class keyword, what was the way to define a class in JavaScript?

在引入class关键字之前,用JavaScript定义类的方法是什么?

In addition, what really is a class in JavaScript? Why do we often speak about prototypes?

另外,JavaScript中的类到底是什么? 为什么我们经常谈论原型

JavaScript 5中的类 (Classes in JavaScript 5)

From Mozilla MDN page about classes:

Mozilla MDN页面上有关类的信息

JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript’s existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.

ECMAScript 2015中引入JavaScript类主要是语法上的糖,它覆盖了JavaScript现有的基于原型的继承 。 类语法不会向JavaScript引入新的面向对象的继承模型。

The key concept here is prototype-based inheritance. Since there is a lot of misunderstanding on what that kind of inheritance is, I will proceed step by step, moving from class keyword to function keyword.

这里的关键概念是基于原型的继承 。 由于对这种继承有很多误解,因此我将逐步进行继承,从class关键字转到function关键字。

class Shape {}
console.log(typeof Shape);
// prints function

It seems that class and function are related. Is class just an alias for function ? No, it isn’t.

看来classfunction是相关的。 class仅仅是function的别名吗? 不,不是。

Shape(2);
// Uncaught TypeError: Class constructor Shape cannot be invoked without 'new'

So, it seems that the people who introduced class keyword wanted to tell us that a class is a function that must be called using the new operator.

因此,引入class关键字的人们似乎想告诉我们,类是必须使用new运算符调用的函数。

var Shape = function Shape() {} // Or just function Shape(){}
var aShape = new Shape();
console.log(aShape instanceof Shape);
// prints true

The example above shows that we can use function to declare a class. We cannot, however, force the user to call the function using the new operator. It is possible to throw an exception if the new operator wasn’t used to call the function.

上面的例子表明我们可以使用function来声明一个类。 但是,我们不能强迫用户使用new运算符来调用该函数。 如果不使用new运算符调用该函数,则可能引发异常。

Anyway I suggest you don’t put that check in every function that acts as a class. Instead use this convention: any function whose name begins with a capital letter is a class and must be called using the new operator.

无论如何,我建议您不要将该检查放到充当类的每个函数中。 而是使用以下约定:名称以大写字母开头的任何函数都是类,必须使用new运算符进行调用。

Let’s move on, and find out what a prototype is:

让我们继续前进,找出原型是什么:

class Shape {
  getName() {
    return 'Shape';
  }
}
console.log(Shape.prototype.getName);
// prints function getName() ...

Each time you declare a method inside a class, you actually add that method to the prototype of the corresponding function. The equivalent in JS 5 is:

每次在类中声明一个方法时,实际上都会将该方法添加到相应函数的原型中。 JS 5中的等效项是:

function Shape() {}
Shape.prototype.getName = function getName() {
  return 'Shape';
};
console.log(new Shape().getName()); // prints Shape

Sometimes the class-functions are called constructors because they act like constructors in a regular class.

有时,类函数称为构造函数,因为它们的作用类似于常规类中的构造函数。

You may wonder what happens if you declare a static method:

您可能想知道如果声明静态方法会发生什么:

class Point {
  static distance(p1, p2) {
    // ...
  }
}

console.log(Point.distance); // prints function distance
console.log(Point.prototype.distance); // prints undefined

Since static methods are in a 1 to 1 relation with classes, the static function is added to the constructor-function, not to the prototype.

由于静态方法与类之间是一对一的关系,因此静态函数将添加到构造函数而不是原型中。

Let’s recap all these concepts in a simple example:

让我们在一个简单的示例中回顾所有这些概念:

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

Point.prototype.toString = function toString() {
  return '(' + this.x + ',' + this.y + ')';
};
Point.distance = function distance() {
  // ...
}

console.log(new Point(1,2).toString()); // prints (1,2)
console.log(new Point(1,2) instanceof Point); // prints true

Up to now, we have found a simple way to:

到目前为止,我们已经找到一种简单的方法来:

  • declare a function that acts as a class

    声明一个充当类的函数
  • access the class instance using the this keyword

    使用this关键字访问类实例

  • create objects that are actually an instance of that class (new Point(1,2) instanceof Point returns true )

    创建实际上是该类实例的对象( new Point(1,2) instanceof Point返回true )

But what about inheritance? What about accessing the super class?

但是继承呢? 如何访问超类?

class Hello {
  constructor(greeting) {
    this._greeting = greeting;
  }

  greeting() {
    return this._greeting;
  }
}

class World extends Hello {
  constructor() {
    super('hello');
  }

  worldGreeting() {
    return super.greeting() + ' world';
  }
}

console.log(new World().greeting()); // Prints hello
console.log(new World().worldGreeting()); // Prints hello world

Above is a simple example of inheritance using ECMAScript 6, below the same example using the the so called prototype inheritance:

上面是使用ECMAScript 6进行继承的简单示例,下面是使用所谓的原型继承的相同示例:

function Hello(greeting) {
  this._greeting = greeting;
}

Hello.prototype.greeting = function () {
  return this._greeting;
};

function World() {
  Hello.call(this, 'hello');
}

// Copies the super prototype
World.prototype = Object.create(Hello.prototype);
// Makes constructor property reference the sub class
World.prototype.constructor = World;

World.prototype.worldGreeting = function () {
  const hello = Hello.prototype.greeting.call(this);
  return hello + ' world';
};

console.log(new World().greeting()); // Prints hello
console.log(new World().worldGreeting()); // Prints hello world

This way of declaring classes is also suggested in the Mozilla MDN example here.

此处的Mozilla MDN示例中还建议了这种声明类的方法。

Using the class syntax, we deduced that creating classes involves altering the prototype of a function. But why is that so? To answer this question we must understand what the new operator actually does.

使用class语法,我们推断出创建类涉及更改函数的原型。 但是为什么会这样呢? 要回答这个问题,我们必须了解new操作员的实际操作。

JavaScript中的新运算符 (New operator in JavaScript)

The new operator is explained quite well in the Mozilla MDN page here. But I can provide you with a relatively simple example that emulates what the new operator does:

此处的Mozilla MDN页面对new运算符进行了很好的解释。 但我可以为您提供一个相对简单的示例,该示例模拟new操作符的作用:

function customNew(constructor, ...args) {
  const obj = Object.create(constructor.prototype);
  const result = constructor.call(obj, ...args);

  return result instanceof Object ? result : obj;
}

function Point() {}
console.log(customNew(Point) instanceof Point); // prints true

Note that the real new algorithm is more complex. The purpose of the example above is just to explain what happens when you use the new operator.

注意,真正的new算法更加复杂。 上面示例的目的仅是说明使用new运算符时发生的情况。

When you write new Point(1,2)what happens is:

当您编写new Point(1,2) ,会发生以下情况:

  • The Point prototype is used to create an object.

    Point原型用于创建对象。

  • The function constructor is called and the just created object is passed as the context (a.k.a. this) along with the other arguments.

    调用函数构造函数,并将刚创建的对象作为上下文(也称为this )与其他参数一起传递。

  • If the constructor returns an Object, then this object is the result of the new, otherwise the object created from the prototype is the result.

    如果构造函数返回一个Object,则此对象是新对象的结果,否则,从原型创建的对象是结果。

So, what does prototype inheritance mean? It means that you can create objects that inherit all the properties defined in the prototype of the function that was called with the new operator.

那么, 原型继承是什么意思呢? 这意味着您可以创建对象,这些对象继承用new运算符调用的函数原型中定义的所有属性。

If you think of it, in a classical language the same process happens: when you create an instance of a class, that instance can use the this keyword to access to all the functions and properties (public) defined in the class (and the ancestors). As opposite to properties, all the instances of a class will likely share the same references to the class methods, because there is no need to duplicate the method’s binary code.

如果考虑到这一点,则用古典语言会发生相同的过程:创建类的实例时,该实例可以使用this关键字来访问该类(以及祖先)中定义的所有函数和属性(公共)。 )。 与属性相反,一个类的所有实例将可能共享对类方法的相同引用,因为不需要重复该方法的二进制代码。

功能编程 (Functional programming)

Sometimes people say that JavaScript is not well suited for Object Oriented programming, and you should use functional programming instead.

有时人们会说JavaScript不太适合面向对象的编程,而应该使用函数式编程。

While I don’t agree that JS is not suited for O.O.P, I do think that functional programming is a very good way of programming. In JavaScript functions are first class citizens (e.g. you can pass a function to another function) and it provides features like bind , call or apply which are base constructs used in functional programming.

虽然我不同意JS不适合OOP,但我确实认为函数式编程是一种非常好的编程方式。 JavaScript函数是一等公民 (例如,您可以将一个函数传递给另一个函数),并且它提供诸如bindcallapply类的功能,这些功能是函数编程中使用的基本构造。

In addition RX programming could be seen as an evolution (or a specialization) of functional programming. Have a look to RxJs here.

另外,RX编程可以看作是功能编程的发展(或专业化)。 在这里看看RxJs

结论 (Conclusion)

Use, when possible, ECMAScript 6 class syntax:

尽可能使用ECMAScript 6 class语法:

class Point {
  toString() {
    //...
  }
}

or use function prototypes to define classes in ECMAScript 5:

或使用函数原型在ECMAScript 5中定义类:

function Point() {}
Point.prototype.toString = function toString() {
  // ...
}

Hope you enjoyed the reading!

希望您喜欢阅读!

翻译自: https://www.freecodecamp.org/news/an-intro-to-object-oriented-programming-in-javascript-objects-prototypes-and-classes-5d135e7361b1/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值