JavaScript OOP 创建对象的7种方式

 

我写JS代码,可以说一直都是面向过程的写法,除了一些用来封装数据的对象或者jQuery插件,可以说对原生对象了解的是少之又少。所以我拿着《JavaScript高级程序设计 第3版》恶补了一下,这里坐下总结笔记,属于菜鸟级别,大神请直接无视。

1、工厂模式

  1. 1 /**
  2. 2 * 工厂模式
  3. 3 */
  4. 4 function createPerson(name,age,job){
  5. 5 var o = new Object();
  6. 6 o.name = name;
  7. 7 o.age = age;
  8. 8 o.job = job;
  9. 9 o.sayName = function(){
  10. 10 console.log(this.name);
  11. 11 };
  12. 12 // 等价于 o.sayName = new Function("console.log(this.name);");
  13. 13 return o;
  14. 14 }
  15. 15 var p1 = createPerson('lvyahui1',12,'devloper');
  16. 16 var p2 = createPerson('lvyahui2',23,'desigen');
  17. 17 p1.sayName();
  18. 18 p2.sayName();
  19. 19 console.log(p1.sayName === p2.sayName); // false

这种方法存在很多问题,比如多个对象的方法是独立的,没用共享。不能判断对象的类型

2、构造函数模式

  1. 1 /**
  2. 2 * 构造函数模式
  3. 3 */
  4. 4 function Person (name,age,job){
  5. 5 this.name = name;
  6. 6 this.age = age;
  7. 7 this.job = job;
  8. 8 this.sayName = function(){
  9. 9 console.log(this.name);
  10. 10 };
  11. 11 }
  12. 12 var pp1 = new Person('lvyahui1',12,'dev');
  13. 13 var pp2 = new Person('lvyahui2',12,'desien');
  14. 14 pp1.sayName();
  15. 15 pp2.sayName();
  16. 16 console.log(pp1.constructor === Person); // true
  17. 17 console.log(pp2.constructor === Person); // true
  18. 18 console.log(pp1 instanceof Object); // true
  19. 19 console.log(pp2 instanceof Person); // true

这中方式解决了对象类型判断的问题,但却没有解决方法共享的问题,方法依然在每个对象里创建了一遍。要解决这样的问题,可以像下面这样

  1. 1 function Home (name,age,job){
  2. 2 this.name = name;
  3. 3 this.age = age;
  4. 4 this.job = job;
  5. 5 this.sayAge = sayAge;
  6. 6 }
  7. 7 function sayAge(){
  8. 8 console.log(this.age);
  9. 9 }

但这样有带来了新的问题,在全局作用域定义的function只能在对象环境运行,违背了全局的概念;而且,当要定义的方法非常多的石猴。就要定义n多的全局函数,毫无封装性可言。

另外,需要注意的是,构造函数也是函数,它与普通函数的唯一区别,就在于调用方式的不同。如下面这样,我门可以拿它当普通函数用,这样属性和方法将被绑定到浏览器全局的执行环境window上,或者绑定到别的对象上运行,将属性方法绑定到别的对象

  1. 1 // 构造函数也是函数
  2. 2 // 1
  3. 3 var person = new Person('hahha',23,'ddd');
  4. 4 // 2 做普通函数
  5. 5 Person('window',11,'2323dd');
  6. 6 //window.sayName();
  7. 7
  8. 8 // 3 在另一个对象的作用域中使用
  9. 9 var o = new Object();
  10. 10 Person.call(o,'lvyahui',23,'2323'),
  11. 11 o.sayName();

3、原型模式

js中每一个函数都有一个特殊的属性-prototype(原型),这个属性是一个指针,指向一个对象。这个对象包含所有以这个函数为构造函数创建的对象共享的属性和方法,首先看下面的代码

  1. 1 /**
  2. 2 * 原型模式
  3. 3 */
  4. 4 function Dog(){}
  5. 5 Dog.prototype.name = 'a mi';
  6. 6 Dog.prototype.age = 1;
  7. 7 Dog.prototype.sayName = function(){
  8. 8 console.log(this.name);
  9. 9 }
  10. 10
  11. 11 var d1 = new Dog(),
  12. 12 d2 = new Dog();
  13. 13 d1.sayName();
  14. 14 d2.sayName();
  15. 15 console.log(d1.sayName === d2.sayName); // true
  16. 16 console.log(Dog.prototype.isPrototypeOf(d1)); // true
  17. 17 console.log(Object.getPrototypeOf(d1)); //返回 [[prototype]]的值
  18. 18 console.log(Object.getPrototypeOf(d1) === Dog.prototype);

这里将属性和方法都添加到了函数的原型对象中。在第4行的代码中我定义了Dog构造方法,这让Dog的原型对象的constructor属性指向了Dog。5-7行代码又为Dog的原型对象添加了2个属性和一个方法。

