ES2015 中的函数式Mixin

原文链接:http://raganwald.com/2015/06/17/functional-mixins.html

在“原型即对象”中,我们看到可以对原型使用 Object.assign 来模拟 mixin,原型是 JavaScript 中类概念的基石。现在我们将回顾这个概念,并进一步探究如何将功能糅合进类。

首先,简单回顾一下:在 JavaScript 中,类是通过一个构造函数和它的原型来定义的,无论你是用 ES5 语法,还是使用 class 关键字。类的实例是通过 new 调用构造器的方式创建的。实例从构造器的 prototype 属性上继承共享的方法。

对象 mixin 模式

如果多个类共享某些行为,或者希望对庞杂的原型对象进行功能提取,这时候就可以使用 mixin 来对原型进行扩展。 
 
如这里的 Todo 类
class Todo {constructor (name) {this.name = name || 'Untitled';this.done = false;}do () {this.done = true;return this;}undo () {this.done = false;return this;}
}

用于颜色编码的 mixin 如下
const Coloured = {setColourRGB ({r, g, b}) {this.colourCode = {r, g, b};return this;},getColourRGB () {return this.colourCode;}
};

将颜色编码功能糅合到 Todo 原型上是简而易行的
Object.assign(Todo.prototype, Coloured);new Todo('test').setColourRGB({r: 1, g: 2, b: 3})//=> {"name":"test","done":false,"colourCode":{"r":1,"g":2,"b":3}}

我们还可以升级为使用私有属性
const colourCode = Symbol("colourCode");const Coloured = {setColourRGB ({r, g, b}) {this[colourCode]= {r, g, b};return this;},getColourRGB () {return this[colourCode];}
};

至此,非常简单明了。我们将这称为一种 “模式”,像菜谱一样,是解决某种问题的独特的代码组织方式。
 

函数式 mixin

上面的对象 mixin 功能完好,但用它来解决问题要分两步走:定义 mixin 然后扩展类的原型。Angus Croll 指出将 mixin 定义成函数而不是对象会是更优雅的做法,并称之为函数式 mixin。再次以 Coloured 为例,将它改写成函数的形式
const Coloured = (target) =>Object.assign(target, {setColourRGB ({r, g, b}) {this.colourCode = {r, g, b};return this;},getColourRGB () {return this.colourCode;}});Coloured(Todo.prototype);

我们可以定义一个工厂函数,并从命名上体现该模式
const FunctionalMixin = (behaviour) =>target => Object.assign(target, behaviour);

现在我们可以精要地定义函数式 mixin:
const Coloured = FunctionalMixin({setColourRGB ({r, g, b}) {this.colourCode = {r, g, b};return this;},getColourRGB () {return this.colourCode;}
});

可枚举性 

如果我们探究 class 声明类的方式下对 prototype 的操作,可以发现声明的方法默认是不可枚举的。这可以避免一个常见问题——遍历实例的 key 时程序员有时忘记检测 .hasOwnProperty。
 
而我们的对象 mixin 模式无法做到这点,定义在 mixin 中的方法默认是可枚举的。如果我们故意将其设置为不可枚举,Object.assign 就不会将它们糅合到目标原型了,因为 Object.assign 只会将可枚举的属性赋值到目标对象上。
 
这将导致以下情形
Coloured(Todo.prototype)const urgent = new Todo("finish blog post");
urgent.setColourRGB({r: 256, g: 0, b: 0});for (let property in urgent) console.log(property);// =>
    namedonecolourCodesetColourRGBgetColourRGB

正如所见,setColourRGB 和 getColourRGB 方法被枚举出来了,而 do 和 undo 方法却没有。这对于不健壮的代码会是个问题,因为我们不可能每次都重写别处的代码,处处加上 hasOwnProperty 检测。
 
该问题使用函数式 mixin 便可迎刃而解,我们可以神乎其神地让 mixin 表现得和 class 声明类似,这是函数式 mixin 的好处之一
const FunctionalMixin = (behaviour) =>function (target) {for (let property of Reflect.ownKeys(behaviour))Object.defineProperty(target, property, { value: behaviour[property] })return target;}

