Объектно-ориентированное программирование с использованием шаблонов проектирования JavaScript

внешний интерфейс Шаблоны проектирования Безопасность JavaScript

для углубленного изучения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Унаследованные свойства и методыprototypeAccess, эти свойства и методы не будут создаваться снова каждый раз при создании нового объекта, как показано на следующем рисунке:

в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Этот метод может изменить среду действия функции, вызвать этот метод в подклассе и выполнить переменные в подклассе в родительском классе, потому что родительский класс передаетсяthisBinding, поэтому подкласс также наследует свойства экземпляра родительского класса, а именно:

    //声明父类
    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Различные режимы проектирования в