markdown JavaScript和对象模型
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown JavaScript和对象模型相关的知识,希望对你有一定的参考价值。
## JavaScript and Object Models
> A "choose your own adventure" story
JavaScript is has both object-oriented and functional heritage, thanks to its two parents: Scheme and Self.
It provides first class functions and makes it simple to compose these function objects into bundles of awesome. Even though I'm an OO "true believer" at heart, I find myself composing my code using functional concepts, and use the OO approach where there's a clear benefit or where I feel that it's the best way to communicate the interface.
Object-oriented software design is by no means the only way to do software design, but it's been an immensely successful model for a very long time now, and provides a clear and well-understood mental model for thinking and communicating about software. Lots of good ideas like encapsulation, delegation, traits and composition fit well into OO design.
In this post I'll examine how OO works in JavaScript, and present a variety of different approaches that can be built on its simple foundations.
### Objects 101
There are two ways to create an object in JavaScript:
#### Using an object literal:
```javascript
var foo = {};
```
#### Using the `new` keyword:
```javascript
var constructor = function() {};
var bar = new constructor();
```
You can attach properties and methods to these objects, and JavaScript provides a very simple prototypal system that provides the a basic primitive for object-oriented software design. This provides two things:
- Objects delegate property lookups to their prototype
- A prototype can be shared between objects
There's no "types", no "inheritence" - just objects and prototypes.
#### An example
```javascript
var Thing = function() {};
var a = new Thing();
// functions have a `prototype` which is used when `new` is
// called we can define properties on the prototype and they
// become part of the object
console.log(a.foo); // => undefined
Thing.prototype.foo = 'bar';
console.log(a.foo); // => 'bar'
```
#### Inheritance
```javascript
var Thing = function() {};
Thing.prototype.be = function() {
return "I'm a thing";
};
Thing.prototype.do = function() {
console.log("Done");
};
var Person = function() {};
Person.prototype = new Thing(); // Person extends Thing
Person.prototype.be = function() {
return "I'm a person!";
};
var foo = new Person();
console.log(foo.be()); // => "I'm a person!"
foo.do(); // => "Done"
var HappyPerson = function() {};
HappyPerson.prototype = new Person(); // HappyPerson extends Person
var bar = new HappyPerson();
Thing.prototype.do = function() {
console.log(this.be());
};
```
In fact, whenever you access a property on an object the VM performs a lookup on the object and its prototype chain and returns the first matching property it finds:
1. looks for the property on the object
1. then look at its prototype
1. then the prototype's prototype
1. and so on until it reaches the end of the prototype chain (at which point it returns `undefined`.)
#### This simple feature gives us awesome power:
- Calls to the object can be delegated to the prototype
- This delegation happens at call-time, so changes you make to the prototype also apply to instances that have already been created
- We can model "classes" of objects and share functionality between them
- It provides the basis for inheritance
- We can use this to implement:
- Classical, class-based OO
- Object composition
- Mixins
- Other, wild and crazy object models
"What more could I possibly want?" I hear you ask. Well, and here's the rub - there's a whole variety of ways to harness this awesome, and that's the topic of today's post.
## Some Different Approaches
- POJOs - Plain Old JavaScript Objects
- util.inherits
- Classical
- ES6
- OOPOOP (tee hee)
- ES7
### POJOs - Plain Old JavaScript Objects
The most basic form is:
```javascript
var Thing = {
title: null,
printTitle: function() {
console.log(this.title);
}
};
var thing = Object.create(Thing);
thing.title = 'one';
thing.printTitle(); // => 'one'
```
How about constructors? Okay, so there's two ways:
#### Using constructor functions
```javascript
var Thing = function(title) {
this.title = title;
};
Thing.prototype = function() {
console.log(this.title);
};
var thing = new Thing('two');
thing.printTitle(); // => 'two'
```
#### Roll your own
```
var Thing = {
init: function(title) {
this.title = title;
return this;
},
printTitle: function() {
console.log(this.title);
}
};
var thing = Object.create(Thing).init('three');
thing.printTitle(); // => 'three'
```
### Prototypal Inheritance
We can achieve inheritance using prototype chaining:
```
var Model = {
data: null,
save: function() {
db.save(this.data);
}
};
var IndexedModel = {
save: function() {
Model.save.call(this); // call the superclass
index.update(data);
}
};
// create the subclass
IndexedModel.prototype = Object.create(Model);
```
However there's something I don't like here: the `IndexedModel.save()` method has to refer to its parent class `Model` by name (because the language doesn't provide a `super` keyword). That's where the "util.inherits" approach comes in...
### util.inherits
This is a really simple technique that is used in the Node.js `util` module. It simply sets up the prototype chain and creates a `_super` property that points to the superclass:
```javascript
var Model = function() {};
Model.prototype = {
data: null,
save: function() {
db.save(this.data);
}
};
var IndexedModel = function() {};
util.inherits(IndexedModel, Model);
IndexedModel.prototype.save = function() {
this.constructor.super_.prototype.save.call(this); // call the superclass
index.update(this.data);
};
```
Hmmn, so we've avoided hard-coding the superclass into `IndexedModel` but I hardly think that `this.constructor.super_.prototype.save.call(this)` is more elegant than `Model.prototype.save.call()`.
So, onwards and upwards...
### Classical OO
Classical OO is single-inheritance and provides a simple method to talk to the superclass.
```
var Class = ...; // an exercise for the reader :)
var Model = Class.extend({
data: null,
save: function() {
db.save(this.data);
}
});
var IndexedModel = Model.extend({
save: function() {
this._super(); // call the superclass
index.update(data);
}
});
```
However, to make this work we need to wrap every function in the prototype so that it set `this._super` every time the function is invoked.
While it does add overhead to provide the `_super()` method, I've never found this to be a problem, and being able to call `_super()` is definitely a win.
However, there's no need to restrict ourselves to single-inheritance and the static "classist" conceptions of Java here. This is JavaScript after all...
By manipulating objects and prototypes, JavaScript gives up great support for multiple inheritance (most often through mixins) and for object composition (where we augment the functionality of an object) using an "OOP implemented using OOP" approach which I affectionately refer to as...
### OOPOOP
SmallTalk and Ruby are languages that implement their OO object model using OO (it's very meta), so each class is an object - an instance of `Class` - and the properties of these objects define the behaviour of objects of that class.
This approach can be reproduced in JavaScript using the prototype as its foundation. This is the approach taken in both [Ember.js](http://emberjs.com/) and [uberproto](http://daffl.github.io/uberproto/).
```javascript
var Class = Proto; // this example uses uberproto
var Person = Class.extend({
init : function(name) {
this.name = name;
},
fullName : function() {
return this.name;
}
});
var BetterPerson = Person.extend({
init : function(name, lastname) {
this._super(name);
this.lastname = lastname;
},
fullName : function() {
return this._super() + ' ' + this.lastname;
}
});
var dave = BetterPerson.create('Dave', 'Doe');
console.log(dave.fullName()); // => 'Dave Doe'
Person.mixin({
init : function() {
this._super.apply(this, arguments);
},
sing : function() {
return 'Laaaa';
}
});
var dude = Person.create('Dude');
console.log(dude.sing()); // => 'Laaaa'
```
### ES6
The next version of JavaScript (called ECMAScript 6 or "ES6") provides some syntactic sugar in the form of a `class` keyword, as well as a `super` keyword. At its core this is very similar to the implementation of classes in CoffeeScript, although ES6 also provides some extra awesome.
The following code blocks are equivalent:
```
// ES5
var Thing = function() {};
Thing.prototype.wildThing = function();
// ES6
class Thing {
wildThing: function() {}
}
```
There's also quite a few other cool features of ES6 classes:
```
class Monster {
// The keyword "constructor" followed by an argument list
// and a body defines the constructor function
constructor(name, health) {
// public and private declarations in the constructor
// declare and initialize per-instance properties
public name = name;
private health = health;
}
// The following defines a method on the prototype
attack(target) {
log('The monster attacks ' + target);
}
// The keyword "get" defines a getter
get isAlive() {
return private(this).health > 0;
}
// Likewise, "set" can be used to define setters.
set health(value) {
if (value < 0) {
throw new Error('Health must be non-negative.')
}
private(this).health = value
}
// After a "public" modifier,
// an identifier optionally followed by "=" and an expression
// declares a prototype property and initializes it to the value
// of that expression.
public numAttacks = 0;
// After a "public" modifier,
// the keyword "const" followed by an identifier and an
// initializer declares a constant prototype property.
public const attackMessage = 'The monster hits you!';
}
```
#### Proxies
Proxies are another feature of ES6, that provide control over the behaviour of the object model in JavaScript. The `Proxy` API provides a bunch of "traps" that you can hook into to monkey with the object model. Most notable of these are `get`, `set`, `delete`, `enumerate`, `iterate`, and `keys`, and the API also provides `has`, `hasOwn`, `defineProperty`, `getPropertyNames`, `getOwnPropertyNames`, `getPropertyDescriptor`, `getOwnPropertyDescriptor`.
This provides us with the ability to perform the `method_missing` magic so beloved of Ruby developers (and god knows what else). Some obvious example uses for this are defining what happens when an undefined property is get, set or called, and performing validation when properties are set.
### ES7 - **Experimental**
Brendan Eich is already working on the addition of "value objects" to JavaScript's object model for ES7 (the version *after* the (upcoming) ES6). This adds support for new primitive types such as `int32`, `int64`, `bignum`, `decimal`, and sets of 4 and 8 numbers for [SIMD](http://en.wikipedia.org/wiki/SIMD) optimisations, and allow operator overloading (`>`, `>=`, `+`, `-`) and the ability to control boolean algebra (`==`, `&&`, `||`).
## ZOMG
Yes, complicated isn't it. JavaScript provides a very simple set of primitives, and over the last 20 years developers have found many wonderful ways to use these simple primitives to implement OO.
Of the options here, I personally prefer the OOPOOP approach when I need to do any serious modelling, although simple prototypal modelling often does the trick when there's only simple inheritance and polymorphism.
I'm really glad to see JavaScript providing some syntactic suger to support the most common patterns in ES6, and now that ES6 is becoming usable through transpilers ([traceur-compiler](https://github.com/google/traceur-compiler) and [jstransform](https://github.com/facebook/jstransform)) I've started using ES6 classes. The OO features in ES6 have been the source of some controversy, but I must say that I think they achieve a nice balance of providing syntactic sugar for the most common case, without hiding too much of what's going on under the hood - it's all just objects and prototypes after all.
## References
1. http://raganwald.com/2014/03/10/writing-oop-using-oop.html
1. https://github.com/daffl/uberproto
1. https://gist.github.com/rahulkmr/9482010
1. http://www.slideshare.net/BrendanEich/value-objects2
1. https://gist.github.com/stefanpenner/384554
以上是关于markdown JavaScript和对象模型的主要内容,如果未能解决你的问题,请参考以下文章
markdown 在Javascript中删除Diacritics(可用作普通函数,`String`对象和jQuery插件的扩展)。
markdown [构建没有setter的对象模型] #design_pattern
markdown MEDIUM BLOG POST - Javascript的3个主要范例:面向对象编程的三个原则[第2部分,共4部分]