( 譯、持續更新 ) JavaScript 上分小技巧(一)


感謝好友破狼提供的這篇好文章,也感謝寫這些知識點的作者們和將他們整理到一起的作者。這是github上的一篇文章,在這里本獸也就只做翻譯,由於本獸英語水平和編程能力都不咋地,如有不好的地方也請多理解體諒。戳這里:原文

能夠為大家提供這些簡短而實用的JavaScript技巧來提高大家編程能力,這對於我來說是件很開心的事。每天僅花上不到2分鍾的時間中,你將可以讀遍JavaScript這門可怕的語言所呈現給我們的特性:performance(性能), conventions(協議), hacks(代碼hack), interview questions(面試問題)及所有其他的東西。

第二篇地址:( 譯、持續更新 ) JavaScript 上分小技巧(二)

第三篇地址:( 譯、持續更新 ) JavaScript 上分小技巧(三)

第四篇地址:( 譯、持續更新 ) JavaScript 上分小技巧(四)

#15 - 使用更簡單的類似indexOf的包含判斷方式
原生的JavaScript沒有contains方法。對檢查字符串或字符串數組項中是否存在某值,你可以這樣做:

var someText = 'JavaScript rules';
if (someText.indexOf('JavaScript') !== -1) {
}
// 或者
if (someText.indexOf('JavaScript') >= 0) {
}

但是我們再看看這些ExpressJs代碼片段。

