博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript设计模式之一:面向对象的Javascript
阅读量:6992 次
发布时间:2019-06-27

本文共 6562 字,大约阅读时间需要 21 分钟。

hot3.png

(本节内容摘自:Javascript设计模式与开发实践一书,作为自己的笔记保存,希望对有需要的朋友有用)

JavaScript没有提供传统面向对象语言中的类式继承,而是通过原型委托的方式来实现对象与对象之间的继承。

一、动态类型语言

编程语言按数据类型分类,大致可以分为静态类型语言和动态类型语言。

静态类型语言在编译时已确定变量类型,而动态语言类型的变量类型要到程序运行时被赋值后才能确定,Javascript是一门典型的动态类型语言。

鸭子类型(duck typing),关于这个有一个故事:从前有个国王,他觉得这个世界上鸭子的叫声很美妙,于是召集大臣要组建一个1000只鸭子组成的合唱团,大臣们找遍了全国却只有999只,最后大臣们发现有一只鸡,它的叫声跟鸭子一模一样,于是这只鸡成为了鸭子合唱团的最后一员。

下面我们用代码来模拟上面的这个故事:

var duck = {    duckSinging: function() {        console.log('嘎嘎嘎');    }};var chicken = {    duckSinging: function() {        console.log('咯咯咯');    }};var choir = [];  //合唱团var joinChoir = function(animal) {    if(animal && typeof animal.duckSinging === 'function') {        choir.push(animal);        console.log('恭喜加入合唱团');        console.log('合唱团已有成员数量:' + choir.length);    }};joinChoir(duck);    //恭喜加入合唱团joinChoir(chicken);	//恭喜加入合唱团

鸭子类型的概念在动态类型语言的面向对象设计中非常重要,利用它我们可以在动态类型语言中实现“面向接口编程”,而不是“面向实现编程”。

二、多态

多态(polymorphism),它的含义是同一操作作用于不同的对象上,可以产生不同的解释和不同的执行效果,换句话说,给不同的对象发送同一消息时,这些对象会根据这个消息分别给出不同的反馈,下面举个栗子:

有一只鸭和一只鸡,它们都会叫,当主人向它们发出“叫”的指令时,鸭会“嘎嘎嘎”的叫,而鸡会“咯咯咯”的叫,两只动物会根据主人发出的同一指令,发出各自不同的声音。

下面我们来看一段多态的Javascript代码:

var makeSound = function(animal) {    if(animal instanceof Duck) {        console.log('嘎嘎嘎');    }else if(animal instanceof Chicken) {        console.log('咯咯咯');    }};var Duck = funcdtion(){};var Chicken = function(){};makeSound(new Duck());        //嘎嘎嘎makeSound(new Chicken());     //咯咯咯

多态背后的思想就是把“做什么”和“谁去做及怎样去做”分离开,也就是将“不变的事”和“可能改变的事”分离开。很显然,上面的代码有问题,如果我们再增加一只狗,就要改动makeSound函数,修改代码是危险且不可取的,我们要让代码变得可扩展,我们将上面的代码进行改动,如下:

//将不变的部分分离出来,这里就是所有的动物都会叫var makeSound = function(animal) {    animal.sound();};//将可变的部分封装起来var Duck = function(){};Duck.prototype.sound = function(){    console.log('嘎嘎嘎');}var Chicken = function(){};Chicken.prototype.sound = function(){    console.log('咯咯咯');}makeSound(new Duck());            //嘎嘎嘎makeSound(new Chicken());         //咯咯咯

如果我们需要增加一只动物,那么我们只需要增加代码即可,而不需要去改动makeSound函数

var Dog = function(){};Dog.prototype.sound = function(){    console.log('汪汪汪');}makeSound(new Dog());        //汪汪汪

由此可见,Javascript的多态性是与生俱来的,它作为一门动态类型语言,既不会检查对象类型,也不会检查参数类型,从上面的例子看出,我们既可以往makeSound函数里传递duck参数,也可以传递chicken参数,所以,一种动物是否能发出声音,只取决于它有没有makeSound方法,而不取决于它是否是某种类型的对象。

