文档章节

JavaScript创建对象(五)——动态原型模式

Bob2100
 Bob2100
发布于 2018/11/05 00:15
字数 1464
阅读 22
收藏 1

对于有其他面向对象语言开发经验的人来说,在看到独立的构造函数和原型时,很可能会感到非常困惑。比如在Java中(没有Java经验的开发者此段可忽略,只要知道后面提出的结论就好了),有类的概念(当然ES6也引入了类,我们这里以ES5为基础),比如以下代码所示:

public class Person {
    private String name;
    private int age;
    private String job;

    public Person(String name, int age, String job) {
        this.name = name;
        this.age = age;
        this.job = job;
    }

    public void sayName(){
        System.out.println(this.name);
    }

}

这是非常简单的一个类,它有三个属性,一个构造函数和一个方法。如果比较JavaScript,function Person就相当于类,但是我们发现,Java中的类是一个整体,而JavaScript除了function Person,还有一个Person.prototype,被定义成了两部分。所以,JavaScript对于对象的封装性还是不够完美,而动态原型模式正是致力于要解决这个问题,它把所有的信息都封装在了构造函数中,通过在构造函数中初始化原型,既很好地体现了封装性,又保持了组合使用构造函数和原型模式的特点,可以说一举两得,非常完美。下面我们来看一个例子:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;

    if(typeof this.sayName != 'function'){
        Person.prototype.sayName = function(){
            console.log(this.name);
        };

        Person.prototype.sayJob = function(){
            console.log(this.job);
        };
    }
}

var p1 = new Person('张三', 18, 'JavaScript');//sayName不存在,添加到原型
var p2 = new Person('李四', 20, 'Java');//sayName已经存在,不会再向原型添加

p1.sayName();//张三
p2.sayName();//李四

如代码所示,第一次创建对象,执行构造函数时,判断sayName()是否存在,如果不存在,就把它添加到原型,使用if判断可以确保只在第一次调用构造函数时初始化原型,避免了每次调用的重复声明。

实际上这里不仅仅可以使用sayName()做为判断条件,还可以使用sayJob(),这个条件只是为了测试原型是否已经初始化,只要是原型初始化之后应该存在的属性或方法都可用来做为判断条件。

之前讲过,原型也可以用对象字面量来重写,那动态原型模式可不可以使用对象字面量呢?我们来尝试一下:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;

    if(typeof this.sayName != 'function'){
        Person.prototype = {
            constructor: Person,
            sayName: function(){
                console.log(this.name);
            }
        }
    }
}

var p1 = new Person('张三', 18, 'JavaScript');//sayName不存在,添加到原型
var p2 = new Person('李四', 20, 'Java');//sayName已经存在,不会再向原型添加

//p1.sayName();//Uncaught TypeError: p1.sayName is not a function
p2.sayName();//李四

发现p1.sayName()报了不是一个函数的错误,如果把p1.sayName()注释掉,p2.sayName()可以正常输出李四,为什么会这样呢?要想解释清楚这个问题,我们先来看一下以下代码:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
}

var p1 = new Person('张三', 18, 'JavaScript');
console.log(Person.prototype);//{constructor: ƒ}

Person.prototype = {
    constructor: Person,
    sayName: function(){
        console.log(this.name);
    }
}
var p2 = new Person('李四', 20, 'Java');
console.log(Person.prototype);//{constructor: ƒ, sayName: ƒ}

// p1.sayName();//Uncaught TypeError: p1.sayName is not a function
p2.sayName();//李四

也是同样的现象,p1.sayName()不是一个函数。那么p1p2的区别在哪儿呢?区别就在于通过new关键字创建对象的先后顺序,是先于重写原型创建,还是后于重写原型创建。

我们知道,通过new关键字创建一个对象,这个对象会有一个属性__proto__指向相应函数的原型,这里代码中的p1正是指向了这个原型,在Chrome的开发工具中可以看到,如下图所示:

但是重写原型,就是创建了一个新对象,函数的指针Person.prototype由引用旧的原型对象改为引用这个新对象,而旧的原型对象现在只被p1.__proto__引用着,实例p1Person原型之间的关系被切断了,所以调用p1.sayName()就报了不是一个函数的错误,因为旧原型对象上没有sayName方法。

