惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
The Last Watchdog
The Last Watchdog
L
Lohrmann on Cybersecurity
P
Proofpoint News Feed
I
Intezer
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cisco Talos Blog
Cisco Talos Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
S
Schneier on Security
罗磊的独立博客
AWS News Blog
AWS News Blog
S
Securelist
J
Java Code Geeks
月光博客
月光博客
V
Vulnerabilities – Threatpost
博客园 - 【当耐特】
有赞技术团队
有赞技术团队
G
GRAHAM CLULEY
Project Zero
Project Zero
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
DataBreaches.Net
The Hacker News
The Hacker News
Know Your Adversary
Know Your Adversary
The GitHub Blog
The GitHub Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Scott Helme
Scott Helme
博客园 - Franky
S
Security Affairs
Cyberwarzone
Cyberwarzone
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Forbes - Security
Forbes - Security
K
Kaspersky official blog
Martin Fowler
Martin Fowler
Schneier on Security
Schneier on Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
宝玉的分享
宝玉的分享
腾讯CDC
Application and Cybersecurity Blog
Application and Cybersecurity Blog
G
Google Developers Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
美团技术团队
MyScale Blog
MyScale Blog
L
LangChain Blog
V
V2EX
N
News | PayPal Newsroom
N
News and Events Feed by Topic
aimingoo的专栏
aimingoo的专栏
Blog — PlanetScale
Blog — PlanetScale
IT之家
IT之家

Ivan on Containers, Kubernetes, and Server-Side

A grounded take on agentic coding for production environments Server-Side Playgrounds Reimagined: Build, Boot, and Network Your Own Virtual Labs [not a] Kubernetes 101 - Pods, Deployments, and Services As an Attempt To Automate Age-Old Infra Patterns JavaScript or TypeScript? How To Benefit From the Dichotomy On Software Design... and Good Writing Building a Firecracker-Powered Course Platform To Learn Docker and Kubernetes How To Publish a Port of a Running Container What Actually Happens When You Publish a Container Port A Visual Guide to SSH Tunnels: Local and Remote Port Forwarding Debugging Containers Like a Pro Docker: How To Debug Distroless And Slim Containers How To Extract Container Image Filesystem Using Docker | iximiuz Labs In Pursuit of Better Container Images: Alpine, Distroless, Apko, Chisel, DockerSlim, oh my! How To Start Programming In Go: Advice For Fellow DevOps Engineers Kubernetes Ephemeral Containers and kubectl debug Command How To Develop Kubernetes CLIs Like a Pro Docker Container Commands Explained: Understand, Don't Memorize | iximiuz Labs Learning Docker with Docker - Toying With DinD For Fun And Profit How To Extend Kubernetes API - Kubernetes vs. Django The Influence of Plumbing on Programming How To Call Kubernetes API from Go - Types and Common Machinery How To Call Kubernetes API using Simple HTTP Client Kubernetes API Basics - Resources, Kinds, and Objects OpenFaaS - Run Containerized Functions On Your Own Terms Learning Containers From The Bottom Up Docker Containers vs. Kubernetes Pods - Taking a Deeper Look | iximiuz Labs Learn-by-Doing Platforms for Dev, DevOps, and SRE Folks How HTTP Keep-Alive can cause TCP race condition How to Work with Container Images Using ctr | iximiuz Labs Multiple Containers, Same Port, no Reverse Proxy... Exploring Go net/http Package - On How Not To Set Socket Options Disposable Local Development Environments with Vagrant, Docker, and Arkade DevOps, SRE, and Platform Engineering My Choice of Programming Languages Prometheus Is Not a TSDB How to learn PromQL with Prometheus Playground Prometheus Cheat Sheet - Basics (Metrics, Labels, Time Series, Scraping) Rust - Writing Parsers With nom Parser Combinator Framework pq - parse and query log files as time series Prometheus Cheat Sheet - Moving Average, Max, Min, etc (Aggregation Over Time) Prometheus Cheat Sheet - How to Join Multiple Metrics (Vector Matching) The Need For Slimmer Containers Understanding Rust Privacy and Visibility Model Bridge vs. Switch: Takeaways from a Real Data Center Tour | iximiuz Labs From LAN to VXLAN: Networking Basics for Non-Network Engineers | iximiuz Labs KiND - How I Wasted a Day Loading Local Docker Images Go, HTTP handlers, panic, and deadlocks Exploring Kubernetes Operator Pattern Making Sense Out Of Cloud Native Buzz Service Discovery in Kubernetes: Combining the Best of Two Worlds API Developers Never REST How Container Networking Works: Building a Bridge Network From Scratch | iximiuz Labs Traefik: canary deployments with weighted load balancing Service Proxy, Pod, Sidecar, oh my! You Need Containers To Build Images You Don't Need an Image To Run a Container Not Every Container Has an Operating System Inside Working with container images in Go Master Go While Learning Containers Implementing Container Runtime Shim: Interactive Containers How to use Flask with gevent (uWSGI and Gunicorn editions) My 10 Years of Programming Experience Implementing Container Runtime Shim: First Code Implementing Container Runtime Shim: runc Kubernetes Repository On Flame Dealing with process termination in Linux (with Rust examples) conman - [the] Container Manager: Inception Journey From Containerization To Orchestration And Beyond Linux PTY - How docker attach and docker exec Commands Work Inside Illustrated introduction to Linux iptables From Docker Container to Bootable Linux Disk Image Пишем свой веб-сервер на Python: протокол HTTP 9001 способ создать веб-сервер на Python Explaining async/await in 200 lines of code Explaining event loop in 100 lines of code Save the day with gevent Пишем свой веб-сервер на Python: процессы, потоки и асинхронный I/O Truly optional scalar types in protobuf3 (with Go examples) Node.js Writable streams distilled Node.js Readable streams distilled How to on starting processes (mostly in Linux) Дайджест интересных ссылок – Июль 2016 Пишем свой веб-сервер на Python: сокеты Мастерить!
Наследование в JavaScript
Ivan Velichko · 2016-01-17 · via Ivan on Containers, Kubernetes, and Server-Side

