Upgrade to Pro — share decks privately, control downloads, hide ads and more …

nodejsday 2020: JavaScript Prototypes Behind the Scenes

nodejsday 2020: JavaScript Prototypes Behind the Scenes

JavaScript is a prototype-oriented language. But for many, what this really means or how it actually works is a little fuzzy. It seems, that as quickly as you learn how prototypes work behind the scenes, as quickly do you forget it again. If you can relate to this feeling, this talk is for you.

We’ll dive into why JavaScript is a prototype-oriented language instead of class-based and what that means for you as a developer. You’ll learn exactly how prototypal inheritance works in JavaScript - in a way that’s easy to remember - so that you can become a better programmer.

Finally, you’ll learn about the security pitfalls that exist in the way prototypal inheritance is implemented in JavaScript and how to write more secure code to avoid those pitfalls.

Thomas Watson

October 02, 2020
Tweet

More Decks by Thomas Watson

Other Decks in Programming

Transcript

  1. wa7son
    Thomas Watson
    wa7son
    github.com/watson

    View Slide

  2. wa7son
    Who am I?
    • Thomas Watson

    • Principal Software Engineer at Elastic

    • Open Source developer at github.com/watson

    • Node.js Core Member

    • Tweets as @wa7son

    • Slides: github.com/watson/talks

    View Slide

  3. wa7son
    JavaScript Prototypes
    Behind the Scenes
    wa7son

    View Slide

  4. wa7son
    In the beginning…
    wa7son

    View Slide

  5. wa7son

    View Slide

  6. wa7son

    View Slide

  7. wa7son

    View Slide

  8. wa7son
    wa7son
    Classes
    vs
    Prototypes

    View Slide

  9. wa7son
    Class Inheritance
    wa7son

    View Slide

  10. wa7son
    Image
    - height
    - width
    - bitmap

    View Slide

  11. wa7son
    Image
    - height
    - width
    - bitmap
    Button
    - height
    - width
    - label
    - onClick()

    View Slide

  12. wa7son
    Button
    - label
    - onClick()
    Image
    - bitmap
    UIElement
    - height
    - width

    View Slide

  13. wa7son
    Button
    - label
    - onClick()
    Image
    - bitmap
    - onClick()
    UIElement
    - height
    - width

    View Slide

  14. wa7son
    Button
    - label
    Image
    - bitmap
    UIElement
    - height
    - width
    - onClick()

    View Slide

  15. wa7son
    Button
    - label
    Image
    - bitmap
    UIElement
    - height
    - width
    ClickableElement
    - onClick()

    View Slide

  16. wa7son
    Banana / Gorilla Problem
    wa7son
    “You wanted a banana but what you got
    was a gorilla holding the banana
    and the entire jungle”
    - Joe Armstrong, the creator of Erlang

    View Slide

  17. wa7son
    Prototypes
    wa7son

    View Slide

  18. wa7son
    wa7son
    Delegates
    • Shared properties / methods
    • Don’t find what you’re looking for on the
    object? Look at its prototype!
    • Conserve memory
    Prototypes

    View Slide

  19. wa7son
    Java’isk Object Creation
    wa7son
    function Cat (name) {
    this.name = name
    }
    Cat.prototype.talk = function () {
    return `Meow, I'm ${this.name}!`
    }
    const whiskers = new Cat('Mr. Whiskers')
    whiskers.talk() // Meow, I'm Mr. Whiskers!

    View Slide

  20. wa7son
    Cleaner Prototype Object Creation
    wa7son
    const proto = {
    talk () {
    return `Meow, I'm ${this.name}!`
    }
    }
    const whiskers = Object.create(proto)
    whiskers.name = 'Mr. Whiskers'
    whiskers.talk() // Meow, I'm Mr. Whiskers!

    View Slide

  21. wa7son
    wa7son
    Delegates Cloning / Concatenation
    • Shared properties / methods
    • Don’t find what you’re looking for on the
    object? Look at its prototype!
    • Conserve memory
    • Default state / mixins
    Prototypes

    View Slide

  22. wa7son
    Mixing it up
    wa7son
    class Image extends UIElement {
    // ...
    }
    const myImage = new Image()
    Object.assign(myImage, Clickable)

    View Slide

  23. wa7son
    wa7son
    Delegates Cloning / Concatenation
    • Shared properties / methods
    • Don’t find what you’re looking for on the
    object? Look at its prototype!
    • Conserve memory
    • Default state / mixins
    Prototypes

    View Slide

  24. wa7son
    Time to get our hands dirty…
    wa7son

    View Slide

  25. {}
    Object Object.prototype
    Function Function.prototype
    null
    Object
    Function
    __proto__ __proto__
    __proto__
    __proto__
    __proto__
    prototype
    prototype
    constructor
    constructor
    constructor
    constructor
    constructor
    Inherited
    Own
    © Thomas Watson // @wa7son

    View Slide

  26. {}
    Object Object.prototype
    Function Function.prototype
    null
    Object
    Function
    __proto__ __proto__
    __proto__
    __proto__
    __proto__
    prototype
    prototype
    constructor
    constructor
    constructor
    constructor
    constructor
    Inherited
    Own
    {}
    __proto__
    constructor
    Object with
    custom prototype
    © Thomas Watson // @wa7son

    View Slide

  27. MyObj.prototype
    Object Object.prototype
    Function Function.prototype null
    Object
    Function
    © Thomas Watson // @wa7son
    __proto__
    __proto__
    __proto__
    __proto__
    __proto__
    prototype
    prototype
    constructor
    constructor
    constructor
    constructor
    constructor
    Inherited
    Own
    new MyObj()
    __proto__
    constructor
    MyObj
    prototype
    constructor
    __proto__

    View Slide

  28. wa7son
    Prototype Pollution
    wa7son

    View Slide

  29. wa7son
    How to clone an object
    wa7son
    function merge (target, source) {
    for (const [key, value] of Object.entries(source)) {
    target[key] = value
    }
    return target
    }
    merge({ a: 1 }, { b: 2 }) // { a: 1, b: 2 }
    merge(
    { a: { foo: 1 } },
    { a: { bar: 2 } }
    ) // { a: { bar: 2 } }

    View Slide

  30. wa7son
    wa7son
    function merge (target, source) {
    for (const [key, value] of Object.entries(source)) {
    target[key] = value
    }
    return target
    }
    merge({ a: 1 }, { b: 2 }) // { a: 1, b: 2 }
    merge(
    { a: { foo: 1 } },
    { a: { bar: 2 } }
    ) // { a: { bar: 2 } }
    How to clone an object

    View Slide

  31. wa7son
    wa7son
    function merge (target, source) {
    for (const [key, value] of Object.entries(source)) {
    target[key] = value
    }
    return target
    }
    merge({ a: 1 }, { b: 2 }) // { a: 1, b: 2 }
    merge(
    { a: { foo: 1 } },
    { a: { bar: 2 } }
    ) // { a: { bar: 2 } }


    How to clone an object

    View Slide

  32. wa7son
    wa7son
    function deepMerge (target, source) {
    for (const [key, value] of Object.entries(source)) {
    if (isObject(value)) {
    const clone = deepMerge({}, value)
    if (isObject(target[key])) {
    deepMerge(target[key], clone)
    } else {
    target[key] = clone
    }
    } else {
    target[key] = value
    }
    }
    return target
    }
    How to deep-clone an object

    View Slide

  33. wa7son
    wa7son
    function deepMerge (target, source) {
    for (const [key, value] of Object.entries(source)) {
    if (isObject(value)) {
    const clone = deepMerge({}, value)
    if (isObject(target[key])) {
    deepMerge(target[key], clone)
    } else {
    target[key] = clone
    }
    } else {
    target[key] = value
    }
    }
    return target
    }
    How to deep-clone an object

    View Slide

  34. wa7son
    wa7son
    function deepMerge (target, source) {
    for (const [key, value] of Object.entries(source)) {
    if (isObject(value)) {
    const clone = deepMerge({}, value)
    if (isObject(target[key])) {
    deepMerge(target[key], clone)
    } else {
    target[key] = clone
    }
    } else {
    target[key] = value
    }
    }
    return target
    }
    How to deep-clone an object

    View Slide

  35. wa7son
    wa7son
    function deepMerge (target, source) {
    for (const [key, value] of Object.entries(source)) {
    if (isObject(value)) {
    const clone = deepMerge({}, value)
    if (isObject(target[key])) {
    deepMerge(target[key], clone)
    } else {
    target[key] = clone
    }
    } else {
    target[key] = value
    }
    }
    return target
    }
    How to deep-clone an object

    View Slide

  36. wa7son
    wa7son
    function deepMerge (target, source) {
    for (const [key, value] of Object.entries(source)) {
    if (isObject(value)) {
    const clone = deepMerge({}, value)
    if (isObject(target[key])) {
    deepMerge(target[key], clone)
    } else {
    target[key] = clone
    }
    } else {
    target[key] = value
    }
    }
    return target
    }
    How to deep-clone an object
    ✅ deepMerge(
    { a: { foo: 1 } },
    { a: { bar: 2 } }
    ) // { a: { foo: 1, bar: 2 } }

    View Slide

  37. wa7son
    The Attack
    wa7son
    function isAdmin (user) {
    return user.admin === true
    }
    const user = {}
    isAdmin(user) // false
    const payload = '{"__proto__":{"admin":true}}'
    deepMerge({}, JSON.parse(payload))
    isAdmin(user) // true

    View Slide

  38. wa7son
    The Attack
    wa7son
    function isAdmin (user) {
    return user.admin === true
    }
    const user = {}
    isAdmin(user) // false
    const payload = '{"__proto__":{"admin":true}}'
    deepMerge({}, JSON.parse(payload))
    isAdmin(user) // true

    View Slide

  39. wa7son
    The Attack
    wa7son
    function isAdmin (user) {
    return user.admin === true
    }
    const user = {}
    isAdmin(user) // false
    const payload = '{"__proto__":{"admin":true}}'
    deepMerge({}, JSON.parse(payload))
    isAdmin(user) // true

    View Slide

  40. wa7son
    The Attack
    wa7son
    function isAdmin (user) {
    return user.admin === true
    }
    const user = {}
    isAdmin(user) // false
    const payload = '{"__proto__":{"admin":true}}'
    deepMerge({}, JSON.parse(payload))
    isAdmin(user) // true

    View Slide

  41. wa7son
    The Attack
    wa7son
    function isAdmin (user) {
    return user.admin === true
    }
    const user = {}
    isAdmin(user) // false
    const payload = '{"__proto__":{"admin":true}}'
    deepMerge({}, JSON.parse(payload))
    isAdmin(user) // true

    View Slide

  42. wa7son
    wa7son
    • Look out for `__proto__` in untrusted user input
    • Look out for `constructor.prototype` in untrusted user input
    The Antidote

    View Slide

  43. {}
    Object Object.prototype
    Function Function.prototype
    null
    Object
    Function
    __proto__ __proto__
    __proto__
    __proto__
    __proto__
    prototype
    prototype
    constructor
    constructor
    constructor
    constructor
    constructor
    Inherited
    Own
    © Thomas Watson // @wa7son

    View Slide

  44. wa7son
    wa7son
    • Look out for `__proto__` in untrusted user input
    • Look out for `constructor.prototype` in untrusted user input
    npm install secure-json-parse
    The Antidote

    View Slide

  45. wa7son
    wa7son
    The Antidote

    View Slide

  46. github.com/watson
    Grazie mille
    wa7son

    View Slide