再来看p2,因为是先重写原型,所以当p2new出来时,p2__proto__属性指向的就是这个新原型,故而调用sayName方法时,向上搜索原型可以找到sayName方法,正常输出李四,下面的示意图可以直观地表示这种情况:

现在回过头来看一开始提出的问题,为什么动态原型模式不能用对象字面量的方式重写。第一次创建实例对象时,先new,然后执行构造函数,重写原型,那么此时实例的__proto__指向的还是原来的原型,不是重写后的原型。第二次创建实例,因为新原型对象已经创建好,所以实例的__proto__指向的就是重写的这个原型。使用给原型添加属性的方式操作的一直是同一个原型,所以也就不存在先后的问题。

这就是动态原型模式,相比组合使用构造函数和原型模式而言,封装性更优秀,但是一个小缺点就是不能使用对象字面量的形式初始化原型,这是需要留意的。开发者在实际应用中可根据具体情况,灵活选择,确定使用哪种方式。

本文参考《JavaScript高级程序设计(第3版)》

© 著作权归作者所有

共有 人打赏支持
Bob2100
粉丝 23
博文 86
码字总数 47062
作品 0
浦东
高级程序员
私信 提问
JavaScript继承(五)——寄生式继承

首先回顾一下原型式继承: 寄生式继承是与原型式继承紧密相关的一种思路,并且同样也是由克罗克福德推而广之的。 说到寄生式继承不得不说工厂模式和寄生构造函数模式创建对象。下面来回顾一下...

Bob2100
02/13
0
0
JavaScript继承(二)——借用构造函数

JavaScript继承(一)——原型链中提出原型链继承的两个问题:一是原型的数据共享问题,二是创建子类型的实例时,不能向父类型的构造函数中传递参数。这两个问题的根源还是在于使用原型模式创...

Bob2100
01/20
0
0
JavaScript创建对象(四)——组合使用构造函数和原型模式

在JavaScript创建对象(三)——原型模式中,我们阐述了原型模式存在的两个问题:一是没办法通过构造函数初始化对象属性,二是共享引用类型的数据导致数据错乱。于是我们提出组合使用两种模式...

Bob2100
2018/10/13
0
0
JavaScript继承(六)——寄生组合式继承

JavaScript继承(三)——组合继承中讲到,组合继承是JavaScript中最常用的继承模式,但是它也有自己的不足之处,现在我们就来剖析它的不足,如下示例: 使用组合继承让继承实际上分为两步:...

Bob2100
02/16
0
0
JavaScript 中的面向对象编程

介绍 JavaScript 是一个强大的面向对象编程语言,但是,并不像传统的编程语言,它采用一个以原型为基础的OOP模型,致使它的语法让大多数开发人员看不懂。另外,JavaScript 也把函数作为首要的...

oschina
2016/09/09
5.1K
7

没有更多内容

加载失败,请刷新页面

加载更多

mysql 系统设置SQL

打开、关闭日志 SET GLOBAL general_log = 'Off'; SET GLOBAL general_log = 'On'; 查看日志是否打开 show variables like '%general%';...

jingshishengxu
45分钟前
2
0
转行学大数据,如何选择如何学习大数据开发?

大数据火了几年了,但是今年好像进入了全民大数据时代,本着对科学的钻(zhun)研(bei)精(tiao)神(cao),我在17年年初开始自学大数据,后经过系统全面学习,于这个月跳槽到现任公司。 现在已经...

董黎明
46分钟前
4
0
RadosClient OSDC

RadosClient.h class librados::RadosClient : public Dispatcher//继承自Dispatcher(消息分发类){public: using Dispatcher::cct; md_config_t *conf;//配置文件private: ......

banwh
今天
2
0
如果让你写一个消息队列,该如何进行架构设计?

面试题 如果让你写一个消息队列,该如何进行架构设计?说一下你的思路。 面试官心理分析 其实聊到这个问题,一般面试官要考察两块: 你有没有对某一个消息队列做过较为深入的原理的了解,或者...

李红欧巴
今天
6
0
错题

无知的小狼
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部