问题
"JavaScript is the worlds most misunderstood language" -D.Crockford
My questions:
- Constructor and prototypes in plain English?
- What is the need of using a prototype? What is the purpose behind using Prototypes and constructors? I mean do they provide more flexibility. I am asking this as I have been using this language for the past six months and never had a situation where I used prototypes and constructor.
I am not looking for any syntax and how to go about things explanations as I do understand some part of what they are, just wanted to know these things in a simpler way. An analogy (non-tech) or example would be great.*
Details why I asked this question (ignore if you want to):
I have been using JavaScript for the past six months and was shocked when I came to know that JavaScript is a prototype-based language.
I went through some Stack Overflow questions about how JavaScript was supposed to be used and came across prototypes and constructors.
I learned it, and now I can say that I am not a noob when it comes to constructors and prototypes. I am familiar with the syntax. But still I think I am missing something and haven't got down to the core of this language, and I do get confused sometimes.
I hope I am clear.
回答1:
Constructor and protoypes in plain English?
Constructor functions create objects and assign prototypes to them. A prototype is an object with various properties that an object can inherit through the prototype chain. As always, examples help:
function Foo() {
}
Foo.prototype.answer = 42;
var f = new Foo();
console.log(f.answer); // "42"
Foo
is a constructor function. When you use new Foo
, the object that Foo.prototype
points to will become the prototype of the object that gets created. When you do f.answer
, since f
doesn't have its own property with the name answer
, the JavaScript engine looks at f
's prototype to see if it has one. Since it does, it uses the value from the prototype and we see "42" in the console. This is how properties are resolved: By looking at an object seeing if it has a property with the given name, and if not, going to its prototype to see if it has the property, and if not going to its prototype, and so on.
Note that a consequence of the above is that adding properties to a prototype after an object has been created using that prototype works just fine; you can use those new properties via the object:
function Foo() {
}
Foo.prototype.answer = 42;
var f = new Foo();
console.log(f.question); // "undefined", neither `f`, nor `Foo.prototype`, nor
// `Object.prototype` has a `question` property
Foo.prototype.question = "Life, the Universe, and Everything";
console.log(f.question); // "Life, the Universe, and Everything"
As of ES5, constructor functions are no longer the only way you can assign prototypes to objects. Now you can also do it via Object.create
. The above is roughly equivalent to this:
var fooProto = {
answer: 42
};
var f = Object.create(fooProto);
console.log(f.answer); // "42"
What is the purpose behind using Prototypes and constructors?
To share characteristics between objects. The properties of a prototype can be functions or data, both of which the objects using that prototype have access to and can reuse.
Re your comment below:
I understood the part about sharing characteristics, but could I get some more detailing in to it
Well, consider a Circle
constructor:
function Circle(radius) {
this.r = radius;
}
Circle.prototype.radius = function() {
return this.r;
};
Circle.prototype.diameter = function() {
return this.r * 2;
};
Circle.prototype.circumference = function() {
return 2 * Math.PI * this.r;
};
Circle.prototype.area = function() {
return Math.PI * this.r * this.r;
};
All objects constructed by Circle
will get Circle.prototype
as their prototype, and so they all have the handy diameter
, circumference
, et. al. functions.
var c1 = new Circle(3);
console.log(c1.area()); // 28.274333882308138
console.log(c1.circumference()); // 18.84955592153876
var c2 = new Circle(5);
console.log(c2.area()); // 78.53981633974483
console.log(c2.circumference()); // 31.41592653589793
They share those properties in a memory-efficient way: Each instance doesn't have its own copy of those properties (which would mean keeping each property name and its value in each obejct); instead, they just have a reference to their prototype, which they share, which has those properties.
回答2:
First of all, I suggest you take a look at this playlist featuring the man himself (Crockford). It might be old, but it really explains JavaScript "logic" very well, and your question is particularly answered in the third video.
I'm going to begin answering this question by describing how objects are depicted in other traditional Object-Oriented Programming languages, because I want to also target the Crockford comment you have posted in the start of the question.
In order to understand Constructors, you first have to have a good understanding of Objects. In traditional OOP languages, an Object is a collection of variables (called properties or fields) that describe the object's state, as well as functions (called methods) that describe it's behaviour. In those (non-JavaScript) languages, the "blueprint" of those objects is called a Class.
So, if I create a Human class in Java, a very simplistic depiction would look like this:
class Human {
String name;
int weight; // kg
int height; // cm
void eat(int foodWeight) {
this.weight += foodWeight;
}
Human(int weight, int height, int name) {
this.weight = weight;
this.height = height;
this.name = name;
}
}
And then, I would create an Object using the above "blueprint" like so:
Human Joe = new Human(90, 180, "Joe");
And now, we say Joe
is an instance of Human
, whose weight is 90 kg and height is 180 cm.
In the class above, you noticed I had a function Human()
that was used to create the object and define it's state as it was created. This is essentially what a Constructor does.
So what's different about JavaScript?
In order to appeal to the masses at the time of it's creation (as you will hear in the video series I posted), JavaScript incorporated some Java-like syntax. What this has done, according to Crockford, is giving programmers the idea that, because they already know/learned some Java, then they can just learn a few new commands and then go ahead and program in JavaScript, while in reality, the differences between the two far outweigh their similarities.
In JavaScript, in order to create an object in a way that it looks like a Java class, you would use the function syntax like so:
var Human = function(name, height, weight) {
this.name = name;
this.height = height;
this.weight = weight;
this.eat = function(foodWeight) {
this.weight += foodWeight;
};
};
And then, if you want to define Joe
as we did above, you would do the following:
var Joe = new Human("Joe", 180, 90);
You can see the similarities between the Java and JavaScript syntaxes shown. So, to answer your first question: JavaScript Constructors are functions that, when called with new
, create and return an implicitly created object, pointed to by this
.
So where does the Prototype come in? Well, In JavaScript, functions are also JS objects themselves, and they have a property called prototype
. So, the Human()
constructor we created above has a property called prototype
, and this property refers to an object whose properties and methods are inherited by Joe
, as well as all other instances of Human
, and this object can be extended in order to create properties that will be inherited by all of those instances.
For example, one of the methods in Function.prototype
is the famous toString
method. You could define
Human.prototype.toString = function() {
return this.name + " is " + this.height + " cm tall and weighs " + this.weight + " kg";
}
then, if you call Joe.toString()
or when you do something like alert(Joe)
that automatically calls toString()
, the value returned would be "Joe is 190 cm tall and weighs 80 kg".
There are many more details about OOP and JavaScript that could be covered in the context of your question, but I think my answer is long enough! I hope this answers your question.
回答3:
Constructor and prototypes in plain English?
As the name "constructor" suggests, it creates something new (an object) and everything it creates follows a template, the prototype.
In JavaScript, any function can be used as a constructor, simply by calling them differently from a normal function call; for example:
function Foo()
{
}
Foo(); // normal function call, returns nothing
var f = new Foo(); // constructor call, returns a new Foo object
alert(f instanceof Foo) // "true"
As mentioned earlier, the prototype is like a template; you can change the prototype during runtime and changes affect all objects that inherit from that prototype. The prototype of any object can be accessed via its constructor's .prototype
property. For example:
var f = new Foo();
Foo.prototype.bar = 'baz';
alert(f.bar) // "baz"
What is the need of using Prototype? I want to understand the purpose behind using Prototypes and constructors? I mean do they provide more flexibility.
Prototypes are used to define shared behaviour and/or data using methods and properties, similar to what you might expect from a class oriented language. They can also inherit from each other, creating a chain of prototypes all the way up to Object; even functions are actually Function
objects.
Without a prototype, you would have to do all the work inside your constructor:
function Foo()
{
// add methods and data
this.bar = 'baz';
}
In the above example you may not see the direct benefit, but there are some:
Memory; adding methods to each object instance consumes more memory than having them made available via the prototype chain. The advantage of not having to traverse the prototype chain is usually levelled by the time taken for instantiation of your objects.
Hierarchy; when your project gets bigger you will eventually need to create some kind of object hierarchy, without prototypes this is more cumbersome.
However, if you wish to create privileged method, you need to attach those in the constructor itself; it's not possible to do this from the prototype; for example:
function Foo()
{
var bar = 'baz';
// privileged method
this.bar = function() {
return bar;
}
}
var f = new Foo();
alert(f.bar()); // "baz"
I am asking this as I have been using this language for past 6 months and never had a situation where I used prototypes and constructor.
If you have used new Option(...)
or new XYZ()
anywhere, you have used a constructor; if you have used .hasOwnProperty()
or .toString()
at any point, you would have used the prototype chain :)
回答4:
The other answers already answer your question pretty well, but I want to add one more aspect of prototype
s to the mix: Inheritance
As the other answers already show, any properties or methods attached to myObject.prototype
are shared between the instances:
var Car = function(color) {
this.color = color;
};
Car.prototype.openDoor = function() {
alert("Door is open!");
}
Now, you can call the honk
method on each instance:
var car1 = new Car('red');
var car2 = new Car('blue');
car1.openDoor();
car2.openDoor();
We could include openDoor
inside the Car
function, i.e.
var Car = function(color) {
this.color = color;
this.openDoor = function() { alert("Door is open!"); }
};
However, this would add an openDoor
method to each instance of Car
, which is very wasteful, especially if it does exactly the same thing for all instances. By adding it to the prototype instead, we share it with all instances.
So far so good, but the power of prototype
s really shows when you assign another object to the prototype:
var Vehicle = function(color) {
this.color = color;
};
Vehicle.prototype.honk = function() {
alert("Honk Honk! I am " + this.color);
}
var Car = function(color, maxPassengers){
this.color = color;
this.maxPassengers = maxPassengers;
}
Car.prototype = new Vehicle();
Car.prototype.constructor = Car;
Car.prototype.openDoor = function(){
alert("Door is open! I have space for " + this.maxPassengers);
}
Since we are assigning Car.prototype
to the Vehicle
constructor, we essentially have chained Car
to Vehicle
and therefore inherited all of its properties and methods. In effect, we inherit
all of Vehicle
s features.
回答5:
What you apparently used so far
As you haven't used constructors (and prototypes) so far, it means you've more or less written procedural JavaScript code that looks like a series of serially executed code from start to end. If you wanted to reuse some lines of code, you've put them inside a function and call it whenever appropriate.
That's fine as long as you don't have too much code on your page and don't need any module reusability, namely objects. Because the larger the code base the harder it becomes to maintain. Modularity helps because it follows the divide and conquer principle.
Constructors and prototypes
This is where constructors and prototypes come into play. Every function in JavaScript can be a constructor if you execute it properly using new
keyword. Basically using constructors and prototypes you can implement your code in object-oriented way where you'd define appropriate object [proto]types and use OOP fundamentals like inheritance, encapsulation and polymorphism.
What's in it for me?
The main advantage of OOP over procedural programming is short- and long-term maintainability.
OK, so let's make an object and see where prototype comes into play
Let's make an object Rectangle
:
var Rectangle = function(width, height) {
this.width = width;
this.height = height;
};
var instance = new Rectangle(4, 8);
console.log(instance.width); // 4
console.log(instance.height); // 8
This creates a rectangle of specified dimension. Let's also add a particular method to this class flip
that flips rectangle. We can do this in two different ways:
Define it as an instance method within constructor:
var Rectangle = function(width, height) { this.width = width; this.height = height; this.flip = function() { var temp = this.width; this.width = this.height; this.height = temp; }; };
Define it on rectangle type or better said prototype
var Rectangle = function(width, height) { this.width = width; this.height = height; }; Rectangle.prototype.flip = function() { var temp = this.width; this.width = this.height; this.height = temp; };
However we define the flip
method usage is the same:
var instance = new Rectangle(4, 8);
instance.flip();
console.log(instance.width); // 8
console.log(instance.height); // 4
But there still is a difference. In case #1 when we create an instance method each object we create will have a separate copy of this method, but if we used #2 all object instances will share the same method.
Using prototype-level methods will therefore save memory resources and any later runtime modifications to this method would be reflected on all instances (already instantiated and future ones).
But there's more
Nobody said we can't create the same method in both ways at the same time: as instance and prototype.
var Rectangle = function(width, height) {
this.width = width;
this.height = height;
this.flip = function() {
var temp = this.width;
this.width = this.height * 2;
this.width = temp / 2;
};
};
Rectangle.prototype.flip = function() {
var temp = this.width;
this.width = this.height;
this.width = temp;
};
In this case our instance method flips and stretches our rectangle while keeping its area the same. Prototype method just flips it.
var instance = new Rectangle(4, 8);
console.log(instance.width); // 4
console.log(instance.height); // 8
instance.flip();
console.log(instance.width); // 16 = 8 * 2
console.log(instance.height); // 2 = 4 / 2
delete instance.flip;
instance.flip();
console.log(instance.width); // 2
console.log(instance.height); // 16
In this example we created two flip
methods. Instance methods have precedence over prototype ones so this gives us the possibility to redefine/rewrite default prototype functionality on a particular object instance.
After an instance method has been called we then deleted it and recalled flip
. Since the instance method didn't exist any more the prototype one was executed, therefore rectangle only got flipped without dimension changes.
Why and where would I use this in real life?
Anywhere really, because whenever your page has, for example, 200 lines of code it will likely become more and more challenging to extend and maintain it later. Changing it to OOP will help. But when you start using it you'd use it either way, because you won't have to refactor anything when page's code grows and will also be consistent with the rest of your application.
Real life example
You can imagine Stack Overflow having defined a class Question
that has all the properties of a question (id, title, details, array of tags, stats, comments, etc.) and all methods related to a question (upvote, downvote, edit, delete, comment, answer, etc.).
The Stack Overflow frontpage would just request a JSON array of question objects and list them using some HTML template that uses these properties. Anything that user does to a question would then reflect to calling one of its methods.
So everything's nicely contained and only has as much functionality as required without any other clutter related to other parts of the page (ads, navigation, login toolbar, etc.). This means whenever there's a bug in question-related functionality, developers only have to go through code related to Question
prototype. They're not distracted by any other page-related code.
回答6:
Hmm well something simple to get you started and not into too many technical stuff.
Consider this:
function Person(){
this.name = '';
this.lastname = '';
this.age = '';
this.speak = function(msg){
alert(msg);
}
}
As you will already know this is a simple object with its own unique properties and methods / functions You would agree that each person has a unique name, lastname and an age.
All good so far... But 99.999%(Assume 100%) people can speak... so they have a common ability or call it a method or a function.
In other words the "Speak ability" is not something unique rather than something common among people. So for the sake of memory consumption and other various technical stuff you could implement "speak" like this:
Person.prototype.speak = function(msg){
alert(msg);
}
What we' ve done now is that whenever you create a person object ( var someone = new Person(); ) he/she will have 3 unique properties and 1 "common" ability (method-function).
In short terms this is more efficient.
Also consider this:
function Person(){
this.name = '';
this.lastname = '';
this.age = '';
this.category = 'human';
}
VS
function Person(){
this.name = '';
this.lastname = '';
this.age = '';
}
Person.prototype.category = 'human'; // common among all people same as speak was.
And something to try on your console, after pasting this last Person function and it's prototype declaration, do this.
var a = new Person();
var b = new Person();
then:
type a and / or b and press enter then try these 2 "commands" and recheck your objects.
a.category = 'whatever';
Person.prototype.category = 'whatever';
回答7:
Prototype is where you usually define functions or default values. If I define a person object and a method getName
of Person then I can safely say that getName
does the same for Jon, Mike and Betty instances (it'll return this.name
). Because the function getName
does the same for every instance of Person, you don't want it to be defined in the Person constructor body:
function Person(name){
this.name = name; // This refers to the current instance
this.getName = function(){
return this.name;
}
}
var Paul = new Person("Paul");// Paul has its own getName function
var Ben = new Person("Ben");// Ben has its own getName function
...
In the code above Person is called the constructor, you can create new instances of Person by calling the constrictor: var someone=new Person
. Now someone
is an instance of person.
You see in the code above that every instance has its own getName, if the object has many functions and you're creating many instances you will be wasting CPU time by initiating the functions every time you create an instance and memory (because every instance has a bunch of functions that do the same thing as every other instances).
For the above created objects, Paul and Ben, the statement Paul.hasOwnProperty('getName')
will be true.
If you put getName on Person.prototype then there will actually be only one getName function for all Person instances. A new Person instance will have getName through Person.prototype, but getName isn't initialized every time I create a Person. When I create a hundred Person instances and then change Person.prototype.getName all these created instances will use the changed getName function.
Then there is inheritance you want to think about (JavaScript doesn't have classes). You can take all those shared methods of Person and copy them to the prototype of (for example) Employee. Because getName is a funciton on Person.prototype and Emloyee inherits it you can call directly employeeInstance.getName()
. If Employee needs some extra work in getName you can override the Person function but still call it (see code below)
Employee.prototype.getName=function(){
return Person.getName.call(this) + " " + this.jobTitle;
}
For more information about constructor functions, inheritance and overriding functions check out this answer.
If you don't understand these words I suggest reading the Java tutorial. It explains why to do this. Although Java technically uses classes, it'll explain what inheritance and overriding is and why to use it.
OOP is kind of hard to explain in one post, but the tutorial above will cover some of it. Java is not JavaScript and things like private members, type checking and interfaces are not supported in JavaScript. On the other hand JavaScript is a lot more flexible when you want to change instances of an object.
The real power of OOP will reveal itself when you check out patterns. You can google for it as there are countless articles on the Internet.
回答8:
A class provides a template (like a stencil) for building objects. In most languages, the stencil is made of diamond, so you can't alter it.
In a prototype-based language, it is like you were tracing the outline of an existing object to make the new object. If you then decide, "I need a larger mouth on this snowman object", you make the mouth larger on the object you are using as a prototype and any objects created from this modified snowman object will have the larger mouth. If you then use one of the old snowmen objects as your prototype, the snowmen objects created from it will have the original, smaller mouth.
A constructor is code for creating a new object given the class or the prototype object (depending on language).
来源:https://stackoverflow.com/questions/18298435/prototype-and-constructor-in-javascript-plain-english