第11行、12行代码创建了两个对象实例,这两个实例中都只包含了一个特殊的属性[[Prototype]],这个属性指向了构造函数的prototype,构造函数的prototype属性指向了原型对象,原型对象的constructor属性有指会了Dog构造方法,这里比较绕,我直接取书上面的图给大家看,稍作了修改,图里面的是Person构造函数,这里实在抱歉,暂时没在ubuntu系统上找到好的作图工具,只能将就了,有知道的可以介绍给我用用。

另外这个图其实还省略了以个继承关系,就是Person对象其实是继承Obejct的,这是默认规则,别的语言也有。这也是js对象一创建就有object的方法和属性的原因。

再一个,上面的代码中的

  • isPrototypeOf方法返回的是参数对象的[[Prototype]]属性是否指向调用isPrototypeOf的对象。
  • getPrototype方法返回[[Prototype]]属性的值。

原型很重要的一个概念是属性搜索(方法我认为也是属性),从实例对象开始一直往原型链上游搜索,如果找到了就停止搜索,所以如过我们在实例对象中添加原型中已有的属性或方法,会屏蔽掉原型链中的属性或方法。看下面的代码

  1. 1 d1.name = 'sai mao'; // 屏蔽原型中的属性
  2. 2 console.log(d1.name);
  3. 3 // 要恢复原型中的属性,必须显示删除实例中的属性
  4. 4 delete d1.name;
  5. 5 console.log(d1.name);

可以看到,可以使用delete恢复原型中的属性,而下面的方法可以用来检测一个属性是在实例对象中,还是在原型链的原型对象中。

  1. 1 // 检测一个属性是在实例中,还是在原型中
  2. 2 d1.name = 'hhhh';
  3. 3 console.log(d1.hasOwnProperty('name')); // 只有在给定的属性存在于对象实例中才返回true
  4. 4 delete d1.name;
  5. 5 console.log(d1.hasOwnProperty('name'));
  6. 6
  7. 7 //单独使用in操作符,只要能在对象中找到属性则返回true
  8. 8 d1.name = 'dsfdsfsd';
  9. 9 console.log('name' in d1); // true
  10. 10 console.log('name' in d2); // ture
  11. 11
  12. 12 //同时使用hasOwnProperty 和 in操作符,就能确定这个属性到底是在原型中还是在实例中
  13. 13 function hasPrototypeProperty(object,name){
  14. 14 return !object.hasOwnProperty(name) && name in object;
  15. 15 }
  16. 16 console.log('d1 hasPrototypeProperty :'+hasPrototypeProperty(d1, 'name'));
  17. 17 console.log('d2 hasPrototypeProperty :'+hasPrototypeProperty(d2, 'name'));

例外有一种更简单的原型写法

  1. 1 // 更简单的原型语法
  2. 2 function Cat(){}
  3. 3 Cat.prototype = {
  4. 4 name : 'mimi',
  5. 5 age : 12,
  6. 6 job : 'doubi',
  7. 7 sayName : function(){
  8. 8 console.log(this.name);
  9. 9 }
  10. 10 };
  11. 11
  12. 12 var cat = new Cat();
  13. 13 console.log(cat instanceof Object);
  14. 14 console.log(cat instanceof Cat);
  15. 15 console.log(cat.constructor === Cat);//false
  16. 16 console.log(cat.constructor === Object);//true

这种方式其实是以字面量的方法重新创建了一个对象,然后赋值给了原型指针。它丢弃了原来的原型对象,所以很显然的原型对象的constructor属性不再指向Cat,而是指向了Obejct,有多种方法可以修复构造函数,比如在定义字面量对象的时候,就显示制定constructor属性为Cat,也可以使用下面的方法。

  1. 1 // 重设Cat的constructor属性
  2. 2 Cat.prototype.constructor = Cat;
  3. 3 // 但这样constructor变成可枚举的了
  4. 4 var cat_keys = Object.keys(Cat.prototype);
  5. 5 console.log(cat_keys);//[ 'name', 'age', 'job', 'sayName', 'constructor' ]
  6. 6 console.log(Object.keys(cat));//[]
  7. 7
  8. 8 // 重设constructor的属性
  9. 9 Object.defineProperty(Cat.prototype,'constructor',{
  10. 10 enumerable:false,
  11. 11 value:Cat
  12. 12 });
  13. 13 cat_keys = Object.keys(Cat.prototype);
  14. 14 console.log(cat_keys);//[ 'name', 'age', 'job', 'sayName' ]

