для углубленного изученияjavascript
, стремясь к стандарту стандартного веб-разработчика, я хочу иметь глубокое понимание идей объектно-ориентированного программирования, улучшить свои способности модульной разработки и писать удобный, эффективный и масштабируемый код. Недавно я читалШаблоны проектирования JavaScript, обобщает и резюмирует его ключевое содержание. Если резюме не детализировано или не понятно, пожалуйста, покритикуйте его~
Что такое объектно-ориентированное программирование (ООП)?
Проще говоря, объектно-ориентированное программирование заключается в том, чтобы абстрагировать ваши требования в объект, а затем проанализировать объект и добавить к нему соответствующие функции (атрибуты) и поведения (методы).Добрый.
Очень важной особенностью объектно-ориентированного подхода является инкапсуляция, хотяjavascript
Этот интерпретируемый слабо типизированный язык не ведет себя как некоторые классические строго типизированные языки (например,C++,JAVA
и т.д.) Существуют специальные способы реализации инкапсуляции классов, но мы можем использоватьjavascript
Гибкие характеристики языка для имитации и реализации этих функций, давайте посмотрим на это вместе~
упаковка
- создать класс
существуетjavascript
Очень легко создать класс вthis
Объекты добавляют свойства или методы для добавления свойств или методов к классам, например:
//创建一个类
var Person = function (name, age ) {
this.name = name;
this.age = age;
}
Мы также можем использовать прототип объекта класса (prototype
) для добавления свойств и методов есть два способа: один — присвоить свойства объекта-прототипа один за другим, а другой — присвоить объект объекту-прототипу класса:
//为类的原型对象属性赋值
Person.prototype.showInfo = function () {
//展示信息
console.log('My name is ' + this.name , ', I\'m ' + this.age + ' years old!');
}
//将对象赋值给类的原型对象
Person.prototype = {
showInfo : function () {
//展示信息
console.log('My name is ' + this.name , ', I\'m ' + this.age + ' years old!');
}
}
Таким образом, мы инкапсулируем необходимые свойства и методы вPerson
Внутри класса, когда мы хотим его использовать, нам сначала нужно его использоватьnew
ключевое слово для инстанцирования (создания) новых объектов через.
Оператор может использовать свойства или методы созданного объекта~
var person = new Person('Tom',24);
console.log(person.name) // Tom
console.log(person.showInfo()) // My name is Tom , I'm 24 years old!
Мы только что сказали, что есть два способа добавления свойств и методов, так в чем же между ними разница?
пройти черезthis
Добавленные свойства и методы добавляются к текущему объекту, иjavascript
Характеристики языка основаны на прототипахprototype
Да, через прототипprototype
к его унаследованным свойствам и методам; черезprototype
Унаследованный метод не относится к самому объекту и используетсяprototype
Поуровневый поиск, поэтому проходимthis
Определенные свойства или методы принадлежат самому объекту, и каждый раз, когда мы передаемnew
оператор при создании нового объекта,this
Указанные свойства и методы также создаются соответствующим образом, но черезprototype
Унаследованные свойства и методыprototype
Access, эти свойства и методы не будут создаваться снова каждый раз при создании нового объекта, как показано на следующем рисунке:
вconstructor
Является ли свойство, когда создается функция или объект, создается объект-прототипconstructor
Свойство, указывающее на функцию или объект, которому принадлежит весь объект-прототип.
Если мы выберем первый способ дать объект-прототип (prototype
), чтобы добавить свойства и методы, выполните следующую инструкцию, чтобы получитьtrue
:
console.log(Person.prototype.constructor === Person ) // true
Так спросят любопытные друзья, тогда я использую второй метод, чтобы дать объект-прототип (prototype
) добавить свойства и методы к результату?
console.log(Person.prototype.constructor === Person ) // false
Черт возьми, какого черта, почему это происходит?
Причина в том, что второй способ — присвоить объекту-прототипу весь объект (prototype
), что приводит к исходному объекту-прототипу (prototype
) в свойствах и методах все будет перезаписано (pass:
Не смешивайте два метода в фактической разработке), тогдаconstructor
Конечно, указание наprototype
) добавить вручнуюconstructor
атрибут, перенаправлениеPerson
, чтобы обеспечить корректность цепочки прототипов, а именно:
Person.prototype = {
constructor : Person ,
showInfo : function () {
//展示信息
console.log('My name is ' + this.name , ', I\'m ' + this.age + ' years old!');
}
}
console.log(Person.prototype.constructor === Person ) // true
- Инкапсуляция свойств и методов
В большинстве объектно-ориентированных языков атрибуты и методы некоторых классов часто скрыты и открыты, поэтому будут такие понятия, как частные атрибуты, частные методы, общедоступные атрибуты и общедоступные методы.
существуетES6
До,javascript
Область видимости на уровне блока отсутствует, но есть область видимости на уровне функции, т. е. к переменным и методам, объявленным внутри функции, нельзя обращаться извне, что можно использовать для имитации создания классов.частная переменнаяа такжечастный метод, По внутренней функцииthis
Созданные свойства и методы, когда класс создает объекты, каждый объект будет создан и доступен для внешнего мира, поэтому мы можем передатьthis
Создайте свойства и методы каксвойства экземпляраа такжеметод экземпляра, однако поthis
Некоторые из созданных методов могут обращаться не только к публичным свойствам и методам объекта, но и к приватным свойствам и приватным методам класса (на момент создания) или к самому объекту.Ввиду сравнительно большой мощности этих методы, они становятсяПривилегированный метод,пройти черезnew
Созданный объект не может пройти.
Оператор обращается к свойствам и методам, добавленным вне класса, доступ к которым возможен только через сам класс, поэтому свойства и методы, определенные вне класса, называются свойствами и методами класса.статические общедоступные свойстваа такжестатический общедоступный метод, через прототип классаprototype
Свойства и методы, добавленные объектом, все его объекты-экземпляры передаются черезthis
доступными, поэтому мы называем эти свойства и методы какобщественная собственностьа такжеобщедоступный метод,Также известен какСвойства прототипаа такжеМетод прототипа.
//创建一个类
var Person = function (name, age ) {
//私有属性
var IDNumber = '01010101010101010101' ;
//私有方法
function checkIDNumber () {}
//特权方法
this.getIDNumber = function () {}
//实例属性
this.name = name;
this.age = age;
//实例方法
this.getName = function () {}
}
//类静态属性
Person.isChinese = true;
//类静态方法
Person.staticMethod = function () {
console.log('this is a staticMethod')
}
//公有属性
Person.prototype.isRich = false;
//公有方法
Person.prototype.showInfo = function () {}
пройти черезnew
Созданный объект может получить доступ только к соответствующим свойствам экземпляра, методам экземпляра, свойствам прототипа и методам прототипа, но не может получить доступ к статическим свойствам и частным свойствам класса.Доступ к частным свойствам и частным методам класса можно получить только через методы сам класс, а именно:
var person = new Person('Tom',24);
console.log(person.IDNumber) // undefined
console.log(person.isRich) // false
console.log(person.name) // Tom
console.log(person.isChinese) // undefined
console.log(Person.isChinese) // true
console.log(Person.staticMethod()) // this is a staticMethod
- Безопасный режим для создания объектов
Когда мы создаем объект, если мы привыклиjQuery
образом, то мы, вероятно, забудем использоватьnew
оператора для построения и напишите следующий код:
//创建一个类
var Person = function (name, age ) {
this.name = name;
this.age = age;
}
var person = Person('Tom',24)
В этот моментperson
Это не то, что мы ожидалиPerson
Пример ~
console.log(person) // undifined
Затем мы создалиname
,age
все исчезли, конечно нет, их повесилиwindow
объект включен,
console.log(window.name) // Tom
console.log(window.age) // 24
мы не используемnew
оператор для создания объектов, при выполненииPerson
метод, эта функция находится вглобальная областьказнен, в это времяthis
Указывает на глобальную переменную, котораяwindow
объекта, поэтому любые добавленные свойства будут добавлены вwindow
на, и нашperson
переменная получаетPerson
Когда результат выполнения, потому что нетreturn
оператор, который возвращает по умолчаниюundifined
.
Чтобы избежать существования этой проблемы, мы можем использовать безопасный режим для ее решения, просто немного поправьте наш класс,
//创建一个类
var Person = function (name, age) {
// 判断执行过程中的 this 是否是当前这个对象 (如果为真,则表示是通过 new 创建的)
if ( this instanceof Person ) {
this.name = name;
this.age = age;
} else {
// 否则重新创建对象
return new Person(name, age)
}
}
хорошо, давайте проверим это сейчас ~
var person = Person('Tom', 24)
console.log(person) // Person
console.log(person.name) // Tom
console.log(person.age) // 24
console.log(window.name) // undefined
console.log(window.age) // undefined
Это спасает нас от забывания использоватьnew
Проблема построения примера~
проход: здесь я используюwindow.name
, это свойство особенное, оноwindow
Строка, которая идет с ним, используется для установки или возврата имени окна хранилища, пожалуйста, замените ее~
наследовать
Наследование также является важным свойством фасетных объектов, ноjavascript
Здесь нет наследования в традиционном смысле, но мы все еще можем использоватьjavascript
особенности языка, имитирующие наследование реализации
наследование классов
Более распространенный метод наследования, принцип которого заключается в том, что мы создаем экземпляр родительского класса, вновь созданный объект копирует свойства и методы в конструкторе родительского класса и обводит__proto__
Укажите на объект-прототип родительского класса, чтобы у нас были методы и свойства объекта-прототипа родительского класса.Мы присваиваем этот объект прототипу подкласса, тогда прототип подкласса может получить доступ к свойствам прототипа и методы родительского класса, а затем для достижения наследования код выглядит следующим образом:
//声明父类
function Super () {
this.superValue = 'super';
}
//为父类添加原型方法
Super.prototype.getSuperValue = function () {
return this.superValue;
}
//声明子类
function Child () {
this.childValue = 'child';
}
//继承父类
Child.prototype = new Super();
//为子类添加原型方法
Child.prototype.getChildValue = function () {
return this.childValue;
}
Давай проверим~
var child = new Child();
console.log(child.getSuperValue()); // super
console.log(child.getChildValue()); // child
Но этот метод наследования будет иметь две проблемы: во-первых, потому что подкласс передает свой прототипprototype
Создайте экземпляр своего родительского класса и наследуйте родительский класс. Пока существует ссылочный тип в общедоступном свойстве родительского класса, он будет использоваться всеми экземплярами в дочернем классе. Если один из дочерних классов изменит атрибут ссылочный тип в значении конструктора родительского класса, который напрямую влияет на другие подклассы, такие как:
//声明父类
function Super () {
this.superObject = {
a: 1,
b: 2
}
}
//声明子类
function Child () {}
//继承父类
Child.prototype = new Super();
}
var child1 = new Child();
var child2 = new Child();
console.log(child1.superObject); // { a : 1 , b : 2 }
child2.superObject.a = 3 ;
console.log(child1.superObject); // { a : 3, b : 2 }
Это вызовет много проблем в следующих операциях!
Во-вторых, так как подклассу передается прототипprototype
Реализовано создание экземпляра родительского класса, поэтому при создании родительского класса параметры не могут быть переданы в родительский класс, а свойства внутри конструктора родительского класса не могут быть инициализированы при создании экземпляра родительского класса.
Для решения этих проблем выводятся другие методы наследования.
Наследование конструктораиспользоватьcall
Этот метод может изменить среду действия функции, вызвать этот метод в подклассе и выполнить переменные в подклассе в родительском классе, потому что родительский класс передаетсяthis
Binding, поэтому подкласс также наследует свойства экземпляра родительского класса, а именно:
//声明父类
function Super (value) {
this.value = value;
this.superObject = {
a: 1,
b: 2
}
}
//为父类添加原型方法
Super.prototype.showSuperObject = function () {
console.log(this.superValue);
}
//声明子类
function Child (value) {
// 继承父类
Super.call(this,value)
}
var child1 = new Child('Tom');
var child2 = new Child('Jack');
child1.superObject.a = 3 ;
console.log(child1.superObject); // { a : 3 , b : 2 }
console.log(child1.value) // Tom
console.log(child2.superObject); // { a : 1, b : 2 }
console.log(child2.value); // Jack
Super.call(this,value)
Этот код является сущностью наследования конструктора, поэтому проблемы наследования классов можно избежать~
Но это наследование не включает прототиповprototype
, поэтому метод-прототип родительского класса не будет унаследован, а если он хочет быть унаследован подклассом, то его необходимо поместить в конструктор, чтобы каждый созданный экземпляр имел отдельную копию и не мог использоваться совместно. чтобы решить эту проблему, с помощью композиционного наследования.
Комбинированное наследование
Пока мы выполняем конструктор суперкласса в роли конструктора экологического подкласса, подкласс в прототипеprorotype
Создав экземпляр родительского класса один раз, вы можете реализовать композиционное наследование, то есть:
//声明父类
function Super (value) {
this.value = value;
this.superObject = {
a: 1,
b: 2
}
}
//为父类添加原型方法
Super.prototype.showSuperObject = function () {
console.log(this.superObject);
}
//声明子类
function Child (value) {
// 构造函数式继承父类 value 属性
Super.call(this,value)
}
//类式继承
Child.prototype = new Super();
var child1 = new Child('Tom');
var child2 = new Child('Jack');
child1.superObject.a = 3 ;
console.log(child1.showSuperObject()); // { a : 3 , b : 2 }
console.log(child1.value) // Tom
child1.superObject.b = 3 ;
console.log(child2.showSuperObject()); // { a : 1, b : 2 }
console.log(child2.value); // Jack
Таким образом, можно объединить преимущества наследования классов и наследования конструкторов и отфильтровать их недостатки. Кажется ли это идеальным, НЕТ, внимательные студенты могут обнаружить, что мы выполняем конструктор родительского класса один раз при использовании наследования конструктора и вызываем другой родительский класс при реализации наследования в стиле класса прототипа подкласса Конструктор родительского класса выполняется дважды, который можно продолжить оптимизировать.
Паразитическая композиционная наследственность
Выше мы изучили композиционное наследование и увидели недостатки этого метода, поэтому мы вывели паразитное композиционное наследование, где паразитное — этопаразитарное наследование, в то время как паразитарное наследование зависит отпрототипное наследование, так что прежде чем учиться, мы должны понятьпрототипное наследованиеа такжепаразитарное наследование.
Прототипное наследование похоже на наследование классов, конечно, есть те же проблемы, код такой:
//原型式继承
function inheritObject (o) {
// 声明一个过渡函数对象
function F () {}
// 过渡对象的原型继承父对象
F.prototype = o;
// 返回过渡对象的一个实例,该实例的原型继承了父对象
return new F();
}
var Super = {
name : 'Super' ,
object : {
a : 1 ,
b : 2
}
}
var child1 = inheritObject(Super);
var child2 = inheritObject(Super);
console.log(child1.object) // { a : 1 , b : 2 }
child1.object.a = 3 ;
console.log(child2.object) // { a : 3 , b : 2 }
Паразитическое наследование является второй инкапсуляцией прототипного наследования, и объект расширяется в процессе инкапсуляции, и новый объект имеет новые свойства и методы.Реализация выглядит следующим образом:
//原型式继承
function inheritObject (o) {
// 声明一个过渡函数对象
function F () {}
// 过渡对象的原型继承父对象
F.prototype = o;
// 返回过渡对象的一个实例,该实例的原型继承了父对象
return new F();
}
// 寄生式继承
// 声明基对象
var Super = {
name : 'Super' ,
object : {
a : 1 ,
b : 2
}
}
function createChild (obj) {
// 通过原型继承创建新对象
var o = new inheritObject(obj);
// 拓展新对象
o.getObject = function () {
console.log(this.object)
}
return o;
}
Когда мы объединяем их характеристики, появляется паразитное наследование композиции, наследование свойств путем заимствования конструкторов и наследование методов через смешанную форму цепочки прототипов.
/**
* 寄生组合式继承
* 传递参数
* childClass 子类
* superClass 父类
* */
//原型式继承
function inheritObject (o) {
// 声明一个过渡函数对象
function F () {}
// 过渡对象的原型继承父对象
F.prototype = o;
// 返回过渡对象的一个实例,该实例的原型继承了父对象
return new F();
}
function inheritPrototype (childClass , superClass) {
// 复制一份父类的原型保存在变量中
var p = inheritObject(superClass.prototype);
// 修复子类的 constructor
p.constructor = childClass;
// 设置子类的原型
childClass.prototype = p;
}
Нам нужно наследовать прототип родительского класса, нам не нужно вызывать конструктор родительского класса, нам нужна только копия прототипа родительского класса, и эта копия может быть получена через прототипное наследование. назначенный объекту подкласса, это приведет к тому, что прототип подкласса испорчен, потому что объект-прототип суперкласса скопирован вP
серединаconstructor
Он не указывает на объект подкласса, поэтому был исправлен и присвоен прототипу подкласса, чтобы подкласс также наследовал прототип родительского класса, но не выполнял конструктор родительского класса.
ок, протестируй:
// 定义父类
function SuperClass (name) {
this.name = name;
this.object = {
a: 1,
b: 2
}
}
// 定义父类的原型
SuperClass.prototype.showName = function () {
console.log(this.name)
}
// 定义子类
function ChildClass (name,age) {
// 构造函数式继承
SuperClass.call(this,name);
// 子类新增属性
this.age = age;
}
// 寄生式继承父类原型
inheritPrototype(ChildClass,SuperClass);
// 子类新增原型方法
ChildClass.prototype.showAge = function () {
console.log(this.age)
}
//
var child1 = new ChildClass('Tom',24);
var child2 = new ChildClass('Jack',25);
console.log(child1.object) // { a : 1 , b : 2 }
child1.object.a = 3 ;
console.log(child1.object) // { a : 3 , b : 2 }
console.log(child2.object) // { a : 1 , b : 2 }
console.log(child1.showName()) // Tom
console.log(child2.showAge()) // 25
Теперь проблем нет, ха, предыдущие проблемы тоже решены, готово~
множественное наследование
картинаJava
,C++
В объектной ориентации у вас есть концепция множественного наследования, ноjavascript
Наследование достигается за счет использования цепочки прототипов, но существует только одна цепочка прототипов, и теоретически множественное наследование невозможно. Но мы можем использоватьjavascript
Гибкость множественного наследования может быть достигнута путем наследования свойств нескольких объектов.
Во-первых, давайте рассмотрим более классический метод наследования свойств отдельных объектов —extend
function extend (target,source) {
//遍历源对象中的属性
for( var property in source ){
//将源对象中的属性复制到目标对象
target[property] = source[property]
}
// 返回目标对象
return target;
}
Однако этот метод представляет собой поверхностный процесс копирования, то есть могут быть скопированы только базовые типы данных, а данные ссылочных типов не могут достичь ожидаемого эффекта, а также произойдет подделка данных:
var parent = {
name: 'super',
object: {
a: 1,
b: 2
}
}
var child = {
age: 24
}
extend(child, parent);
console.log(child); //{ age: 24, name: "super", object: { a : 1 , b : 2 } }
child.object.a = 3;
console.log(parent); //{ name: "super", object: { a : 3 , b : 2 } }
Следуя этой идее, для достижения множественного наследования необходимо скопировать атрибуты нескольких входящих объектов в исходный объект, а затем реализовать наследование атрибутов нескольких объектов.jQuery
в рамкеextend
метод, чтобы преобразовать нашу функцию выше ~
//判断一个对象是否是纯对象
function isPlainObject(obj) {
var proto, Ctor;
// (1) null 肯定不是 Plain Object
// (2) 使用 Object.property.toString 排除部分宿主对象,比如 window、navigator、global
if (!obj || ({}).toString.call(obj) !== "[object Object]") {
return false;
}
proto = Object.getPrototypeOf(obj);
// 只有从用 {} 字面量和 new Object 构造的对象,它的原型链才是 null
if (!proto) {
return true;
}
// (1) 如果 constructor 是对象的一个自有属性,则 Ctor 为 true,函数最后返回 false
// (2) Function.prototype.toString 无法自定义,以此来判断是同一个内置函数
Ctor = ({}).hasOwnProperty.call(proto, "constructor") && proto.constructor;
return typeof Ctor === "function" && Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object);
}
function extend() {
var name, options, src, copy, clone, copyIsArray;
var length = arguments.length;
// 默认不进行深拷贝
var deep = false;
// 从第二个参数起为被继承的对象
var i = 1;
// 第一个参数不传布尔值的情况下,target 默认是第一个参数
var target = arguments[0] || {};
// 如果第一个参数是布尔值,第二个参数是 target
if (typeof target == 'boolean') {
deep = target;
target = arguments[i] || {};
i++;
}
// 如果target不是对象,我们是无法进行复制的,所以设为 {}
if (typeof target !== "object" && !( typeof target === 'function')) {
target = {};
}
// 循环遍历要复制的对象们
for (; i < length; i++) {
// 获取当前对象
options = arguments[i];
// 要求不能为空 避免 extend(a,,b) 这种情况
if (options != null) {
for (name in options) {
// 目标属性值
src = target[name];
// 要复制的对象的属性值
copy = options[name];
// 解决循环引用
if (target === copy) {
continue;
}
// 要递归的对象必须是 plainObject 或者数组
if (deep && copy && (isPlainObject(copy) ||
(copyIsArray = Array.isArray(copy)))) {
// 要复制的对象属性值类型需要与目标属性值相同
if (copyIsArray) {
copyIsArray = false;
clone = src && Array.isArray(src) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
}
target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}
return target;
};
Этот метод по умолчанию использует неглубокую копию, то есть:
var parent = {
name: 'super',
object: {
a: 1,
b: 2
}
}
var child = {
age: 24
}
extend(child,parent)
console.log(child); // { age: 24, name: "super", object: { a : 1 , b : 2 } }
child.object.a = 3;
console.log(parent) // { name: "super", object: { a : 3 , b : 2 } }
Нам просто нужно передать первый параметр какtrue
, вы можете глубоко копировать, то есть:
extend(true,child,parent)
console.log(child); // { age: 24, name: "super", object: { a : 1 , b : 2 } }
child.object.a = 3;
console.log(parent) // { name: "super", object: { a : 1 , b : 2 } }
хорошо~ этоjavascript
Некоторое знание объектно-ориентированного языка на китайском языке, вы можете внимательно посмотреть на друзей здесь, я вам верюjavascript
С дальнейшим пониманием и пониманием объектно-ориентированного программирования на китайском языке это также для более позднегоШаблоны проектированияОбучение заложило основу, и я продолжу делиться ею в будущем.javascript
Различные режимы проектирования в