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


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 相关文章推荐
基于jquery ajax 用户无刷新登录方法详解
Apr 28 Javascript
JS+JSP checkBox 全选具体实现
Jan 02 Javascript
javascript精确统计网站访问量实例代码
Dec 19 Javascript
根据输入邮箱号跳转到相应登录地址的解决方法
Dec 13 Javascript
详解使用nvm安装node.js
Jul 18 Javascript
Js中使用正则表达式验证输入是否有特殊字符
Sep 07 Javascript
详解几十行代码实现一个vue的状态管理
Jan 28 Javascript
JS实现获取当前所在周的周六、周日示例分析
May 11 Javascript
async/await让异步操作同步执行的方法详解
Nov 01 Javascript
详解vue中在循环中使用@mouseenter 和 @mouseleave事件闪烁问题解决方法
Apr 07 Javascript
微信小程序连续签到7天积分获得功能的示例代码
Aug 20 Javascript
vue实现标签云效果的示例
Nov 09 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
一条久听不愿放下的DIY森海MX500,三言两语话神奇
2021/03/02 无线电
PHP JSON 数据解析代码
2010/05/26 PHP
php中通过虚代理实现延迟加载的实现代码
2011/06/10 PHP
CI(CodeIgniter)框架配置
2014/06/10 PHP
thinkphp中空模板与空模块的用法实例
2014/11/26 PHP
php+ajax无刷新分页实例详解
2015/12/07 PHP
php数据结构之顺序链表与链式线性表示例
2018/01/22 PHP
php设计模式之工厂方法模式分析【星际争霸游戏案例】
2020/01/23 PHP
thinkphp5.1框架模板赋值与变量输出示例
2020/05/25 PHP
飞鱼(shqlsl) javascript作品集
2006/12/16 Javascript
event.srcElement 用法笔记e.target
2009/12/18 Javascript
JavaScript中检查对象property的存在性方法介绍
2014/12/30 Javascript
jquery实现的省市区三级联动
2015/04/02 Javascript
jQuery页面加载初始化的3种方法(推荐)
2016/06/02 Javascript
artDialog+plupload实现多文件上传
2016/07/19 Javascript
JQuery 设置checkbox值二次无效的解决方法
2016/07/22 Javascript
node.js中 stream使用教程
2016/08/28 Javascript
浅谈javascript alert和confirm的美化
2016/12/15 Javascript
Node.js中.pfx后缀文件的处理方法
2017/03/10 Javascript
面试常见的js算法题
2017/03/23 Javascript
ES6中Proxy与Reflect实现重载(overload)的方法
2017/03/30 Javascript
JS按条件 serialize() 对应标签的使用方法
2017/07/24 Javascript
基于vue+canvas的excel-like组件实例详解
2017/11/28 Javascript
浅谈angular2子组件的事件传递(任意组件事件传递)
2018/09/30 Javascript
python比较两个列表是否相等的方法
2015/07/28 Python
Python 多线程Threading初学教程
2017/08/22 Python
Django模板导入母版继承和自定义返回Html片段过程解析
2019/09/18 Python
python使用matplotlib绘制雷达图
2019/10/18 Python
Python下载网易云歌单歌曲的示例代码
2020/08/12 Python
Python爬虫回测股票的实例讲解
2021/01/22 Python
Html5 Canvas动画基础碰撞检测的实现
2018/12/06 HTML / CSS
美国体育用品在线:Modell’s Sporting Goods
2018/06/07 全球购物
幼儿园中秋节活动方案2013
2014/01/29 职场文书
给学校的建议书400字
2015/09/14 职场文书
python 机器学习的标准化、归一化、正则化、离散化和白化
2021/04/16 Python
Django中session进行权限管理的使用
2021/07/09 Python