// examples/mvc/lib/boot.js
for (var key in obj) {
  // "reserved" exports
  if (~['name', 'prefix', 'engine', 'before'].indexOf(key)) continue;

// examples/lib/utils.js
exports.normalizeType = function(type){
  return ~type.indexOf('/')
    ? acceptParams(type)
    : { value: mime.lookup(type), params: {} };
};

// examples/web-service/index.js
// key is invalid
if (!~apiKeys.indexOf(key)) return next(error(401, 'invalid api key'));

問題是~位運算符。"運算符執行操作這樣的二進制表達式,但他們返回標准的JavaScript的數值."
他們將-1轉換為0,而0在JavaScript中又是false。

var someText = 'text';
!!~someText.indexOf('tex'); // someText 包含 "tex" - true
!~someText.indexOf('tex'); // someText 不包含 "tex" - false
~someText.indexOf('asd'); // someText 不包含 "asd" - false
~someText.indexOf('ext'); // someText 包含 "ext" - true

String.prototype.includes()

在ES6(ES 2015)中介紹了includes()方法可以用來確定是否一個字符串包含另一個字符串:

'something'.includes('thing'); // true

在ECMAScript 2016 (ES7)中,甚至數組都可以這樣操作,如indexOf:

!!~[1, 2, 3].indexOf(1); // true
[1, 2, 3].includes(1); // true

不幸的是,這只是在Chrome,Firefox,Safari 9或以上的瀏覽器中被支持。

#14 - arrow 函數(ES6)
介紹下ES6里的新功能,arrow函數可能會是個很方便的工具,用更少行數寫更多代碼。他的名字來源於他的語法,=>和小箭頭->比就像一個“胖胖的箭頭”。可能有些人知道,這種函數類型和其他靜態語言如lambda表達式的匿名函數。它被稱為匿名,因為這些箭頭函數沒有一個描述性的函數名。
那么這樣有什么好處呢?
語法:更少的LOC,不用一次次的鍵入函數關鍵字。
語義:從上下文中捕捉關鍵字this。
簡單語法案例:
看看下面的兩段代碼片段,他們做的是一樣的工作。你能很快的理解arrow函數的功能。

// arrow函數的日常語法
param => expression
// 可能也會寫在括號中
// 括號是多參數要求
(param1 [, param2]) => expression

// 使用日常函數
var arr = [5,3,2,9,1];
var arrFunc = arr.map(function(x) {
  return x * x;
});
console.log(arr)

// 使用arrow函數
var arr = [5,3,2,9,1];
var arrFunc = arr.map((x) => x*x);
console.log(arr)

正如你所看到的,這個例子中的arrow函數可以節省你輸入括號內參數和返回關鍵字的時間。建議把圓括號內的參數輸入,如 (x,y) => x+y 。在不同的使用情況下,它只是

用來應對遺忘的一種方式。但是上面的代碼也會這樣執行:x => x*x.目前看來,這些僅僅是導致更少的LOC和更好的可讀性的句法改進。
this 綁定
還有一個更好的理由使用arrow函數。那就是在會出現this問題的背景下。使用arrow函數,你就不用擔心.bind(this)和 that=this 了。因為arrow函數會從上下文中找到this。

看下面的例子:

// 全局定義this.i
this.i = 100;
var counterA = new CounterA();
var counterB = new CounterB();
var counterC = new CounterC();
var counterD = new CounterD();

// 不好的示例
function CounterA() {
  // CounterA's `this` 實例 (!! 忽略這里)
  this.i = 0;
  setInterval(function () {
    // `this` 指全局對象,而不是 CounterA's `this`
    // 因此,開始計數與100,而不是0 (本地的 this.i)
    this.i++;
    document.getElementById("counterA").innerHTML = this.i;
  }, 500);
}
// 手動綁定 that = this
function CounterB() {
  this.i = 0;
  var that = this;
  setInterval(function() {
    that.i++;
    document.getElementById("counterB").innerHTML = that.i;
  }, 500);
}
// 使用 .bind(this)
function CounterC() {
  this.i = 0;
  setInterval(function() {
    this.i++;
    document.getElementById("counterC").innerHTML = this.i;
  }.bind(this), 500);
}
// 使用 arrow函數
function CounterD() {
  this.i = 0;
  setInterval(() => {
    this.i++;
    document.getElementById("counterD").innerHTML = this.i;
  }, 500);
}

關於arrow函數的進一步信息可以看這里 。查看不同的語法選請訪問該站點

#13 - 測量一個JavaScript代碼塊性能的技巧
快速測量一個JavaScript塊的性能,我們可以使用控制台的功能像console.time(label)和console.timeEnd(label)

console.time("Array initialize");
var arr = new Array(100),
    len = arr.length,
    i;
for (i = 0; i < len; i++) {
    arr[i] = new Object();
};
console.timeEnd("Array initialize"); // 輸出: Array initialize: 0.711ms

更多信息Console object, JavaScript benchmarking
demo:jsfiddle-codepen (在瀏覽器控制台輸出)

#12 - ES6中參數處理
在許多編程語言中,函數的參數是默認的,而開發人員必須顯式定義一個參數是可選的。在JavaScript中的每個參數是可選的,但我們可以這一行為而不讓一個函數利用ES6的默認值作為參數。

const _err = function( message ){
  throw new Error( message );
}
const getSum = (a = _err('a is not defined'), b = _err('b is not defined')) => a + b
getSum( 10 ) // throws Error, b is not defined
getSum( undefined, 10 ) // throws Error, a is not defined

_err是立即拋出一個錯誤的函數。如果沒有一個參數作為值,默認值是會被使用,_err將被調用,將拋出錯誤。你可以在Mozilla開發者網絡看到的更多默認參數的例子。

#11 - 提升
理解提升將幫助你組織你的function。只需要記住,變量聲明和定義函數會被提升到頂部。變量的定義是不會的,即使你在同一行中聲明和定義一個變量。此外,變量聲明讓系統知道變量存在,而定義是將其賦值給它。

function doTheThing() {
  // 錯誤: notDeclared is not defined
  console.log(notDeclared);
  // 輸出: undefined
  console.log(definedLater);
  var definedLater;
  definedLater = 'I am defined!'
  // 輸出: 'I am defined!'
  console.log(definedLater)
  // 輸出: undefined
  console.log(definedSimulateneously);
  var definedSimulateneously = 'I am defined!'
  // 輸出: 'I am defined!'
  console.log(definedSimulateneously)
  // 輸出: 'I did it!'
  doSomethingElse();
  function doSomethingElse(){
    console.log('I did it!');
  }
  // 錯誤: undefined is not a function
  functionVar();
  var functionVar = function(){
    console.log('I did it!');
  }
}

為了使事情更容易閱讀,在函數作用域內提升變量的聲明將會讓你明確該變量的聲明是來自哪個作用域。在你需要使用變量之前定義它們。在作用域底部定義函數,確保代碼清晰規范。

#10 - 檢查一個對象是否有屬性
當你要檢查一個對象是否存在某個屬性時,你可能會這樣做 :

var myObject = {
  name: '@tips_js'
};
if (myObject.name) { ... }

這是可以的,但你必須知道這個還有兩原生的方式,in operator 和 object.hasownproperty,每個對象是對象,既可用方法。每個object都繼承自Object,這兩個方法都可用。
兩個方法的一些不同點:

var myObject = {
  name: '@tips_js'
};
myObject.hasOwnProperty('name'); // true
'name' in myObject; // true
myObject.hasOwnProperty('valueOf'); // false, valueOf 是從原型鏈繼承的
'valueOf' in myObject; // true

他們之間的不同在於檢查的性質,換句話說,當該對象本身有查找的屬性時hasOwnProperty返回true,然而,in operator不區分屬性創建的對象和屬性繼承的原型鏈。
這里有另外一個例子:

var myFunc = function() {
  this.name = '@tips_js';
};
myFunc.prototype.age = '10 days';
var user = new myFunc();
user.hasOwnProperty('name'); // true
user.hasOwnProperty('age'); // false, 因為age是原型鏈上的

點擊看例子。同時,建議在檢查對象的屬性存在時,閱讀這些有關的常見錯誤

#09 - 模板字符串
截至ES6,JS已經有模板字符串作為替代經典的字符串引用。
案例:普通字符串

var firstName = 'Jake';
var lastName = 'Rawr';
console.log('My name is ' + firstName + ' ' + lastName);
// My name is Jake Rawr

模板字符串:

var firstName = 'Jake';
var lastName = 'Rawr';
console.log(`My name is ${firstName} ${lastName}`);
// My name is Jake Rawr

在模板字符串中${}中,你可以不用寫/n或者簡單邏輯來實現多行字符串。
您還可以使用函數來修改模板字符串的輸出,它們被稱為模板字符串的標記。你可能還想讀到更多的理解模板字符串相關信息

#08 - 將節點列表轉換為數組
querySelectorAll 方法返回一個和數組類似的節點列表對象。這些數據結構類似數組,因為經常以數組形式出現,但是又不能用數組的方法,比如map和foreach。這里有一個快速、安全、可重用的方式將一個節點列表到一個DOM元素數組:

const nodelist = document.querySelectorAll('div');
const nodelistToArray = Array.apply(null, nodelist);
//later on ..
nodelistToArray.forEach(...);
nodelistToArray.map(...);
nodelistToArray.slice(...);
//etc...

apply方法是將一系列數組格式的參數傳遞給一個給定this的函數。MDN指出,apply將會調用類似數組的對象,而這正是querySelectorAll所返回的。因為我們不需要在函數的上下文中指定this,所以我們傳入null或0。返回的結果是一組能使用數組方法的DOM元素數組。

如果你使用的是es2015可以利用...(spread operator)

const nodelist = [...document.querySelectorAll('div')]; // 返回的是個真實的數組
//later on ..
nodelist.forEach(...);
nodelist.map(...);
nodelist.slice(...);
//etc...

#07 - "use strict" 和"偷懶"
嚴格模式讓開發人員更加安全的編寫JavaScript。
默認情況下,JavaScript允許開發者偷懶,例如,我們在第一次聲明變量的時候可以不用var,雖然這可能看起來像一個沒有經驗的開發人員,同時這也是很多錯誤的根源,變量名拼寫錯誤或意外地將它提到了外部作用域。

程序員喜歡讓電腦為我們做些無聊的事,如檢查一些我們工作的錯誤。"use strict"指令會幫助我們做這些錯誤檢查,將我們的錯誤轉換成JavaScript錯誤。
我們把這個指令可以通過添加在一個js文件的頂部:

// 整個script文件都將是嚴格模式語法
"use strict";
var v = "Hi!  I'm a strict mode script!";
或者在函數內:
function f()
{
// 函數范圍內的嚴格模式語法
  'use strict';
  function nested() { return "And so am I!"; }
  return "Hi!  I'm a strict mode function!  " + nested();
}
function f2() { return "I'm not strict."; }

在包含這個指令的JavaScript文件或者函數內,我們將一些較大的JavaScript項目中的不良行為直接在JavaScript引擎執行中禁止了。在其他情況中,嚴格模式改變以下的行為:
· 變量只有在前面 var 聲明了才能用
· 試圖寫入只讀屬性產生的誤差
· 必須用 new 關鍵字調用構造函數
· this 不會默認指向全局對象
· 非常有限的使用eval()
· 保護保留字符或未來保留字符不被作為變量名使用
嚴格模式在新項目中是很有好處的,但是在大多數地方沒使用到它的老項目里使用它是非常具有挑戰性的。當你把多個文件合並到一個文件時,它也是個問題,就像可能導致整個文件都在嚴格模式下執行。
它不是一個聲明,只是一個字面量,早期版本的瀏覽器會忽略它。嚴格模式支持:
· IE 10+
· FF 4+
· Chrome 13+
· Safari 5.1+
· Opera 12+
參閱MDN對於嚴格模式的描述。

#06 - 處理一個數組或單個元素作為參數的方法
相比於寫個單獨的方法去分別操作一個數組和一個元素作為參數的函數,更好的是寫一個通用的函數,這樣就都可以操作。這類似於一些jQuery的方法(css匹配將修改所有的選擇器)。
你僅需要先將一切放進數組,Array.concat會接收數組或單一的對象:

function printUpperCase(words) {
  var elements = [].concat(words);
  for (var i = 0; i < elements.length; i++) {
    console.log(elements[i].toUpperCase());
  }
}

printUpperCase現在可以接收無論單一的元素作為參數還是一個數組:

printUpperCase("cactus");
// => CACTUS
printUpperCase(["cactus", "bear", "potato"]);
// => CACTUS
//  BEAR
//  POTATO

#05 - undefined 和 null 的不同
· undefined指的是一個變量未被聲明,或者一個變量被聲明但未賦值
· null是指一個特定的值,即"沒有值"
. JavaScript給未賦值的變量默認定義為undefined
· JavaScript不會給未賦值的變量設置null值,它被程序員用來表示一個無價值的值
· undefined在json格式數據中是無效的,而null有效
· undefined 類型是 undefined
· null類似是object.為什么呢?
· 兩者都是原始值
· 兩者都被認為false(Boolean(undefined) // false, Boolean(null) // false)。
· 辨認變量是不是undefined

typeof variable === "undefined"

· 檢查變量是不是null

variable === "null"

從值考慮他們是相等的,但是從類型和值共同考慮他們是不相等的

null == undefined // true
null === undefined // false

#04 - 以非ASCII字符形式來排序字符串
JavaScript有個原生的方法對字符串格式的數組進行排序,做一個簡單的array.sort()將會把字符串們按首字母的數序排列。當然,也可以提供自定義排序功能。

['Shanghai', 'New York', 'Mumbai', 'Buenos Aires'].sort();
// ["Buenos Aires", "Mumbai", "New York", "Shanghai"]

當你試圖用非ASCII字符,如 ['é', 'a', 'ú', 'c']這樣的進行排序,你會得到一個奇怪的結果['c', 'e', 'á', 'ú'],因為只有用英語的語言才能排序,所以發生這種情況。
看一個簡單的例子:

// Spanish
['único','árbol', 'cosas', 'fútbol'].sort();
// ["cosas", "fútbol", "árbol", "único"] // 錯誤的排序
// German
['Woche', 'wöchentlich', 'wäre', 'Wann'].sort();
// ["Wann", "Woche", "wäre", "wöchentlich"] // 錯誤的排序

幸運的是,有兩種方法來避免這種行為,國際化的ECMAScript API提供了localecompare和and Intl.Collator。
這兩種方法都有自己的自定義參數,以便將其配置來充分的完成功能。
使用 localeCompare()

['único','árbol', 'cosas', 'fútbol'].sort(function (a, b) {
  return a.localeCompare(b);
});
// ["árbol", "cosas", "fútbol", "único"]
['Woche', 'wöchentlich', 'wäre', 'Wann'].sort(function (a, b) {
  return a.localeCompare(b);
});
// ["Wann", "wäre", "Woche", "wöchentlich"]

使用 intl.collator()

['único','árbol', 'cosas', 'fútbol'].sort(Intl.Collator().compare);
// ["árbol", "cosas", "fútbol", "único"]
['Woche', 'wöchentlich', 'wäre', 'Wann'].sort(Intl.Collator().compare);
// ["Wann", "wäre", "Woche", "wöchentlich"]

· 每個方法都可以自定義位置
· 在FF瀏覽器,intl.collator()會更快,當比較的是較大的數值或字符串
因此,當你在用英語以外的語言來賦值給字符串數組時,記得要使用此方法來避免意外的排序。

#03 - 改善嵌套條件
我們怎樣才能改善並且使JavaScript中的if語句做出更有效的嵌套。

if (color) {
  if (color === 'black') {
    printBlackBackground();
  } else if (color === 'red') {
    printRedBackground();
  } else if (color === 'blue') {
    printBlueBackground();
  } else if (color === 'green') {
    printGreenBackground();
  } else {
    printYellowBackground();
  }
}

一個改善的辦法是用switch語句代替嵌套的if語句。雖然代碼是更加簡潔有序,但不推薦這樣做,因為很難debug。這里指出原因。

switch(color) {
  case 'black':
    printBlackBackground();
    break;
  case 'red':
    printRedBackground();
    break;
  case 'blue':
    printBlueBackground();
    break;
  case 'green':
    printGreenBackground();
    break;
  default:
    printYellowBackground();
}

但是,當我們有多個判斷條件的情況下呢?在這種情況下,如果我們想讓代碼更加簡潔有序,我們可以使用switch。如果我們將true的作為一個參數傳遞給該switch語句,它可以讓我們在每一個情況下放置一個條件。

switch(true) {
  case (typeof color === 'string' && color === 'black'):
    printBlackBackground();
    break;
  case (typeof color === 'string' && color === 'red'):
    printRedBackground();
    break;
  case (typeof color === 'string' && color === 'blue'):
    printBlueBackground();
    break;
  case (typeof color === 'string' && color === 'green'):
    printGreenBackground();
    break;
  case (typeof color === 'string' && color === 'yellow'):
    printYellowBackground();
    break;
}

但我們必須避免在每一個條件下進行多次檢查,盡量避免使用switch。我們也必須考慮到最有效的方法是通過一個object。

var colorObj = {
  'black': printBlackBackground,
  'red': printRedBackground,
  'blue': printBlueBackground,
  'green': printGreenBackground,
  'yellow': printYellowBackground
};

if (color in colorObj) {
  colorObj[color]();
}

這里有更多相關的信息。

#02 - ReactJs 子級構造的keys是很重要的
keys是代表你需要傳遞給動態數組的所有組件的一個屬性。這是一個獨特的和指定的ID,react用它來標識每個DOM組件以用來知道這是個不同的組件或者是同一個組件。使用keys來確保子組件是可保存的並且不是再次創造的,並且防止怪異事情的產生。
· 使用已存在的一個獨立的對象值
· 定義父組件中的鍵,而不是子組件

//不好的
...
render() {
    <div key={{item.key}}>{{item.name}}</div>
}
...
//好的
<MyComponent key={{item.key}}/>

· 使用數組不是個好習慣
· random()從不會執行

//不好的
<MyComponent key={{Math.random()}}/>

· 你可以創建你的唯一id,請確保該方法是快速的並已經附加到對象上的
· 當子級的數量是龐大的或包含復雜的組件,使用keys來提高性能
· 你必須為所有的子級ReactCSSTransitionGroup提供key屬性

#01 - AngularJs: $digest vs $apply
AngularJs最讓人欣賞的特點是雙向數據綁定。為了是它工作,AngularJs評估模型的變化和視圖的循環($digest)。你需要了解這個概念,以便了解框架是如何在引擎中工作的。
Angular評估每時的每個事件的變化。這就是$digest循環。有時你必須手動觸發一次新的循環,你必須有正確的選擇,因為這個階段是性能方面表現出最具影響力的。
$apply
這個核心方法讓你來啟動一次$digest循環。這意味着所有的watch列表中的對象都將被檢查,整個應用程序啟動了$digest循環。在內部,執行可選的函數參數之后,調用$rootScope.$digest();
$digest
在這種情況下,$digest方法在當前作用域和它的子作用域執行,你應該注意到,父級的作用域將不被檢查,並沒有受到影響。
建議:
· 只在瀏覽器DOM事件在Angular之外被觸發的時候使用$apply或者$digest
· 給$apply傳遞函數表達式,這存在一個錯誤處理機制:允許在digest周期中整合變化。

$scope.$apply(() => {
    $scope.tip = 'Javascript Tip';
});

· 如果你僅僅想更新當前作用域或者他的子作用域,用$digest,並且防止整個應用程序的$digest。性能不言而喻咯。
· 當$apply有很多東西綁定時,這對機器來說是個艱難的過程,可能會導致性能問題。
· 如果你使用的是Angular 1.2.x以上的,使用$evalAsync。這是一個在當前循環或下一次循環的期間或對表達式做出評估的核心方法,這可以提高你的應用程序的性能。

#00 - 在數組插入一個項

將一個元素插入到現有數組中,是一個常見的任務,你可以使用push在數組的末尾添加元素,使用unshift在開始的位置,或者使用splice。

這些都是已知的方法,但這並不意味着沒有更高性能的實現方式。

在數組的末尾添加一個元素很容易與push(),但還有一個更高性能的途徑。

var arr = [1,2,3,4,5];
arr.push(6); //在Chrome (window 7)上提高了50%左右的速度
arr[arr.length] = 6; //在Chrome 47.0.2526.106 (Mac OS X 10.11.1)上提高了 43% 的速度

這兩種方法都修改了數組,不相信?看這個jsperf
現在,如果我們正在嘗試將一個項目添加到數組的開頭:

var arr = [1,2,3,4,5];
arr.unshift(0); //在Chrome (window 7)上提高了15%左右的速度
[0].concat(arr); //在Chrome 47.0.2526.106 (Mac OS X 10.11.1)上提高了 98% 的速度

這里更詳細一點:unshift編輯原有的數組,concat返回一個新數組。jsperf 
添加在陣列中的物品很容易使用splice,它是做它的最高效的方式。

var items = ['one', 'two', 'three', 'four'];
items.splice(items.length / 2, 0, 'hello');

          ### 2016-01-25 更新 ###          

未完待續...(該文章知識點如在github有更新,這邊每隔一小段時間也會做相對應的更新,原諒本獸時間不是那么充足)

本篇文章也會在微信號"shuang_lang_shuo"發表,同時大家也多支持破狼的處女作新書《AngularJS深度剖析與最佳實踐》。 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM