对于【JS】183-『表单开发』一次即通关的5个技巧感兴趣的读者,本文将会是一篇不错的选择,并为您提供关于【JS】1-原型继承的原理解析、【JS】100-浅拷贝与深拷贝、【JS】1004-几张动图教你
对于【JS】183-『表单开发』一次即通关的 5 个技巧感兴趣的读者,本文将会是一篇不错的选择,并为您提供关于【JS】1-原型继承的原理解析、【JS】100-浅拷贝与深拷贝、【JS】1004- 几张动图教你学会 EventLoop、【JS】1027- 几个优雅的 JavaScript 运算符使用技巧的有用信息。
本文目录一览:- 【JS】183-『表单开发』一次即通关的 5 个技巧
- 【JS】1-原型继承的原理解析
- 【JS】100-浅拷贝与深拷贝
- 【JS】1004- 几张动图教你学会 EventLoop
- 【JS】1027- 几个优雅的 JavaScript 运算符使用技巧
【JS】183-『表单开发』一次即通关的 5 个技巧
本文由 IMWeb 社区 imweb.io 授权转载自腾讯内部 KM 论坛,原作者:easonruan。点击阅读原文查看 IMWeb 社区更多精彩文章。
笔者目前正在开发一个涉及较多表单的场景的新项目。但由于是新项目进度赶,产品人员紧缺,表单需求往往没有考虑得很周全。
那作为一名前端开发,如何辅助产品尽可能让表单需求一次即通关,减少反复沟通以及提缺陷修缺陷的时间,从而加快项目进度?
以下是笔者在项目中在表单开发方面的一些总结:
以下演示案例为 vue 项目,组件库为 element-ui
1. 重视通用型表单验证
业务场景:
表单中如果涉及手机号码,因为手机号码是特殊场景,我们很容易想到特殊的校验规则 —— 手机号的正则校验。
然而对于一个通用型字段,如标题 title 、描述 desc 等基本的字段,它们实在太普通太一般,导致我们放松了警惕。
导致问题:开发与测试反复在 tapd
提缺陷修缺陷,在一堆小问题上浪费了大量时间,工作效率低。
解决方法:
避免用户的输入前后有空格,即 trim
限制最大输入长度,即 max-length
不能包含特殊字符,即 emoji 表情是否能输入等
// form rules
export default {
title: [
/**
* Tips 避免用户的输入前后有空格
* 可以使用 v-model.trim 指令自动清除用户前后空格,
* 技术手段能解决的,我们避免提示用户
**/
{ max: 50, message: ''不能超过50个字'', trigger: ''blur'' },
{
pattern: /^[ -~ 一-龥 -。 - -〗]+$/,
// - 基本拉丁字母 https://unicode-table.com/cn/blocks/basic-latin/
// 一-龥 中文 https://unicode-table.com/cn/blocks/cjk-unified-ideographs/
// -。 半角及全角形式字符 https://unicode-table.com/cn/blocks/halfwidth-and-fullwidth-forms/
// - 英文标点 https://unicode-table.com/cn/blocks/general-punctuation/
// -〗 中文标点 https://unicode-table.com/cn/blocks/cjk-symbols-and-punctuation/
message: "不能包含特殊字符",
trigger: "blur"
}
]
}
2. 避免重复提交
业务场景:当用户快速点击提交按钮,导致问题:页面会重复发请求给后端。
解决方法虽然很简单,但这却是开发最容易忽略的,也是 tapd 上最经常见的缺陷问题。
解决方法一:在业务代码执行完之前不能再次触发
export default {
methods: {
onSubmit () {
// 可以与Loading搭配使用
if (this.isCommitting) return;
this.isCommitting = true;
// 表单验证以及业务请求代码
this.isCommitting = false;
},
}
}
解决方法二:经过评论的提醒,遗漏了利用 debounce
防抖实现防二次点击操作。
export default {
mounted() {
// `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
this.debouncedSaveForm = _.debounce(
this.onSubmit, // 回调函数
500, // 时间窗口的间隔
{
leading: true, // 点击立即执行
trailing: false // 延迟时间过后不执行
}
);
},
methods: {
onSubmit () {
if (this.isCommitting) return;
this.isCommitting = true;
// 表单验证以及业务请求代码
this.isCommitting = false;
},
}
}
3. 表单提交或出错时的 Loading 提示
业务场景:表单提交后没展示 Loading 导致问题:当请求 request 较久时,页面像是卡死了,没任何响应,用户体验很差。
业务场景:遇到错误时没隐藏 Loading 导致问题:当请求 request 出错时,Loading 没关闭,页面流程进行不下去。
解决方案:
export default {
methods: {
onSubmit () {
// 容易忽略一:发送请求前,没展示Loading
this.$loading.show(''努力加载中...'');
request(''apiUrl'', data)
.then(() => {})
.catch(err => {
// 容易忽略二:请求出错时,没隐藏Loading
this.$loading.hide();
})
},
}
}
4. 表单重新打开时,要重置表单数据
业务场景:如果表单是属于弹窗 Dialog 内,部分开发为了代码可复用性,新增和编辑是共用同一个表单代码。
导致问题:用户在编辑某一条数据后,再点击新增,会发现新增表单里面的内容是上一条编辑内容的数据。
解决方案:
export default {
mounted() {
// 页面初始化时,先备份表单数据
this._bak_form = _.cloneDeep(this.form);
},
methods: {
onOpenDialog(actionType, tableRowIndex, data) {
if (actionType === "add") {
// 新增时,需要恢复为默认数据
this.form = _.cloneDeep(this._bak_form);
/**
* Tips
* 这里不能用解构 this.form = {...this._bak_form},
* 不然会导致,改了form里面的(object或array类型)数据,同时会影响到_bak_form的数据
* 这是因为引用数据类型的指针还是指向同一个地址。
**/
} else if (actionType === "edit") {
// 编辑
this.tableRowIndex = tableRowIndex;
this.form = _.cloneDeep(data);
}
this.actionType = actionType;
this.visible = true;
},
}
}
Tips 避免在关闭窗口时恢复为默认数据
造成问题:恢复为默认数据会触发表单校验规则,因此会有显眼的警告 “XX 不能为空”。
触发原因:与此同时,窗口的 visible 变为 false,假若窗口的隐藏式有过渡效果的话,窗口隐藏需要 500ms,而重置表单是立即生效的,用户是会看到一闪而过的红色警告。
解决方法: 一是避免在关闭窗口时恢复为默认数据 二是使用
resetFields
将所有字段值重置为初始值并移除校验结果(但不能解决点编辑后再点新增时,恢复为默认数据)
5. 不小心点击关闭页面时,要提示让用户确认
业务场景:当用户在填写一个长表单时,手误点了关闭页面或者点击去到其他页面。
导致问题:用户花时间填写的表单数据会丢失,用户又要重新填一遍。用户体验大大降低。
解决方法:
export default {
watch: {
"visible": value => {
if (!value) {
// 当弹窗关闭不涉及表单时,清除事件
window.onbeforeunload = null;
return;
}
// 当弹窗显示有表单数据时,网页跳转或者关闭时提醒用户
window.onbeforeunload = e => (e.returnValue = "确定离开当前页面?");
}
},
}
总结
最后,我汇总一下上面 5 个技巧点的真实场景 Demo:
https://codepen.io/ryqsky
以上都不是什么新内容,但如果工作中能重视并注意到这些细节问题,就能一次即通关表单开发需求,减少与产品测试反复沟通的时间,加快项目的进度。
不然等到产品或测试临下班前发现这些问题时,会出现这样的场景:开发被拖着对着屏幕敲代码修缺陷,测试重新打开缺陷,反复循环 N 次。
N = 上面提到的 5 个技巧点
中华人民共和国消防法第二条是 “消防工作贯彻预防为主,防消结合的方针”
,放在需求开发上也是一样道理:** 提前发现提前修复才能避免当代码发生 “火灾” 时的一团乱。
关注我们
IMWeb 团队隶属腾讯公司,是国内最专业的前端团队之一。
我们专注前端领域多年,负责过 QQ 资料、QQ 注册、QQ 群等亿级业务。目前聚焦于在线教育领域,精心打磨 腾讯课堂 及 企鹅辅导 两大产品。
社区官网:
http://imweb.io/
加入我们:
https://hr.tencent.com/position_detail.php?id=45616
每一个 “在看”,都是对我最大的肯定!
本文分享自微信公众号 - 前端自习课(FE-study)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与 “OSC 源创计划”,欢迎正在阅读的你也加入,一起分享。
【JS】1-原型继承的原理解析
在JavaScript当中,对象A如果要继承对象B的属性和方法,那么只要将对象B放到对象A的原型链上即可。而某个对象的原型链,就是由该对象开始,通过__proto__
属性连接起来的一串对象。__proto__
属性是JavaScript对象中的内部属性,任何JavaScript对象,包括我们自己构建的对象,JavaScript的built-in
对象,任何函数(在JavaScript当中,函数也是对象)都具有这个属性。如下图就是一个原型链的例子:
上图中,A,B,C分别代表3个对象,蓝色箭头串接起来的所有对象就构成了对象C的原型链,其中C的_proto__
属性指向B,B的__proto__
属性指向A,A的__proto__
属性可能指向更高层的对象,也可能指向null
(表示A不继承任何对象的属性和方法)。
如果我们引用了C的某个属性或者方法,那么JavaScript就会顺着C的原型链进行查找,即首先查找对象C本身,看所引用的属性名或者方法名是否存在,如果存在就停止查找直接返回,如果不存在,就通过C的__proto__
属性找到原型链中的B对象,继续在B对象中查找,如果B对象中找到所引用的属性名或者方法名,那么就停止查找直接返回,如果B对象中也不存在,就通过对象B的__proto__
属性找到原型链中的A对象,继续重复上述查找过程,直到找到所引用的属性或者方法为止(同时也可能查找完对象C的整个原型链也没有找到所引用的属性或者方法,那么该属性或者方法就是undefined
的)。
因此,只要能够成功的为某一个对象构造出我们需要的原型链,那么就能让该对象继承我们想要它继承的方法或者属性。而想要成功构造对象的原型链,就还必须理解prototype
属性,JavaScript当中已经存在的原型链,以及当我们创建对象时,原型链被构造的过程。
prototype属性
prototype属性存在于JavaScript的任何函数当中,这个属性指向的对象就是所谓的原型对象,在构造原型链时需要原型对象。
JavaScript当中已经存在的原型链
在JavaScript当中存在Object
,Function
,Array
,String
,Boolean
,Number
,Date
,Error
,RegExp
这9个built-in函数,一个built-in
的Math对象
,通过这上述9个built-in函数
我们可以创建相应的对象,同时,这9个built-in函数
的prototype
属性所指向的原型对象也是built-in
的。下面的图示解释了这几个函数以及各自prototype
属性所指向的原型对象之间的关系
(如果此图看不清,可打开 http://p3nqtyvgo.bkt.clouddn.com/20180927_02.png 下载 )
上面的图示中,黄色方框代表built-in函数对象
,深绿色方框代表built-in函数
的prototype属性
指向的原型对象,名字都叫xx prototype object
,浅绿色方框(即Math对象)代表普通对象,蓝色箭头连接非built-in函数对象(无论是普通对象如Math,还是原型对象)的__proto__
属性,而土黄色箭头连接函数对象的__proto__
属性。
通过上图可以发现,所有built-in函数对象的原型链最终都指向Function prototype object
,所有非函数对象的原型链最终都指向Object prototype object
,并且Function prototype object
的__proto__
属性也指向Object prototype object
,Object prototype object
的__proto__
属性指向为null
。
因此,Object prototype object
是所有原型链的顶端,通过原型链查找规则可知,所有built-in
函数对象同时继承了Object prototype object
和Function prototype object
上的属性和方法,而所有非built-in函数对象只继承了Object prototype object
上的方法。Function prototype object
包含了所有函数共享的属性和方法,而Object prototype object
包含了所有对象都共享额属性和方法。
对于上图中原型对象包含的constructor属性,下文当中有解释。
创建对象时,原型链的构造过程
在JavaScript当中创建对象有2中方式,一种是通过定义函数使用new方法来构造,另一种是使用对象字面量的方式,即:
var obj = {
name: "Jim Green"
};
使用这两种方式创建对象时,对象的原型链构造过程有所不同。
1. 使用函数的方式构造对象
使用函数的方式构造对象分为两步:首先需要定义一个函数作为构造函数,然后使用new方法构造对象。接下来就来看一下这两个步骤会发生什么。
假设我们定义了一个函数名为F
,此时JavaScript会为我们做两件事,第一:根据我们定义的函数创建一个函数对象,第二,设置这个函数的__proto__
属性和prototype
属性。其中__proto__
属性指向built-in
的Function prototype object
,而prototype
属性指向一个为函数对象F新创建的原型对象,这个新创建的原型对象通过调用new Object()
构造出来,并且为这个新创建的对象添加constructor
属性,该属性指向函数对象F
。最后的结果如下图所示:
上图中为了方便,没有画出Function prototype object
和Object prototype object
的constructor
属性。而F
prototype object
的__proto__
属性为何指向Object prototype object
,下文介绍new操作符时有解释。
当我们使用new方法调用F函数的时候,JavaScript也会为我们做两件事,第一,分配内存作为新创建的对象,第二,将新创建的对象的proto属性指向函数F的原型对象,结果如下图:
上图中,obj
就是调用new方法通过函数F
创建出来的对象,我们可以看到对象obj的原型链包含了函数F
的原型对象,以及Object prototype object
,这样,对象obj
通过原型链查找规则,就能继承函数F
的原型对象,以及Object prototype object
上面定义的属性和方法了。并且如果我们想知道一个对象是由哪个方法构建的,只需要访问这个对象的constructor
属性即可,上例中,只要我们访问obj.constructor
,那么就知道obj是由函数F
创建的。同时,由于F
prototype object
上文中介绍是由new Object
函数创建的,根据此处介绍,F prototype object
的__proto__
属性应该指向Object函数
的原型对象,即Object prototype object
。
2. 使用对象字面量定义对象
当使用对象字面量创建对象时,JavaScript会为我们做两件事:
1 分配内存作为新创建的对象。
2 将新创建对象的__proto__
属性指向Object prototype object
。
结果如下图所示:
上图为了简化,同样没有画出Object prototype object
的constructor
属性
继承
理解了上面所讲的原理之后,假设目前有一个对象A
(这个对象可以是任意的,包括JavaScript built-in的对象,任何函数对象,任何原型对象,以及我们自己new出来的对象),现在想创建一个对象obj
,让obj
继承A
的属性和方法。
通过上面的介绍,我们知道创建对象有两种方式,但是使用对象字面量创建的对象其原型链总是只包含两个对象,一个是其自己,一个是Object prototype object
,根本不可能包含对象A
,无法达到让对象obj
继承对象A
属性和方法的效果。因此,只能使用函数的方式创建对象,让对象A包含在新创建对象obj的原型链中即可。
根据上面的讲解,如果是用函数的方式创建对象,那么在调用new
方法时,新创建对象的__proto__
属性会指向函数的原型对象。因此,只要在调用函数之前,将函数的原型对象换成A
,然后再调用new
方法,就可以将对象A包含在新创建的对象obj
的原型链中,这样通过原型链查找规则,obj
就继承了A的属性和方法。假设用来创建对象obj
的函数为B
,则相关代码为:
B.prototype = A;
B.prototype.constructor = B;
var obj = new B(传入的参数)
上面代码中的B.prototype.constructor = B
,是因为对象A中可能没有constructor
属性,或者constructor
属性不指向B
,而为了方便通过访问任何由B
函数创建的对象的constructor
属性,就可以正确的知道该对象是使用函数B
构造出来的。相关图示如下图:
上图中虚线框所包围的B prototype object
就是定义函数B
时,JavaScript为函数B生成的原型对象,但是该对象被我们用代码替换成了对象A。由于这个被替换的B prototype object
没有其他地方再用到,因此会被回收掉。