предисловие
Все пришли с целью поиска объектов, но в этой статье я попытаюсь разобраться в связанных вещах, таких как прототип, новый, конструктор, instanceof, цепочка прототипов, наследование и класс с самого начала объекта.У всех разные мнения. Обязательно поставьте палец вверх~
объект
Прежде чем вы захотите понять концепции прототипа, цепочки прототипов и наследования, вы должны сначала понять, что такое объект.
Объект в ECMAScript на самом деле представляет собой набор данных и функций. - третье издание расширенного программирования javascript
Объяснение объекта, данное Красной книгой, вполне уместно
Объект — это тип данных, тип данных в js делится напримитивный типа такжетип ссылки, примитивные типы также называются примитивными типами или типами значений.
Примитивные типы: Undefined, Null, Boolean, Number, String
Тип ссылки: Объект
Два типа данных Undefined и Null упомянуты выше.Это довольно причудливые типы данных только с одним уникальным значением.Значения undefined и null.С логической точки зрения значение null представляет собой нулевой указатель объекта, который именно то, что используетсяtypeof null
Причина возврата объекта — это причина возврата объекта, а значение undefined было введено в третьем издании ECMA для формального различия между нулевыми указателями на объекты и неинициализированными (назначенными) переменными.
Считая символ es6, типов данных всего 7. Некоторые студенты хотят спросить, съедены ли Array и Set Map?
Массив фактически классифицируется как объект, а объект делится на:
- Array
- Function
- Date
- RegExp
- Set
- Map
- ...
не верь мне
typeof Symbol() // symbol
typeof [] // object
typeof new Date // object
typeof new Set() // object
typeof function(){} // function
Массив, набор, дата все возвращают объект, толькоtypeof function(){}
Функция возвращается, потому что сама функция является подклассом объекта, но по сравнению с обычными объектами она внутренне реализует[[call]]
метод, используемый для указания того, что объект может быть вызван.Когда typeof оценивает объект, если объект содержит[[call]]
метод, он вернет функцию.Другими словами, функция — это реальный объект.
Теперь, когда вы знаете, что такое объект, как вам получить объект?Конечно, первое, что приходит вам на ум, этоnew
, некоторые студенты могут сказать, что я обычноvar O={}
Вы также можете объявить объект. Этот способ объявления объекта с помощью литералов является просто ярлыком. Программисты называют это синтаксическим сахаром. Подумайте об этом, будь то RegExp, Array или Object. .
Для двух способов определения объектов, новых и литералов, Красная книга дает следующее объяснение:
- новый конструктор
- Литералы объектов (сокращение для упрощения создания объектов с большим количеством свойств)
Поскольку объект новый, откуда берется новая функция? Ха-ха-ха, есть два способа объявить функцию: "функциональное объявление" и "объявление функционального выражения". Разница в том, что функциональное объявление имеет механизм продвижения функции. можете обратиться к предыдущей статье за подробностями, и природа нового также упоминалась ранее, поэтому я не буду повторяться.
Для подъема функции, пожалуйста, обратитесь к этой статье
В этой статье рассказывалось о процессе нового, и вы можете сослаться на него
Знание того, что такое объект и откуда он берется, и, наконец, упоминание о том, как использовать объект, в основном относится к тому, как вызывать свойства объекта, не более чем через «оператор точки» и «синтаксис квадратных скобок» для вызова из С функциональной точки зрения эти два метода. Нет никакой разницы, просто синтаксис квадратных скобок более многофункциональный, он поддерживает следующие три улучшения:
- Доступ к свойствам через переменные
- Имя свойства содержит символы, вызывающие синтаксическую ошибку
- В имени свойства используется ключевое слово или зарезервированное слово.
var obj = {
name: 'n',
'var': 'v',
'type name': 't'
}
var name = 'name'
obj[name] // n
obj['var'] // v
obj['type name'] // t
Redbook рекомендует использовать оператор точки для доступа к свойствам объекта, если только мы не должны обращаться к свойствам через переменные.
прототип
новая функция
Объект — это набор данных и функций, функция — это подкласс объекта, а функция определенно имеет такие свойства, какfunction.name
представляет имя функции,function.length
представляет собой длину формального параметра,function.prototype
Это прототип.
кfunction fun(){}; var obj = new fun()
Например, fun — это функция. Когда он новый, новый obj — это экземпляр объекта. Эта функция fun новая, и мы называем ее конструктором. Экземпляр obj имеет атрибут по умолчанию __proto__, __proto__ указывает на прототип, только что Допустим, fun.prototype также указывает на прототип, тогдаfun.prototype === obj.__proto__
Установлено.
Здесь небольшое отступление.При создании новой функции круглые скобки после нее используются для передачи параметров.Если параметров нет, круглые скобки могут быть написаны или нет, как в приведенном выше примере.
new fun
а такжеnew fun()
Это не имеет значения, но Красная книга рекомендует писать скобки после новой функции, несмотря ни на что.
знать сейчасfun.prototype === obj.__proto__ === 原型
, что в прототипе? каждый можетconsole.log(fun.prototype)
Я не буду делать скриншот, когда выйду и посмотрю.Прототип - это объект, и он есть по умолчанию.constructor
свойство, конструктор указывает на саму функцию, то естьfun.prototype.constructor === obj.__proto__.constructor === fun
.
Важно добавить, что экземпляр имеет свойство __proto__, потому что экземпляр является объектом, а это означает, что все объекты имеют свойство __proto__.
новый объект
Связь между пользовательскими функциями, объектами-экземплярами и прототипами была описана выше, давайте посмотрим, какова связь между объектами, объявленными через литералы, и прототипами.
var obj1 = {a: 1}
var obj2 = new Object(); obj2.a = 1
obj2 получается через новый объект, потому что __proto__ экземпляра равен прототипу конструктора, поэтомуobj2.__proto__ === Object.prototype
obj2.__proto__.constructor === Object.prototype.constructor === Object
;
Объявление obj1 литералом — это просто синтаксический сахар, а нижний слой реализован на основе new, поэтому по-прежнему можно заменить obj2 в приведенном выше уравнении на obj1, то есть прототипы obj1 и obj2 одинаковы. предметobj2.__proto__ === obj1.__proto__
;
function.proto(Функции также являются объектами)
Как упоминалось выше, все объекты будут иметь атрибут __proto__, а функция является подмножеством объекта, также является объектом, тогдаfunction.__proto__
Что это такое? Прежде чем кратко ответить на этот вопрос, мы должны сначала выяснить, как появилась функция.Поскольку все объекты создаются с помощью new, могут ли функции также создаваться с помощью new? Конечно! Отсюда нам нужно различать функцию и функцию (обратите внимание на случай).
функция: функция нижнего регистра — это совместное слово, используемое для объявления функции. Функция: Функция в верхнем регистре является встроенной функцией js, а все пользовательские методы являются экземплярами функции.
var fun1 = new Function('x', 'y', 'return x + y')
function fun2(){}
В настоящее время, хотя fun1 является функцией, это также объект, экземпляр, созданный командой new, поэтомуfun1.__proto__ === Function.prototype
, Fun1 заменяется на fun2 и остается в силе.
Так как функция может быть объявлена как функция с помощью new, то кто создал функцию с помощью new, это может быть только сама функция, зацикливание? Да, вот структура цикла, __proto__ экземпляра объекта равен прототипу конструктора, то естьFunction.__proto__ === Function.prototype
.
__proto__ — это свойство объекта экземпляра, прототип — это свойство конструктора, исключение для fun1, fun1 — это экземпляр Function, который также может использоваться в качестве конструктора, чтобы быть новым, поэтому fun1 имеет как __proto__, так и прототип.
Наконец, давайте посмотрим, как устанавливаются эти три формулы.
fun.prototype !== fun.__proto__
fun.prototype !== Function.__proto__
fun.__proto__ === Function.__proto__
fun.prototype представляет собой прототип конструктора. В настоящее время fun рассматривается как конструктор, который может быть новым. Прототип конструктора равен __proto__ экземпляра, то естьfun.prototype === (new fun).__proto__
,а такжеfun.__proto__
Function.__proto__
Это не имеет значения, уравнения 1 и 2 выполняются.
fun.__proto__ представляет собой прототип экземпляра В настоящее время fun рассматривается как экземпляр Function, поэтому __proto__ экземпляра равен прототипу параметра построения, то естьfun.__proto__ === Function.prototype
, из-за особенности функцииFunction.__proto__ === Function.prototype
такfun.__proto__ === Function.__proto__
, Уравнение 3 выполняется.
proto.proto(прототип прототипа)
Подводя итог вышеизложенному, можно сказать, что у объекта есть __proto__, указывающий на прототип, у конструктора есть прототип, который также указывает на прототип, а прототип также является объектом. прототип?
function fun(){}
var objFun = new fun()
var obj1 = {a: 1}
var obj2 = new Object()
Взяв приведенный выше код в качестве примера, прототип obj1, obj2 и Object имеет значение null.obj1.__proto__ === Object.prototype
obj1.__proto__.__proto__ === null
obj2.__proto__.__proto__ === null
Object.prototype.__proto__ === null
Функция — это функция. Объект, на который указывает Function.prototype, также является объектом, созданным Object, поэтомуFunction.prototype.__proto__ === Object.prototype
Function.prototype.__proto__.__proto__ === null
Прототип имеет механизм восходящего поиска, и когда он достигает вершины цепочки прототипов, он возвращает null,То есть вершина цепочки прототипов равна нулю..
instanceof (различать массив и объект)
Когда мы используем typeof для определения ссылочного типа, возвращаемое значение является объектом или функцией, что делает невозможным эффективное различение таких типов, как Array и Date.Для различения ссылочных типов можно использовать команду instanceof. instanceof используется для определения того, принадлежит ли экземпляр определенному конструктору;
Синтаксис instanceof прост[] instanceof Array
, правило суждения соответствует [].protoЭта строка проверяется на наличие той же ссылки, что и строка Array.prototype, и если да, то она возвращает true, а если найдена конечная точка null, то возвращает false, если такая же ссылка не найдена.
function fun(){}
var obj = new fun()
fun instanceof Function // true
obj instanceof fun // true
obj instanceof Object // true
Array instanceof Function // true
Array instanceof Object // true
[] instanceof Array // true
[] instanceof Object // true
Function instanceof Object // true
new Date instanceof Object // true
new Date instanceof Date // true
Date instanceof Function // true
Легко понять результаты этих instanceofs на основе вышеупомянутого содержания.Следует отметить, что, поскольку все экземпляры в js основаны на Object, любой экземпляр instanceof Object вернет true.
Экземпляры совместно используют методы-прототипы (цепочка прототипов)
Дизайн js поддерживает экземпляры для доступа к методам прототипа, и прототип конструктора также указывает на прототип.Нам нужно только повесить публичные методы экземпляра на прототип, чтобы получить доступ ко всем его экземплярам, например:
Object.prototype.logObj = function(){ console.log('logObj') }
Object.prototype.logMy = function(){ console.log('logMy_prototype') }
Function.prototype.logFun = function(){ console.log('logFun') }
var obj = {
logMy: function(){ console.log('logMy') }
}
obj.logMy() // logMy
obj.logObj() // logObj
obj.logFun() // err: not a function
function fun(){}
fun.logFun() // logFun
fun.logMy() // logMy_prototype
Взяв в качестве примера obj.logMy и obj.logObj, когда объект получает доступ к методу (или свойству), он сначала ищет собственные частные методы объекта, такие как obj.logMy, если нет, он ищет методы и свойства на прототип, такой как obj .logObj, если он не найден, он будет идти вверх по цепочке __proto__, такой как fun.logMy, до тех пор, пока вершина не вернет значение null, что является цепочкой прототипов.
То же самое верно и для fun.logMy.Когда свойство logMy не найдено в Function.prototype, поиск продолжится, потому что Function.prototype указывает на объект, а объект создается на основе Object, поэтому Object.prototype будет найден ниже.
Методы массива, которые мы часто используем, такие как push, filter, строковый метод indexOf и т. д., реализованы по этому принципу, так что мы можем сами расширять некоторые функциональные методы.
hasOwnProperty (свойство само по себе или прототип)
Поскольку экземпляры могут совместно использовать свойства и методы прототипа, как определить, принадлежит ли свойство или метод самому экземпляру или прототипу? js реализует встроенный метод hasOwnProperty для Object.prototype, функция которого заключается в том, чтобы определить, исходит ли свойство от прототипа.
Object.prototype.a = 'a'
var obj = {b: 'b'}
obj.hasOwnProperty('a') // false
obj.hasOwnProperty('b') // true
наследовать
Многое из того, о чем я говорил в прошлом, — это проложить путь к наследованию.Существует много способов наследования.Я расскажу об этом позже.Теперь давайте разберемся, что такое наследование.
Например, в классе А 100 учеников. Сходство между этими 100 учениками состоит в том, что все они посещают занятия в классе А, изучают одни и те же курсы, завуч один и тот же человек и т. д. Конечно, есть и различия. такие как имя, возраст, пол, рост, вес и т. д., как мы используем данные для представления этих 100 одноклассников? Как абстрагировать то же самое, что и родительский класс, и персонализированные вещи каждого одноклассника в качестве подкласса, и подкласс унаследовать родительский класс, чтобы избежать дублирования кода, и информация каждого одноклассника является полной и независимой. Теперь проблема абстрагируется, так как нам нужно, чтобы родительский класс имел несколько свойств и методов, а подкласс имел несколько свойств и методов, чтобы несколько подклассов имели свойства и методы родительского класса, не влияя друг на друга.Это наследование.
Конструктор реализует наследование
function fun() {
this.name = 'fun'
}
fun.prototype.myLog = function() { console.log(1) }
function obj() {
fun.call(this)
this.type = 'child'
}
var O = new obj
console.log(O.myLog) // undefined
Принцип: Суть наследования, реализованного вызовом, заключается в изменении указателя this, чтобы this в родительском классе указывало на контекст подкласса, чтобы свойства или методы, установленные this в родительском классе, записывались в подкласс .
Недостатки: можно наследовать только свойства и методы конструктора родительского класса, а свойства и методы прототипа родительского класса наследовать нельзя.
Наследование через цепочку прототипов
function fun() {
this.name = 'fun'
this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { console.log(1) }
function obj(type) {
this.type = type
}
obj.prototype = new fun()
var O1 = new obj('o1')
var O2 = new obj('o2')
O1.name = 'is O1'
O1.arr.push('123')
console.log(O1.myLog) // 可以继承原型上的属性和方法
console.log(O2.name) // fun
console.log(O2.arr) // [1, 2, 3, '123']
Принцип: используйте механизм поиска по цепочке прототипов для достижения наследования и назначьте obj.prototype в качестве экземпляра родительского класса.Когда obj используется в качестве конструктора для поиска свойств в его экземпляре O1, порядок поиска следующийO1本身 -> obj.prototype(fun实例)-> fun.prototype
Таким образом, свойства конструктора родительского класса могут наследоваться. Он также может наследовать свойства прототипа родительского класса.
Недостаток: Потому что O1.proto === O2.protoПоэтому при изменении свойств в конструкторе родительского класса O1 и O2 будут влиять друг на друга.В примере при изменении O1.arr меняется и O2.arr, что и является причиной, а O1.name изменяет O2.name не меняется, т.к. при установке значения сначала будет искать на самом О1.Если атрибут name не найден, то значение имени будет установлено на самом O1.В это время это никак не влияет.protoимя на . Независимо от того, должны ли значения O1 и O2 находиться в своих собственных конструкторах или в конструкторах родительского класса, они должны поддерживаться независимо и взаимодействовать друг с другом — это то, что мы не хотим видеть.
Конструктор + цепочка прототипов для достижения наследования
function fun() {
this.name = 'fun'
this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { console.log(1) }
function obj () {
fun.call(this)
this.type = 'obj'
}
obj.prototype = new fun()
var O1 = new obj()
var O2 = new obj()
O1.arr.push('123')
console.log(O1.arr) // [1, 2, 3, '123']
console.log(O2.arr) // [1, 2, 3]
Принцип: при изменении контекста в этой точке с помощью fun.call(this) свойства и методы конструктора родительского класса устанавливаются в подкласс, который не зависит друг от друга, чтобы избежать влияния; obj.prototype = new fun( ) реализует наследование прототипа свойств и методов родительского класса.
Недостатки: этот метод реализует наследование, конструктор родительского класса будет выполняться дважды в fun.call(this) и obj.prototype = new fun(), а свойства конструктора родительского класса находятся в самом подклассе и подкласс присутствует на прототипе, что приводит к выполнениюdelete O1.arr
Просто удалите атрибут arr в самом O1, прототип O1 все еще существует, и к O1.arr все еще можно получить доступ в соответствии с механизмом восходящего поиска цепочки прототипов.
function fun() {
this.name = 'fun'
this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { console.log(1) }
function obj () {
fun.call(this)
this.type = 'obj'
}
obj.prototype = new fun()
var O1 = new obj()
O1.arr.push('123')
console.log(O1.arr) // [1, 2, 3, "123"]
delete O1.arr
console.log(O1.arr) // [1, 2, 3]
Конструктор + цепочка прототипов для достижения наследования (оптимизация)
function fun() {
this.name = 'fun'
this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { console.log(1) }
function obj() {
fun.call(this)
this.type = 'obj'
}
obj.prototype = fun.prototype // 把实例改成了引用解决了上诉问题
var O1 = new fun()
var O2 = new obj()
O1 instanceof obj // true
O2 instanceof obj // true
(new fun()).__proto__.constructor // 父类函数
(new obj()).__proto__.constructor // 父类函数
Принцип: Этот принцип обсуждаться не будет, но принцип тот же, что и выше.
Недостаток: поскольку obj.prototype = fun.prototype, экземпляры родительского и дочернего классов невозможно различить.
Object.create реализует наследование
function fun() {
this.name = 'fun'
this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { console.log(1) }
function obj() {
fun.call(this)
this.type = 'obj'
}
obj.prototype = Object.create(fun.prototype)
obj.prototype.constructor = obj
var O1 = new fun()
var O2 = new obj()
O1 instanceof obj // false
O2 instanceof obj // true
(new fun()).__proto__.constructor // 父类函数 fun()
(new obj()).__proto__.constructor // 子类函数 obj()
Принцип: создайте промежуточный объект с помощью функции создания, чтобы различать два объекта, потому что прототип объекта, созданного с помощью создания, является параметром функции создания.
Преимущества: Реализовано наследование и реализована изоляция родительско-дочерних классов.
Object.assign(target, source,...) присваивает значения всех перечислимых свойств из одного или нескольких исходных объектов целевому объекту и возвращает целевой объект. Его можно использовать для копирования объектов.Когда целевой объект имеет только атрибуты первого уровня и не имеет атрибутов второго уровня, этот метод является глубоким копированием, но когда в объекте есть объекты, этот метод является поверхностным копированием после второго атрибуты уровня.
Наследовать несколько объектов одновременно
function fun1() {
this.name1 = 'fun1'
this.arr1 = [1, 2, 3]
}
fun1.prototype.myLog1 = function() { console.log(1) }
function fun2() {
this.name2 = 'fun2'
this.arr2 = [11, 22, 33]
}
fun2.prototype.myLog2 = function() { console.log(2) }
function obj() {
fun1.call(this)
fun2.call(this)
this.type = 'obj'
}
obj.prototype = Object.assign(obj.prototype, fun1.prototype, fun2.prototype)
obj.prototype.constructor = obj
var O = new obj()
Object.assign(target, ...sources): этот метод используется для копирования значений всех перечисляемых свойств из одного или нескольких исходных объектов в целевой объект, он возвращает целевой объект.
Другие полностью соответствуют приведенному выше объяснению, поэтому я не буду говорить больше.
class
class — это новое дополнение к es6, давайте посмотрим, как использовать класс для создания объекта и реализации наследования.
класс делает объект
function Student(name) {
this.teacher = '王老师'
this.name = name
}
Student.prototype.hello = function () {
console.log(`我是${this.name},我的老师是${this.teacher}。`)
}
var xiaoming = new Student('小明')
var xiaohong = new Student('小红')
class Student {
constructor(name) { // 构造函数
this.teacher = '王老师'
this.name = name
}
hello() { // 定义在原型对象上的函数
console.log(`我是${this.name},我的老师是${this.teacher}。`)
}
}
var xiaoming = new Student('小明')
var xiaohong = new Student('小红')
Класс, определяемый class, также нуждается в new, когда объект должен быть создан.Это соответствует вышеупомянутым объектам, которые являются новыми.Разница в том, что код класса, определяемый ключевым словом class, является более кратким и позволяет избежать рассеивания монтирования прототипы код.
наследование классов
class Base {
constructor(name) {
this.name = name
this.school = 'xx大学'
this.course = ['语文', '数学']
this.teacher = '王老师'
}
modifyTeacher(tName) {
this.teacher = tName
}
}
class Student extends Base {
constructor(name) {
super(name)
this.time = new Date()
}
addCourse(course) {
this.course.push(course)
}
}
var xiaoming = new Student('小明')
var xiaohong = new Student('小红')
Совместное ключевое слово extends: extends используется для наследования родительского класса, а дочерний класс имеет свойства и методы родительского класса. (extends означает, что объект цепочки прототипов исходит из Base). super(): super используется для вызова конструктора родительского класса, иначе атрибут name родительского класса не может быть инициализирован нормально.
Наследование достигается с помощью совместного ключевого слова extends, что намного чище, чем реализация кода через цепочку прототипов.Два экземпляра xiaoming и xiaohong имеют атрибуты время, имя, школа, курс, учитель и методы moditeacher и addCourse, и они не влияют друг на друга.
В чем разница между классом, представленным ES6, и исходным наследованием прототипа JavaScript? На самом деле между ними нет никакой разницы Роль класса состоит в том, чтобы позволить движку JavaScript реализовать код цепочки прототипов, который нам нужно написать самим. Короче говоря, преимущество использования классов заключается в том, что это значительно упрощает код цепочки прототипов.