下面我们再来看一个在实际项目中可能会遇到的例子,假设我们要编写一个地图应用,有两家地图API可供选择,他们都提供了show方法,代码如下:

var googleMap = {    show: function(){        console.log('开始渲染谷歌地图');    }};var renderMap = function(){    googleMap.show();};renderMap();        //开始渲染谷歌地图

现在我们需要把谷歌地图换成百度地图

var googleMap = {    show: function(){        console.log('开始渲染谷歌地图');    }};var baiduMap = {    show: function(){        console.log('开始渲染百度地图');    }};var renderMap = function(type) {    if(type === 'google') {        googleMap.show();    }else if(type === 'baidu'){        baiduMap.show();    }};renderMap('google');            //开始渲染谷歌地图renderMap('baidu');             //开始渲染百度地图

OK,现在问题来了,如果我再增加一个搜搜地图呢?那就要改动renderMap函数,继续在里面添加条件分支语句,所以,看下面的代码:

var googleMap = {    show: function(){        console.log('开始渲染谷歌地图');    }};var baiduMap = {    show: function(){        console.log('开始渲染百度地图');    }};//把相同的部分抽象出来,也就是显示地图var renderMap = function(map){    if(map.show instanceof Function){        map.show();    }};renderMap(googleMap);        //开始渲染谷歌地图renderMap(baiduMap);         //开始渲染百度地图

这时,我们如果需要添加其他的地图API

var sosoMap = {    show: function(){        console.log('开始渲染搜搜地图');    }};renderMap(sosoMap);

在Javascript中,函数是一等对象,函数本身也是对象,函数用来封装行为并能被四处传递,当我们向函数发出“调用”消息时,这些函数会返回不同的执行结果。

二、封装

封装的目的就是将信息隐藏,一般我们讨论的是对数据和实现进行封装,除此之外更广泛的是对封装类型和封装变化。

1、封装数据

在其他许多编程语言中提供了private、public、protected等关键字来实现封装,但Javascript没有,我们只有依靠变量的作用域来实现,而且只能模拟出public和private这两种封装特性