将上面 mixin 的主体部分作为一种代码模板一遍遍写出来不但累人而且容易出错,而将其封装到函数里则是一种小进步。
 
 

mixin 的职责

和类一样,mixin 是元对象:它们给实例定义行为。除了以方法的形式定义对象的行为,类还负责初始化实例。有的时候,类和元对象还会具有其他的功能。
 
例如,有时某个概念涉及到一组人尽皆知的常量。如果使用类,那么将这些常量定义在 class 本身上则非常方便,这时 class 本身充当了命名空间的作用
class Todo {constructor (name) {this.name = name || Todo.DEFAULT_NAME;this.done = false;}do () {this.done = true;return this;}undo () {this.done = false;return this;}
}Todo.DEFAULT_NAME = 'Untitled';// If we are sticklers for read-only constants, we could write:
// Object.defineProperty(Todo, 'DEFAULT_NAME', {value: 'Untitled'});

我们无法使用 “简单 mixin” 做同样的事,因为默认情况下,“简单 mixin” 的所有属性最终都被糅合到实例的 prototype 上。例如,我们想定义 Coloured.RED, Coloured.GREEN, Coloured.BLUE,但我们并不想在任何实例个体上定义 RED, GREEN, BLUE。
 
同样,我们可以借助函数式 mixin 来解决该问题。FunctionalMixin 工厂函数将接收一个可选的字典,该字典包含只读的 mixin 属性,该字典通过一个特殊的键给出
const shared = Symbol("shared");function FunctionalMixin (behaviour) {const instanceKeys = Reflect.ownKeys(behaviour).filter(key => key !== shared);const sharedBehaviour = behaviour[shared] || {};const sharedKeys = Reflect.ownKeys(sharedBehaviour);function mixin (target) {for (let property of instanceKeys)Object.defineProperty(target, property, { value: behaviour[property] });return target;}for (let property of sharedKeys)Object.defineProperty(mixin, property, {value: sharedBehaviour[property],enumerable: sharedBehaviour.propertyIsEnumerable(property)});return mixin;
}FunctionalMixin.shared = shared;

现在我们便可以这样写
const Coloured = FunctionalMixin({setColourRGB ({r, g, b}) {this.colourCode = {r, g, b};return this;},getColourRGB () {return this.colourCode;},[FunctionalMixin.shared]: {RED:   { r: 255, g: 0,   b: 0   },GREEN: { r: 0,   g: 255, b: 0   },BLUE:  { r: 0,   g: 0,   b: 255 },}
});Coloured(Todo.prototype)const urgent = new Todo("finish blog post");
urgent.setColourRGB(Coloured.RED);urgent.getColourRGB()//=> {"r":255,"g":0,"b":0}

 

mixin 本身的方法

JavaScript 中属性未必是值(和函数相对)。有时候,类也具有方法。同样,有时 mixin 具有自己的方法也是合理的,比如当涉及到 instanceof 时。
 
在 ECMAScript 的之前版本中,instanceof 操作符检查实例的 prototype 是否和构造函数的 prototype 相匹配。它和“类”配合使用没啥问题,但却无法直接和 mixin 协同工作
urgent instanceof Todo//=> true

urgent instanceof Coloured//=> false

这是 mixin 存在的问题。另外,程序员可能根据需要创建动态类型,或者直接使用 Object.create 和 Object.setPrototypeOf 管理原型,它们都可能导致 instanceof 工作不正常。ECMAScript 2015 提供了一种方式来覆写内置的 instanceof 的行为,即对象可以定义一个特殊的方法,该方法属性的名字是一个既定的符号——Symbol.hasInstance。
 
我们可以简单测试一下
Object.defineProperty(Coloured, Symbol.hasInstance, {value: (instance) => true});
urgent instanceof Coloured//=> true
{} instanceof Coloured//=> true

