类和原型的设计模式之复制与委托差异


Posted in Javascript onJuly 07, 2022

小引

JavaScript 技能持有者一定有问过这个问题:

JavaScript 是面向对象语言吗?

你期望得到的答案应该为:“是” 或 “不是”。

但是可惜,你得不到这样简单的答案!

你大概了解一通之后,你会被告知:

JavaScript 不是纯粹的面向对象语言!

wtf!为什么是不纯粹?能不能纯粹一点?!我们喜欢纯粹,不喜欢混沌!

......

实际上,死扣定义真的没太必要。定义背后的故事才是最重要的!

看完本篇,你就会明白这种“混沌”是什么、来自何处,以及去往何方!!

撰文不易,多多鼓励。点赞再看,养成习惯。???

“类”设计模式

妇孺皆知,面向对象三大特性:【封装】、【继承】、【多态】。

  • 所谓封装,即把客观事物封装成抽象的类。
  • 所谓继承,即子类继承父类的能力。
  • 所谓多态,即子类可以用更特殊的行为重写所继承父类的通用行为。

其中,“类”的概念最最关键!【类】描述了一种代码的组织结构形式,它是软件中对真实世界中问题领域的建模方法。

举个例子:

就好比我们现实中修房子,你要修一个“写字楼”、或者一个“居民楼”、或者一个“商场”,你就得分别找到修“写字楼”、“居民楼”、“商场”的【设计蓝图】。

但是设计蓝图只是一个建筑计划,并不是真正的建筑。要想在真实世界实现这个建筑,就得由建筑工人将设计蓝图的各类特性(比如长宽高、功能)【复制】到现实世界来。

这里的【设计蓝图】就是【类】,【复制】的过程就是【实例化】,【实例】就是【对象】。

类的内部通常有一个同名的构造方法,我们设想下,它的伪代码就可能是这样的:

class Mall { // “商场”类
    Mall( num ){ // 同名构造方法
        garage = num // 地下车库数量
    }
    shop( goods ) { // 买东西
       output( "We can buy: ", goods )
    }
}
// 构造函数大多需要用 new 来调,这样语言引擎才知道你想要构造一个新的类实例。
vanke = new Mall(1) // vanke 有 1 个地下车库
vanke.shop("KFC") // "We can buy: KFC"

java 是典型的面向对象语言。基于“类”,我们再通过以下一段 java 代码来看看对继承和多态的理解。

public abstract class Animal{ // 抽象类
     abstract void sound();
}
public class Chicken extends Animal{ // 继承
    public void sound(){
      sound("咯咯咯");
    }
}
public class Duck extends Animal{
    public void sound(){
      sound("嘎嘎嘎");
    }
}
public static void main(String args[]){
  Aninal chicken = new Chicken();
  Animal duck = new Duck();
  chicken.sound(); //咯咯咯
  duck.sound();  //嘎嘎嘎
}

鸡和鸭都属于动物分类,都可以发出叫声(继承),但是它们却可以发出不同的叫声(多态),很容易理解。

继承可以使子类获得父类的全部功能; 多态可以使程序有良好的扩展;

回想下:在 JS 中,我们可能会怎样写:

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

这里既没用到继承,也没用到多态。这样【写判断】是代码“不清爽”的罪魁祸首!

此处留一个疑问,如果不用判断,还可以怎么写?

在 vue2 中,我们可能会这么写:

