多态、封装、继承

多态

同一个操作作用于不同的对象,得到不同的结果。
重点分离:做什么 和 谁去做。
鸭子辩型:如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。

// 做什么
function sayName( people ) {
    people.sayName()
}

// 谁去做
const xiaoming = {
    name: '小明',
    sayName() {
        console.log( this.name )
    }
}

const lihong = {
    name: '李红',
    sayName() {
        console.log( this.name )
    }
}

// 同一个操作作用在不同的对象
sayName( xiaoming )  // 小明
sayName( lihong )  // 李红

封装

只让别人知道你想让别人知道的,不让别人知道你不想让别人知道的。
封装的目的是将信息隐藏,数据、类型、实现、变化都能封装。

  • 封装数据

Javascript 中要达到封装的目的,只有通过创建作用域来达到隐藏的目的。

const xiaoming = ( function() {
// 封装私有数据,变量命名前缀加_下划线是潜规则,表示是私有的
 const _name = '小明',
     _age = 22
// 向外界提供公共方法
  return {
    getName() { return _name },
    getAge() { return _age }
  }
})()

xiaoming.getName()  // '小明'
xiaoming.getAge()  // 22
xiaoming._name  // undefined
xiaoming._age  // undefined
  • 封装类型

Javascript是弱类型语言,封装类型没多大意义。(完)

  • 封装实现

对象只需要对外提供一个固定的接口,而内部如何实现这个接口,对外界来说应该是透明的,外界不用关心这个对象内部如何去实现这个接口,外界只需要调用这个接口就行了。只要这个接口不变,内部的实现如何去改变都不会影响外界。只要你打一个电话给我,你都会得到我送给你的一颗同品牌同口味同款式的棒棒糖,你不用管我怎么得到这颗棒棒糖,你想吃的时候只需要打一个电话给我就好了嘿嘿嘿。

/**
 * 有一个 obj对象,这个对象有一个接口
 * 外界只要调用这个接口,就会返回一个数字 666
 * 接口的实现对于外界来说是透明的,外界只需调用接口
 */
const obj = ( function() {
  let _num = 666
  return {
    generate666() { return _num }
  }
})()

// 外界只需要调用这个对象的generate666接口就行了
obj.generate666()  // 666

/**
 * 某天这个对象的内部实现改了,但是接口没有改
 * 依旧是对外返回一个数字 666
 */
const obj = ( function() {
  let _num = 600 + 60 + 6
  return {
    generate666() { return _num }
  }
})()

// 外界依旧只是调用这个对象的generate666接口就行了
obj.generate666()  // 666
  • 封装变化:

考虑你的设计中哪些地方可能变化,这种方式与关注会导致重新设计的原因相反。它不是考虑什么时候会迫使你的设计改变,而是考虑你怎样才能够在不重新设计的情况下进行改变。这里的关键在于封装发生变化的概念,这是许多设计模式的主题。
----《设计模式》

找到变化的东西,封装起来,和不变的分开。这样就能够容易地把变化的东西替换掉。

继承

顺藤摸瓜图

Javascript 是基于原型的语言,没有类的概念。但是你可以用类的概念去理解,实际上在 Javascript 中,“类”也是一个对象。
原型模式是一种设计模式,也是一种编程泛型。
在诸如 JAVA 或者 Python 这种以类为中心的面向对象的语言来说,类和对象的关系就像是设计图和成品的关系。要创造成品(对象),就先设计图纸(类),然后按照图纸(类)制造出(new)成品(对象)。
而在 Javascript 中,虽然有 new 这个关键字,但是他做的事情和 JAVA 或者 Python 做的事情不一样。
Javascript 中 new 做的事情是通过找到一个对象的原型进行克隆,获得新对象。
Javascript 中绝大部分数据都是对象,根对象是 Object.prototype,所有对象都克隆这个根对象。

// 所有对象都克隆 Object.prototype
const o1 = {}
const o2 = new Object()

Object.getPrototypeOf( o1 ) === Object.prototype  // true
Object.getPrototypeOf( o2 ) === Object.prototype  // true

// 如果在 Object.prototype 上增加一个属性,那么所有对象都会增加这个属性
Object.prototype.six = 666

const o3 = {}
const o4 = new Object()
const o5 = new Date()
const o6 = new Number( 66 )

o3.six  // 666
o4.six  // 666
o5.six  // 666
o6.six  // 666

要创建一个新对象,不是通过实例化类,而是找到一个原型对象进行克隆,得到新对象。
而在现代浏览器中,都会向外暴露一个 __proto__ 属性(在ECMA规范中的描述是 [[ Prototype ]]),这个属性指向这个对象的原型对象。原型对象会有一个 constructor 属性来存储它的构造函数名。
这样在访问一个对象的时候,如果本身有属性,就返回本身的属性,如果没有,对象会去它的原型对象上找这个属性,如果原型对象上还没有,又会去原型对象的原型对象找,一层一层找,如果你愿意一层一层一层地拨开原型对象的属性,直到尽头 null。如果找不到了,你会发现,你会流泪,就返回 undefined。这不就是原型链的原理吗嘿嘿嘿。
这个克隆的过程是引擎帮忙实现的。但是我们也可以实现一下 new 的过程。

function A() {
  this.ownNum = 666
  this.ownFn = function() { console.log(123) }
}
A.prototype.protoNum = 888
A.prototype.protoFn =  function() { console.log(456) }
A.prototype.constructor = A  // 隐含属性,无需设置

const a = new A()
a.constructor === A  // true
a.__proto__ === A.prototype  // true

/** new 的过程 **/
// 克隆 Object.prototype
const o1 = new Object()

// 让 o1 的 [[ Prototype ]] 指针指向一个原型对象
o1.__proto__ = A.prototype

// A.call( o1 ):给 o1 设置新的属性,o1可能被改变(取决于 A构造函数 中是否使用了 this)
// o2 = A.call( o1 ):A 构造函数可能直接 return 一个新对象,而不是默认 return 构造出来的 o1 对象
// 比如 function A() { ... return {b: 9} },那么在 new A() 之后返回的是 { b: 9 },而不是默认的 o1
const o2 = A.call( o1 )

// 总是返回一个这个新建的对象
// 无论是 o1 还是 { b: 9 },如果有 { b: 9 },那么这个的优先级是最高的
return typeof o2 === 'object' ? o2 : o1

而这上述所有过程涉及到的继承是这样的:(每一个函数都有 prototype 属性)

/*-------------------< 代码的层面 >---------------------*/
// 引擎实现的代码 [native code]
function Object() {}
Object.prototype.xx = xx

// 引擎实现的代码 [native code]
function Function() {}
Function.prototype.yy = yy

// 用户编写的代码
function A() {}
A.prototype.zz = zz

// new一个对象
const a = new A()
/*-------------------< 代码的层面 />---------------------*/

/*-------------------< 背后的逻辑 >---------------------*/
a.__proto__ => A.prototype

A.__proto__ => Function.prototype  // typeof A === 'function'
A.prototype.constructor => A 
A.prototype.__proto__ => Object.prototype  // typeof A.prototype === 'object'

Function.__proto__ => Object.prototype
Function.prototype.constructor => Function
Function.prototype.__proto__ => Object.prototype  // typeof Function.prototype === 'function'

Object.__proto__ => Function.prototype  // typeof Object === 'function'
Object.prototype.constructor => Object
Object.prototype.__proto__ => null
/*-------------------< 背后的逻辑 />---------------------*/

在访问 a 的时候,如果本身有想要的属性,就会返回本身的属性,此时用 a.hasOwnProperty( 'a自有属性' ) 进行属性检测是返回 true 。如果 a 本身的自有属性没有,此时就展现原型链的作用了。a会通过 a.__proto__ 访问到 A.prototype ,在 A.prototype 找到了就返回,如果还没找到,就继续一直往下找,直到终点 null ,还没找到,就返回 undefined

可以说,最初的时候,每一个对象都是一样的,都是从 Object.prototype 上克隆而来,都是空白的。但是 Javascript 的世界是缤纷多彩的,大家都一样就没什么意思了。
于是有些对象被人为(引擎)增加了 length 长度的数据属性,有了 pushshift 等方法属性,再用语法层面的 [] 符号表示,就成为了定义中的数组,即Array

function Array() {
  this.length = xxx
}
Array.prototype.push = function() {}
Array.prototype.shift = function() {}

var arr = []
// 等价
var arr = new Array()

是不是和 function A() {} 的套路很像?是的。
同样道理,Date 对象也是如此,因为这个对象有了 GMT 时间的数据属性等,又有了 getDaygetMonth 等方法,就成了定义中的日期对象。
Javascript 的继承的另一种理解,更像是“借用”。类似 Object.prototype.toString.call( ... )
继承的本质是改变这个对象的 __proto__ 指向。
一个对象有两种属性,自有属性和继承属性。自有属性就是本身自己身上有的东西,访问之后直接就给你返回的,继承属性其实算是借用的属性,是通过访问自身的 __proto__ 的指向对象属性,这时候已经是在访问其他对象了,不是自己的属性。
就好比如两个对象其实是不认识,不相关的,但是你把 A对象 的 proto 属性改为 B对象,这两个对象就产生了关系,这时候你也可以称为继承。
这种理解和其他基于 CLASS 类与对象的继承有很大的区别。
此外,继承也是代码重用的手段,Javascript 中的继承不是通过严格意义上的拓展父类来实现的,而是通过原型实现的。
形如 Photoshop,一般都不会直接操作原始图层,都会复制(克隆)一份图层,在复制的图层上进行编辑。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容