var myObject = (function(){    var _name = 'sven';        //私有(private)变量    return {        getName: function(){        //公开(public)方法            return _name;        }    }})();console.log(myObject.getName());        //svenconsole.log(myObject._name);            //undefined

另外,在ES6中,可以通过Symbol来创建私有属性。 

2、封装实现

3、封装类型

4、封装变化

三、继承

Javascript中继承是基于原型模式的,而像Java、C++等这些是基于类的的面向对象语言,我们要创建一个对象,必须先定义一个Class,然后从这个Class里实例化一个对象出来。然而Javascript中并没有类,所以,在JavaScript中对象是被克隆出来的,也就是一个对象通过克隆另一个对象来创建自己。

我们假设编写一个网页版的飞机大战游戏,这个飞机拥有分身技能,当使用这个技能时,页面上会出现多个同样的飞机,这时我们就需要使用到原型模式来克隆飞机。ES5提供了Object.create方法来克隆对象,看如下代码:

var Plane = function(){    this.blood = 100;    this.attackLevel = 1;    this.defenseLevel = 1;};var plane = new Plane();plane.blood = 500;plane.attackLevel = 10;plane.defenseLevel = 7;var clonePlane = Object.create(plane);console.log(clonePlane);                //Object{blood: 500, attatckLevel: 10, defenseLevel: 7}//在不支持Object.create方法的浏览器中,使用以下代码:Object.create = Object.create || function(obj){    var F = function(){};    F.prototype = obj;    return new F();}

原型继承遵循以下原则,Javascript也不例外:

a、所有的数据都是对象

b、要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它

c、对象会记住它的原型

d、如果对象无法响应某个请求,它会把这个请求委托给它自己的原型

Javascript中存在一个根对象Object.prototype,它是一个空对象,所有的对象都是从这个根对象中克隆而来的,Object.prototype就是它们的原型。

var obj1 = new Object();var obj2 = {};//利用ES5提供的Object.getPrototypeOf方法来查看它们的原型console.log(Object.getPrototypeOf(obj1) === Object.prototype);    //trueconsole.log(Object.getPrototypeOf(obj2) === Object.prototype);    //true

通过new运算符从构造器中得到一个对象,看下面的代码:

function Person(name){    this.name = name;};Person.prototype.getName = function(){    return this.name;};var a = new Person('Kaindy');console.log(a.name);            //Kaindyconsole.log(a.getName());        //Kaindyconsole.log(Object.getPrototypeOf(a) === Person.prototype);            //true

上面代码中的Person并不是一个类,而是函数构造器,Javascript的函数既可以作为普通函数使用,也可以作为构造器调用,当使用new运算符时,函数就成了构造器,这个创建对象的过程,也就是先克隆了Object.prototype,然后再做其他的一些操作。

在Javascript中,每个对象都会记住它的原型,准确的说,应该是对象的构造器有原型。每个对象都有一个名为__proto__的隐藏属性,这个属性会指向它的构造器的原型对象

var a = new Object();console.log(a.__proto__ === Object.prototype);        //true

实际上,每个对象就是通过自身隐藏的__proto__属性来记住自己的构造器原型.

如果对象无法响应请求,它会把这个请求委托给它的构造器的原型,我们来看下面的代码:

var obj = {name: 'Kaindy'};var A = function(){};A.prototype = obj;var a = new A();console.log(a.name);        //Kaindy

我们来看下引擎做了什么,

首先,我们需要打印出对象a的name属性,尝试遍历对象a的所有属性,但没找到name

接着,对象a把查找name属性这个请求委托给了它自己的构造器原型,也就是a.__proto__,而a.__proto__指向了A.prototype,A.prototype被设置为了对象obj。

最后在obj中找到了name属性,并返回它的值。

结束:Object.create是原型模式的天然实现,目前大多数主流浏览器都支持此方法,但它效率并不高,比通过构造函数创建对象要慢,最新的ES6带来了Class语法,看起来像一门基于类的语言,但原理还是通过原型机制来创建对象,看下面的代码:

class Animal {    constructor(name) {        this.name = name;    }    getName() {        return this.name;    }}class Dog extends Animal {    constructor(name) {        super(name);    }    speak() {        return "woof";    }}var dog = new Dog("Scamp");console.log(dog.geName() + ' says ' + dog.speak());

转载于:https://my.oschina.net/u/2399867/blog/631701

你可能感兴趣的文章
人工智能领域技术落地已迫在眉睫
查看>>
Spark大数据处理系列之Machine Learning
查看>>
被 281 亿个传感器包围时,我们如何重新定义生活?
查看>>
openSUSE 11.2 安装飞鸽传书 g2ipmsg
查看>>
用大数据做产业组织 用“互联网+”做产业服务
查看>>
针对小型企业的CRM系统HeyMarket 可以防止员工在醉酒后向客户发送“骚扰短信
查看>>
5G和WIFI谁能够将用户价值最大化?
查看>>
Aspect引领北美外呼产品市场
查看>>
海外工业巨头“掘金”中国西部智能家居市场
查看>>
联发科10月营收238亿新台币 近6个月低点
查看>>
《Arduino开发实战指南:LabVIEW卷》——2.2 Arduino程序结构及基本函数
查看>>
连马云都要打包带走的美食,你不来一份儿?
查看>>
乐视云升级品牌同步启动全球域名
查看>>
回归服务器市场,看来AMD的Naples还需回答更多问题
查看>>
《Web应用漏洞侦测与防御:揭秘鲜为人知的攻击手段和防御技术》——2.3 小结...
查看>>
Mirics联合展讯推出嵌入式CMMB PCTV解决方案
查看>>
QC缺陷管理操作-细说
查看>>
干货丨5个问题鉴定大数据安全分析真伪!
查看>>
大话敏捷测试
查看>>
漫画赏析: Vi 还是不 Vi,这是个问题
查看>>