当然,上面的例子在语义上是不对的。然而借助该技术,我们可以这样做
const shared = Symbol("shared");function FunctionalMixin (behaviour) {const instanceKeys = Reflect.ownKeys(behaviour).filter(key => key !== shared);const sharedBehaviour = behaviour[shared] || {};const sharedKeys = Reflect.ownKeys(sharedBehaviour);const typeTag = Symbol("isA");function mixin (target) {for (let property of instanceKeys)Object.defineProperty(target, property, { value: behaviour[property] });target[typeTag] = true;return target;}for (let property of sharedKeys)Object.defineProperty(mixin, property, {value: sharedBehaviour[property],enumerable: sharedBehaviour.propertyIsEnumerable(property)});Object.defineProperty(mixin, Symbol.hasInstance, {value: (instance) => !!instance[typeTag]});return mixin;
}FunctionalMixin.shared = shared;urgent instanceof Coloured//=> true
{} instanceof Coloured//=> false

你需要为了照顾 instanceof 而专门做此实现吗?很可能不需要,因为自己实现一套多态机制是不得已而为之的做法。但这一点使得编写测试用例很顺手,并且一些激进的框架开发者可能在函数的多分派和模式匹配上求索,或许会用上这一点。
 

总结

对象 mixin 的迷人之处在于简单:它不需要在对象字面值和 Object.assign 之上做一层抽象。
 
然而,通过 mixin 模式定义的行为和通过 class 关键字定义的行为存在些许差异。体现差异的两个例子分别是可枚举性以及 mixin 自身的属性(如常量和类似于 [Symbol.hasInstance] 的 mixin 自身方法)。
 
函数式 mixin 使得实现上述功能成为可能,不过生成函数式 mixin 的 FunctionalMixin 函数引入了一定复杂性。
 
一般来说,最好保持同一个问题域下的代码表现尽可能相似,而这有时不可避免地增加基础代码的复杂性。但这一点更多的是一种指导思想,而非需要恪守的万能教条。出于此,对象 mixin 模式和函数式 mixin 在 JavaScript 中都有各自的一席之地

转载于:https://www.cnblogs.com/averyby/p/10026365.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/352108.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spring中的设计模式_面试:设计模式在spring中的应用

设计模式为我们解决一类问题提供了最佳的解决方案,我们在实际工作其实不太常用到,以至于会经常想不到设计模式。究其原因都是我们只是在使用别人框架的缘故,在这些框架的代码中经常能看到设计模式的影子,我们以spring为例&#xf…

linux route命令的使用详解

route命令用于显示和操作IP路由表。要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现。在Linux系统中,设置路由通常是 为了解决以下问题:该Linux系统在一个局域网中,局…

C如何将二维数组作为返回值

做大作业遇到这样一个问题:在子函数里申请了一个二维数组,在主函数里要用到二维数组里的数据,但是在主函数里又不能提前申请(因为不知道数组长度),所以需要将数组return得到。 子函数: float **…

Spring的IOC底层实现

IOC的底层实现 续图: 转载于:https://www.cnblogs.com/phyger/p/10027712.html

python传文件给java_python使用简单http协议来传送文件

python使用简单http协议来传送文件!在ubuntu环境下,局域网内可以使用nc来传送文件,也可以使用基于Http协议的方式来下载文件我们可以使用python -m SimpleHTTPServer ${port}来启动服务默认的端口是8000,另外我们也可以指定端口&a…

C定义全局变量

程序工程中往往遇到这样的问题:某个变量是贯穿始终的,主函数以及不同的子函数都要用到这个变量,并且要调用子函数改变变量的值。这时候全局变量就起到一个桥梁作用,在函数外定义,在主函数中调用定义,在子函…

tensorflow(centos 7.0 64)安装

tensorflow安装 系统centos 7.0 64位: python版本:(注意tensorflow目前只支持python2.7版本) 安装pip yum update -y && yum install -y python python-devel epel-release.noarch python-pip 使用pip安装tensorflow pip install https://storag…

午餐前如何安装OpenStack Cloud

图1. QuickStart的内部工作原理 云安装程序 如果我告诉您可以在必须停下来吃午餐之前进行OpenStack Cloud环境设置,该怎么办? 您会感到惊讶吗? 你今天可以做吗? 在大多数情况下,我敢打赌您的答案是不可能的&#…

