发布于2020-11-19 20:12 阅读(945) 评论(0) 点赞(13) 收藏(1)
学习资料:拉勾课程《大前端高薪训练营》
阅读建议:文章较长,搭配文章的侧边栏目录进行食用,体验会更佳哦!
内容说明:本文不做知识点的搬运工,文中只记录个人对该技术点的认识和理解以及该技术在日常开发中的使用场景
封装是面向对象的重要原则,它在代码中的体现主要是以下两点:
以下是基本封装示例:
class Animal{
constructor(name) {
this.name = name;// 实例属性
}
cry() {// 实例方法
console.log('cry');
}
static getNum(){// 静态方法
return AnimalES6.num
}
}
Animal.num = 42;// 静态属性
继承是面向对象最显著的一个特性,它在代码中的体现主要是以下两点:
以下是定义一个Cat类并对上述Animal类的继承示例:
class Cat extends Animal{
constructor(name, type) {
super(name);// 必须先构造父类空间
this.type = type;
}
cry() {
console.log('miao miao');// 方法重写
}
}
多态指允许不同的对象对同一消息做出不同响应,在Java中,实现多态有以下三个条件:
由于JavaScript是弱类型语言,所以JavaScript实现多态,不存在父类引用指向子类对象的问题。
以下再定义一个Dog类,实现Animal实例对象、Cat实例对象和Dog实例对象对同样的cry调用做出不同的响应示例:
class Dog extends Animal{
constructor(name, type) {
super(name);
this.type = type;
}
cry() {
console.log('wang wang');
}
}
const ani = new Animal('不知名动物');
const cat = new Cat('小白', '美短');
const dog= new Dog('大黑', '二哈');
ani.cry();// 输出 cry
cat.cry();// 输出 miao miao
dog.cry();// 输出 wang wang
Symbol是一种新的原始数据类型,用来表示独一无二的值。此外,它也是对象属性名的第二种数据类型(另一种是字符串)。
接下来列举几个在日常开发中可能会用到Symbol数据类型的场景:
魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。 —阮一峰
如下含有魔法字符串的代码示例:
const obj = {type: 'type2'};
function fn1() {
if (obj.type === 'type1') {
// xxx
} else if (obj.type ==='type2') {
// xxx
}
}
function fn2() {
if (obj.type === 'type1') {
// xxx
} else if (obj.type ==='type2') {
// xxx
}
}
// ...其它对obj.type的判断
在上述代码中,大量出现的type1与type2字符串就是魔法字符串。我们分析这样大量使用魔法字符串可能会出现的问题:
接下来使用Symbol对上述代码改造:
const obj = {type: 'type2'};
const objType = {
type1: Symbol(),
type2: Symbol(),
}
function fn1() {
if (obj.type === objType.type1) {
// xxx
} else if (obj.type === objType.type2) {
// xxx
}
}
function fn2() {
if (obj.type === objType.type1) {
// xxx
} else if (obj.type === objType.type2) {
// xxx
}
}
假设我们对一个对象需要做如下的访问控制:
以下是没有实现访问控制的代码:
// index.js
export const Obj = {
attr1: 'public Attr1',// 公有
attr2: 'public Attr2',// 公有
attr3: 'protect Attr3',// 保护
attr4: 'protect Attr4',// 保护
attr5: 'private Attr5',// 私有
attr6: 'private Attr6',// 私有
}
接下来使用Symbol对上述代码改造:
// protectKey.js
export const attr3 = Symbol('attr3');
export const attr4 = Symbol('attr4');
// index.js
import { attr3, attr4 } from './protect.js';
const attr5 = Symbol('attr5');
const attr6 = Symbol('attr6');
export const Obj = {
attr1: 'public Attr1',// 公有
attr2: 'public Attr2',// 公有
[attr3]: 'protect Attr3',// 保护
[attr4]: 'protect Attr4',// 保护
[attr5]: 'private Attr5',// 私有
[attr6]: 'private Attr6',// 私有
}
如上代码就实现了对我们所需要的访问控制,外部对不能访问的成员是无法感知的,因为外部对这些不能访问的成员不但不支持读写,甚至也不能遍历和序列号操作。
在我们以往的日常开发中,我们基本上对对象的访问控制都是设置为公有的,很少设置为私有,设置为保护的就更是没见过。但少归少,至少说明了ES6引入的Symbol能帮助我们实现类似Java中保护和私有成员的访问控制。
如下示例,封装一个集合Collection,它对模块外部具有私有属性size与私有方法logAdd:
const size = Symbol('size');
const logAdd = Symbol('logAdd');
export class Collection {
constructor() {
this[size] = 0;// 私有属性
}
add(item) {
this[this[size]] = item;
this[size]++;
this[logAdd]();
}
[logAdd]() {// 私有方法
console.log( 'now size' + this[size])
}
}
Set对于JavaScript而言是一种新的数据结构,相对于数组用于存储有序、可重复的元素集合,Set用于存储有序、不可重复的元素集合。
接下来列举几个在日常开发中可能会用到Set数据结构的场景:
// 数组去重
let arr = [1,1,2,3];
arr = Array.from(new Set());// 经过性能比较测试,表现优秀
// arr = [1,2,3]
// 字符串去重
let str = 'aaabbsf';
let newStr = '';
new Set(str).forEach(item) => {newStr += item});
// newStr absf
下面截取阮一峰ES6对Set的说明案例:
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
Map对于JavaScript而言也是一种新的数据结构,用于存储键值对形式的字典 / 双列集合。在Map对象出现之前,我们通常使用Object对象来做键值对的存储,下面对比一下Map对象实现键值对存储与普通对象存储键值对的区别:
经过上面的对比分析可以得出结论,不到必须使用引用类型作为键的情况下,我们都用Object对象字面量的方式来定义并存储键值对会更好一些。
接下来叙述在日常开发中可能会用到Map数据结构的场景:
经验尚浅,日常开发示例暂时没想到,有机会补上。但是Map结构的出现告诉了我们这些JavaScript开发者,此后在JavaScript中我们也可以很简单的实现对象之间的映射关系。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。而for…of循环是ES6 创造出的一种新的遍历命令,它可以配合迭代器使用,只要实现了Iterator接口的任意对象就可以使用for…of循环遍历。
在JavaScript常见的数据结构如Array、Set、Map、伪数组arguments等等一系列对象的原型上都有Symbol.iterator标识,并且有默认的Iterator实现。普通对象是没有这个接口标识以及iterator的实现的,但是我们可以手动为普通对象添加这个标识以及对应的iterator实现,示例代码如下:
// test1.js:封装者封装
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['喝茶'],
// 添加Symbol.iterator标识接口以及iterator实现
[Symbol.iterator]: function () {
const all = [...this.life, ...this.learn, ...this.work]
let index = 0
return {
next: function () {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
// test2.js:调用者遍历
for (const item of todos) {
console.log(item)
}
上述代码的优点是封装者在对外界遍历没有影响的情况下,对数据进行了更细粒度的管理。是一种解耦合的代码优化操作!
这三者都与异步编程有关,之后会单独拎出来写在另一篇博客当中,在此文中就不做赘述了。
模板字符串就不做介绍了,标签函数在定义时和普通函数没什么区别。区别在调用上,标签函数以模板字符串作为参数输入,并且有独特的规则完成形实参匹配。接下来看一个简单的例子:
// 标签函数定义
const fn = (literals, ...values) => {
console.log('字面量数组', literals);
console.log('变量数组', values);
console.log('字面量数组是否比变量数组多一个元素', literals.length -1 === values.length);// true
let output = "";
let index; // 不能放在for里,因为index在块级作用域之外还有访问
for (index = 0; index < values.length; index++) {
output += literals[index] + values[index];
}
output += literals[index]
return output;
};
// 标签函数调用
const name = '张三';
const age = 18;
const result = fn`姓名:${ name },年龄:${ age }`;
上述示例运行结果:
经过上述例子我们可以大概得知标签函数的形实参匹配规则:
通过上面的例子和解析,我们认识了标签函数调用的执行规则。根据标签函数和模板字符串的配合机制,我们很容易就想到这种机制可以实现模板引擎甚至是定义内部语言的功能。
接下来叙述在日常开发中我们可能会用到标签函数的场景:
在日常开发中,我们很可能会碰到这么一个需求:
先分析一下这样做的风险:由于用户的输入直接作为了p标签的内容,当用户输入一个<script>标签等任意HTML标签时,如果我们直接把它交给p标签,那么浏览器就会把它当成inneHTML进行解析后执行其中的脚本或者渲染HTML,这肯定是不被期望且有风险的。所以我们在把用户的输入交给p标签展示之前,应该对其中的一些特殊字符进行转义,防止被浏览器解析为标签,接下来示例中我们用标签函数实现这个转义过程:
// 0.标签函数
function SaferHTML(templateData) {// 这里使用隐式参数arguments来访问模板字符串中的所有变量
let s = templateData[0];
for (let i = 1; i < arguments.length; i++) {
let arg = String(arguments[i]);
s += arg.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
s += templateData[i];
}
return s;
}
// 1.用户输入
let sender = '<script>alert("abc")</script>';
// 2.转义后的用户输入
const safeSender = SaferHTML`${sender}`;
// 3.把safeSender渲染到标签元素中
// xxx
在我们的项目中支持国际化(i18n)的逻辑本身非常简单,只需要界面中的所有字符串变量化,而后这些变量自动根据项目的当前语音渲染出该语言下的字符串。使用函数式编程的思想来实现的基本思路如下:
// 语言包resource
const enUS = {
'Welcome to': 'Welcome to',
'you are visitor number': 'you are visitor number'
}
const zhCN = {
'Welcome to': '你好',
'you are visitor number': '你的访问号码'
}
// 根据当前语言和语言包得到i18n标签函数
function i18nInit(language, zhCNResource, enUSResource) {
return (literals, ...values) => {
let output = "";
let index;
let resource;
switch (language) { // 根据当前语言获得语言包
case 'zh-CN':
resource = zhCNResource;
break;
case 'en-US':
resource = enUSResource;
break;
}
for (index = 0; index < values.length; index++) {
output += resource[literals[index]] + values[index]; // 把字面量作为键得到语言包中对应的翻译
}
output += resource[literals[index]]
return output;
}
}
// 翻译语言
let currentLanguage = 'zh-CN';
const i18n = i18nInit(currentLanguage, zhCN, enUS );
i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`
jsx标签函数,实现了将一个含有html、css、js的模板字符串解析为一个React 对象的功能。它的模板解析功能很强大,以至于我们把它称之为一门语言。思想和原理大概如此,由于博主暂未看过jsx源码,下文对此不再赘述。
Refelect是JavaScript的一个新内置对象(非函数类型对象),与Math对象上挂载了很多用于数学处理方面的方法一样,Refelect对象身上挂在了一套用于操作对象的方法。
下表总结列举了Refelect对象上的13个操作对象的静态方法的作用,以及在Reflect出现之前的实现方案:
作用 | 不用Reflect实现 | 用Reflect闪现 |
---|---|---|
属性写入 | target.propertyKey = value | Reflect.set(target, propertyKey, value[, receiver]) |
属性读取 | target.propertyKey | Reflect.get(target, propertyKey[, receiver]) |
属性删除 | delete target.propertyKey | Reflect.deleteProperty(target, propertyKey) |
属性包含 | propertyKey in target | Reflect.has(target, propertyKey) |
属性遍历 | Object.keys(target) | Reflect.ownKeys(target) |
属性描述定义属性 | Object.defineProperty(target, propertyKey, attributes) | Reflect.defineProperty(target, propertyKey, attributes) |
属性描述读取 | Object.getOwnPropertyDescriptor(target, propertyKey) | Reflect.getOwnPropertyDescriptor(target, propertyKey) |
原型读取 | target.prototype / Object.getPrototypeOf(target) | Reflect.getPrototypeOf(target) |
原型写入 | target.prototype = prototype / Object.setPrototypeOf(target, prototype) | Reflect.setPrototypeOf(target, prototype) |
获取对象可扩展标记 | Object.isExtensible(target) | Reflect.isExtensible(target) |
设置对象不可扩展 | Object.preventExtensions(target) | Reflect.preventExtensions(target) |
函数对象调用 | target(…argumentsList) / target.apply(this, argumentsList) | Reflect.apply(target, thisArgument, argumentsList) |
构造函数对象调用 | new target(…args) | Reflect.construct(target, argumentsList[, newTarget]) |
由上面刚刚总结出的表格内容可以得知,Reflect在对象层面以及属性层面的Api都有相应的实现,并且比单独的Object原型更加全面。那么我们在日常开发中如何选择呢,出于代码的运行性能、可读性以及统一操作思想考虑,个人是这么选择的,,日常简洁的属性读写、函数对象调用操作不用Reflect,其它都统一使用Reflect对象操作(也就是不用操作符delete、in以及重叠的Object原型上的方法)。
Proxy是JavaScript的一个新内置对象(函数类型对象),它的实例对象用于定义对象基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
在上述Reflect的介绍中,我们发现在日常开发中,我们可以也经常对对象进行对象层面和属性层面的很多操作,既然是操作,那么我们就希望能够具备对这些操作进行AOP处理的能力,也即实现代理操作,那么应该怎么做呢?ES5提供了存取器属性get、set,这让我们具备了代理一个对象的属性读写操作以进行AOP处理的能力。但是这时候对于其它对对象操作行为的代理方案仍然没有官方的实现方案。直到ES6的Proxy出现,这才让我们具备了对这些各种类型的对象操作进行代理以进行AOP处理的能力(上述Reflect的13个静态方法对应的对象操作全部都可以AOP处理)。
既然Object.defineProperty和Reflect都可以代理对象操作,那么我们对比一下两者的代理原理和优缺点以备往后甄选方案:
接下来叙述在日常开发中我们可能会见到 / 用到Proxy代理的场景:
const person = {
name: 'zce',
age: 20
}
const personProxy = new Proxy(person, {
get (target, property) {
return property in target ? target[property] : 'default'
},
set (target, property, value) {
if (property === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError(`${value} is not an int`)
}
}
target[property] = value
}
})
personProxy.age = 100
personProxy.gender = true
console.log(personProxy.name)
console.log(personProxy.xxx)
const list = []
const listProxy = new Proxy(list, {
set (target, property, value) {
console.log('set', property, value)
target[property] = value
return true // 表示设置成功
}
})
listProxy.push(100)
listProxy.push(100)
本文结束,谢谢观看。
如若认可,一键三连。
原文链接:https://blog.csdn.net/jw2268136570/article/details/109563207
作者:长这么胖
链接:http://www.javaheidong.com/blog/article/772/3ee1f2306de4eeb1b6a6/
来源:java黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!