原型模式也不是没有问题,比如不好向构造函数传递参数。它最大的问题是对引用类型的原型属性的共享问题,看下面的代码

  1. 1 // 原型模式最大的问题在于对引用类型的属性共享问题
  2. 2
  3. 3 function House(){}
  4. 4
  5. 5 House.prototype = {
  6. 6 constructor:House,
  7. 7 friends:['lvyahui','d']
  8. 8 };
  9. 9
  10. 10 var h1 = new House(),
  11. 11 h2 = new House();
  12. 12 h1.friends.push('li');
  13. 13 console.log(h1.friends);//[ 'lvyahui', 'd', 'li' ]
  14. 14 console.log(h2.friends);//[ 'lvyahui', 'd', 'li' ]

4、构造函数与原型对象方式组合的模式

组合模式可以说吸取了构造函数模式与原型模式的优点,既保证了每个对象实例都有自己独立的属性和方法,同时有可以实现共享的属性和方法。

  1. 1 /**
  2. 2 * 常用方式,组合使用构造函数模式和原型模式
  3. 3 */
  4. 4
  5. 5 function Movie(name,length){
  6. 6 this.name= name;
  7. 7 this.length = length;
  8. 8 this.links = ['h1','h2'];
  9. 9 }
  10. 10 Movie.prototype = {
  11. 11 constructor:Movie,
  12. 12 sayName : function (){
  13. 13 console.log(this.name);
  14. 14 }
  15. 15 };
  16. 16 var m1 = new Movie('diany1',14),
  17. 17 m2 = new Movie('diany2',23);
  18. 18 m1.links.push('h3');
  19. 19
  20. 20 console.log(m1.links);
  21. 21 console.log(m2.links);
  22. 22 console.log(m1.links === m2.links);
  23. 23 console.log(m1.sayName === m2.sayName);

这种方式集各家之长,我想这种方式应该是用的比较多的了吧(本人还未毕业,对企业里实际情况不太了解,有知道的可以悄悄告诉我)

当然,还有一种更好的写法,就是所谓的动态原型模式

5、动态原型模式

  1. 1 function Miss(name,age){
  2. 2 this.name = name;
  3. 3 this.age = age;
  4. 4
  5. 5 if(typeof this.sayName != 'function'){
  6. 6 Miss.prototype.sayName = function(){
  7. 7 console.log(this.name);
  8. 8 }
  9. 9 }
  10. 10 }
  11. 11
  12. 12 var miss = new Miss('lvyahui',12);
  13. 13 miss.sayName();

这种方式的在保持了组合模式的优点的前提下,让代码看起了封装性更好,也更安全。

6、寄生构造模式

这中方式与工厂模式,就只有一点区别,通过new 构造函数的形式创建对象,像下面这样,注意它只带创建对象的时候与工厂模式有区别(16行 new)

  1. 1 /**
  2. 2 * 寄生构造模式
  3. 3 */
  4. 4
  5. 5 function createPerson2(name,age,job){
  6. 6 var o = new Object();
  7. 7 o.name = name;
  8. 8 o.age = age;
  9. 9 o.job = job;
  10. 10 o.sayName = function(){
  11. 11 console.log(this.name);
  12. 12 };
  13. 13 // 等价于 o.sayName = new Function("console.log(this.name);");
  14. 14 return o;
  15. 15 }
  16. 16 var p1 = new createPerson2('lvyahui1',12,'devloper');

7、稳妥构造函数模式

这种模式基于稳妥对象的概念,这种对象是没有公共属性,它的方法中也不使用this的对象。大家都知道js中的this一直都是让人头疼的问题。

稳妥模式与寄生模式类似,区别在于

  • 不通过new操作符调用构造函数
  • 不在行创建对象的实例方法中使用this
  1. 1 /**
  2. 2 * 稳妥构造模式
  3. 3 */
  4. 4 function Girl(name,age){
  5. 5 var o = new Object();
  6. 6 o.sayName = function(){
  7. 7 console.log(name);
  8. 8 };
  9. 9 o.sayAge = function(){
  10. 10 console.log(age);
  11. 11 };
  12. 12 return o;
  13. 13 }
  14. 14 var gril = Girl('d',21);
  15. 15 console.log(gril.sayName());
  16. 16 console.log(gril.sayAge());
  17. 17 // 输出
  18. 18 // d
  19. 19 // undefined
  20. 20 // 21
  21. 21 // undefined
  22. 22 // 为什么呢?

大家知道这个输出为什么是这么吗?知道的留言告诉我吧。

好了,就写这么多了,总结一下,原生的创建js对象的最普适的方法应该是组合模式了吧。

 

OOP 创建对象的7种方式的更多相关文章

  1. JavaScript OOP 创建对象的7种方式

    我写JS代码,可以说一直都是面向过程的写法,除了一些用来封装数据的对象或者jQuery插件,可以说对原生对象了解的是少之又少.所以我拿着<JavaScript高级程序设计 第3版>恶补了一 ...

  2. 第184天:js创建对象的几种方式总结

    面向对象编程(OOP)的特点: 抽象:抓住核心问题 封装:只能通过对象来访问方法 继承:从已有的对象下继承出新的对象 多态:多对象的不同形态 一.创建对象的几种方式 javascript 创建对象简单 ...

  3. js中面向对象(创建对象的几种方式)

    1.面向对象编程(OOP)的特点: 抽象:抓住核心问题 封装:只能通过对象来访问方法 继承:从已有的对象下继承出新的对象 多态:多对象的不同形态 一.创建对象的几种方式 javascript 创建对象 ...

  4. js中面向对象(创建对象的几种方式)

    1.面向对象编程(OOP)的特点: 抽象:抓住核心问题 封装:只能通过对象来访问方法 继承:从已有的对象下继承出新的对象 多态:多对象的不同形态 注:本文引用于 http://www.cnblogs. ...

  5. Java中创建对象的几种方式

    Java中创建对象的五种方式: 作为java开发者,我们每天创建很多对象,但是我们通常使用依赖注入的方式管理系统,比如:Spring去创建对象,然而这里有很多创建对象的方法:使用New关键字.使用Cl ...

  6. C++创建对象的两种方式

    C++创建对象有两种方式,在栈上创建对象(Objects on the Stack)和在堆上创建对象(Objects on the Heap). 假设我们有以下的类: #include <str ...

  7. spring入门:beans.xml不提示、别名、创建对象的三种方式

    spring的版本是2.5 一.beans.xml文件不提示 Location:spring-framework-2.5.6.SEC01\dist\resources\spring-beans-2.5 ...

  8. Java创建对象的4种方式?

     [Java创建对象的4种方式?] 1)通过new语句实例化一个对象 2)通过反射机制创建对象 3)通过clone()方法创建一个对象  (复制) 4)通过反序列化方式创建对象

  9. Java创建对象的几种方式

    解析:Java创建对象的几种方式(重要):(1) 用new语句创建对象,这是最常见的创建对象的方法.(2) 运用反射手段,调用java.lang.Class或者java.lang.reflect.Co ...

随机推荐

  1. Android运用自己的标题栏

    Android程序的标题栏TitleBar区域很单调,如果想个性化一些可以通过下面的方法来为自己软件的标题定制一个layout布局文件,比如浏览器的标题栏,它包含了网站的Favicon,自定义的进度条 ...

  2. 乐在其中设计模式(C#) - 提供者模式(Provider Pattern)

    原文:乐在其中设计模式(C#) - 提供者模式(Provider Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 提供者模式(Provider Pattern) 作者:weba ...

  3. 【C++探索之旅】第一部分第三课:第一个C++程序

    内容简介 1.第一部分第三课:第一个C++程序 2.第一部分第四课预告:内存的使用 第一个C++程序 经过上两课之后,我们已经知道了什么是编程,编程的语言,编程的必要软件,C++是什么,我们也安装了适 ...

  4. mvc+webapi+dapper+ef codefirst项目搭建

    首先项目是mvc5+webapi2.0+orm数据处理(dapper)+ef动态创建数据库. 1.项目框架层次结构: mvc项目根据不同的业务和功能进行不同的区域划分[今后项目维护起来方便],mode ...

  5. Hibernate HQL详细说明

    1.  Hibernate HQL详细说明 1.1.  hql一个简短的引论 Hibernate它配备了一种非常强大的查询语言.这种语言看起来非常像SQL.但是不要 要对相位的语法结构似,HQL是很有 ...

  6. 采用ToolRunner执行Hadoop基本面分析程序

    为了简化执行作业的命令行.Hadoop它配备了一些辅助类.GenericOptionsParser它是一类.经常用来解释Hadoop命令行选项,并根据需要.至Configuration采取相应的对象设 ...

  7. IIS URL Rewrite Module防盗链规则配置方法

    IIS版本:IIS 7.5 URL Rewrite组件:IIS URL Rewrite Module(http://www.iis.net/downloads/microsoft/url-rewrit ...

  8. Visual Studio 2013中c#语言规范5.0

    文件位置 vs安装路径 "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC#\Specifications\2052\CSharp ...

  9. 2014阿里巴巴web前实习生项目分析(1)

    以下简化CSS代码: div.container{ width:500px; background-image:url(/img/sprite.png); background-repeat:no-r ...

  10. 软测试是一个烂摊子?NO——【软测试】

    软测试是一个烂摊子权?我开始也是这么认为的.这充分证明,.我并没有考虑,整个合并没有类似的项目. 前几天跟慕夏交流了怎样做总结,听完她讲的,我開始学着为细节的知识点找联系. 只是今天跟老师一交流,才发 ...