Оглавление

  • Наследование
    • Наследование на основе классов
    • Прототипное наследование
  • Прототипное наследование в JavaScript
  • Запутывающая всех конструкция new
  • Заключение
  • Полезные ссыкли по теме

Наследование

Наследование - это один из способов повторного использования кода. Оно бывает двух типов: на основе классов (class-based) и прототипное (prototypal). В любом случае, так как речь про объектно-ориентированное программирование, наследование - это про создание множества объектов, обладающих общими свойствами, на основе уже существующих.

Level up your server-side game — join 20,000 engineers getting insightful learning materials straight to their inbox.

Рассмотрим пример на некотором вымышленном языке программирования удивительно похожем на JavaScript:

// Простейший способ создать объект
var greeterInstance = {
    person: null,
    greeting: function() { return 'Hello ' + this.person; }
};
greeterInstance.person = 'Alice';

// Некоторый код, который использует greeterInstance ...

// А здесь нам нужен аналогичный объект, но для Bob'а.
var greeterInstance2 = {
    person: null,
    greeting: function() { return 'Hello ' + this.person; }
};
greeterInstance2.person = 'Bob';
// ...

Упс, похоже на copy/paste! Такой код будет трудно поддерживать, так как изменения придется вносить сразу во все места, где создается instance. Попробуем улучшить:

function createGreeter(person) {
    return {
        person: person,
        greeting: function() { return 'Hello ' +  this.person; }
    };
}

var aliceGreeter = createGreeter('Alice');

// Некоторый код, который использует aliceGreeter ...

var bobGreeter = createGreeter('Bob');
// ...

Отлично, теперь мы можем создавать множество похожих объектов, используя createGreeter(), и мы избавились от дублирования кода. Это уже наследование? Нет, так как никто ни от кого ничего не наследует. Это способ повторного использования кода - да.

function createGreeter(person) {
    return {
        person: person,
        greeting: function() { return 'Hello ' +  this.person; }
    };
}

function createGateKeeper(person) {
    var keeper = {
        opened: false,
        open: function() {
            this.opened = true;
            console.log(this.greeting());
        }
    };

    var greeter = createGreeter(person);
    for (var k in keeper) {
        greeter[k] = keeper[k];
    }
    return greeter;
}

var gateKeeper = createGateKeeper('Alice');
gateKeeper.open();

А вот это уже больше похоже на наследование, так как с помощью createGateKeeper() мы можем создавать множество похожих объектов, каждый из которых основывается на объектах типа Greeter.

Наследование на основе классов

Рассмотрим аналогию из реального мира. Класс можно воспринимать, как чертеж, по которому создаются изделия на заводе (объекты). Чертеж изделия != самому изделию. Это лишь информация о том, как построить изделие (создать объект). В основанном на классах наследовании, одни чертежи наследуют общие свойства от других чертежей. А затем, объекты, созданные на основе таких чертежей, обладают свойствами, определенными как в первых, так и во вторых. Рассмотрим пример на Python 3:

class Greeter:
    def __init__(self, person):
        self.person = person

    def greeting(self):
        return 'Hello ' + self.person

aliceGreeter = Greeter('Alice')
# Во многих языках (Java, C++, C#, etc) принято писать aliceGreeter = new Greeter(...).
# Обратите внимание на ключевое слово new. Такую реализацию наследования
# принято называть "классической".

print(aliceGreeter.greeting())

# "Чертеж" для gate keeper'ов построен на основе чертежа для greeter'ов.
# Т.е. наследует все его свойства. А также добавляет собственные.
class GateKeeper(Greeter):
    def __init__(self, person):
        super().__init__(person)
        self.opened = False

    def open(self):
        self.opened = True
        print(self.greeting())

gateKeeper = GateKeeper('Alice')
gateKeeper.open()

Прототипное наследование

В прототипном наследовании отсутствует понятие чертежа (класса). Тут речь скорее идет о некотором первородном объекте-образце. Этот объект используется для создания множества других, идентичных ему объектов, расширяющих его дополнительными свойствами. Т.е. это похоже на завод, где нет чертежей изделия, которое нужно производить, но есть образец в единственном экземпляре. Задача инженеров на таком заводе научиться воспроизводить копии такого изделия по образцу и встраивать в них новые функции. И да, само по себе изделие №0 также является полноправным изделием (вспомним, что класс != объект этого класса).

Прототипное наследование можно реализовать минимум двумя разными способами:

  • копированием всех свойств основного объекта в создаваемый на этапе его построения
  • делегированием обращений к свойтвам, не заданным в создаваемом объекте, базовому объекту

Оба способа имеют свои плюсы и минусы. Рассмотрим реализацию первого способа:

function createBaseObject() {
    return {
        foo: 'bar',
        method1: function() { return 42; }
    };
}

var base = createBaseObject();

function createChildObject() {
    var child = {
        baz: 9001,
        method2: function() { return 43; }
    };

    for (var k in base) {
        child[k] = base[k];
    }
    return child;
}

Минусы:

  • Так как каждый child объект содержит копию свойств base требуется дополнительная память.
  • Дополнительное время на копирование свойств base в child при создании.

Плюсы:

  • Скорость обращения к свойствам child'ов не страдает за счет делегирования (см. ниже).
  • Изменение base объекта после создания child'ов не влияет на уже созданные объекты (это может быть как плюсом, так и минусом).

Рассмотрим реализацию на основе делегирования (обращаю внимание, что это не JavaScript, а некоторый вымышленный язык удивительно на него похожий):

function createBaseObject() {
    return {
        foo: 'bar',
        method1: function() { return 42; }
    };
}

var base = createBaseObject();

function createChildObject() {
    return {
        baz: 9001,
        method2: function() { return 43; },
        __get__: function(prop) {
            assert !this.hasOwnProperty(prop)
            return base[prop];  // Делегирование обращения базовому объекту
        }
    };
}

Предположим, что "магический" метод __get__ переопределяет поведение при обращении к свойтвам, которые не заданы у самого объекта. Т.е. внутри __get__ вызов this.hasOwnProperty(prop) всегда возращает false.

Минусы:

  • Скорость такого кода должна быть ниже (если не рассматривать различные оптимизации под капотом языка) за счет дополнительного уровня косвенности, вводимого методом __get__.

Плюсы:

  • Создание объектов происходит быстрее.
  • Требуется меньше дополнительной памяти.

JavaScript - весьма гибкий язык. Прототипное наследование в нем можно реализовать обоими способами. На самом деле, код из примера реализации прототипного наследования копированием свойств базового объекта (см. выше) является рабочим в JavaScript. Однако, JavaScript из коробки реализует функциональность, схожую с методом __get__ из второго примера. Использование родных для языка механизмов, на мой взгляд, является предпочтительным для реализации наследования, потому что они потенциально могут быть оптимизорованы движком языка.

Для ссылки на базовый объект при обращении к свойствам, не заданным у текущего объекта, используется свойство [[Prototype]]. Т.е., для того, чтобы унаследовать один объект от другого, нужно каким-либо способом задать для наследника [[Prototype]] равным ссылке на базовый объект. Простейший (но не стандартизованный до ES6 и не самый быстрый при этом) способ - это использовать свойство __proto__:

var base = {
    foo: 'bar'
};

var child = {
    baz: 42,
    __proto__: base
};

console.log(child.baz);
console.log(child.foo);  // Делегирование обращения базовому объекту

До ES6 было как минимум два "законных" способа сделать это. Первый и не самый прямолинейный - это использование ключевого слова new. Поговорим о нем чуть позже. Второй же - изобретенная Дугласом Крокфордом функция Object.create() (ссылка), которая в итоге была добавлена в сам язык.

Код из примера выше можно переписать следующим образом:

var base = {
    foo: 'bar'
};

var child = Object.create(base);  // создает новый объект с заданным прототипом
child.baz = 42;

console.log(child.baz);
console.log(child.foo);

Обернув две строки создание child в функцияю createChild() мы создадим удобную реализацию прототипного наследования от base.

Запутывающая всех конструкция new

Основная причина, усложняющая понимание реализации наследования в JavaScript - это конструкция new, добавленная в язык с целью популяризовать его, сделав похожим на языки с "классической" схемой наследования.

Как уже было сказано выше, с помощью new можно создать объект с заданным прототипом. Для этого нам понадобится функция.

var base = {
    greeting: function() { return 'Hello ' + this.person; }
};

function Greeter(person) {
    this.person = person;
}

Greeter.prototype = base;

var greeter = new Greeter('Alice');
console.log(greeter.greeting());          // prints "Hello Alice"
console.log(greeter.__proto__ === base);  // prints "true"

Функции наподобие Greeter в JavaScript называются конструкторами (а иногда не совсем корректно - классами). При вызове new Greeter() создается новый объект, this внутри конструктора ссылается на этот объект. А в качестве прототипа этого объекта задается объект Greeter.prototype. Таким образом, вводится дополнительный уровень косвенности.

This indirection was intended to make the language seem more familiar to classically trained programmers, but failed to do that, as we can see from the very low opinion Java programmers have of JavaScript. JavaScript’s constructor pattern did not appeal to the classical crowd. It also obscured JavaScript’s true prototypal nature. As a result, there are very few programmers who know how to use the language effectively. (с) Дуглас Крокфорд

Теперь, обладая этими знаниями, мы можем без труда понять первоначальную реализацию Object.create():

Object.create = function(o) {
    function F() {}
    F.prototype = o;
    return new F();
};

В результате вызова Object.create() будет создан новый пустой объект (new F()), прототипом которого будет объект o. И достигается это за счет описанной выше особенности JavaScript.

Заключение

Если подходить к вопросу наследования в JavaScript с "правильной" стороны, все оказывается достаточно просто и прозрачно. Как это зачастую и бывает в Computer Science, под капотом все просто и строится на базовых понятиях. В данном случае - на делегировании. С другой стороны, JavaScript позволяет организовать наследование множеством способов, каждый из которых по-своему "правильный", обладает определенными достоинствами и недостатками. Осознание этого может вызывать затрудненния.

Полезные ссыкли по теме

Подпишись на обновления блогa, чтобы не пропустить следующий пост!