摘要:盲目使用替換后可能會導致預期意外的結果。在中,許多種方法來處理函數的參數默認值,參數數量,參數命名。此外,處理后的值,無論是解決還是拒絕的結果值,都是不可改變的。
這是一個 ES2015(ES6) 的Cheatsheet,其中包括提示、小技巧、最佳實踐和一些代碼片段,幫助你
完成日復一日的開發工作。
var 與 let / const 聲明
代碼執行塊替換立即執行函數
箭頭函數
字符串
解構
模塊
參數
類
Symbols
Maps
WeakMaps
Promises
Generators
Async Await
var versus let / const除了 var 以外,我們現在多了兩個新的標識符來聲明變量的存儲,它們就是 let 和 const。
不同于 var ,let 和 const 語句不會造成聲明提升。
一個 var 的例子:
var snack = "Meow Mix"; function getFood(food) { if (food) { var snack = "Friskies"; return snack; } return snack; } getFood(false); // undefined
讓我們再觀察下面語句中,使用 let 替換了 var 后的表現:
let snack = "Meow Mix"; function getFood(food) { if (food) { let snack = "Friskies"; return snack; } return snack; } getFood(false); // "Meow Mix"
當我們重構使用 var 的老代碼時,一定要注意這種變化。盲目使用 let 替換 var 后可能會導致預期意外的結果。
注意:let 和 const 是塊級作用域語句。所以在語句塊以外引用這些變量時,會造成引用錯誤 ReferenceError。
console.log(x); let x = "hi"; // ReferenceError: x is not defined
最佳實踐: 在重構老代碼時,var 聲明需要格外的注意。在創建一個新項目時,使用 let 聲明一個變量,使用 const 來聲明一個不可改變的常量。Replacing IIFEs with Blocks
我們以往創建一個 立即執行函數 時,一般是在函數最外層包裹一層括號。
ES6支持塊級作用域(更貼近其他語言),我們現在可以通過創建一個代碼塊(Block)來實現,不必通過創建一個函數來實現,
(function () { var food = "Meow Mix"; }()); console.log(food); // Reference Error
使用支持塊級作用域的ES6的版本:
{ let food = "Meow Mix"; } console.log(food); // Reference ErrorArrow Functions
一些時候,我們在函數嵌套中需要訪問上下文中的 this。比如下面的例子:
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; // Cannot read property "name" of undefined }); };
一種通用的方式是把上下文中的 this 保存在一個變量里:
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { var that = this; // Store the context of this return arr.map(function (character) { return that.name + character; }); };
我們也可以把 this 通過屬性傳進去:
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }, this); };
還可以直接使用 bind:
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }.bind(this)); };
使用 箭頭函數,this 的值不用我們再做如上幾段代碼的特殊處理,直接使用即可。
上面的代碼可以重寫為下面這樣:
function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(character => this.name + character); };
最佳實踐:使用箭頭函數,再也不用考慮 this 的問題了。
當我們編寫只返回一個表達式值的簡單函數時,也可以使用箭頭函數,如下:
var squares = arr.map(function (x) { return x * x }); // Function Expression
const arr = [1, 2, 3, 4, 5]; const squares = arr.map(x => x * x); // Arrow Function for terser implementation
最佳實踐:盡可能地多使用 箭頭函數。Strings
在ES6中,標準庫也被同樣增強了,像字符串對象就新增了 .includes() 和 .repeat() 方法。
.includes( )var string = "food"; var substring = "foo"; console.log(string.indexOf(substring) > -1);
現在,我們可以使用 .inclues() 方法,替代以往判斷內容 > -1 的方式。
.includes() 方法會極簡地返回一個布爾值結果。
const string = "food"; const substring = "foo"; console.log(string.includes(substring)); // true.repeat( )
function repeat(string, count) { var strings = []; while(strings.length < count) { strings.push(string); } return strings.join(""); }
在ES6中,我們可以使用一個極簡的方法來實現重復字符:
// String.repeat(numberOfRepetitions) "meow".repeat(3); // "meowmeowmeow"Template Literals
使用 字符串模板字面量,我可以在字符串中直接使用特殊字符,而不用轉義。
var text = "This string contains "double quotes" which are escaped.";
let text = `This string contains "double quotes" which don"t need to be escaped anymore.`;
字符串模板字面量 還支持直接插入變量,可以實現字符串與變量的直接連接輸出。
var name = "Tiger"; var age = 13; console.log("My cat is named " + name + " and is " + age + " years old.");
更簡單的版本:
const name = "Tiger"; const age = 13; console.log(`My cat is named ${name} and is ${age} years old.`);
ES5中,我們要這樣生成多行文本:
var text = ( "cat " + "dog " + "nickelodeon" );
或者:
var text = [ "cat", "dog", "nickelodeon" ].join(" ");
字符串模板字面量 讓我們不必特別關注多行字符串中的換行轉義符號,直接換行即可:
let text = ( `cat dog nickelodeon` );
字符串模板字面量 內部可以使用表達式,像這樣:
let today = new Date(); let text = `The time and date is ${today.toLocaleString()}`;Destructuring
解構讓我們可以使用非常便捷的語法,直接將數組或者對象中的值直接分別導出到多個變量中,
Destructuring Arrays解構數組
var arr = [1, 2, 3, 4]; var a = arr[0]; var b = arr[1]; var c = arr[2]; var d = arr[3];
let [a, b, c, d] = [1, 2, 3, 4]; console.log(a); // 1 console.log(b); // 2Destructuring Objects
解構對象
var luke = { occupation: "jedi", father: "anakin" }; var occupation = luke.occupation; // "jedi" var father = luke.father; // "anakin"
let luke = { occupation: "jedi", father: "anakin" }; let {occupation, father} = luke; console.log(occupation); // "jedi" console.log(father); // "anakin"Modules
ES6之前,瀏覽器端的模塊化代碼,我們使用像Browserify這樣的庫,
在 Node.js 中,我們則使用 require。
在ES6中,我們現在可以直接使用AMD 和 CommonJS這些模塊了。
module.exports = 1; module.exports = { foo: "bar" }; module.exports = ["foo", "bar"]; module.exports = function bar () {};Exporting in ES6
在ES6中,提供了多種設置模塊出口的方式,比如我們要導出一個變量,那么使用 變量名 :
export let name = "David"; export let age = 25;??
還可以為對象 導出一個列表:
function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } export { sumTwo, sumThree };
我們也可以使用簡單的一個 export 關鍵字來導出一個結果值:
export function sumTwo(a, b) { return a + b; } export function sumThree(a, b, c) { return a + b + c; }
最后,我們可以 導出一個默認出口:
function sumTwo(a, b) { return a + b; } function sumThree(a, b, c) { return a + b + c; } let api = { sumTwo, sumThree }; export default api;
最佳實踐:總是在模塊的 最后 使用 export default 方法。Importing in ES6
它讓模塊的出口更清晰明了,節省了閱讀整個模塊來尋找出口的時間。
更多的是,在大量CommonJS模塊中,通用的習慣是設置一個出口值或者出口對象。
最受這個規則,可以讓我們的代碼更易讀,且更方便的聯合使用CommonJS和ES6模塊。
ES6提供了好幾種模塊的導入方式。我們可以多帶帶引入一個文件:
import "underscore";
這里需要注意的是, 整個文件的引入方式會執行該文件內的最上層代碼。
就像Python一樣,我們還可以命名引用:
import { sumTwo, sumThree } from "math/addition";
我們甚至可以使用 as 給這些模塊重命名:
import { sumTwo as addTwoNumbers, sumThree as sumThreeNumbers } from "math/addition";
另外,我們能 引入所有的東西(原文:import all the things) (也稱為命名空間引入)
import * as util from "math/addition";
最后,我們能可以從一個模塊的眾多值中引入一個列表:
import * as additionUtil from "math/addtion"; const { sumTwo, sumThree } = additionUtil;
像這樣引用默認對象:
import api from "math/addition"; // Same as: import { default as api } from "math/addition";
我們建議一個模塊導出的值應該越簡潔越好,不過有時候有必要的話命名引用和默認引用可以混著用。如果一個模塊是這樣導出的:
// foos.js export { foo as default, foo1, foo2 };
那我們可以如此導入這個模塊的值:
import foo, { foo1, foo2 } from "foos";
我們還可以導入commonjs模塊,例如React:
import React from "react"; const { Component, PropTypes } = React;
更簡化版本:
import React, { Component, PropTypes } from "react";
注意:被導出的值是被 綁定的(原文:bingdings),而不是引用。Parameters
所以,改變一個模塊中的值的話,會影響其他引用本模塊的代碼,一定要避免此種改動發生。
在ES5中,許多種方法來處理函數的 參數默認值(default values),參數數量(indefinite arguments),參數命名(named parameters)。
ES6中,我們可以使用非常簡潔的語法來處理上面提到的集中情況。
function addTwoNumbers(x, y) { x = x || 0; y = y || 0; return x + y; }
ES6中,我們可以簡單為函數參數啟用默認值:
function addTwoNumbers(x=0, y=0) { return x + y; }
addTwoNumbers(2, 4); // 6 addTwoNumbers(2); // 2 addTwoNumbers(); // 0Rest Parameters
ES5中,遇到參數數量不確定時,我們只能如此處理:
function logArguments() { for (var i=0; i < arguments.length; i++) { console.log(arguments[i]); } }
使用 rest 操作符,我們可以給函數傳入一個不確定數量的參數列表:
function logArguments(...args) { for (let arg of args) { console.log(arg); } }Named Parameters
命名函數
ES5中,當我們要處理多個 命名參數 時,通常會傳入一個 選項對象 的方式,這種方式被jQuery采用。
function initializeCanvas(options) { var height = options.height || 600; var width = options.width || 400; var lineStroke = options.lineStroke || "black"; }
我們可以利用上面提到的新特性 解構 ,來完成與上面同樣功能的函數:
We can achieve the same functionality using destructuring as a formal parameter
to a function:
function initializeCanvas( { height=600, width=400, lineStroke="black"}) { // ... } // Use variables height, width, lineStroke here
如果我們需要把這個參數變為可選的,那么只要把該參數解構為一個空對象就好了:
function initializeCanvas( { height=600, width=400, lineStroke="black"} = {}) { // ... }Spread Operator
我們可以利用展開操作符(Spread Operator)來把一組數組的值,當作參數傳入:
Math.max(...[-1, 100, 9001, -32]); // 9001Classes
在ES6以前,我們實現一個類的功能的話,需要首先創建一個構造函數,然后擴展這個函數的原型方法,就像這樣:
function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } Person.prototype.incrementAge = function () { return this.age += 1; };
繼承父類的子類需要這樣:
function Personal(name, age, gender, occupation, hobby) { Person.call(this, name, age, gender); this.occupation = occupation; this.hobby = hobby; } Personal.prototype = Object.create(Person.prototype); Personal.prototype.constructor = Personal; Personal.prototype.incrementAge = function () { return Person.prototype.incrementAge.call(this) += 20; };
ES6提供了一些語法糖來實現上面的功能,我們可以直接創建一個類:
class Person { constructor(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } incrementAge() { this.age += 1; } }
繼承父類的子類只要簡單的使用 extends 關鍵字就可以了:
class Personal extends Person { constructor(name, age, gender, occupation, hobby) { super(name, age, gender); this.occupation = occupation; this.hobby = hobby; } incrementAge() { super.incrementAge(); this.age += 20; console.log(this.age); } }
最佳實踐:ES6新的類語法把我們從晦澀難懂的實現和原型操作中解救出來,這是個非常適合初學者的功能,而且能讓我們寫出更干凈整潔的代碼。Symbols
符號(Symbols)在ES6版本之前就已經存在了,但現在我們擁有一個公共的接口來直接使用它們。
Symbols對象是一旦創建就不可以被更改的(immutable)而且能被用做hash數據類型中的鍵。
調用 Symbol() 或者 Symbol(描述文本) 會創建一個唯一的、在全局中不可以訪問的符號對象。
一個 Symbol() 的應用場景是:在自己的項目中使用第三方代碼庫,且你需要給他們的對象或者命名空間打補丁代碼,又不想改動或升級第三方原有代碼的時候。
舉個例子,如果你想給 React.Component 這個類添加一個 refreshComponent 方法,但又確定不了這個方法會不會在下個版本中加入,你可以這么做:
const refreshComponent = Symbol(); React.Component.prototype[refreshComponent] = () => { // do something }Symbol.for(key)
使用 Symbol.for(key) 也是會創建一個不可改變的Symbol對象,但區別于上面的創建方法,這個對象是在全局中可以被訪問到的。
調用兩次 Symbol.for(key) 會返回相同的Symbol實例。
提示:這并不同于 Symbol(description)。
Symbol("foo") === Symbol("foo") // false Symbol.for("foo") === Symbol("foo") // false Symbol.for("foo") === Symbol.for("foo") // true
一個Symbols常用的使用場景,是需要使用特別 Symbol.for(key) 方法來實現代碼間的協作。
這能讓你在你的代碼中,查找包含已知的接口的第三方代碼中Symbol成員。(譯者:這句話好難翻。。。原文:This can be
achieved by having your code look for a Symbol member on object arguments from third parties that contain some known interface. )舉個例子:
function reader(obj) { const specialRead = Symbol.for("specialRead"); if (obj[specialRead]) { const reader = obj[specialRead](); // do something with reader } else { throw new TypeError("object cannot be read"); } }
之后在另一個庫中:
const specialRead = Symbol.for("specialRead"); class SomeReadableType { [specialRead]() { const reader = createSomeReaderFrom(this); return reader; } }
注意:Symbol.iterable 在ES6中像其他可枚舉的對象,如數組,字符串,generators一樣,當這個方法被調用時會激活一個枚舉器并返回一個對象。Maps
Maps 是一個Javascript中很重要(迫切需要)的數據結構。
在ES6之前,我們創建一個 hash 通常是使用一個對象:
var map = new Object(); map[key1] = "value1"; map[key2] = "value2";
但是,這樣的代碼無法避免函數被特別的屬性名覆蓋的意外情況:
getOwnProperty({ hasOwnProperty: "Hah, overwritten"}, "Pwned"); TypeError: Property "hasOwnProperty" is not a function
Maps 讓我們使用 set,get 和 search 操作數據。
let map = new Map(); map.set("name", "david"); map.get("name"); // david map.has("name"); // true
Maps最強大的地方在于我們不必只能使用字符串來做key了,現在可以使用任何類型來當作key,而且key不會被強制類型轉換為字符串。
let map = new Map([ ["name", "david"], [true, "false"], [1, "one"], [{}, "object"], [function () {}, "function"] ]); for (let key of map.keys()) { console.log(typeof key); // > string, boolean, number, object, function }
提示:當使用 map.get() 判斷值是否相等時,非基礎類型比如一個函數或者對象,將不會正常工作。
有鑒于此,還是建議使用字符串,布爾和數字類型的數據類型。
我們還可以使用 .entries() 方法來遍歷整個map對象:
for (let [key, value] of map.entries()) { console.log(key, value); }WeakMaps
在ES5之前的版本,我們為了存儲私有數據,有好幾種方法。像使用這種下劃線命名約定:
class Person { constructor(age) { this._age = age; } _incrementAge() { this._age += 1; } }
在一個開源項目中,命名規則很難維持得一直很好,這樣經常會造成一些困擾。
此時,我們可以選擇使用WeakMaps來替代Maps來存儲我們的數據:
let _age = new WeakMap(); class Person { constructor(age) { _age.set(this, age); } incrementAge() { let age = _age.get(this) + 1; _age.set(this, age); if (age > 50) { console.log("Midlife crisis"); } } }
使用WeakMaps來保存我們私有數據的理由之一是不會暴露出屬性名,就像下面的例子中的 Reflect.ownKeys():
const person = new Person(50); person.incrementAge(); // "Midlife crisis" Reflect.ownKeys(person); // []
一個使用WeakMaps存儲數據更實際的例子,就是有關于一個DOM元素和對該DOM元素(有污染)地操作:
let map = new WeakMap(); let el = document.getElementById("someElement"); // Store a weak reference to the element with a key map.set(el, "reference"); // Access the value of the element let value = map.get(el); // "reference" // Remove the reference el.parentNode.removeChild(el); el = null; value = map.get(el); // undefined
上面的例子中,一個對象被垃圾回收期給銷毀了,WeakMaps會自動的把自己內部所對應的鍵值對數據同時銷毀。
提示:結合這個例子,再考慮下jQuery是如何實現緩存帶有引用的DOM元素這個功能的,使用了WeakMaps的話,當被緩存的DOM元素被移除的時,jQuery可以自動釋放相應元素的內存。Promises
通常情況下,在涉及DOM元素存儲和緩存的情況下,使用WeakMaps是非常適合的。
Promises讓我們讓我們多縮進難看的代碼(回調地獄):
func1(function (value1) { func2(value1, function (value2) { func3(value2, function (value3) { func4(value3, function (value4) { func5(value4, function (value5) { // Do something with value 5 }); }); }); }); });
寫成這樣:
func1(value1) .then(func2) .then(func3) .then(func4) .then(func5, value5 => { // Do something with value 5 });
在ES6之前,我們使用bluebird 或者
Q。現在我們有了原生版本的 Promises:
new Promise((resolve, reject) => reject(new Error("Failed to fulfill Promise"))) .catch(reason => console.log(reason));
這里有兩個處理函數,resolve(當Promise執行成功完畢時調用的回調函數) 和 reject (當Promise執行不接受時調用的回調函數)
Promises的好處:大量嵌套錯誤回調函數會使代碼變得難以閱讀理解。
使用了Promises,我們可以讓我們代碼變得更易讀,組織起來更合理。
此外,Promise處理后的值,無論是解決還是拒絕的結果值,都是不可改變的。
下面是一些使用Promises的實際例子:
var fetchJSON = function(url) { return new Promise((resolve, reject) => { $.getJSON(url) .done((json) => resolve(json)) .fail((xhr, status, err) => reject(status + err.message)); }); };
我們還可以使用 Promise.all() 來異步的 并行 處理一個數組的數據。
var urls = [ "http://www.api.com/items/1234", "http://www.api.com/items/4567" ]; var urlPromises = urls.map(fetchJSON); Promise.all(urlPromises) .then(function (results) { results.forEach(function (data) { }); }) .catch(function (err) { console.log("Failed: ", err); });Generators
就像Promises如何讓我們避免回調地獄一樣,Generators也可以使我們的代碼扁平化,同時給予我們開發者像開發同步代碼一樣的感覺來寫異步代碼。Generators本質上是一種支持的函數,隨后返回表達式的值。
Generators實際上是支持暫停運行,隨后根據上一步的返回值再繼續運行的一種函數。
下面代碼是一個使用generators函數的簡單例子:
function* sillyGenerator() { yield 1; yield 2; yield 3; yield 4; } var generator = sillyGenerator(); console.log(generator.next()); // { value: 1, done: false } console.log(generator.next()); // { value: 2, done: false } console.log(generator.next()); // { value: 3, done: false } console.log(generator.next()); // { value: 4, done: false }
就像上面的例子,當next運行時,它會把我們的generator向前“推動”,同時執行新的表達式。
我們能利用Generators來像書寫同步代碼一樣書寫異步代碼。
// Hiding asynchronousity with Generators function request(url) { getJSON(url, function(response) { generator.next(response); }); }
這里我們寫個generator函數將要返回我們的數據:
function* getData() { var entry1 = yield request("http://some_api/item1"); var data1 = JSON.parse(entry1); var entry2 = yield request("http://some_api/item2"); var data2 = JSON.parse(entry2); }
借助于 yield,我們可以保證 entry1 確實拿到數據并轉換后再賦值給 data1。
當我們使用generators來像書寫同步代碼一樣書寫我們的異步代碼邏輯時,沒有一種清晰簡單的方式來處理期間可能會產生的錯誤或者異常。在這種情況下,我們可以在我們的generator中引入Promises來處理,就像下面這樣:
function request(url) { return new Promise((resolve, reject) => { getJSON(url, resolve); }); }
我們再寫一個函數,其中使用 next 來步進我們的generator的同事,再利用我們上面的 request 方法來產生(yield)一個Promise。
function iterateGenerator(gen) { var generator = gen(); var ret; (function iterate(val) { ret = generator.next(); if(!ret.done) { ret.value.then(iterate); } })(); }
在Generator中引入了Promises后,我們就可以通過Promise的 .catch 和 reject 來捕捉和處理錯誤了。
使用了我們新版的Generator后,新版的調用就像老版本一樣簡單可讀(譯者注:有微調):
iterateGenerator(function* getData() { var entry1 = yield request("http://some_api/item1"); var data1 = JSON.parse(entry1); var entry2 = yield request("http://some_api/item2"); var data2 = JSON.parse(entry2); });
在使用Generator后,我們可以重用我們的老版本代碼實現,以此展示了Generator的力量。
當使用Generators和Promises后,我們可以像書寫同步代碼一樣書寫異步代碼的同時優雅地解決了錯誤處理問題。
此后,我們實際上可以開始利用更簡單的一種方式了,它就是async-await。
async await 隨著ES2016版本就要發布了,它給我們提供了一種更輕松的、更簡單的可以替代的實現上面 Generators 配合 Promises 組合代碼的一種編碼方式,讓我們來看看例子:
var request = require("request"); function getJSON(url) { return new Promise(function(resolve, reject) { request(url, function(error, response, body) { resolve(body); }); }); } async function main() { var data = await getJSON(); console.log(data); // NOT undefined! } main();
不斷更新、修復...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/107929.html
摘要:下面我們從前端基礎和底層原理開始講起。對于和這三個對應于矢量圖位圖和圖的渲染來說,給前端開發帶來了重武器,很多小游戲也因此蓬勃發展。這篇文章受眾之大,后來被人重新整理并發布為,其中還包括中文版。 showImg(https://segmentfault.com/img/bVbjM5r?w=1142&h=640); 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! 這...
摘要:編程書籍的整理和收集最近一直在學習深度學習和機器學習的東西,發現深入地去學習就需要不斷的去提高自己算法和高數的能力然后也找了很多的書和文章,隨著不斷的學習,也整理了下自己的學習筆記準備分享出來給大家后續的文章和總結會繼續分享,先分享一部分的 編程書籍的整理和收集 最近一直在學習deep learning深度學習和機器學習的東西,發現深入地去學習就需要不斷的去提高自己算法和高數的能力然后...
閱讀 920·2021-11-25 09:43
閱讀 1293·2021-11-17 09:33
閱讀 3010·2019-08-30 15:44
閱讀 3310·2019-08-29 17:16
閱讀 480·2019-08-28 18:20
閱讀 1636·2019-08-26 13:54
閱讀 553·2019-08-26 12:14
閱讀 2174·2019-08-26 12:14