当前位置: 首页 > news >正文

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

对于有其他面向对象语言开发经验的人来说,在看到独立的构造函数和原型时,很可能会感到非常困惑。比如在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版)》

转载于:https://juejin.im/post/5bdf1cf06fb9a049cd53a3a1

相关文章:

  • Asp.Net Mvc + ComBoost.Mvc快速开发
  • Laravel Mix运行时关于es2015报错解决方案
  • 建站初级指南
  • 一个ViewGroup#dispatchDraw()中的NP分析
  • LINUX命令 cp: omitting directory 出现的问题解决办法
  • 枚举类的简单应用
  • 手把手教你启用Win10的Linux子系统(超详细)
  • [转载]C# Double toString保留小数点方法
  • 自动化部署打破混乱之墙 助力开发、运维、测试协同作战
  • spring restTemplate 上传数据流/字节数组
  • Windows下leapmotion中touchless的使用
  • Session丢失的问题!(转)
  • 架构探险笔记4-使框架具备AOP特性(上)
  • QT 字符串相等间距字符间增加字符
  • 第六篇:面向对象
  • angular2开源库收集
  • FineReport中如何实现自动滚屏效果
  • go append函数以及写入
  • JavaScript 一些 DOM 的知识点
  • JavaScript对象详解
  • Laravel Telescope:优雅的应用调试工具
  • nginx 负载服务器优化
  • Node + FFmpeg 实现Canvas动画导出视频
  • PaddlePaddle-GitHub的正确打开姿势
  • python学习笔记 - ThreadLocal
  • React-redux的原理以及使用
  • SegmentFault 社区上线小程序开发频道,助力小程序开发者生态
  • vue 个人积累(使用工具,组件)
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • Vue--数据传输
  • Vue学习第二天
  • 程序员最讨厌的9句话,你可有补充?
  • 二维平面内的碰撞检测【一】
  • 分布式任务队列Celery
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 聊聊directory traversal attack
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 前端_面试
  • 容器服务kubernetes弹性伸缩高级用法
  • 如何在GitHub上创建个人博客
  • 算法-图和图算法
  • 学习使用ExpressJS 4.0中的新Router
  • 新海诚画集[秒速5センチメートル:樱花抄·春]
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • #Linux(make工具和makefile文件以及makefile语法)
  • (C语言)fread与fwrite详解
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (算法)Travel Information Center
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • .a文件和.so文件
  • .NET LINQ 通常分 Syntax Query 和Syntax Method
  • .NET/C# 使用 SpanT 为字符串处理提升性能
  • .net2005怎么读string形的xml,不是xml文件。
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示