#JavaScript best practices JS最佳實(shí)踐## 0 簡介> 最佳實(shí)踐起初比較棘手伟骨,但最終會(huì)讓你發(fā)現(xiàn)這是非常明智之舉。##? 1.合理命名方法及變量名,簡潔且可讀```var someItem = 'some string',? ? anotherItem = 'another string',? ? oneMoreItem = 'one more string';let [ , , third] = ["foo", "bar", "baz"];let [x, y = 'b'] = ['a']; var {x, y = 5} = {x: 1};```## 2. 避免全局變量```var myNameSpace = {? current:null,? init:function(){...},? change:function(){...},? verify:function(){...}}//or myNameSpace = function(){? var current = null;? function init(){...}? function change(){...}? function verify(){...}}();// 暴露出去myNameSpace = function(){? var current = null;? function verify(){...}? return{? ? init:function(){...}? ? change:function(){...}? }}();// 全部私有化(function(){? var current = null;? function init(){...}? function change(){...}? function verify(){...}})();```## 3. 堅(jiān)持嚴(yán)格模式 嚴(yán)格模式:[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode)### 嚴(yán)格模式主要有以下限制首繁。- 變量必須聲明后再使用- 函數(shù)的參數(shù)不能有同名屬性,否則報(bào)錯(cuò)- 不能使用with語句- 不能對(duì)只讀屬性賦值陨囊,否則報(bào)錯(cuò)- 不能使用前綴0表示八進(jìn)制數(shù)弦疮,否則報(bào)錯(cuò)- 不能刪除不可刪除的屬性,否則報(bào)錯(cuò)- 不能刪除變量delete prop蜘醋,會(huì)報(bào)錯(cuò)胁塞,只能刪除屬性delete global[prop]- eval不會(huì)在它的外層作用域引入變量- eval和arguments不能被重新賦值- arguments不會(huì)自動(dòng)反映函數(shù)參數(shù)的變化- 不能使用arguments.callee- 不能使用arguments.caller- 禁止this指向全局對(duì)象- 不能使用fn.caller和fn.arguments獲取函數(shù)調(diào)用的堆棧- 增加了保留字(比如protected、static和interface)> 清潔有效的代碼意味著更少的confusing bugs 去fix压语,更容易地遞交給其他開發(fā)人員和更強(qiáng)的代碼安全性啸罢。## 4. 關(guān)鍵點(diǎn)寫注釋>起初可能看起來是不必要的,但是相信我胎食,你最好盡可能地注釋你的代碼## 5. 避免或減少其它技術(shù)(DOM),優(yōu)化DOM操作> 網(wǎng)頁如何生成的扰才?![http://www.ruanyifeng.com/blogimg/asset/2015/bg2015091501.png](http://www.ruanyifeng.com/blogimg/asset/2015/bg2015091501.png)大致分以下五步:1. HTML代碼轉(zhuǎn)化成DOM;2. CSS代碼轉(zhuǎn)化成CSSOM(CSS Object Model)厕怜;3. 結(jié)合DOM和CSSOM衩匣,生成一棵渲染樹(包含每個(gè)節(jié)點(diǎn)的視覺信息);4. 渲染(render)---耗時(shí):? ? 1. 生成布局(flow)粥航,將所有渲染樹的所有節(jié)點(diǎn)進(jìn)行平面合成(layout)琅捏;? ? 2. 繪制(paint)在屏幕;### 提升頁面性能技巧- 避免交叉DOM的讀寫操作递雀,讀寫分別放在一起柄延;- 若某樣式通過重排獲得,最好緩存一下映之;- 集中改變樣式```el.className += " theclassname";el.style.cssText += "; left: " + left + "px; top: " + top + "px;";```- 盡量使用離線DOM而非網(wǎng)頁里的DOM,改變?cè)貥邮嚼唬鏲loneNode杠输;- 先將元素的display設(shè)為none,再做操作- position屬性為absolute或fixed的元素秕衙,重排的開銷會(huì)比較小蠢甲,因?yàn)椴挥每紤]它對(duì)其他元素的影響。- 只在必要的時(shí)候据忘,才將元素的display屬性為可見鹦牛,因?yàn)椴豢梢姷脑夭挥绊懼嘏藕椭乩L搞糕。另外,visibility : hidden的元素只對(duì)重繪有影響曼追,不影響重排- 使用虛擬DOM的腳本庫窍仰,比如React等。- 使用 window.requestAnimationFrame()礼殊、window.requestIdleCallback() 這兩個(gè)方法調(diào)節(jié)重新渲染### 常見做法#### 1.集中處理樣式讀寫:```var left = div.offsetLeft;var top? = div.offsetTop;div.style.left = left + 10 + "px";div.style.top = top + 10 + "px";```> 樣式表越簡單驹吮,重排和重繪就越快。重排和重繪的DOM元素層級(jí)越高晶伦,成本就越高碟狞。table元素的重排和重繪成本,要高于div元素#### 2.使用模版字符串:```$('#result').append(`? There are${basket.count}items? in your basket,${basket.onSale}are on sale!`);const tmpl = addrs => `? ? ${addrs.map(addr => `? ? ? `).join('')}
${addr.first}
${addr.last}
`;```## 6. 首選使用字面量婚陪,推薦短路運(yùn)算符```var cow = {? colour:'brown',? commonQuestion:'What now?',? moo:function(){? ? console.log('moo);? },? feet:4,? accordingToLarson:'will take over the world'族沃,? name:null,};var awesomeBands = [? 'Bad Religion',? 'Dropkick Murphys',? 'Flogging Molly',? 'Red Hot Chili Peppers',? 'Pornophonique'];var x = v || 10;//動(dòng)態(tài)添加屬性或方法cow.name = 'xx';//避免//推薦,但最佳在定義對(duì)象時(shí)將name屬性值設(shè)為nullObject.defineProperty(cow,'name',{? ? configurable:false,? ? writable:false,? ? enumerable:true,? ? value:'xx'});```## 7. 模塊化去耦合---一個(gè)方法一個(gè)任務(wù)```function createLink(text,url){? var newLink = document.createElement('a');? newLink.setAttribute('href',url);? newLink.appendChild(document.createTextNode(text));? return newLink;}function createMenu(){? var menu = document.getElementById('menu');? var items = [? ? {t:'Home',u:'index.html'},? ? {t:'Sales',u:'sales.html'},? ? {t:'Contact',u:'contact.html'}? ];? for(var i=0;i對(duì)于那些不熟悉的人泌参,“eval”函數(shù)可以讓我們?cè)L問JavaScript的編譯器脆淹。 事實(shí)上,我們可以通過將字符串作為參數(shù)傳遞給eval,返回其的結(jié)果及舍。這不僅會(huì)大大降低您的腳本的性能未辆,還會(huì)造成巨大的安全風(fēng)險(xiǎn),因?yàn)樗o文本中傳遞的功能太多了锯玛。 避免咐柜!```if(a===b){? //...}```## 9. 避免多重嵌套```function renderProfiles(o){? var out = document.getElementById('profiles');? for(var i=0;i在談?wù)摯a和數(shù)據(jù)安全性時(shí)要牢記的要點(diǎn)之一是不要信任任何數(shù)據(jù)確保進(jìn)入系統(tǒng)的所有數(shù)據(jù)都是干凈的,正是您需要的攘残。 這在后端寫出從URL檢索的參數(shù)時(shí)最重要拙友。 在JavaScript中,測(cè)試發(fā)送到您的函數(shù)的參數(shù)類型(如使用typeof 或instanceof)非常重要歼郭。```function buildMemberList(members){? if(typeof members === 'object' &&? ? ? typeof members.slice === 'function'){? ? var all = members.length;? ? var ul = document.createElement('ul');? ? for(var i=0;iJSLint takes a JavaScript source and scans it. If it finds a problem, it returns a message describing the problem and an approximate location within the source. The problem is not necessarily a syntax error, although it often is. JSLint looks at some style conventions as well as structural problems. It does not prove that your program is correct. It just provides another set of eyes to help spot problems."
- JSLint Documentation
## 14. 將腳本置底 ---使用戶盡可能快地加載頁面
## 15. 擁抱ES6+最佳實(shí)踐 [http://es6.ruanyifeng.com/#docs/style](http://es6.ruanyifeng.com/#docs/style)
## 16. 站在巨人肩上遗契,閱讀優(yōu)秀源碼
- bootstrap: [https://github.com/twbs/bootstrap/blob/v4-dev/js/src/carousel.js](https://github.com/twbs/bootstrap/blob/v4-dev/js/src/carousel.js)
參考點(diǎn): ES6,模塊化代碼封裝病曾,可配置性牍蜂,可擴(kuò)展性,變量命名方式泰涂,核心注釋
```
import Util from './util'
/**
* --------------------------------------------------------------------------
* Bootstrap (v4.0.0-beta): carousel.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* --------------------------------------------------------------------------
*/
const Carousel = (($) => {
/**
* ------------------------------------------------------------------------
* Constants
* ------------------------------------------------------------------------
*/
const NAME? ? ? ? ? ? ? ? ? = 'carousel'
const VERSION? ? ? ? ? ? ? ? = '4.0.0-beta'
const DATA_KEY? ? ? ? ? ? ? = 'bs.carousel'
const EVENT_KEY? ? ? ? ? ? ? = `.${DATA_KEY}`
const DATA_API_KEY? ? ? ? ? = '.data-api'
const JQUERY_NO_CONFLICT? ? = $.fn[NAME]
const TRANSITION_DURATION? ? = 600
const ARROW_LEFT_KEYCODE? ? = 37 // KeyboardEvent.which value for left arrow key
const ARROW_RIGHT_KEYCODE? ? = 39 // KeyboardEvent.which value for right arrow key
const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
const Default = {
interval : 5000,
keyboard : true,
slide? ? : false,
pause? ? : 'hover',
wrap? ? : true
}
const DefaultType = {
interval : '(number|boolean)',
keyboard : 'boolean',
slide? ? : '(boolean|string)',
pause? ? : '(string|boolean)',
wrap? ? : 'boolean'
}
const Direction = {
NEXT? ? : 'next',
PREV? ? : 'prev',
LEFT? ? : 'left',
RIGHT? ? : 'right'
}
const Event = {
SLIDE? ? ? ? ? : `slide${EVENT_KEY}`,
SLID? ? ? ? ? : `slid${EVENT_KEY}`,
KEYDOWN? ? ? ? : `keydown${EVENT_KEY}`,
MOUSEENTER? ? : `mouseenter${EVENT_KEY}`,
MOUSELEAVE? ? : `mouseleave${EVENT_KEY}`,
TOUCHEND? ? ? : `touchend${EVENT_KEY}`,
LOAD_DATA_API? : `load${EVENT_KEY}${DATA_API_KEY}`,
CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`
}
const ClassName = {
CAROUSEL : 'carousel',
ACTIVE? : 'active',
SLIDE? ? : 'slide',
RIGHT? ? : 'carousel-item-right',
LEFT? ? : 'carousel-item-left',
NEXT? ? : 'carousel-item-next',
PREV? ? : 'carousel-item-prev',
ITEM? ? : 'carousel-item'
}
const Selector = {
ACTIVE? ? ? : '.active',
ACTIVE_ITEM : '.active.carousel-item',
ITEM? ? ? ? : '.carousel-item',
NEXT_PREV? : '.carousel-item-next, .carousel-item-prev',
INDICATORS? : '.carousel-indicators',
DATA_SLIDE? : '[data-slide], [data-slide-to]',
DATA_RIDE? : '[data-ride="carousel"]'
}
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
class Carousel {
constructor(element, config) {
```
- jquery:[https://github.com/jquery/jquery/blob/master/src/core/ready.js](https://github.com/jquery/jquery/blob/master/src/core/ready.js)
```
define( [
"../core",
"../var/document",
"../core/readyException",
"../deferred"
], function( jQuery, document ) {
"use strict";
// The deferred used on DOM ready
var readyList = jQuery.Deferred();
jQuery.fn.ready = function( fn ) {
readyList
.then( fn )
// Wrap jQuery.readyException in a function so that the lookup
// happens at the time of error handling instead of callback
// registration.
.catch( function( error ) {
jQuery.readyException( error );
} );
return this;
};
```
- vue.js [https://github.com/vuejs/vue/blob/dev/src/core/config.js](https://github.com/vuejs/vue/blob/dev/src/core/config.js)
參考點(diǎn): ES6鲫竞,模塊化,可配置可擴(kuò)展,MVVM核心思想逼蒙,Viral DOM, 數(shù)據(jù)類型判斷
```
/* @flow */
import {
no,
noop,
identity
} from 'shared/util'
import { LIFECYCLE_HOOKS } from 'shared/constants'
export type Config = {
// user
optionMergeStrategies: { [key: string]: Function };
silent: boolean;
productionTip: boolean;
performance: boolean;
devtools: boolean;
errorHandler: ?(err: Error, vm: Component
//...
//index.js
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
initGlobalAPI(Vue)
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
})
Vue.version = '__VERSION__'
export default Vue
```
```
export const hasSymbol =
typeof Symbol !== 'undefined' && isNative(Symbol) &&
typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys)
/**
* Defer a task to execute it asynchronously.
*/
export const nextTick = (function () {
const callbacks = []
let pending = false
let timerFunc
function nextTickHandler () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// the nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore if */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve()
var logError = err => { console.error(err) }
timerFunc = () => {
p.then(nextTickHandler).catch(logError)
// in problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
} else if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// use MutationObserver where native Promise is not available,
// e.g. PhantomJS IE11, iOS7, Android 4.4
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
function mergeData (to: Object, from: ?Object): Object {
if (!from) return to
let key, toVal, fromVal
const keys = Object.keys(from)
for (let i = 0; i < keys.length; i++) {
key = keys[i]
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
mergeData(toVal, fromVal)
}
}
return to
}
/**
* Normalize all injections into Object-based format
*/
function normalizeInject (options: Object) {
const inject = options.inject
if (Array.isArray(inject)) {
const normalized = options.inject = {}
for (let i = 0; i < inject.length; i++) {
normalized[inject[i]] = inject[i]
}
}
```
- react.js [https://github.com/facebook/react/blob/master/src/shared/utils/getComponentName.js](https://github.com/facebook/react/blob/master/src/shared/utils/getComponentName.js)
參考點(diǎn):ES6从绘, 數(shù)據(jù)類型判斷,模塊化
```
'use strict';
import type {ReactInstance} from 'ReactInstanceType';
import type {Fiber} from 'ReactFiber';
function getComponentName(
instanceOrFiber: ReactInstance | Fiber,
): string | null {
if (typeof instanceOrFiber.getName === 'function') {
// Stack reconciler
const instance = ((instanceOrFiber: any): ReactInstance);
return instance.getName();
}
if (typeof instanceOrFiber.tag === 'number') {
// Fiber reconciler
const fiber = ((instanceOrFiber: any): Fiber);
const {type} = fiber;
if (typeof type === 'string') {
return type;
}
if (typeof type === 'function') {
return type.displayName || type.name;
}
}
return null;
}
module.exports = getComponentName;
```
### 參考鏈接:
1. [https://www.w3.org/wiki/JavaScript_best_practices][1]
2. [https://code.tutsplus.com/tutorials/24-javascript-best-practices-for-beginners--net-5399][2]
3. [http://www.ruanyifeng.com/blog/2015/09/web-page-performance-in-depth.html](http://www.ruanyifeng.com/blog/2015/09/web-page-performance-in-depth.html)
4. [http://domenicodefelice.blogspot.sg/2015/08/how-browsers-work.html](http://domenicodefelice.blogspot.sg/2015/08/how-browsers-work.html)
[1]: https://www.w3.org/wiki/JavaScript_best_practices
[2]: https://code.tutsplus.com/tutorials/24-javascript-best-practices-for-beginners--net-5399