通过实践编写优雅的JavaScript代码


Posted in Javascript onMay 30, 2019

有没有似曾相识

如果你对于代码,除了关注是否能准确的执行业务逻辑,还关心代码本身是怎么写的,是否易读,那么你应该会关注如何写出干净优雅的代码。作为专业的工程师,除了保证自己的代码没有bug,能正确的完成业务逻辑,还应该保证几个月后的自己,或者其他工程师,也能够维护自己的代码。你写的每一段代码,通常情况下,都不会是 一次性 工作,通常伴随着后续的不断迭代。如果代码不够优雅,那么将来维护这段代码的人(甚至你自己),都将感到非常痛苦。祈祷吧,将来面对这些糟糕代码的人,不是你自己,而是别人。

OK,我们先来简单定义下,什么是 干净优雅 的代码:干净优雅的代码,应该是自解释的,容易看懂的,并且很容易修改或者扩展一些功能 。

现在,静下来回忆一下,有多少次,当你接手前辈留下来的糟糕代码而懵逼时,心里默默的说过 "我*"的:

"我*,那是啥玩意儿"
"我*,这段代码是干啥的”
"我*,这个变量又是干啥的"

嗯,下面这个图片完美的展示了这种情形:

通过实践编写优雅的JavaScript代码

引用 Robert C. Martin 的名言来说明这种情况:

丑陋的代码也能实现功能。但是不够优雅的代码,往往会让整个开发团队都跪在地上哭泣。

在这篇文章里,我主要讲下载 JavaScript里怎么书写干净优雅的代码,但是对于其他编程语言,道理也是类似的。

JavaScript优雅代码的最佳实践

1. 强类型校验

使用 === 而不是 == 。

// If not handled properly, it can dramatically affect the program logic. It's like, you expect to go left, but for some reason, you go right.
0 == false // true
0 === false // false
2 == "2" // true
2 === "2" // false
// example
const value = "500";
if (value === 500) {
console.log(value);
// it will not be reached
}
if (value === "500") {
console.log(value);
// it will be reached
}

2. 变量命名

变量、字段命名,应该包含它所对应的真实含义。这样更容易在代码里搜索,并且其他人看到这些变量,也更容易理解。

错误的示范

let daysSLV = 10;
let y = new Date().getFullYear();
let ok;
if (user.age > 30) {
ok = true;
}

正确的示范

const MAX_AGE = 30;
let daysSinceLastVisit = 10;
let currentYear = new Date().getFullYear();
...
const isUserOlderThanAllowed = user.age > MAX_AGE;

不要在变量名中加入不必要的单词。

错误的示范

let nameValue;
let theProduct;

正确的示范

let name;
let product;

不要强迫开发者去记住变量名的上下文。

错误的示范

const users = ["John", "Marco", "Peter"];
users.forEach(u => {
doSomething();
doSomethingElse();
// ...
// ...
// ...
// ...
// Here we have the WTF situation: WTF is `u` for?
register(u);
});

正确的示范

const users = ["John", "Marco", "Peter"];
users.forEach(user => {
doSomething();
doSomethingElse();
// ...
// ...
// ...
// ...
register(user);
});

不要在变量名中添加多余的上下文信息。

错误的示范

const user = {
userName: "John",
userSurname: "Doe",
userAge: "28"
};

...

user.userName;

正确的示范

const user = {
name: "John",
surname: "Doe",
age: "28"
};

...

user.name;

3. 函数相关

尽量使用足够长的能够描述函数功能的命名。通常函数都会执行一个明确的动作或意图,那么函数名就应该是能够描述这个意图一个动词或者表达语句,包含函数的参数命名也应该能清晰的表达具体参数的含义。

错误的示范

function notif(user) {
// implementation
}

正确的示范

function notifyUser(emailAddress) {
// implementation
}

避免函数有太多的形参。比较理想的情况下,一个函数的参数应该 <=2个 。函数的参数越少,越容易测试。

错误的示范

function getUsers(fields, fromDate, toDate) {
// implementation
}

正确的示范

function getUsers({ fields, fromDate, toDate }) {
// implementation
}
getUsers({
fields: ['name', 'surname', 'email'],
fromDate: '2019-01-01',
toDate: '2019-01-18'
});

如果函数的某个参数有默认值,那么应该使用新的参数默认值语法,而不是在函数里使用 || 来判断。

错误的示范

function createShape(type) {
const shapeType = type || "cube";
// ...
}

正确的示范

function createShape(type = "cube") {
// ...
}

一个函数应该做一件事情。避免在一个函数里,实现多个动作。

错误的示范

function notifyUsers(users) {
users.forEach(user => {
const userRecord = database.lookup(user);
if (userRecord.isVerified()) {
notify(user);
}
});
}

正确的示范

function notifyVerifiedUsers(users) {
users.filter(isUserVerified).forEach(notify);
}
function isUserVerified(user) {
const userRecord = database.lookup(user);
return userRecord.isVerified();
}

使用 Object.assign 来给对象设置默认值。

错误的示范

const shapeConfig = {
type: "cube",
width: 200,
height: null
};
function createShape(config) {
config.type = config.type || "cube";
config.width = config.width || 250;
config.height = config.width || 250;
}
createShape(shapeConfig);

