看了文章目录且看过《JS设计模式与开发实践》的同学是不是有点熟悉,本文章便是基于学习该书第一部分第一节所做的相关笔记以及个人心得

动态类型语言

不知道之前大家是否学过其它面向对象语言——C#、JAVA等等,笔者之前是先学了C#,当时命名变量都要使用规定的数据类型,比如你要定义一个整型数据

1
public int a = 3; //定义一个整型数值a

又比如你要定义一个字符串类型的数据

1
public string a = '我是猪';

按照编程语言的数据类型分类,这便是静态类型语言——在编译时便以确定变量的类型

而到后面我学了JavaScript,第一次感觉到被变量类型释放,几乎一切数据类型都能var解决,要是不能,就用es6的let解决

不管你想数值型、字符串型、还是布尔型,通通var,通通var,var a = 3var a = "我是猪"

那这又是什么类型的语言呢,没错,正如标题——动态类型语言,来看看动态类型语言的特点

动态类型的变量类型要到程序运行时,待变量被赋予某个值之后,才会具有某种类型

我们的JavaScript便是这种类型的语言

那么,这两者又有什么优缺点呢,静态类型数据优点便是在刚开始编译时便能发现类型不匹配的错误,缺点便是——有点复杂,正如书所说,迫使程序员按照契约来编写程序;而动态类型语言则可以让代码看起来更简洁,程序员可以把更多的精力放在业务实现上,但就是无法保证变量的类型,从而使程序在运行过程中可能会发生与变量类型有关的错误

总结下,动态类型语言对变量类型的宽容给实际编码带来很大的灵活性。由于无需类型检测,我们可以尝试调用任何对象的任意方法,而无需考虑它原本是否被设计为拥有该方法,想一下,如果在C#,你定义不同类型的对象,但它们有同一个方法,而一个需求只需要具有该方法的对象,而不考虑它是属于何种类的,怎么实现呢,是不是很难(向上转型,强制类型转换)。书里有一个故事——鸭子类型,便形象生动的描述了该需求~嘎嘎嘎🦆

多态

简介

含义:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果

也就是:你会唱歌,别人也会唱歌,但可能他会唱高音,而你,低音则更好听,则便是多态,两个人同样会唱歌这个动作,但是声音却不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var singing = function(animal) {
animal.singSong()
}

var Human = function(){
this.singSong = function() {
console.log("啊~~")
}
}

var Bird = function(){
this.singSong = function() {
console.log("叽叽喳喳")
}
}


singing(new Human())
singing(new Bird())

好吧,这里拿了人和鸟来比了,准确来说可能是叫,问题不大

运行完,当然就是一个’啊啊啊’,一个’叽叽叽’啦

这就是多态

使用继承来得到多态

在传统的静态语言编程中,如果我们要让不同的类实例化出来的对象分别完成同样的动作,比如‘叫’,我们一般就会使用继承这一特性去完成,定义一个动物类,定义默认动作——如大多数动物都会的“叫”,然后再定义不同的类(人、牛)去继承该类,然后再完成各自具体的叫声或叫法

而当如果我们只想说“看,牛在叫”,而不是“看,牛在哞哞哞”时,这个时候我们就要直接使用基类—动物类的‘叫’这个动作,因为这个动作的默认执行对象类型是animal,而不能duck、cow、human等等,那么,这个时候我们就要使用向上转型来实现了

而在JavaScript中,如果我们要使用该特性,就比较简单粗暴了,管它什么向上转型,你不是会叫吗,我直接把你这个动物类怼到函数不就行,就像上面的唱歌,这便是动态语言的好处,注重实现,不注重主体——给我叫,管你猪或鸭

多态在面向对象程序设计的作用

看完这部分,总结成一段话

多态可以把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句,增加代码可阅读性

就比如营长喊开炮时,各个职位的人开始负责不同的事,运输兵运送炮弹,炮兵负责开炮,辅助员(我不知道该叫啥)装填弹药,而不是营长按照人员单,来到炮兵面前说,你开炮,接着跑到装炮弹员旁边,你装;跑到运输兵,你去运炮弹,这么一来,可能营长跑着跑着,自己已经中弹身亡了有木有,或还没等执行完,敌人已经冲到你面前了会不会。

所以在js中,我们一般会封装一个函数去执行不同操作对象的同个动作,从而避免代码维护性难度的增加

比如我们要使用arcgis提供的地图服务,然后在某一天,我们因为需求需要改成腾讯的,如果不封装从而实现多态的话,在以后我们可能会因为换一个api而改动大量的代码,这是极其痛苦q的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var renderMap = function(type) {
type.map()
}


