JavaScript
Понятие класса отсутствует, и наследование в основном достигается через цепочку прототипов. Обычно наследование означает операции копирования, однакоJavaScript
По умолчанию свойства объекта не копируются, вместо этогоJavaScript
Просто создайте ассоциацию (указатель объекта-прототипа) между двумя объектами, чтобы один объект мог получить доступ к свойствам и функциям другого объекта посредством делегирования, чтобы вместо вызова наследования делегирование было более точным.
прототип
когда мы
new
Доступ к новому экземпляру объекта можно получить напрямую, ничего не делаяtoString
,valueOf
и другие нативные методы. Так откуда же взялись эти методы? Ответ — прототипы.
Когда консоль выводит пустой объект, мы видим, что есть много методов, которые были «инициализированы», смонтированными на встроенном__proto__
объект включен. это встроенный__proto__
— это указатель на объект-прототип, который автоматически создается (явно или неявно) при создании нового объекта ссылочного типа и монтируется в новый экземпляр. Когда мы пытаемся получить доступ к свойству/методу объекта экземпляра, если есть свойство/метод объекта экземпляра, вернуть свойство/метод экземпляра, если нет, перейти к__proto__
Найдите соответствующее свойство/метод в указанном объекте-прототипе. Вот почему мы пытаемся получить доступ к пустому объектуtoString
а такжеvalueOf
Причина, по которой другие методы все еще могут быть доступны,JavaScript
Формально наследование осуществляется таким образом.
Конструктор
Если экземпляр__proto__
Это просто указатель на объект-прототип, что означает, что объект-прототип был создан раньше, так когда же был создан объект-прототип? Это представитКонструкторКонцепция чего-либо.
По сути, конструктор — это обычная функция, если эту функцию можно использоватьnew
ключевое слово для создания своего экземпляра объекта, затем мы вызываем эту функциюКонструктор.
// 普通函数
function person () {}
// 构造函数,函数首字母通常大写
function Person () {}
const person = new Person();
Объект-прототип создается при объявлении конструктора. При объявлении конструктора также создается объект-прототип, который затем монтируется в объект конструктора.prototype
На территории:
При создании объекта-прототипаconstructor
свойство, указывающее на конструктор, который его создал. Таким образом, отношения между ними тесно связаны.
Если вы будете внимательны, то можете обнаружить, что у объекта-прототипа также есть свои собственные
__proto__
, это и не удивительно, ведь все есть объект. __proto__ объекта-прототипа указывает наObject.prototype
. ТакObject.prototype.__proto__
Он существует? На самом деле его не существует.Если вы его распечатаете, то обнаружите, что этоnull
. Это также доказываетObject
даJavaScript
Происхождение типа данных в .
На данный момент мы примерно понимаем приблизительную связь между прототипами и конструкторами.Мы можем использовать диаграмму, чтобы представить эту связь:
Сеть прототипов
После разговора о прототипе мы можем поговорить о цепочке прототипов.Если вы понимаете механизм прототипа, цепочку прототипов можно легко объяснить. На самом деле, на картинке выше__proto__
связанные цепные отношения, называемыеСеть прототипов.
Роль цепочки прототипов: Причина, по которой цепочка прототипов так важна, заключается в том, что она определяетJavaScript
Реализация наследования в . Когда мы обращаемся к свойству, механизм поиска выглядит следующим образом:
- Доступ к атрибутам экземпляра объекта, возвращение, если есть, передача, если нет
__proto__
Перейдите к объекту-прототипу, чтобы найти его. - Если объект-прототип найден, он будет возвращен, если не найден, продолжить поиск по __proto__ объекта-прототипа.
- слой за слоем был найден
Object.prototype
, если целевое свойство найдено, то оно будет возвращено, а если не найдено, то будет возвращеноundefined
, дальше вниз смотреть не буду, потому что глядя вниз__proto__
то естьnull
.
Благодаря приведенному выше объяснению для экземпляра, сгенерированного конструктором, мы должны понять его объект-прототип. Все в JavaScript является объектом, поэтому конструктор также должен быть объектом.__proto__
, то конструктор__proto__
что это такое?
Можем распечатать и посмотреть:
Я только что вспомнил, что все функции можно использоватьnew Function()
способ создания, то этот ответ очень естественный, немного интересный, а затем попробуйте другие конструкторы.
Это также доказывает, что все функцииFunction
пример. Подождите минутку, кажется, что-то не так, тогдаFunction.__proto__
Не будет ли. . .
Согласно приведенной выше логике, если мы скажем так,Function
Ты не создал себя? На самом деле, мы не должны понимать это так, потому что как встроенный объект JS,Function
Объект уже существует до того, как будет сгенерирован ваш скриптовый файл. Где вы можете назвать себя? Эта вещь похожа на «Дао» и «Цянькунь» в метафизике. Можете ли вы объяснить, кто их создал? День рождения, солнце, луна и Гены не рождаются и не уничтожаются. . . Забудь, будет написано как культиватор когда я спущусь.=. знак равно
почемуFunction.__proto__
равныйFunction.prototype
Есть несколько поговорок:
- Для согласованности с другими функциями
- Чтобы проиллюстрировать отношения, такие как доказательство того, что все функции
Function
пример. - функции можно назвать
call
bind
Эти встроенные API-интерфейсы могут быть написаны таким образом, чтобы экземпляры функций могли использовать эти API-интерфейсы.
будь осторожен:
Несколько замечаний о прототипах, цепочках прототипов и конструкторах:
-
__proto__
Это нестандартное свойство, если вы хотите получить доступ к прототипу объекта, рекомендуется использовать новый ES6Reflect.getPrototypeOf
илиObject.getPrototypeOf()
метод, а не напрямуюobj.__proto__
, потому что нестандартный атрибут означает, что атрибут может быть изменен или удален непосредственно в будущем. Точно так же при изменении прототипа объекта лучше всего также использоватьES6
который предоставилReflect.setPrototypeOf
илиObject.setPrototypeOf
.
let target = {};
let newProto = {};
Reflect.getPrototypeOf(target) === newProto; // false
Reflect.setPrototypeOf(target, newProto);
Reflect.getPrototypeOf(target) === newProto; // true
- функция будет иметь
prototype
,КромеFunction.prototype.bind()
за пределами. - объекты будут иметь
__proto__
,КромеObject.prototype
снаружи (на самом деле он тоже существует, но онnull
). - Все функции создаются из Function, т. е. их
__proto__
равныFunction.prototype
. -
Function.prototype
равныйFunction.__proto__
.
Загрязнение прототипа
Загрязнение прототипа означает, что злоумышленник каким-то образом модифицирует прототип объекта JavaScript.
Что вы имеете в виду, принцип на самом деле очень прост. если мы положимObject.prototype.toString
Измените его на это:
Object.prototype.toString = function () {alert('原型污染')};
let obj = {};
obj.toString();
Затем, когда мы запустим этот код, в браузере появится всплывающее окно.alert
, родной объектtoString
Метод был переписан, и все объекты при вызовеtoString
будут затронуты.
Вы можете сказать, как кто-то может быть настолько глуп, чтобы написать такой код в исходном коде, разве это не выстрел себе в ногу? Да, в исходниках такое никто бы не написал, но злоумышленник мог пройтиформаилиИзменить содержание запросаи другие способы использования прототипа загрязнения для атаки, рассмотрим следующую ситуацию:
'use strict';
const express = require('express');
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser');
const path = require('path');
const isObject = obj => obj && obj.constructor && obj.constructor === Object;
function merge(a, b) {
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}
function clone(a) {
return merge({}, a);
}
// Constants
const PORT = 8080;
const HOST = '0.0.0.0';
const admin = {};
// App
const app = express();
app.use(bodyParser.json())
app.use(cookieParser());
app.use('/', express.static(path.join(__dirname, 'views')));
app.post('/signup', (req, res) => {
var body = JSON.parse(JSON.stringify(req.body));
var copybody = clone(body)
if (copybody.name) {
res.cookie('name', copybody.name).json({
"done": "cookie set"
});
} else {
res.json({
"error": "cookie not set"
})
}
});
app.get('/getFlag', (req, res) => {
var аdmin = JSON.parse(JSON.stringify(req.cookies))
if (admin.аdmin == 1) {
res.send("hackim19{}");
} else {
res.send("You are not authorized");
}
});
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
Если приведенный выше фрагмент кода присутствует на сервере, злоумышленник может просто добавитьcookie
установлен в{__proto__: {admin: 1}}
Вторжение в систему может быть завершено.
Прототипы решений по борьбе с загрязнением
Прежде чем рассматривать решение проблемы загрязнения прототипа, мы можем рассмотретьlodash
Предыдущий подход команды к решению проблемы загрязнения прототипа:
Код очень прост, пока вы сталкиваетесьconstructor
или__proto__
Такие деликатные слова сразу выйдут из исполнения. Это, конечно, эффективное средство предотвращения загрязнения прототипа, и, конечно же, у нас есть другие средства:
- использовать
Object.create(null)
, метод создает прототип какnull
, так что любое расширение прототипа не вступит в силу:
const obj = Object.create(null);
obj.__proto__ = { hack: '污染原型的属性' };
console.log(obj); // => {}
console.log(obj.hack); // => undefined
-
использовать
Object.freeze(obj)
Заморозить указанный объект, чтобы его свойства нельзя было изменить, сделав его нерасширяемым объектом:Object.freeze(Object.prototype); Object.prototype.toString = 'evil'; console.log(Object.prototype.toString); // => ƒ toString() { [native code] }
-
Учреждать
JSON schema
, при разборе пользовательского ввода передатьJSON schema
Фильтровать конфиденциальные имена ключей. -
Избегайте небезопасных рекурсивных слияний. это похоже на
lodash
Метод восстановления повышает безопасность операции слияния и пропускает обработку имен конфиденциальных ключей.
наследовать
Наконец, мы можем поговорить о наследовании.Давайте сначала рассмотрим концепцию наследования и посмотрим, что говорит Baidu:
наследоватьдаобъектно-ориентированныйКонцепция технологии программного обеспечения, связанная сполиморфизм,упаковкаВсегообъектно-ориентированныйтри основные характеристики. Наследование позволяет подклассам иметьАтрибутыа такжеметодИли переопределить, добавить свойства и методы и т. д.
Для программистов это объяснение относительно легко понять. Затем, прокрутив вниз, я увидел важное описание:
Создание подклассов может добавлять новые данные и новые функции и может наследовать все функции родительского класса, но не может выборочно наследовать некоторые функции родительского класса.Наследование — это отношение класса к классу, а не отношение объекта к объекту.
Это неловко,JavaScript
Откуда берется класс, есть только объекты. Означает ли это, что чистое наследование невозможно? Отсюда вступительная фраза:Вместо наследования термин делегирование является более точным.
ноJavaScript
Она очень гибкая, и хотя эта особенность гибкости приносит ей много недостатков, она также создает много замечательных преимуществ. Неважно, что наследование классов не предусмотрено изначально, мы можем сделать это более разнообразным способом.JavaScript
Наследование в , например, использованиеObject.assign
:
let person = { name: null, age: null };
let man = Object.assign({}, person, { name: 'John', age: 23 });
console.log(man); // => { name: 'John', age: 23 }
использоватьcall
а такжеapply
:
let person = {
name: null,
sayName: function () {
console.log(this.name);
},
sayAge: function () {
console.log(this.age);
}
};
let man = { name: 'Man', age: 23 };
person.sayName.call(man); // => Man
person.sayAge.apply(man); // => 23
Даже мы можем использовать глубокое копирование объектов для завершения операций, похожее на наследство ...JS
Существует множество способов реализации наследования в , но найти некоторые проблемы несложно, взглянув на приведенный выше код:
- Инкапсуляция не сильная, слишком сумбурно, и писать очень неудобно.
- Просто невозможно определить, откуда наследуется дочерний объект.
Есть ли способ решить эти проблемы? мы можем использоватьJavaScript
Наиболее распространенный способ наследования:прототипное наследование
Наследование цепочки прототипов
Наследование цепочки прототипов означает, что экземпляры объекта соединены последовательно через цепочку прототипов, при доступе к определенному атрибуту целевого объекта его можно искать по цепочке прототипов, чтобы добиться эффекта, аналогичного наследованию.
// 父类
function SuperType (colors = ['red', 'blue', 'green']) {
this.colors = colors;
}
// 子类
function SubType () {}
// 继承父类
SubType.prototype = new SuperType();
// 以这种方式将 constructor 属性指回 SubType 会改变 constructor 为可遍历属性
SubType.prototype.constructor = SubType;
let superInstance1 = new SuperType(['yellow', 'pink']);
let subInstance1 = new SubType();
let subInstance2 = new SubType();
superInstance1.colors; // => ['yellow', 'pink']
subInstance1.colors; // => ['red', 'blue', 'green']
subInstance2.colors; // => ['red', 'blue', 'green']
subInstance1.colors.push('black');
subInstance1.colors; // => ['red', 'blue', 'green', 'black']
subInstance2.colors; // => ['red', 'blue', 'green', 'black']
В приведенном выше коде используется самая простая цепочка наследования прототипов, так что подкласс может наследовать свойства родительского класса.**Ключевым шагом прототипного наследования является связывание прототипа подкласса с прототипом родительского класса, так что цепочка прототипов может быть связана**. Здесь прототип подкласса напрямую указывает на экземпляр родительского класса для завершения ассоциации.
Вышеупомянутое является начальным состоянием прототипного наследования, когда мы проанализируем приведенный выше код, мы обнаружим, что проблемы все еще есть:
- При создании экземпляра подкласса нельзя передавать параметры конструктору супертипа.
- Созданный таким образом прототип подкласса будет содержать свойства экземпляра родительского класса, вызывая проблему синхронного изменения свойств ссылочного типа.
наследование композиции
Объединение наследства с помощьюcall
Вызов конструктора родительского класса в конструкторе дочернего класса решает две указанные выше проблемы:
// 组合继承实现
function Parent(value) {
this.value = value;
}
Parent.prototype.getValue = function() {
console.log(this.value);
}
function Child(value) {
Parent.call(this, value)
}
Child.prototype = new Parent();
const child = new Child(1)
child.getValue();
child instanceof Parent;
Однако у него все еще есть проблемы: конструктор родительского класса вызывается дважды (один раз при создании прототипа подкласса и один раз при создании экземпляра подкласса), что приводит к существованию атрибутов экземпляра родительского класса в прототипе подкласса, расточительному ОЗУ.
Наследование паразитарного состава
Ввиду недостатков комбинированного наследования возникло «паразитическое комбинированное наследование»: использованиеObject.create(Parent.prototype)
Создайте новый объект-прототип и назначьте его подклассу, чтобы устранить недостатки наследования композиции:
// 寄生组合继承实现
function Parent(value) {
this.value = value;
}
Parent.prototype.getValue = function() {
console.log(this.value);
}
function Child(value) {
Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false, // 不可枚举该属性
writable: true, // 可改写该属性
configurable: true // 可用 delete 删除该属性
}
})
const child = new Child(1)
child.getValue();
child instanceof Parent;
Модель паразитарного комбинированного наследования в настоящее время признана в отрасли относительно надежной.JS
схема наследования,ES6
изclass
унаследовано вbabel
После экранирования нижний слой также реализуется путем наследования паразитных комбинаций.
Решение о наследственных отношениях
Когда мы используем наследование цепочки прототипов, как оценить связь между экземпляром объекта и целевым типом?
instanceof
мы можем использоватьinstanceof
определить, существуют ли между ними отношения наследования.instanceof
Буквальное значение: является ли xx экземпляром xxx. верни, если даtrue
в противном случае вернутьсяfalse
:
function Parent () {}
function Child () {}
Child.prototype = new Parent();
let parent = new Parent();
let child = new Child();
parent instanceof Parent; // => true
child instanceof Child; // => true
child instanceof Parent; // => true
child instanceof Object; // => true
instanceof
По сути, отношения наследования оцениваются путем просмотра цепочки прототипов, поэтому их можно использовать только для оценки ссылочного типа, и они недействительны для базового типа Мы можем вручную реализовать упрощенную версию.instanceof
:
function _instanceof (obj, Constructor) {
if (typeof obj !== 'object' || obj == null) return false;
let construProto = Constructor.prototype;
let objProto = obj.__proto__;
while (objProto != null) {
if (objProto === construProto) return true;
objProto = objProto.__proto__;
}
return false;
}
Object.prototype.isPrototypeOf(obj)
так же доступноObject.prototype.isPrototypeOf
Чтобы косвенно определить отношения наследования, этот метод используется для определения того, существует ли объект в цепочке прототипов другого объекта:
function Foo() {}
function Bar() {}
function Baz() {}
Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);
var baz = new Baz();
console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true
Эта статья была включена вСтолбец «Руководство по фронтенд-интервью»
Связанные ссылки
- Подробное изучение JavaScript от прототипа к цепочке прототипов
- Последнее: все, что вам нужно знать о JavaScript, стоящем за серьезным недостатком безопасности Lodash
- предварительное интервью
Рекомендуемый прошлый контент
- Тщательно разбирайтесь в троттлинге и анти-тряске
- [Основные] Принципы и применение протоколов HTTP и TCP/IP
- [Combat] webpack4 + ejs + express перенесет вас в многостраничную архитектуру проекта приложения
- Цикл событий в браузере
- Интервьюер: Расскажите мне о контексте казни.
- Интервьюер: Расскажите о масштабе и закрытиях.
- Интервьюер: Расскажите о модульности в JS.
- Интервьюер: Расскажите о модульности в JS.