正确的示范

const shapeConfig = {
type: "cube",
width: 200
// Exclude the 'height' key
};
function createShape(config) {
config = Object.assign(
{
type: "cube",
width: 250,
height: 250
},
config
);
...
}
createShape(shapeConfig);

不要在函数参数中,包括某些标记参数,通常这意味着你的函数实现了过多的逻辑。

错误的示范

function createFile(name, isPublic) {
if (isPublic) {
fs.create(`./public/${name}`);
} else {
fs.create(name);
}
}

正确的示范

function createFile(name) {
fs.create(name);
}
function createPublicFile(name) {
createFile(`./public/${name}`);
}

不要污染全局变量、函数、原生对象的 prototype。如果你需要扩展一个原生提供的对象,那么应该使用 ES新的 类和继承语法来创造新的对象,而不是去修改原生对象的prototype 。

错误的示范

Array.prototype.myFunc = function myFunc() {
// implementation
};

正确的示范

class SuperArray extends Array {
myFunc() {
// implementation
}
}

4. 条件分支

不要用函数来实现 否定 的判断。比如判断用户是否合法,应该提供函数 isUserValid() ,而不是实现函数isUserNotValid() 。

错误的示范

function isUserNotBlocked(user) {
// implementation
}
if (!isUserNotBlocked(user)) {
// implementation
}

正确的示范

function isUserBlocked(user) {
// implementation
}
if (isUserBlocked(user)) {
// implementation
}

在你明确知道一个变量类型是 boolean 的情况下,条件判断使用 简写。这确实是显而易见的,前提是你能明确这个变量是boolean类型,而不是 null 或者 undefined 。

错误的示范

if (isValid === true) {
// do something...
}
if (isValid === false) {
// do something...
}

正确的示范

if (isValid) {
// do something...
}
if (!isValid) {
// do something...
}

在可能的情况下,尽量 避免 使用条件分支。优先使用 多态 和 继承 来实现代替条件分支。

错误的示范

class Car {
// ...
getMaximumSpeed() {
switch (this.type) {
case "Ford":
return this.someFactor() + this.anotherFactor();
case "Mazda":
return this.someFactor();
case "McLaren":
return this.someFactor() - this.anotherFactor();
}
}
}

正确的示范

class Car {
// ...
}
class Ford extends Car {
// ...
getMaximumSpeed() {
return this.someFactor() + this.anotherFactor();
}
}
class Mazda extends Car {
// ...
getMaximumSpeed() {
return this.someFactor();
}
}
class McLaren extends Car {
// ...
getMaximumSpeed() {
return this.someFactor() - this.anotherFactor();
}
}

5. ES的类

在ES里,类是新规范引入的语法糖。类的实现和以前 ES5 里使用 prototype 的实现完全一样,只是它看上去更简洁,你应该优先使用新的类的语法。

错误的示范

const Person = function(name) {
if (!(this instanceof Person)) {
throw new Error("Instantiate Person with `new` keyword");
}
this.name = name;
};
Person.prototype.sayHello = function sayHello() { /**/ };
const Student = function(name, school) {
if (!(this instanceof Student)) {
throw new Error("Instantiate Student with `new` keyword");
}
Person.call(this, name);
this.school = school;
};
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.printSchoolName = function printSchoolName() { /**/ };

正确的示范

class Person {
constructor(name) {
this.name = name;
}
sayHello() {
/* ... */
}
}
class Student extends Person {
constructor(name, school) {
super(name);
this.school = school;
}
printSchoolName() {
/* ... */
}
}

使用方法的 链式调用。很多开源的JS库,都引入了函数的链式调用,比如 jQuery 和 Lodash 。链式调用会让代码更加简洁。在 class 的实现里,只需要简单的在每个方法最后都返回 this,就能实现链式调用了。

错误的示范

class Person {
constructor(name) {
this.name = name;
}
setSurname(surname) {
this.surname = surname;
}
setAge(age) {
this.age = age;
}
save() {
console.log(this.name, this.surname, this.age);
}
}
const person = new Person("John");
person.setSurname("Doe");
person.setAge(29);
person.save();

正确的示范

class Person {
constructor(name) {
this.name = name;
}
setSurname(surname) {
this.surname = surname;
// Return this for chaining
return this;
}
setAge(age) {
this.age = age;
// Return this for chaining
return this;
}
save() {
console.log(this.name, this.surname, this.age);
// Return this for chaining
return this;
}
}
const person = new Person("John")
.setSurname("Doe")
.setAge(29)
.save();

6. 避免冗余代码

通常来讲,我们应该避免重复写相同的代码,不应该有未被用到的函数或者死代码(永远也不会执行到的代码)的存在。
我们太容易就会写出重复冗余的代码。举个栗子,有两个组件,他们大部分的逻辑都一样,但是可能由于一小部分差异,或者临近交付时间,导致你选择了把代码拷贝了一份来修改。在这种场景下,要去掉冗余的代码,只能进一步提高组建的抽象程度。