export default {
  data() {
      return {
      },
      mounted(){
          this.Chicken()
          this.Duck()
      },
      methods:{
          funtion AnimalSound(sound){
              console.log("叫声:" + sound)
          },
          funtion Chicken(){
              this.AnimalSound("咯咯咯")
          },
          funtion Duck(){
              this.AnimalSound("嘎嘎嘎")
          }
      }
  }

像这种函数嵌套调用是很常见的。没有看到继承,也没有看到多态,甚至都没有看到最根本的“类”?!

(实际上,每个函数都是一个 Function 对象。按照最开始定义所述,对象是类的实例,所以也是能在函数中看到“类”的!)

在 JavaScript 中,函数成了第一等公民! 函数似乎什么都能做!它可以返回一个对象,可以赋值给一个变量,可以作为数组项,可以作为对象的一个属性......

但这明显不是“类的设计模式”吧!

“类的设计模式” 意味着对【设计蓝图】的【复制】,在 JS 各种函数调用的场景下基本看不到它的痕迹。

“原型”设计模式

其实,众所周知,JS 也是能做到【继承】和【多态】的!只不过它不是通过类复制的方式,而是通过原型链委托的方式!

一图看懂原型链?

类和原型的设计模式之复制与委托差异

看不懂?没关系,记住这两句话再来看:

  • 一个对象的显示原型的构造函数指向对象本身(很熟悉有没有?在本文哪里见过?)
  • 一个对象的隐式原型指向构造这个对象的函数的显示原型。

原来,JS 不是通过在类里面写同名构造函数的方式来进一步实现的实例化,它的构造函数在原型上!这种更加奇特的代码服用机制有异于经典类的代码复用体系。

这里再附一个经典问题?JS new 操作会发生什么?

会是像类那样进行复制吗?

答案是否定的!

JS 访问一个对象的属性或方法的时候,先在对象本身中查找,如果找不到,则到原型中查找,如果还是找不到,则进一步在原型的原型中查找,一直到原型链的最末端。复制不是它所做的,这种查找的方式才是!对象之间的关系更像是一种委托关系,就像找东西,你在我这找不到?就到有委托关系的其它人那里找找看,再找不到,就到委托委托关系的人那里找......直至尽头,最后还找不到,指向 null。

所以:JavaScript 和面向对象的语言不同,它并没有类来作为对象的抽象模式或者设计蓝图。JavaScript 中只有对象,对象直接定义自己的行为。对象之间的关系是委托关系,这是一种极其强大的设计模式。在你的脑海中对象并不是按照父类到子类的关系垂直组织的,而是通过任意方向的委托关联并排组织的!

不过你也可以通过这种委托的关系来模拟经典的面向对象体系:类、继承、多态。但“类”设计模式只是一种可选的设计模式,你可以模拟,也可以不模拟!

现实是 ES6 class 给我们模拟了:

class Widget { 
    constructor(width,height) { 
        this.width = width || 50; 
        this.height = height || 50; 
        this.$elem = null; 
    } 
    render($where){ 
        if (this.$elem) { 
            this.$elem.css( { 
                width: this.width + "px", 
                height: this.height + "px" 
            }).appendTo( $where ); 
        } 
    } 
} 
class Button extends Widget { 
    constructor(width,height,label) { 
        super( width, height ); 
        this.label = label || "Default"; 
        this.$elem = $( "<button>" ).text( this.label ); 
    } 
    render($where) { 
        super.render( $where ); 
        this.$elem.click( this.onClick.bind( this ) ); 
    } 
    onClick(evt) { 
        console.log( "Button '" + this.label + "' clicked!" ); 
    } 
}

看起来,非常不错,很清晰!

没有 .prototype 显示原型复杂的写法,也无需设置 .proto 隐式原型。还似乎用 extends 、super 实现了继承和多态。

然而,这只是语法糖的陷阱!JS 没有类,没有复制,它的机制是“委托”。

class 并不会像传统面向类的语言一样在申明时作静态复制的行为,如果你有意或者无意修改了父类,那子类也会收到影响。

举例:

class C { 
 constructor() { 
    this.num = Math.random(); 
 } 
 rand() { 
    console.log( "Random: " + this.num ); 
 } 
} 
var c1 = new C(); 
c1.rand(); // "Random: 0.4324299..."
C.prototype.rand = function() { 
    console.log( "Random: " + Math.round( this.num * 1000 )); 
}; 
var c2 = new C(); 
c2.rand(); // "Random: 867"
c1.rand(); // "Random: 432" ——噢!

ES6 class 混淆了“类设计模式”和“原型设计模式&rdquo;。它最大的问题在于,它的语 法有时会让你认为,定义了一个 class 后,它就变成了一个(未来会被实例化的)东西的 静态定义。你会彻底忽略 Class 是一个对象,是一个具体的可以直接交互的东西。当然,它还有其它细节问题,比如属性覆盖方法、super 绑定的问题,有兴趣自行了解。

总地来说,ES6 的 class 想伪装成一种很好的语法问题的解决方案,但是实际上却让问题更难解决而且让 JavaScript 更加难以理解。 —— 《你不知道的 JavaScript》

小结

  • “类设计模式”的构造函数挂在同名的类里面,类的继承意味着复制,多态意味着复制 + 自定义。
  • “原型设计模式”的构造函数挂在原型上,原型的查找是一种自下而上的委托关系。
  • “类设计模式”的类定义之后就不支持修改。
  • “原型设计模式”讲究的是一种动态性,任何对象的定义都可以修改,这和 JavaScript 作为脚本语言所需的动态十分契合!

你可以用“原型设计模式”来模拟“类设计模式”,但是这大概率是得不偿失的。

最后,如果再被问道:JavaScript 是面向对象语言吗?

如果这篇文章看懂了,就可以围绕:“类设计模式”和“原型设计模式”来吹了。

如果本文没有看懂,就把下面的标答背下来吧......

类和原型的设计模式之复制与委托差异

参考

命名函数表达式探秘

函数式和面向对象编程有什么区别?

tutorials/js.mp

JavaScript 轻量级函数式编程

你不知道的JavaScript

以上就是类和原型的设计模式之复制与委托差异的详细内容,更多关于类原型设计模式复制委托差异的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
xss文件页面内容读取(解决)
Nov 28 Javascript
基于JQuery的模拟苹果桌面Dock效果(稳定版)
Oct 15 Javascript
js或者jquery判断图片是否加载完成实现代码
Mar 20 Javascript
详解addEventListener的三个参数之useCapture
Mar 16 Javascript
jQuery构造函数init参数分析续
May 13 Javascript
jQuery UI设置固定日期选择特效代码分享
Aug 27 Javascript
JavaScript中SetInterval与setTimeout的用法详解
Nov 10 Javascript
浅谈Angular2 ng-content 指令在组件中嵌入内容
Aug 18 Javascript
js中的闭包学习心得
Feb 06 Javascript
javascript高仿热血传奇游戏实现代码
Feb 22 Javascript
Layer组件多个iframe弹出层打开与关闭及参数传递的方法
Sep 25 Javascript
浅谈vue项目利用Hbuilder打包成APP流程,以及遇到的坑
Sep 12 Javascript
JS高级程序设计之class继承重点详解
Jul 07 #Javascript
JS class语法糖的深入剖析
Jul 07 #Javascript
MutationObserver在页面水印实现起到的作用详解
Jul 07 #Javascript
js作用域及作用域链工作引擎
Promise静态四兄弟实现示例详解
Jul 07 #Javascript
Three.js实现雪糕地球的使用示例详解
二维码条形码生成的JavaScript脚本库
Jul 07 #Javascript
You might like
支持php4、php5的mysql数据库操作类
2008/01/10 PHP
解决FastCGI 进程超过了配置的活动超时时限的问题
2013/07/03 PHP
php使用CURL不依赖COOKIEJAR获取COOKIE的方法
2015/06/17 PHP
php之static静态属性与静态方法实例分析
2015/07/30 PHP
php微信公众号开发之欢迎老朋友
2018/10/20 PHP
JAVASCRIPT  THIS详解 面向对象
2009/03/25 Javascript
百度留言本js 大家可以参考下
2009/10/13 Javascript
js改变img标签的src属性在IE下没反应的解决方法
2013/07/23 Javascript
js实现收缩菜单效果实例代码
2013/10/30 Javascript
初步使用bootstrap快速创建页面
2016/03/03 Javascript
Angular的MVC和作用域
2016/12/26 Javascript
微信小程序项目实践之主页tab选项实现
2018/07/18 Javascript
vue 修改 data 数据问题并实时显示的方法
2018/08/27 Javascript
JS实现可针对算术表达式求值的计算器功能示例
2018/09/04 Javascript
vue-drawer-layout实现手势滑出菜单栏
2020/11/19 Vue.js
Python 文件重命名工具代码
2009/07/26 Python
python将多个文本文件合并为一个文本的代码(便于搜索)
2011/03/13 Python
在Python的Flask框架中构建Web表单的教程
2016/06/04 Python
node.js获取参数的常用方法(总结)
2017/05/29 Python
Python实现的特征提取操作示例
2018/12/03 Python
python实现给scatter设置颜色渐变条colorbar的方法
2018/12/13 Python
在python tkinter界面中添加按钮的实例
2020/03/04 Python
python语言的优势是什么
2020/06/17 Python
Python爬虫抓取指定网页图片代码实例
2020/07/24 Python
Python读取图像并显示灰度图的实现
2020/12/01 Python
HTML利用九宫格原理进行网页布局
2020/03/13 HTML / CSS
函授本科自我鉴定
2014/02/04 职场文书
模具专业毕业推荐信
2014/03/08 职场文书
元旦晚会活动总结
2014/07/09 职场文书
债务追讨授权委托书范本
2014/10/16 职场文书
2015庆祝七一建党节94周年活动总结
2015/03/20 职场文书
培训感想范文
2015/08/07 职场文书
离婚起诉书范文2016
2015/11/26 职场文书
2016秋季运动会开幕词
2016/03/04 职场文书
Java如何实现通过键盘输入一个数组
2022/02/15 Java/Android
Python中文分词库jieba(结巴分词)详细使用介绍
2022/04/07 Python