持续更新地址 https://wdd.js.org/js-best-pr...
一千个读者有一千个哈姆雷特
,每一个人都有本身的code style。我也曾为了要不要加分号给同事闹个脸红脖子粗,实际上有必要吗? 其实JavaScript已经有了比较流行的几个风格javascript
我本身使用的是JavaScript Standard Style
, 我之因此使用这个,是由于它有一些工具。可让你写完代码后,一旦保存,就自动帮你把你的风格的代码修正成标准分割,而不是死记硬背应该怎么写。看完这个页面,你就应该立马爱上JavaScript Standard Style , 若是你用vscode, 刚好你有写vue, 你想在.vue文件中使用standard风格,那么你须要看看这篇文章php
不少时候,咱们不是从零开始,开发新代码。而是去维护别人的代码,以他人的工做成果为基础。确保本身的代码可维护,是赠人玫瑰,手留余香的好事。一方面让别人看的舒服,另外一方面也防止本身长时间没看过本身的代码,本身都难以理解。
可维护的代码的一些特征css
可理解
易于理解代码的用途可适应
数据的变化,不须要彻底重写代码可扩展
要考虑将来对核心功能的扩展可调试
给出足够的信息,让调试的时候,肯定问题所在不可分割
函数的功能要单一,功能粒度不可分割,可复用性加强// Good if (wl && wl.length) { for (i = 0, l = wl.length; i < l; ++i) { p = wl[i]; type = Y.Lang.type(r[p]); if (s.hasOwnProperty(p)) { if (merge && type == 'object') { Y.mix(r[p], s[p]); } else if (ov || !(p in r)) { r[p] = s[p]; } } } }
There are only two hard problem in Computer Science cache invalidation and naming things.---Phil Karlton
// Good var count = 10; var myName = "wdd"; var found = true; // Bad: Easily confused with functions var getCount = 10; var isFound = true; // Good function getName() { return myName; } // Bad: Easily confused with variable function theName() { return myName; } // Bad: var btnOfSubmit = $('#submit'); // Good: var $btnOfSubmit = $('#submit'); // Bad:给App添加一个处理聊天事件的函数,通常都是和websocket服务端推送消息相关 App.addMethod('createChat',function(res){ App.log(res); }); // Bad: 此处调用,这里很容易误觉得这个函数是处理建立聊天的逻辑函数 App.createChat(); // Good: App.addMethod('onCreateChat',function(res){ App.log(res); }); // Good:此处调用 App.onCreateChat();
变量命名不只仅是一种科学,更是一种艺术。总之,要短小精悍,见名知意。有些名词能够反应出变量的类型。
变量名
名词 | 数据类型含义 |
---|---|
count, length,size | 数值 |
name, title,message | 字符串 |
i, j, k | 用来循环 |
car,person,student,user | 对象 |
success,fail | 布尔值 |
payload | post数据的请求体 |
method | 请求方式 |
函数名
动词 | 含义 |
---|---|
can | Function returns a boolean |
has | Function returns a boolean |
is | Function returns a boolean |
get | Function returns a nonboolean |
set | Function is used to save a value |
一些与函数名搭配的经常使用动词
动词 | 用法 |
---|---|
send | 发送 |
resend | 重发 |
validate | 验证 |
query | 查询 |
create | 建立 |
add | 添加 |
delete | 删除 |
remove | 移除 |
insert | 插入 |
update | 更新,编辑 |
copy | 复制 |
render | 渲染 |
close | 关闭 |
open | 开启 |
clear | 清除 |
edit | 编辑 |
query | 查询 |
on | 当事件发生 |
list | 渲染一个列表,如用户列表renderUsersList() |
content | 渲染内容,如用户详情的页面 renderUserContent() |
接口经常使用的动词
对于http请求的最经常使用的四种方法,get,post,put,delete,有一些经常使用的名词与其对应html
含义 | 请求方法 | 词语 | 栗子 |
---|---|---|---|
增长 | post | create | createUser,createCall |
删除 | delete | delete | deleteUser |
修改 | put | update | updateUser,updateProfile |
查询 | get | get,query | getUser,queryUser(无条件查询使用get,有条件查询使用query) |
学会使用单复数命名函数
函数名 | 含义 |
---|---|
getUser() | 获取一个用户,通常是经过惟一的id来获取 |
getUsers() | 获取一组用户,通常是经过一些条件来获取 |
createUser() | 建立一个用户 |
createUsers() | 建立一组用户 |
常量
var MAX_COUNT = 10; var URL = "http://www.nczonline.net/";
构造函数
// Good function Person(name) { this.name = name; } Person.prototype.sayName = function() { alert(this.name); }; var me = new Person("wdd");
底层http请求接口函数
eg:前端
app-main.js app-event.js app-user-manger.js
本身写的js文件最好和引用的一些第三方js分别放置在不一样的文件夹下。vue
alert的缺点
java
更优雅的提醒方式
webpack
//通常事件订阅的写法,以jQuery的写法为栗子 $(document).on('click','#btn-get-users',function(event){ event.stopPropagation(); //下面的省略号表示执行获取全部用于并显示在页面上的逻辑 // Bad ... ... ... // });
若是增长了需求,当点击另一个按钮的时候,也要执行获取全部用户并显示在页面上,那么上面省略的代码又要复制一份。若是接口有改动,那么须要在两个不一样的地方都要修改。
因此,应该这样。git
$(document).on('click','#btn-get-users',function(event){ event.stopPropagation(); //将应用逻辑分离在其余个函数中 // Good App.getUsers(); App.renderUsers(); });
// Bad ReqApi.tenant.queryUsers({},function(res){ if(!res.success){ console.error(res); return; } //对数据的处理 ... ... ... });
上面代码对数据的处理直接写死在异步请求里面,若是换了一个请求,可是数据处理方式是同样的,那么又要复制一遍数据处理的代码。最好的方式是将数据处理模块化成为一个函数。github
// Good ReqApi.tenant.queryUsers({},function(res){ if(!res.success){ console.error(res); return; } //对数据的处理 App.renderUsers(res.data); });
异步请求只处理请求,不处理数据。函数的功能要专注,功能粒度不可分割。
若是你须要一个函数去验证输入框是不是空,以下。这种方式就会绑定死了这个只能验证id为test的输入框,换成其余的就不行
// bad function checkInputIsEmpty(){ var value = $('#test').val(); if(value){ return true; } else{ return false; } } // good function isEmptyInput(id){ var value = $('#'+id).val(); if(value){ return true; } else{ return false; } }
javascript动态性质是的几乎任何东西在任什么时候间都能更改,这样就很容易覆写了一些默认的方法。致使一些灾难性的后果。若是你不负责或者维护某个对象,那么你就不能对它进行修改。
// Bad 两个全局变量 var name = "wdd"; funtion getName(){ console.log(name); } // Good 一个全局变量 var App = { name:"wdd", sayName:funtion(){ console.log(this.name);//若是这个函数当作回调数使用,这个this可能指向window, } };
单一的全局变量即是命名空间的概念,例如雅虎的YUI,jQuery的$等。
funtion sortArray(values){ // 避免 if(values != null){ values.sort(comparator); } }
function sortArray(values){ // 推荐 if(values instanceof Array){ values.sort(compartor); } }
代码中与null比较越少,就越容易肯定代码的目的,消除没必要要的错误。
配置数据是一些硬代码(hardcoded),看下面的栗子
function validate(value){ if(!value){ alert('Invalid value'); location.href = '/errors/invalid.php'; } }
上面代码里有两个配置数据,一个是UI字符串('Invalid value'),另外一个是一个Url('/error/invalid.php')。若是你把他们写死在代码里,那么若是当你须要修改这些地方的时候,那么你必须一处一处的检查并修改,并且还可能会遗漏。
var Config = { "MSG_INVALID_VALUE":"Invalid value", "URL_INVALID":"/errors/invalid.php" }
在开发过程当中,可能随处留下几个console.log,或者alert语句,这些语句在开发过程当中是颇有价值的。可是项目一旦进入生产环境,过多的console.log可能影响到浏览器的运行效率,过多的alert会下降程序的用户体验。而咱们最好不要在进入生产环境前,一处一处像扫雷同样删除或者注释掉这些调试语句。
最好的方式是设置一个开关。
//全局命令空间 var App = { debug:true, log:function(msg){ if(debug){ console.log(msg); } }, alert:function(msg){ if(debug){ alert(msg); } } }; //使用 App.log('获取用户信息成功'); App.alert('密码不匹配'); //关闭日志输出与alert App.debug = false;
没使用promise以前的回调函数写法
// bad:没使用promise以前的回调函数写法 function sendRequest(req,successCallback,errorCallback){ var inputData = req.data || {}; inputData = JSON.stringify(inputData); $.ajax({ url:req.base+req.destination, type:req.type || "get", headers:{ sessionId:session.id }, data:inputData, dataType:"json", contentType : 'application/json; charset=UTF-8', success:function(data){ successCallback(data); }, error:function(data){ console.error(data); errorCallback(data); } }); } //调用 sendRequest(req,function(res){ ... },function(res){ ... });
使用promise以后
function sendRequest(req){ var dfd = $.Deferred(); var inputData = req.data || {}; inputData = JSON.stringify(inputData); $.ajax({ url:req.base+req.destination, type:req.type || "get", headers:{ sessionId:session.id }, data:inputData, dataType:"json", contentType : 'application/json; charset=UTF-8', success:function(data){ dfd.resolve(data); }, error:function(data){ dfd.reject(data); } }); return dfd.promise(); } //调用 sendRequest(req) .done(function(){ //请求成功 ... }) .fail(function(){ //请求失败 ... });
假如前端要去接口获取用户信息并显示出来,若是你的请求格式是正确的,可是接口返回400以上的错误,你必须经过提醒来告知测试,这个错误是接口的返回错误,而不是前端的逻辑错误。
对资源的操做包括获取、建立、修改和删除资源,这些操做正好对应HTTP协议提供的GET、POST、PUT和DELETE方法。
对应方式
请求类型 | 接口前缀 |
---|---|
GET | .get, |
POST | .create 或者 .get |
PUT | .update |
DELETE | .delete |
说明
示例:
// 与用户相关的接口 App.api.user = {}; // 获取一个用户: 通常来讲是一个指定的Id,例如userId App.api.user.getUser = function(){ ... }; // 获取一组用户: 通常来讲是一些条件,获取条件下的用户,筛选符合条件的用户 App.api.user.getUsers = function(){ ... }; // 建立一个用户 App.api.user.createUser = function(){ }; // 建立一组用户 App.api.user.createUsers = function(){ }; // 更新一个用户 App.api.user.updateUser = function(){ }; // 更新一组用户 App.api.user.updateUsers = function(){ }; // 更新一个用户 App.api.user.updateUser = function(){ }; // 更新一组用户 App.api.user.updateUsers = function(){ }; // 删除一个用户 App.api.user.deleteUser = function(){ }; // 删除一组用户 App.api.user.deleteUsers = function(){ };
优化循环
减值迭代
:从最大值开始,在循环中不断减值的迭代器更加高效简化终止条件
:因为每次循环过程都会计算终止条件,因此必须保证它尽量快。也就是避免其余属性查找简化循环体
:因为循环体是执行最多的,因此要确保其最大限度地优化。// **Bad** 某些代码求值 eval("alert('hello')"); // **Bad** 建立新函数 var sayHi = new Function("alert('hello')"); // **Bad** 设置超时 setTimeout("alert('hello')");
性能的其余注意事项
case 的分支不要超过128条
废弃
)// 方式1:Bad var count = 5; var name = 'wdd'; var sex = 'male'; var age = 10; // 方式2:Good var count = 5, name = 'wdd', sex = 'male', age = 10;
2017-03-07 理论上方式2可能要比方式1性能高一点。可是我在实际使用中,这个快一点几乎是没什么感觉的。就像你没法感觉到小草的生长同样。反而可读性更为重要。因此,每行最好只定义一个变量,而且每行都有一个var,并用分号结尾。
// Good var name = values[i++];
// Good var values = ['a','b','c']; var person = { name:'wdd', age:10 };
只要有可能,尽可能使用数组和对象字面量的表达式来消除没必要要的语句
在JavaScript各个方面中,DOM无疑是最慢的一部分。DOM操做与交互要消耗大量的时间。由于他们每每须要从新渲染整个页面或者某一部分。进一步说,看似细微的操做也可能花好久来执行。由于DOM要处理很是多的信息。理解如何优化与DOM的交互能够极大的提升脚本完成的速度。
调用频率很是高的dom查找,能够将DOM缓存在于一个变量中
// 最简单的dom缓存 var domCache = {}; function myGetElement(tag){ return domCache[tag] = domCache[tag] || $(tag); }
// 先看下面的极端状况 app.user.mother.parent.home.name = 'wdd' app.user.mother.parent.home.adderess = '上海' app.user.mother.parent.home.weather = '晴天' // 更优雅的方式 var home = app.user.mother.parent.home; home.name = 'wdd'; home.address = '上海', home.weather = '晴天'
注意
使用上面的方式是有前提的,必须保证app.user.mather.parent.home是一个对象,由于对象是传递的引用。若是他的类型是一个基本类型,例如:number,string,boolean,那么复制操做仅仅是值传递,新定义的home的改变,并不会影响到app.user.mather.parent.home的改变。
+'4.1' === 4.1
4.1+'' === '4.1'
'4.99' | 0 === 4
建议读者自行扩展
DRY(dont't repeat yoursele: 不要重复你本身)
高内聚低耦合
开放闭合
最小意外
单一职责(single responsibility)
建议使用Object.prototype.toString.call()方法检测数据类型
function isArray(value){ return Object.prototype.toString.call(value) === "[object Array]"; } function isFunction(value){ return Object.prototype.toString.call(value) === "[object Function]"; } function isRegExp(value){ return Object.prototype.toString.call(value) === "[object RegExp]"; } function isNativeJSON(){ return window.JSON && Object.prototype.toString.call(JSON) === "[object JSON]"; }
对于ie中一COM对象形式实现的任何函数,isFunction都返回false,由于他们并不是原生的javascript函数。
在web开发中,可以区分原生与非原生的对象很是重要。只有这样才能确切知道某个对象是否有哪些功能
以上全部的正确性的前提是:Object.prototype.toString没有被修改过
function Person(name){ this.name = name; } //使用new来建立一个对象 var one = new Person('wdd'); //直接调用构造函数 Person();
因为this是运行时分配的,若是你使用new来操做,this指向的就是one。若是直接调用构造函数,那么this会指向全局对象window,而后你的代码就会覆盖window的原生name。若是有其余地方使用过window.name, 那么你的函数将会埋下一个深藏的bug。
那么,如何才能建立一个做用域安全的构造函数?
function Person(name){ if(this instanceof Person){ this.name = name; } else{ return new Person(name); } }
假设有一个方法X,在A类浏览器里叫A,在b类浏览器里叫B,有些浏览器并无这个方法,你想实现一个跨浏览器的方法。
惰性载入函数的思想是:在函数内部改变函数自身的执行逻辑
function X(){ if(A){ return new A(); } else{ if(B){ return new B(); } else{ throw new Error('no A or B'); } } }
换一种写法
function X(){ if(A){ X = function(){ return new A(); }; } else{ if(B){ X = function(){ return new B(); }; } else{ throw new Error('no A or B'); } } return new X(); }
// 下面代码在谷歌浏览器中执行 > var person = {name: 'wdd'}; undefined > Object.preventExtensions(person); Object {name: "wdd"} > person.age = 10 10 > person Object {name: "wdd"} > Object.isExtensible(person) false
密封对象不可扩展,而且不能删除对象的属性或者方法。可是属性值能够修改。
> var one = {name: 'hihi'} undefined > Object.seal(one) Object {name: "hihi"} > one.age = 12 12 > one Object {name: "hihi"} > delete one.name false > one Object {name: "hihi"}
最严格的防篡改就是冻结对象。对象不可扩展,并且密封,不能修改。只能访问。
函数节流的思想是:某些代码不能够没有间断的连续重复执行
var processor = { timeoutId: null, // 实际进行处理的方法 performProcessing: function(){ ... }, // 初始化调用方法 process: function(){ clearTimeout(this.timeoutId); var that = this; this.timeoutId = setTimeout(function(){ that.performProcessing(); }, 100); } } // 尝试开始执行 processor.process();
页面若是有十个区域要动态显示当前时间,通常来讲,能够用10个定时来实现。其实一个中央定时器就能够搞定。
中央定时器动画 demo地址:http://wangduanduan.coding.me...
var timers = { timerId: 0, timers: [], add: function(fn){ this.timers.push(fn); }, start: function(){ if(this.timerId){ return; } (function runNext(){ if(timers.timers.length > 0){ for(var i=0; i < timers.timers.length ; i++){ if(timers.timers[i]() === false){ timers.timers.splice(i, 1); i--; } } timers.timerId = setTimeout(runNext, 16); } })(); }, stop: function(){ clearTimeout(timers.timerId); this.timerId = 0; } };
推荐阅读:JS函数式编程中文版
ajax在使用的时候,例如点击按钮,获取某个列表。须要注意如下方面
尽可能将全部代码封装在函数中,不要暴露全局变量
每一个函数的函数体中,代码行越少越好,最好一个函数中就一句代码
若是你认为前端不须要关于协议的知识,那么你就是大错特错了。其实不只仅是前端,全部的开发者都应该学习底层的协议。由于他们是互联网通讯的基石。
推荐三本必读的书籍
或者你一也能够看看关于协议方面的一些问题,以及若是你遇到过,你是否知道如何解决: