JavaScript创建对象(三)——原型模式
在JavaScript创建对象(二)——构造函数模式中提到,构造函数模式存在相同功能的函数定义多次的问题。本篇文章就来讨论一下该问题的解决方案——原型模式。
首先我们来看下什么是原型。我们在创建一个函数时,这个函数会包含一个属性prototype
,这个属性是一个指针,它指向一个对象——该函数的原型对象,这就是原型,它包含了该函数类型的所有实例可共享的属性和方法,见下面示意图:
如图所示,声明了一个函数Person
。在JavaScript中,一个函数被声明的同时就具有了一些属性,其中有一个叫做prototype
,它指向了该函数的原型对象,即上述示例中的Person Prototype
。同时,这个原型对象有一个叫做constructor
的属性反过来又指向了该函数对象。
当我们创建一个函数的实例时,例如上面的var personObj = new Person('张三', 12);
,这个实例也会有一个属性指向该函数的原型对象,在Chrome的开发工具中显示为__proto__
。
上面我们说原型的属性可以被该函数类型的所有实例所共享,那具体是怎么实现呢?看下面的示例:
function Person(){ } //给原型添加自定义属性和方法 Person.prototype.name = '张三'; Person.prototype.sayName = function(){ console.log(this.name); } var p1 = new Person(); //给p1添加age属性 p1.age = 18; console.log(p1.name);//张三 console.log(p1.age);//18 p1.sayName();//张三 var p2 = new Person(); console.log(p2.name);//张三 console.log(p2.age);//undefined p2.sayName();//张三
在上面的代码中,我们并没有给实例添加name
属性和sayName
方法,但是依然可以通过实例调用,貌似实例天生就具有了原型的属性和方法,其实不是的,下面是在Chrome的开发工具中看到的内容:
我们看到,p1
是Person
类型的,我们给p1
设置了age
属性,这里也能看到age
是18
。另外我们看到p1
有个__proto__
属性,这个就是我们在原型示意图中说的指向原型对象的属性。
代码读取对象实例某个属性的时候会执行一次搜索,首先搜索对象实例,如果搜索到了就返回,如果没有则会继续搜索__proto__
指向的原型对象,搜索到了就返回。所以上面例子中p1.age
是搜索到了p1
的age
属性返回了,p1.name
是搜索到Person Prototype
的name
返回了,p2.age
在Person Prototype
中也没搜到,于是返回了undefined
。这就是实例对象共享原型属性的原理。
除了上面的写法,原型还有一种更简单的定义方式,就是用一个包含所有属性和方法的对象字面量来重写整个原型对象,这样避免了每当给原型添加一个属性就要书写一遍Person.prototype
的繁琐,同时从视觉上看也更好地封装了原型的功能,如下代码所示:
function Person{ } Person.prototype = { constructor: Person, name: '张三', age: 18, job: 'JavaScript', sayName: function(){ console.log(this.name); } }
现在回到文章一开始提出的相同功能的函数定义多次的问题,因为函数原型的属性和方法可以由所有实例所共享,所以只要在原型中定义一次,所有实例就都可以使用,这样就完美解决了构造函数模式的问题。
总结一下,与构造函数模式相比:
- 原型模式不必在构造函数中定义属性和方法,而是直接定义在原型中。
- 这些属性和方法被所有实例共享。
原型模式虽然好用,但也不是没有缺点。首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。其次,最大的问题是由原型的共享本性带来的,下面来分析一下原型的共享问题。
通过共享,我们解决了构造函数模式相同功能的函数定义多次的问题,所以共享对于函数是有好处的。对于基本类型的属性,如上面的name
、age
,因为属性的搜索机制是从实例到原型,所以可以通过给实例添加一个同名的属性,屏蔽掉原型中相应的属性,问题也不大。然而,对于引用类型的数据来说,问题就比较严重了。来看下面的示例:
function Person(){ } Person.prototype = { constructor: Person, name: '张三', age: 18, job: 'JavaScript', friends: ['小明', '小刚'], sayName: function(){ console.log(this.name); } } var p1 = new Person(); var p2 = new Person(); p2.name = '李四'; p1.friends.push('小红');//张三交了个女朋友小红 console.log(p1.friends);//["小明", "小刚", "小红"] console.log(p2.friends);//["小明", "小刚", "小红"],我擦,小红怎么也成了李四的女朋友 console.log(p1.friends == p2.friends);//true
如上所示,Person.prototype
包含了一个引用类型,数组friends
,其中friends
只是一个指针,['小明', '小刚']
才是真正的对象。通过p1.friends
修改了这个数组,因为共享的问题,p2.friends
访问的也是同一个数组。假如我们的初衷就是共享一个数组,那么也没问题。但是多数情况下应该是不想共享的场景。比如这里,张三新交了一个女朋友小红,结果小红同时也是李四的女朋友,是张三有这癖好?是小红劈腿?还是人家张三只是单纯地交了个女朋友,被你搞得复杂了?这个说不清,既然说不清,那么程序就有问题。所以很少有人单独使用原型模式,那么这个问题怎么解决呢?办法还是有的,那就是组合使用构造函数模式和原型模式,这个实现也很简单,但为了区分原型模式,后面将会单独列一篇文章。
本文参考《JavaScript高级程序设计(第3版)》,关于原型模式的其他特点读者可以查阅第6.2.3章节,里面有详细的说明。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
大白话讲解分布式缓存并发冲突问题及其解决方案:zk分布式锁
大白话讲解分布式缓存并发冲突问题及其解决方案:zk分布式锁 一、背景介绍 如果您更喜欢看视频教程,可以看本头条号发布的视频教程,绝对大白话,手把手带你体验整个冲突的演示过程及解决方案:两种方式,随机挑选 缓存架构之实战演练基于zk分布式锁解决分布式缓存并发冲突问题 1、源架构: 2、分布式缓存并发冲突问题 二、项目整合 1、广告服务系统 **功能:**为媒体提供广告的源头服务 从本地缓存中获取广告 从redis缓存中获取广告 从db获取广告,并更新到redis缓存 2、 缓存服务系统 消息监听,实时增量更新redis缓存 定时全量更新redis缓存 3、广告管理系统 广告的增删改查 发送广告更改的mq消息 三、rabbitmq消息重复解决方案 1、是什么造成了消息的重复呢? 生产者在使用publisher confirm机制的时候,发送完一条消息等待RabbitMQ返回确认通知,此时网络断开,生产者捕获到异常情况,为了确保消息可靠性,选择重新发送,这样RabbitMQ中就有两条同样的消息。这样,在消费者消费的时候,就会重复消费,尤其是在交易系统/充值系统/银行转账系统……中,这问题就大...
- 下一篇
Java 11 教程
原文链接:https://wangwei.one/posts/921ad319.html Java11 已于 2018/09/25 成功发布,不过目前 绝大多数人 在生产环境仍旧使用的是Java 8。这篇以案例为主的教程涵盖了从 Java 9 到 Java 11的绝大多数重要的语法与API特性。让我们开始吧! 局部变量类型推断 Java 10引入了一个新的语言关键字var,它可以在声明局部变量 时替换类型信息( 局部 意味着方法体内的变量声明)。 Java 10之前,变量的声明形式如下: String text = "Hello Java 9"; 现在,你可以使用 var 替换 String 。编译器将会从变量的赋值中推断出它的正确类型。在这个例子里 变量text 即为 String 类型: var text = "Hello Java 10"; 不同于 Javascript 中的 var 关键字,Java中的 var 声明的变量仍旧是静态类型。你不能再次赋予另一个与原类型不符的变量值。 var text = "Hello Java 11"; text = 23; // ERROR: ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程