实现多线程的方式之实现Callable接口

package com.hls.juc;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;/** * 创建多线程的方式 : 4种 * 1.继承Thread类 * 2.实现Runable接口 * 3.实现Callable接口 * 4.使用线程池创建线程 * *…

b 树查找时间复杂度_你心里是没点B树吗?

点击上方“零一视界”,选择“星标”公众号资源干货,第一时间送达1 引言数据库的增删改查等操作是开发过程中最为常见也是尤为重要的,尤其是现在大数据的兴起,导致数据存储量急剧增加,提升数据的操作效率就变得尤为关键…

Opencv imshow显示不出来图片

VSOPENCV处理图像时,imshow显示图片一片灰色,刚开始以为图片太大一直加载不出来,但是一直等不到显示出来,最后发现是因为最后忘记写 cvWaitKey(0);//或者waitKey(0);

vue 2个方法先后执行_有效快速制作工资条的2个方法

发工资是每个月员工们最期待的事情,但是对于HR来说却是非常头痛的工作。如何快速制作工资条?相信很多HR朋友们都很想知道,那么今天本文就和大家分享2个制作工资条的高效方法。 第一种:传统方式(Excel制作)制…

Opencv图像保存到电脑及显示

针对两种类型的图片有两种不同的方法: 1、对于Mat类型图像,用imwrite、imshow Mat img_goodmatch; imwrite("最终匹配结果.bmp", img_goodmatch); //“”里面为路径及图片名,可以改为绝对路径 …

机器学习算法库scikit-learn的安装

scikit-learn 是一个python实现的免费开源的机器学习算法包,从字面意思可知,science 代表科学,kit代表工具箱,直接翻译过来就是用于机器学习的科学计算包。 安装scikit-learn有两种方式: (1)安装…

从头基于空镜像scratch创建一个新的Docker镜像

我们在使用Dockerfile构建docker镜像时,一种方式是使用官方预先配置好的容器镜像。优点是我们不用从头开始构建,节省了很多工作量,但付出的代价是需要下载很大的镜像包。 比如我机器上docker images返回的这些基于nginx的镜像,每个…

python bool转string_Python:可以返回boolean和string吗?

原始问题我已经创建了一个等待特定字符串出现在串行端口上的函数,并返回所有字符读取,直到找到该字符串,否则返回false.这很方便,但我想知道它是否被认为是不好的做法?澄清:主要目标是等待特定字符串在给定的时间内出现.除IO错误外,可能的结果为True(字符…

在CockroachDB上运行Flowable

什么是CockroachDB? CockroachDB是一个我一直关注很长一段时间的项目。 这是一个开放源代码的Apache 2许可数据库( Github链接 ), 极大地从Google Spanner白皮书中汲取了灵感 。 它的核心是可水平扩展的键值存储。 但是&#xff0…

C error :Run-Time Check Failure #2 - Stack around the variable 'b' was corrupted.

运行程序遇到这样的错误:Run-Time Check Failure #2 - Stack around the variable b was corrupted. 检查后发现原因在于:数组b越界了,int b[4]里面有4个元素,包含b[0],b[1],b[2],b[3],没有b[4],赋值的时候把某个数据赋…

如何配置Apache虚拟主机?(基于IP、基于端口、基于域名)

一、Apache虚拟机配置前的准备工作 1、下载yum源2、安装yum包3、安装httpd包4、查看并关闭selinux5、取消中心主机cd /etc/httpd/confvim httpd.conf修改文件中的内容如下:当以上这些工作准备好之后,我们就可以来配置虚拟机了二、我们首先来做一个基于IP…

[bat]删除文件

删除文件 del /f /s /q D:\HRG\NEW_Vn\CSV\*.meta 删除空文件夹 只能先删完文件夹中的文件,再删除空文件夹 rd /s /q D:\HRG\NEW_Vn\CSV\ 脚本 修改 echo off echo --------------------WARNING-------------------- echo [%1] folder will be deleted echo -------…