Skip to content

Commit

Permalink
Add prototype inheritance by hand.
Browse files Browse the repository at this point in the history
  • Loading branch information
timoxley committed Oct 10, 2013
1 parent fa1b24f commit 93b6f8d
Show file tree
Hide file tree
Showing 4 changed files with 312 additions and 0 deletions.
1 change: 1 addition & 0 deletions menu.json
Expand Up @@ -6,6 +6,7 @@
"Basic: Every Some",
"Basic: Reduce",
"Basic: Call",
"Prototypical Inheritance By Hand",
"Basic: Inheritance with Object.create",
"Basic: Inheritance without Object.create",
"Implement Map with Reduce",
Expand Down
182 changes: 182 additions & 0 deletions problems/prototypical_inheritance_by_hand/problem.txt
@@ -0,0 +1,182 @@
We're going to implement a rough analog of JavaScript's prototypical inheritance by hand,
to ensure we fully understand how it fits together.

########
# Task #
########

Implement the functions `New`, `Create` and `Lookup` to simulate JavaScript's
`new`, `Object.create` and property lookup mechanisms respectively.

Throughout this exercise, you will avoid using any built-in JavaScript inheritance features.
Instead you will need to use your own New, Create and Lookup functions as well as `__PROTO__`
to represent an instance's prototype, and `PROTOTYPE` to represent a constructor's prototype.

i.e.

* New(Apple, 1,2,3) == new Apple(1,2,3)
* `obj.__PROTO__` == obj.__proto__ || Object.getPrototypeOf(obj)
* `Constructor.PROTOTYPE` == `Constructor.prototype`

##################
# Part 1: Lookup #
##################

`Lookup` will simulate the behaviour of JavaScript's property lookup mechanism.
When you reference any object's property in JavaScript, it will
'walk up the prototype chain' to find the property, otherwise it will return `undefined`.

Your `Lookup` function will be passed a context object, and the property String
that we're looking for. If the property is found on the current context,
return that property, otherwise check the context's prototype, `__PROTO__`.

If a property cannot be found in the object's prototype chain, simply
return `undefined`.

```js

var cat = {
color: 'black'
}

var kitten = {
size: 'small'
}

var otherKitten = {
size: 'small',
color: 'grey'
}

kitten.__PROTO__ = cat
otherKitten.__PROTO__ = cat

Lookup(kitten, 'color') // => 'black'
Lookup(otherKitten, 'color') // => 'grey'

Lookup(kitten, 'wings') // => undefined

// changing properties on the prototype should
// affect any instances that inherit from it.
cat.color = 'blue'

Lookup(kitten, 'color') // => 'blue'

// overridden properties should still work
Lookup(otherKitten, 'color') // => 'grey'

```

##################
# Part 2: Create #
##################

`Create` will simulate the behaviour of `Object.create`.

`Create` will be passed an object, and you must return a new object with its
prototype (`__PROTO__`) set to the supplied object.

```js
fuction Cat() {

}

Cat.PROTOTYPE.speak = function() {
return 'Meow!'
}

function Kitten() {
Cat.apply(this, arguments)
}

Kitten.PROTOTYPE = Create(Cat.PROTOTYPE)

var kitten = New(Kitten)
Lookup(kitten, 'speak')() // => 'Meow!'

```

###############
# Finale: New #
###############

`New` will simulate the behaviour of JavaScript's `new` keyword.

The first argument passed to `New` will be a constructor function (i.e. a type).
Subsequent parameters must be passed to the constructor function when creating the
the new object.

`New` will return new objects using the supplied constructor function.

```js

function Cat(color) {
this.color = color
}

var blackCat = New(Cat, 'black')
blackCat.color // => black

var brownCat = New(Cat, 'brown')
brownCat.color // => brown

```

The constructor function passed to `New` may have a `.PROTOTYPE` property.
All objects created with this constructor will have their `__PROTO__`
set to the constructor's `.PROTOTYPE` property.

```js

function Cat(color) {
this.color = color
}

Cat.PROTOTYPE.speak = function() {
return 'Meow!'
}

function Kitten() {
Cat.apply(this, arguments)
}

Kitten.PROTOTYPE = Create(Cat)

Kitten.PROTOTYPE.speak = function() {
return 'Mew.'
}

var blackCat = New(Cat, 'black')
blackCat.color // => black

var brownCat = New(Cat, 'brown')
brownCat.color // => brown

```

