一:面向对象:类class
面向对象三大特性之封装
封装是面向对象的重要原则,它在代码中的体现主要是以下两点:
封装整体:把对象的属性和行为封装为一个整体,其中内部成员可以分为静态成员(也叫类成员)和实例成员,成员之间又可细分为属性和方法。访问控制:外部对对象内部属性和行为的访问权限,简单来分时就是私有和公有两种权限。以下是基本封装示例:
面向对象三大特性之继承
继承是面向对象最显著的一个特性,它在代码中的体现主要是以下两点:
子类对象具有父类对象的属性和行为子类对象可以有它自己的属性和行为以下是定义一个Cat类并对上述Animal类的继承示例:
面向对象三大特性之多态
多态指允许不同的对象对同一消息做出不同响应,在Java中,实现多态有以下三个条件:
继承重写父类引用指向子类对象由于JavaScript是弱类型语言,所以JavaScript实现多态,不存在父类引用指向子类对象的问题。
以下再定义一个Dog类,实现Animal实例对象、Cat实例对象和Dog实例对象对同样的cry调用做出不同的响应示例:
二:数据类型Symbol
Symbol是一种新的原始数据类型,用来表示独一无二的值。此外,它也是对象属性名的第二种数据类型(另一种是字符串)。
接下来列举几个在日常开发中可能会用到Symbol数据类型的场景:
1):消除魔法字符串
魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。—阮一峰
如下含有魔法字符串的代码示例:
在上述代码中,大量出现的type1与type2字符串就是魔法字符串。我们分析这样大量使用魔法字符串可能会出现的问题:
在添加逻辑时,我们每次判断obj的类型都需要输入该魔法字符串,这时不但没有输入提示需要一个一个字符输入,而且一旦字符少输、多输或者输入错误,都会导致代码运行错误。在修改逻辑时,如果type1变成了type3,那么就需要把代码里所有的type1找到并替换成type3。接下来使用Symbol对上述代码改造:
2):实现对象的保护成员/私有成员
假设我们对一个对象需要做如下的访问控制:
attr1和attr2公有成员:外部可以访问attr3和attr4保护成员:外部受限访问,需要引入键attr3和attr4才能访问attr5和attr6私有成员:外部不能访问,仅支持当前模块文件内部访问以下是没有实现访问控制的代码:
接下来使用Symbol对上述代码改造:
如上代码就实现了对我们所需要的访问控制,外部对不能访问的成员是无法感知的,因为外部对这些不能访问的成员不但不支持读写,甚至也不能遍历和序列号操作。
在我们以往的日常开发中,我们基本上对对象的访问控制都是设置为公有的,很少设置为私有,设置为保护的就更是没见过。但少归少,至少说明了ES6引入的Symbol能帮助我们实现类似Java中保护和私有成员的访问控制。
3):实现类的保护成员、私有成员
如下示例,封装一个集合Collection,它对模块外部具有私有属性size与私有方法logAdd:
三:数据结构Set
Set对于JavaScript而言是一种新的数据结构,相对于数组用于存储有序、可重复的元素集合,Set用于存储有序、不可重复的元素集合。
接下来列举几个在日常开发中可能会用到Set数据结构的场景:
1):数组去重、字符串去重等任何可迭代类型的去重
2):集合间操作:交集、并集、差集
下面截取阮一峰ES6对Set的说明案例:
四:数据结构Map
Map对于JavaScript而言也是一种新的数据结构,用于存储键值对形式的字典/双列集合。在Map对象出现之前,我们通常使用Object对象来做键值对的存储,下面对比一下Map对象实现键值对存储与普通对象存储键值对的区别:
功能角度:Object对象只能使用字符串或者Symbol类型作为键,而Map对象可以使用任何数据类型作为键。Map对象使用引用类型作为键时,以内存地址是否一致来作为判断两个键是否相同的标准。构造与读写角度:Object对象字面量构造并存储键值对的方式比Map方便,其读写操作也比Map需要调用get、set方法而言性能更好(性能分析工具初步对比分析)。常用Api角度:Object对象的原型为Object.protoype,而Map对象的原型为Map.prototype,两者对常用的键值对操作都有相应的api可以调用,不过Map原型上定义的Api更加纯粹一些。序列化角度:Object对象存储键值时支持序列化,而Map对象不支持。经过上面的对比分析可以得出结论,不到必须使用引用类型作为键的情况下,我们都用Object对象字面量的方式来定义并存储键值对会更好一些。
接下来叙述在日常开发中可能会用到Map数据结构的场景:
1):实现对象之间的一对一、一对多、多对多(桥Map方式)的关系
经验尚浅,日常开发示例暂时没想到,有机会补上。但是Map结构的出现告诉了我们这些JavaScript开发者,此后在JavaScript中我们也可以很简单的实现对象之间的映射关系。
五:迭代器Iterator和forof
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。而for…of循环是ES6创造出的一种新的遍历命令,它可以配合迭代器使用,只要实现了Iterator接口的任意对象就可以使用for…of循环遍历。
在JavaScript常见的数据结构如Array、Set、Map、伪数组arguments等等一系列对象的原型上都有Symbol.iterator标识,并且有默认的Iterator实现。普通对象是没有这个接口标识以及iterator的实现的,但是我们可以手动为普通对象添加这个标识以及对应的iterator实现,示例代码如下:
上述代码的优点是封装者在对外界遍历没有影响的情况下,对数据进行了更细粒度的管理。是一种解耦合的代码优化操作!
六:promise、generator和Async
这三者都与异步编程有关,之后会单独拎出来写在另一篇博客当中,在此文中就不做赘述了。
七:模板字符串和标签函数
模板字符串就不做介绍了,标签函数在定义时和普通函数没什么区别。区别在调用上,标签函数以模板字符串作为参数输入,并且有独特的规则完成形实参匹配。接下来看一个简单的例子:
上述示例运行结果:
经过上述例子我们可以大概得知标签函数的形实参匹配规则:
模板中字面量数组的形实参匹配:模板字符串以类似/${[^}]+}/g的正则规则进行split得到其内所有字面量组成的数组,而后作为实参匹配标签函数的第一个形参literals模板中所有变量的形实参匹配:模板字符串以/${[^}]+}/g的正则规则进行match找到所有的JS变量数组,解析得到其值后,按顺序作为实参匹配标签函数剩下的形参,上例代码中用rest剩余参数作为形参接收所有实参。通过上面的例子和解析,我们认识了标签函数调用的执行规则。根据标签函数和模板字符串的配合机制,我们很容易就想到这种机制可以实现模板引擎甚至是定义内部语言的功能。
接下来叙述在日常开发中我们可能会用到标签函数的场景:
1):把可能作为innerHtml的string中的特殊字符转义,使它不被解析为HTML标签
在日常开发中,我们很可能会碰到这么一个需求:
一个input输入框接收用户的输入另一个p标签用来展示这个用户的输入先分析一下这样做的风险:由于用户的输入直接作为了p标签的内容,当用户输入一个script标签等任意HTML标签时,如果我们直接把它交给p标签,那么浏览器就会把它当成inneHTML进行解析后执行其中的脚本或者渲染HTML,这肯定是不被期望且有风险的。所以我们在把用户的输入交给p标签展示之前,应该对其中的一些特殊字符进行转义,防止被浏览器解析为标签,接下来示例中我们用标签函数实现这个转义过程:
2):i18n国际化
在我们的项目中支持国际化(i18n)的逻辑本身非常简单,只需要界面中的所有字符串变量化,而后这些变量自动根据项目的当前语音渲染出该语言下的字符串。使用函数式编程的思想来实现的基本思路如下:
输入:需要翻译的字符串键映射关系:根据输入获得输出,具体映射逻辑与当前语言与语言包有关输出:翻译后的字符串
3):定义语言,如jsx
jsx标签函数,实现了将一个含有html、css、js的模板字符串解析为一个React对象的功能。它的模板解析功能很强大,以至于我们把它称之为一门语言。思想和原理大概如此,由于博主暂未看过jsx源码,下文对此不再赘述。
八:内置对象Refelect
Refelect是JavaScript的一个新内置对象(非函数类型对象),与Math对象上挂载了很多用于数学处理方面的方法一样,Refelect对象身上挂在了一套用于操作对象的方法。
下表总结列举了Refelect对象上的13个操作对象的静态方法的作用,以及在Reflect出现之前的实现方案:
由上面刚刚总结出的表格内容可以得知,Reflect在对象层面以及属性层面的Api都有相应的实现,并且比单独的Object原型更加全面。那么我们在日常开发中如何选择呢,出于代码的运行性能、可读性以及统一操作思想考虑,个人是这么选择的,,日常简洁的属性读写、函数对象调用操作不用Reflect,其它都统一使用Reflect对象操作(也就是不用操作符delete、in以及重叠的Object原型上的方法)。
九:内置对象Proxy
Proxy是JavaScript的一个新内置对象(函数类型对象),它的实例对象用于定义对象基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
在上述Reflect的介绍中,我们发现在日常开发中,我们可以也经常对对象进行对象层面和属性层面的很多操作,既然是操作,那么我们就希望能够具备对这些操作进行AOP处理的能力,也即实现代理操作,那么应该怎么做呢?ES5提供了存取器属性get、set,这让我们具备了代理一个对象的属性读写操作以进行AOP处理的能力。但是这时候对于其它对对象操作行为的代理方案仍然没有官方的实现方案。直到ES6的Proxy出现,这才让我们具备了对这些各种类型的对象操作进行代理以进行AOP处理的能力(上述Reflect的13个静态方法对应的对象操作全部都可以AOP处理)。
既然Object.defineProperty和Reflect都可以代理对象操作,那么我们对比一下两者的代理原理和优缺点以备往后甄选方案:
代理原理:Object.defineProperty的原理是通过将数据属性转变为存取器属性的方式实现的属性读写代理。而Proxy方式的原理则是这个内置Proxy对象内部有一套监听机制,在传入handler对象作为参数构造代理对象后,一旦代理对象的操作触发后,就会进入handler中对应注册的处理函数而后可以有选择的使用Reflect将操作转发被代理对象上。代理局限性:Object.defineProperty始终还是局限于属性层面的读写代理,对于对象层面以及属性的其它操作代理它都无法实现。鉴于此,由于数组对象push、pop等方法的存在,它对于数组元素的读写代理并不方便。而使用Proxy则可以很方便的监视数组操作。自我代理:Object.defineProperty方式可以代理到自身(代理之后使用对象本身即可),也可以代理到别的对象身上(代理之后需要使用代理对象)。Proxy方式只能代理到Proxy实例对象上。这一点在其它说法中是Proxy对象不需要侵入对象就可以实现代理,实际上Object.defineProperty方式也可以不侵入。接下来叙述在日常开发中我们可能会见到/用到Proxy代理的场景:
1):实现属性读写AOP
2):实现数组操作的监视
本文结束,谢谢观看。如若认可,一键三连。
原作者姓名:iamjwe原出处:CSDN