//模拟arcgis的底图
var Arcgis = function() {
this.map = function() {
console.log('arcgis')
}
}

//模拟腾讯地图
var Tencent = function() {
this.map = function() {
console.log('Tencent map')
}
}

renderMap(new Arcgis()) //arcgis
renderMap(new Tencent()) //Tencent map

如果在实际开发中遇到同个实现效果的api不同方法名的话,我们可以借助设配器模式(待补充)来解决该问题

封装

总而言之——封装的目的就是将信息隐藏,不仅仅包括隐藏数据、还包括隐藏设计细节、实现细节、以及对象的类型,封装使得对象之间的耦合度变松散,对象之间只通过暴露的API接口来通信

一般就是通过抽象类和接口来实现封装,像上面的地图调用、以及动物唱歌,算了还是叫声好一点,就是封装的过程,在这个过程你就会不自觉发现同样实现了多态

原型模式和基于原型继承的JavaScript对象系统

Object.create

在以类为中心的面向对象编程语言中,类和对象的关系可以想象成铸模和铸件的关系,对象总是从类中创建而来。而在原型编程的思想中,类并不是必需的,对象未必需要从类中创建而来,一个对象是通过克隆另外一个对象所得到的。

如果使用原型模式,我们只需要调用负责克隆的方法,完成对象的创建

ECMAScript 5提供了Object.create 方法,可以用来克隆对象

1
2
3
4
5
6
7
8
9
10
11
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, attackLevel: 10, defenseLevel: 7}

Io语言体验

好的,我没有体验,官网地址,反正它就是明显的以克隆的方式创造“世界”,Io语言中最初只有一个根对象Object,其它对象就像克隆一样,从它不断“衍生”出来

通过Io学习主要记住以下几点

  1. 原型链就是从根节点到当前节点的所有对象

    比如:object是Animal的原型,而Animai是Dog的原型,它们之间便是一条原型链

  2. JS同Io一样,基于原型链的委托机制就是原型继承的本质

  3. 原型编程的重要特性:当对象无法响应某个请求时,会把该请求委托给它自己的原型

现原型编程范型至少包括以下基本规则

  • 所有的数据都是对象
  • 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
  • 对象会记住它的原型
  • 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型

JavaScript中的原型继承

JavaScript 同样遵守原型编程的基本规则

所有的数据都是对象

JavaScript 中的根对象是 Object.prototype 对象。Object.prototype 对象是一个空的对象。我们在 JavaScript 遇到的每个对象,实际上都是从 Object.prototype 对象克隆而来的, Object.prototype 对象就是它们的原型。

在 JavaScript 语言里,我们并不需要关心克隆的细节,因为这是引擎内部负责实现的。

JS的克隆

JavaScript 的函数既可以作为普通函数被调用, 也可以作为构造器被调用。当使用 new 运算符来调用函数时,此时的函数就是一个构造器。 用 new 运算符来创建对象的过程,实际上也只是先克隆 Object.prototype 对象,再进行一些其他额外操作的过程。

对象会记住它的原型

JavaScript 给对象提供了一个名为_proto_的隐藏属性,某个对象的_proto_属性默认会指 向它的构造器的原型对象,即{Constructor}.prototype。

_proto_就是对象跟“对象构造器的原型”联系起来的纽带。(_proto_:指向该对象的构造函数的原型对象,prototype指向该方法的原型对象——来自知乎

对象委托

JavaScript 的对象最初都是由 Object.prototype 对象克隆而来的,但对象构造器的原型并不仅限于 Object.prototype 上,而是可以动态指向其他对象。

原型链并不是无限长的,当对象通过原型链找某个属性找到根节点而找不到时,则会返回undefined(Object.prototype 的原型是 null)

原型继承的未来

除了根对象 Object.prototype 本身之外,任何对象都会有一个原型。而通过 Object.create( null )可以创建出没有原型的对象。

ECMAScript 6 带来了新的 Class 语法。这让 JavaScript 看起来像是一门基于类的语言, 但其背后仍是通过原型机制来创建对象。

总结

上面就是自己在学习第一部分第一节所画下以及自己所理解的重点了,里面依旧有许多对目前的自己晦涩难懂的代码,实在是太菜了,在以后慢慢吃透js(不可能,还没吃完就来新的😂)的过程中回来看看这些自己不懂的代码和概念吧

但毕竟收获是有的,通过之前学的C#结合JavaScript来进一步理解多态、封装以及在这两种不同编程风格的代码实现,也了解了原来js的对象是通过原型模式(克隆)实现继承的,大开眼界