We also need to simulate one other behaviour of the `new` keyword:
If the constructor itself returns a value, `New` will return that value.

```js

function Cat(){
return 3
}
var cat = new Cat() // 3
var cat = New(Cat) // 3

```

##############
# Conditions #
##############

* Do not use any built-in javascript prototypical inheritance features.
* Do not call `new`
* Do not use `__proto__`, `Object.getPrototypeOf` or `Object.setPrototypeOf`

#########
# Hints #
#########

* Use `hasOwnProperty` to discover if an object has a property.
104 changes: 104 additions & 0 deletions problems/prototypical_inheritance_by_hand/setup.js
@@ -0,0 +1,104 @@
"use strict"

var input = require('../../input')

module.exports = input().wrap(function(input, submission) {
var New = submission.New
var Lookup = submission.Lookup
var Create = submission.Create

var cat = {
color: 'black'
}

var kitten = {
size: 'small'
}

var otherKitten = {
size: 'small',
color: 'grey'
}

kitten.__PROTO__ = cat
otherKitten.__PROTO__ = cat

console.log()
console.log('Testing Lookup:')
console.log()
console.log("Lookup(kitten, 'color')", Lookup(kitten, 'color'))
console.log("Lookup(otherKitten, 'color')", Lookup(otherKitten, 'color'))
console.log("Lookup(kitten, 'wings')", Lookup(kitten, 'wings'))


console.log("cat.color = 'blue'")
cat.color = 'blue'

console.log("Lookup(kitten, 'color')", Lookup(kitten, 'color'))
console.log("Lookup(otherKitten, 'color')", Lookup(otherKitten, 'color'))

console.log()
console.log('Testing Create:')
console.log()

console.log("var cat = Create({color: 'black'})")
console.log("var otherCat = Create(cat)")
var cat = Create({color: 'black'})
var otherCat = Create(cat)

console.log("Lookup(cat, 'color')", Lookup(cat, 'color'))
console.log("Lookup(otherCat, 'color')", Lookup(otherCat, 'color'))
console.log("otherCat.__PROTO__ === cat", otherCat.__PROTO__ === cat)

console.log()
console.log('Testing New:')
console.log()

var rootProto = Object.create(null)

var Cat = function Cat(color) {
this.color = color
}

Cat.PROTOTYPE = rootProto

Cat.PROTOTYPE.speak = function() {
return 'Meow!'
}
var Kitten = function Kitten() {
Cat.apply(this, arguments)
}

Kitten.PROTOTYPE = Create(Cat.PROTOTYPE)

Kitten.PROTOTYPE.speak = function() {
return 'Mew.'
}

console.log("var cat = New(Cat, 'red')")
var cat = New(Cat, 'red')

console.log("var kitten = New(Kitten, 'blue')")
var kitten = New(Kitten, 'blue')

console.log("Lookup(cat, 'color')", Lookup(cat, 'color'))
console.log("Lookup(cat, 'speak')()", Lookup(cat, 'speak')())
console.log("Lookup(kitten, 'color')", Lookup(kitten, 'color'))
console.log("Lookup(kitten, 'speak')()", Lookup(kitten, 'speak')())
console.log()
console.log("Changing Cat prototype to return MEOW from speak()...")

Cat.PROTOTYPE.speak = function() {
return 'MEOW.'
}

console.log("Lookup(cat, 'speak')()", Lookup(cat, 'speak')())
console.log("Lookup(kitten, 'speak')()", Lookup(kitten, 'speak')())
console.log()
console.log('Testing constructor can return any object...')
console.log('e.g. Dog constructor returns 3.')
function Dog() {
return 3
}
console.log("New(Dog)", New(Dog))
})
25 changes: 25 additions & 0 deletions problems/prototypical_inheritance_by_hand/solution.js
@@ -0,0 +1,25 @@
function Lookup(context, property) {
if (!context) return undefined
if (Object.prototype.hasOwnProperty.call(context, property)) return context[property]
return Lookup(context.__PROTO__, property)
}

function Create(proto) {
return {
__PROTO__: proto
}
}

function New(Type) {
var obj = Create(Type.PROTOTYPE)
var args = [].slice.call(arguments, 1) // remove Type arg
var result = Type.apply(obj, args)
if (result) return result
return obj
}

module.exports = {
Lookup: Lookup,
Create: Create,
New: New
}

0 comments on commit 93b6f8d

Please sign in to comment.