面型对象之设计模式(浅谈)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面型对象之设计模式(浅谈)相关的知识,希望对你有一定的参考价值。
这篇文章是根据高程中的相关章节总结的,看了几遍,在这里总结一下笔记。感觉面向对象这一篇,最重要的就是理清思路,理解一下各个设计模式的机理以及优缺点。
在学习之初,首先要明白一点,为何要学习JS的各种设计模式?这个问题要归咎于JS本身了,JS本身并没有“类”的概念(这里不考虑ES6),因此不可能像其他强类型的编程语言一样,通过“类”创建对象。这里只能通过几种设计模式创建。
一、工厂模式
首先,JS提供了Object引用类型,可根据构造函数或者对象字面量创建对象,比如:
利用object构造函数创建两个属性相同,方法一致的对象:
var person1 = new Object(); person1.name = "shane"; person1.age = 25; person1.sayName = function(){ console.log(this.name); };
var person2 = new Object(); person2.name = "Peter"; person2.age = 30; person2.sayName = function(){ console.log(this.name); };
这种创建方式的问题是:如果要创建n个属性相同,方法一致的对象,是否要像上述一样写n次?这也太麻烦了,也不符合实际。那么如何去解决这种问题?引出了工厂模式。
概念:工厂函数通俗的说就是利用函数的方式将特定的创建对象的步骤封装起来,提高复用性。
那么此时上述的利用Object创建的两个相同属性以及方法的对象,就可用工厂函数封装起来:
function person(name,age){ var o = new Object(); o.name = name; o.age = age; o.sayName = function(){ console.log(this.name); }; return o; } //调用函数以创建对象实例 var person1 = person("shane",25); var person2 = person("Peter",30);
瞧!是不是只是利用函数的封装,使用时直接调用即可,很简单。
缺点:工厂函数的简单的封装并未解决对象识别的问题--创建的自定义的构造函数意味着可以将其作为一种特定的类型。因此引出了“构造函数模式”。
二、构造函数模式
概念:构造函数模式通俗来讲就是自定义一个构造函数,然后利用new操作符创建这个构造函数对应的实例。
例如:
//定义自定义构造函数
function Person(name,age){ this.name = name; this.age = age; this.sayName = function(){ console.log(this.name); } }
//利用new操作符创建构造函数实例
var person1 = new Person("shane",25);
var person2 = new Person("peter",30);
注意到:这里并没有显式写出return语句,但是为何能够创建出对象的实例?这就要简单了解一下new操作符在这个过程中起到了什么作用。
利用自定义构造函数创建对象的过程:
- 使用new操作符创建对象;
- 调用构造函数,将新创建的对象赋值给构造函数内部的this;
- 在构造函数内部使用this为新创建的对象添加成员(属性或方法);
- 默认的返回新创建的对象。
注意:由于利用自定义构造函数创建对象时,不必显式添加return语句,但是如果不小心添加上了return语句,那么对结果会有什么影响?
- 如果添加的return语句返回的是空值(return;)或者返回的是基本类型值或null,那么最终返回的依然是默认的新创建的对象;
- 如果添加的return语句返回的是引用类型的值,那么这是要注意最终返回的是新添加的return语句。
那么利用自定义构造函数模式,那么其对应的实例都会属于自定义的类型,这个解决了工厂模式遗留的问题。(验证方式:可利用instanceof,有些内容涉及到后面的原型链以及继承知识)
缺点:构造函数模式虽然解决了自定义类型的识别问题,但是也不完善,无法做到共享的属性和方法以及每个实例独有的属性和方法分离。因此,引出了“原型模式”。
三、原型模式
概念:原型模式就是利用每个函数的prototype属性(其是一个指向函数原型的指针)访问原型中的属性和方法,从而致力于解决实例之间成员“共享问题”。
例如:
//自定义一个构造函数 function Person(){} //利用对象的动态特性,给原型对象添加成员(属性和方法) Person.prototype.name = "shane"; Person.prototype.age = 25; Person.prototype.sayName = function(){ console.log(this.name); }; //利用new操作符创建实例 var person1 = new Person(); person1.sayName(); var person2 = new Person(); person2.sayName();
但是要注意的是:这里只能使用共享的属性和方法,暂时还不能独立使用每个实例一些特殊属性和方法。也即是说,上述代码,sayName返回的内容一样。
那么既然提到了原型的使用,那么也要了解一下原型内部的一些原理:
首先,只要是定义了一个函数,那么这个函数就有prototype属性,该属性指向函数的对象(在这里是Person.prototype)。接着对于函数Person的原型来说,其内部有一个constructor属性,该属性值指向prototype属性所在的函数的指针(在这里是Person,注意:这里说法很严谨,并不是直接说指向其构造函数指针,因为还要涉及原型链,有可能constructor指向构造函数原型的原型)。对于两个实例来说,每个实例都有内部属性prototype,指向的是构造函数的原型对象。
这里补充一个内容:
在拥有原型的情况下,要访问属性以及方法时,JS的查找顺序是什么样的?(这里先不考虑原型链,假设就是有一个构造函数的原型,不包含原型的原型Object,只是为了简化,研究方便)以查找属性为例
- 首先会从实例中的属性进行查找,如果有直接返回;如果没有进行下一步;
- 此时,直接在实例对应的原型中查找该属性,如果有返回,如果没有则返回undefined。(如果是方法,则报错)
这里有一个例子:
function Person(){ } Person.prototype.name = "shane"; Person.prototype.books = { book1: "javascript" }; //实例 var person1 = new Person(); person1.name = "peter"; console.log(person1.name); var person2 = new Person(); person2.books.book1 = "Economist"; console.log(person2.books.book1); console.log(person2.books);
解释:
创建的第一个实例中,想要给name属性赋值,注意“赋值”而不是“获取”。因此,此时会直接在实例person1,新创建一个名为name的属性,其值为"peter"。但是原型中存在了同样属性名的属性,那么在JS中,会直接将原型中的同名属性屏蔽掉。因此,利用person1.name访问时,返回的是“peter”。
创建的第二个实例中,则与第一个实例相反。会先通过person2.books去访问,如果在实例中没有books对象,那么会向上查找。可知,在原型中找到了books对象;接着修改其中的book1的值,因此,此时通过这种方式修改了原型中的属性值。
缺点:原型最大的劣势是,其所有的实例的属性以及属性值都是相同的,因此原型的“共享性”。因此,仅仅有原型还不行,因为实例自身拥有的属性和方法也需要访问到。=>引出了“组合使用构造函数模式和原型模式”。
三、组合使用构造函数模式和原型模式
概念:显而易见,组合模式就是结合了构造函数模式以及原型模式的优点。实例之间既可以利用原型模式共享的属性和方法,也可以使用实例自身拥有的属性和方法。
例如:
//定义自定义构造函数,并且传参 function Person(name,age){ this.name = name; this.age = age; }
//给对应的原型添加成员 Person.prototype = { constructor: Person, sayName: function(){ console.log(this.name); } }; //创建实例 var person1 = new Person(‘shane‘,25); var person2 = new Person(‘peter‘,30);
注意:重写原型时,原型中的constructor不再指向原构造函数,而是指向Object,因此可以通过属性重新使得原型指向原构造函数。
这种模式应该是使用最广泛、认同度最高的一种创建自定义类型方法。
四、动态原型模式
概念:动态原型模式感觉是组合模式的一种变形,一种优化模式。因为这种模式通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
function Person(name,age){ this.name = name; this.age = age; if(typeof this.sayName != ‘function‘){ Person.prototype.sayName = function(){ console.log(this.name); } } } //创建实例 var person1 = new Person(‘shane‘,25); person1.sayName(); var person2 = new Person(‘peter‘,30); person2.sayName();
五、寄生构造函数模式
概念:通俗讲寄生构造函数模式就是创建一个函数,然后封装创建对象的代码,最后利用return语句返回在构造函数内部新创建的对象。
例如:
//寄生构造函数模式 function Person(name,age){ var o = new Object(); o.name = name; o.age = age; o.sayName = function(){ console.log(this.name); }; return o; } var person1 = new Person(‘shane‘,24); person1.sayName(); //比较构造函数模式 function Person(name,age){ this.name = name; this.age = age; this.sayName = function(){ console.log(this.name); }; } var person1 = new Person(‘peter‘,30); person1.sayName(); //相比较工厂模式 function person(name,age){ var o = new Object(); o.name = name; o.age = age; o.sayName = function(){ console.log(this.name); }; return o; } var person1 = person(‘bob‘,20); person1.sayName();
看到这个概念,我联想到了“工厂函数模式”,作为设计模式的“初始者”,工厂函数模式也是利用函数将创建对象的代码封装起来,提高复用性。但是两者的区别是调用方式不同,工厂函数模式是利用普通函数的调用模式;而寄生构造函数模式利用你new操作符调用,并且寄生构造函数模式在构造函数的内部也是用了return语句进行返回。工厂函数模式尽然与寄生构造函数模式如此相像,那么为何还创造。书上说,这种模式在上述几种模式不适用的情况下的一种模式。然后我又联想到了“噶构造函数模式”,进行这三者的比较。(So, stupid,huh?)
劣势:
由于是在构造函数内部重新定义了一个对象,因此返回的这个对象与构造函数后者构造函数的原型之间没有任何关系。因此,不能依赖instanceof操作符确定对象的类型。因此,这和“工厂函数模式”很相似啊!
六、稳妥构造函数模式
概念:稳妥构造函数模式有一个与“工厂函数模式”类似的,这个模式没有公共属性,不能使用this与new操作符,因此最适合在安全环境中使用。
这种模式与“构造函数模式”不同的是除了不能使用this之外,也不能使用new操作符。
例如:
//稳妥构造函数模式 function Person(name,age){ var o = new Object(); o.sayName = function(){ console.log(name); }; return o; } //调用 var person1 = Person(‘shane‘,25); person1.sayName(); //工厂函数模式 function Person(name,age){ var o = new Object(); o.name = name; o.age = age; o.sayName = function(){ console.log(this.name); }; return o; } var person1 = Person(‘peter‘,30); person1.sayName(); //构造函数模式 function Person(name,age){ this.name = name; this.age = age; this.sayName = function(){ console.log(this.name); }; } var person1 = new Person(‘bob‘,20); person1.sayName();
劣势:
这种类似于“工厂函数模式”的模式,自然不能够利用instanceof检测类型。
以上是关于面型对象之设计模式(浅谈)的主要内容,如果未能解决你的问题,请参考以下文章