至于死代码,正如它名字所代表的含义。这些代码的存在,可能是在你开发中的某个阶段,你发现某段代码完全用不上了,于是就把它们放在那儿,而没有删除掉。你应该在代码里找出这样的代码,并且删掉这些永远不会执行的函数或者代码块。我能给你的惟一建议,就是当你决定某段代码再也不用时,就立即删掉它,否则晚些时候,可能你自己也会忘记这些代码是干神马的。

当你面对这些死代码时,可能会像下面这张图所描绘的一样:

通过实践编写优雅的JavaScript代码

结论

上面这些建议,只是一部分能提升你代码的实践。我在这里列出这些点,是工程师经常会违背的。他们或许尝试遵守这些实践,但是由于各种原因,有的时候也没能做到。或许当我们在项目的初始阶段,确实很好的遵守了这些实践,保持了干净优雅的代码,但是随着项目上线时间的临近,很多准则都被忽略了,尽管我们会在忽略的地方备注上TODO 或者REFACTOR (但正如你所知道的,通常 later也就意味着never)。

OK,就这样吧,希望我们都能够努力践行这些最佳实践,写出 干净优雅 的代码 ☺

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
picChange 图片切换特效的函数代码
May 06 Javascript
基于jquery ajax 用户无刷新登录方法详解
Apr 28 Javascript
jquery.cookie() 方法的使用(读取、写入、删除)
Dec 05 Javascript
打造个性化的功能强大的Jquery虚拟键盘(VirtualKeyboard)
Oct 11 Javascript
jquery实现的横向二级导航效果代码
Aug 26 Javascript
BootStrap.css 在手机端滑动时右侧出现空白的原因及解决办法
Jun 07 Javascript
使用Angular.js实现简单的购物车功能
Nov 21 Javascript
javascript数据结构之串的概念与用法分析
Apr 12 Javascript
vue中如何实现变量和字符串拼接
Jun 19 Javascript
使用 vue.js 构建大型单页应用
Feb 10 Javascript
微信小程序实现下拉菜单切换效果
Mar 30 Javascript
vue webpack打包后图片路径错误的完美解决方法
Dec 07 Javascript
AngularJs中$cookies简单用法分析
May 30 #Javascript
JS使用new操作符创建对象的方法分析
May 30 #Javascript
vue滚动固定顶部及修改样式的实例代码
May 30 #Javascript
简述pm2常用命令集合及配置文件说明
May 30 #Javascript
Vue实现固定定位图标滑动隐藏效果
May 30 #Javascript
浅谈Vue的响应式原理
May 30 #Javascript
vue实现固定位置显示功能
May 30 #Javascript
You might like
PHP gbk环境下json_dencode传送来的汉字
2012/11/13 PHP
php中将数组转成字符串并保存到数据库中的函数代码
2013/09/29 PHP
php的memcache类分享(memcache队列)
2014/03/26 PHP
跟我学Laravel之路由
2014/10/15 PHP
php使用类继承解决代码重复的问题
2015/02/11 PHP
PHP获取HTTP body内容的方法
2018/12/31 PHP
jQuery 浮动广告实现代码
2008/12/25 Javascript
扩展javascript的Date方法实现代码(prototype)
2010/11/20 Javascript
js操作textarea方法集合封装(兼容IE,firefox)
2011/02/22 Javascript
一个简单的Ext.XTemplate的实例代码
2012/03/18 Javascript
jQuery使用元素属性attr赋值详解
2015/02/27 Javascript
教你JS中的运算符乘方、开方及变量格式转换
2016/08/09 Javascript
利用Node.JS实现邮件发送功能
2016/10/21 Javascript
a标签置灰不可点击的实现方法
2017/02/06 Javascript
javascript代码优化的8点总结
2018/01/29 Javascript
微信小程序switch组件使用详解
2018/01/31 Javascript
详解react关于事件绑定this的四种方式
2018/03/09 Javascript
nodejs 使用 js 模块的方法实例详解
2018/12/04 NodeJs
vue 解除鼠标的监听事件的方法
2019/11/13 Javascript
js实现时钟定时器
2020/03/26 Javascript
在Python的Flask框架下使用sqlalchemy库的简单教程
2015/04/09 Python
Python的Django框架下管理站点的基本方法
2015/07/17 Python
Python生成数字图片代码分享
2017/10/31 Python
在Pycharm中执行scrapy命令的方法
2019/01/16 Python
Django异步任务线程池实现原理
2019/12/17 Python
python随机模块random使用方法详解
2020/02/14 Python
vscode+PyQt5安装详解步骤
2020/08/12 Python
Python threading模块condition原理及运行流程详解
2020/10/05 Python
python中的yield from语法快速学习
2020/11/06 Python
使用CSS3来制作消息提醒框
2015/07/12 HTML / CSS
中国跨镜手机配件批发在线商店:TVC-Mall
2019/08/20 全球购物
空气环保标语
2014/06/12 职场文书
初中毕业典礼演讲稿
2014/09/09 职场文书
春季运动会开幕词
2015/01/28 职场文书
教您怎么制定西餐厅运营方案 ?
2019/07/05 职场文书
人生感悟经典句子
2019/08/20 职场文书