let 取代 var,在let和const之間,建議優先使用const,尤爲是在全局環境,不該該設置變量,只應設置常量。es6
靜態字符串一概使用單引號或反引號,不使用雙引號。動態字符串使用反引號。web
// good const a = 'foobar'; const b = `foo${a}bar`; const c = 'foobar';
使用數組成員對變量賦值時,優先使用解構賦值。編程
const arr = [1, 2, 3, 4]; // bad const first = arr[0]; const second = arr[1]; // good const [first, second] = arr;
函數的參數若是是對象的成員,優先使用解構賦值。canvas
// bad function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; } // good function getFullName(obj) { const { firstName, lastName } = obj; } // best function getFullName({ firstName, lastName }) { }
若是函數返回多個值,優先使用對象的解構賦值,而不是數組的解構賦值。這樣便於之後添加返回值,以及更改返回值的順序。數組
單行定義的對象,最後一個成員不以逗號結尾。多行定義的對象,最後一個成員以逗號結尾。瀏覽器
// good const a = { k1: v1, k2: v2 }; const b = { k1: v1, k2: v2, };
對象儘可能靜態化,一旦定義,就不得隨意添加新的屬性。若是添加屬性不可避免,要使用Object.assign方法。服務器
// bad const a = {}; a.x = 3; // if reshape unavoidable const a = {}; Object.assign(a, { x: 3 }); // good const a = { x: null }; a.x = 3;
對象的屬性和方法,儘可能採用簡潔表達法,這樣易於描述和書寫。網絡
var ref = 'some value'; // bad const atom = { ref: ref, value: 1, addValue: function (value) { return atom.value + value; }, }; // good const atom = { ref, value: 1, addValue(value) { return atom.value + value; }, };
使用擴展運算符(...)拷貝數組。使用Array.from方法,將相似數組的對象轉爲數組。數據結構
當即執行函數能夠寫成箭頭函數的形式。app
(() => { console.log('Welcome to the Internet.'); })();
那些須要使用函數表達式的場合,儘可能用箭頭函數代替
。由於這樣更簡潔,並且綁定了this。簡單的、單行的、不會複用的函數,建議採用箭頭函數。若是函數體較爲複雜,行數較多,仍是應該採用傳統的函數寫法。
不要在函數體內使用arguments變量,使用rest運算符(...)代替。使用默認值語法設置函數參數的默認值。
// bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); }
只有模擬現實世界的實體對象時,才使用Object。若是隻是須要key: value的數據結構,使用Map結構。由於Map有內建的遍歷機制。
老是用Class,取代須要prototype的操做。使用extends實現繼承。
堅持使用Module語法,使用import和export。若是模塊只有一個輸出值,就使用export default,若是模塊有多個輸出值,就不使用export default,export default與普通的export不要同時使用。
若是模塊默認輸出一個函數,函數名的首字母應該小寫。若是模塊默認輸出一個對象,對象名的首字母應該大寫。
ESLint是一個語法規則和代碼風格的檢查工具,能夠用來保證寫出語法正確、風格統一的代碼。
二進制數組
(ArrayBuffer對象、TypedArray視圖和DataView視圖)是 JavaScript 操做二進制數據的一個接口。它並非真正的數組,而是相似數組的對象
。
二進制數組由三類對象組成:ArrayBuffer對象、TypedArray視圖和DataView視圖。
ArrayBuffer對象表明原始的二進制數據,TypedArray視圖用來讀寫簡單類型的二進制數據,DataView視圖用來讀寫複雜類型的二進制數據。
TypedArray視圖支持的數據類型一共有9種(DataView視圖支持除Uint8C之外的其餘8種)。
不少瀏覽器操做的API,用到了二進制數組操做二進制數據,下面是其中的幾個。
ArrayBuffer對象表明儲存二進制數據的一段內存,它不能直接讀寫,只能經過視圖(TypedArray視圖和DataView視圖)來讀寫,視圖的做用是以指定格式解讀二進制數據。
ArrayBuffer也是一個構造函數,能夠分配一段能夠存放數據的連續內存區域。
var buf = new ArrayBuffer(32);//生成了一段32字節的內存區域,每一個字節的值默認都是0 //DataView視圖的建立,須要提供ArrayBuffer對象實例做爲參數。 var dataView = new DataView(buf); dataView.getUint8(0) //0
TypedArray視圖,與DataView視圖的一個區別是,它不是一個構造函數,而是一組構造函數,表明不一樣的數據格式。
var buffer = new ArrayBuffer(12); var x1 = new Int32Array(buffer); x1[0] = 1; var x2 = new Uint8Array(buffer); x2[0] = 2; x1[0] // 2
上面代碼對同一段內存,分別創建兩種視圖:32位帶符號整數(Int32Array構造函數)和8位不帶符號整數(Uint8Array構造函數)。因爲兩個視圖對應的是同一段內存,一個視圖修改底層內存,會影響到另外一個視圖。
TypedArray視圖的構造函數,除了接受ArrayBuffer實例做爲參數,還能夠接受普通數組做爲參數,直接分配內存生成底層的ArrayBuffer實例,並同時完成對這段內存的賦值。
var typedArray = new Uint8Array([0,1,2]); typedArray.length // 3 typedArray[0] = 5; typedArray // [5, 1, 2]
ArrayBuffer.prototype.byteLength返回所分配的內存區域的字節長度。若是要分配的內存區域很大,有可能分配失敗(由於沒有那麼多的連續空餘內存),因此有必要檢查是否分配成功。
var buffer = new ArrayBuffer(32); if (buffer.byteLength === 32) { // 成功 } else { // 失敗 }
ArrayBuffer.prototype.slice()容許將內存區域的一部分,拷貝生成一個新的ArrayBuffer對象。slice方法接受兩個參數,第一個參數表示拷貝開始的字節序號(含該字節),第二個參數表示拷貝截止的字節序號(不含該字節)。若是省略第二個參數,則默認到原ArrayBuffer對象的結尾。
除了slice方法,ArrayBuffer對象不提供任何直接讀寫內存的方法,只容許在其上方創建視圖,而後經過視圖讀寫。
ArrayBuffer.isView() 返回一個布爾值,表示參數是否爲TypedArray實例或DataView實例。
ArrayBuffer對象做爲內存區域,能夠存放多種類型的數據。同一段內存,不一樣數據有不一樣的解讀方式,這就叫作「視圖」(view)。ArrayBuffer有兩種視圖,一種是TypedArray視圖,另外一種是DataView視圖。前者的數組成員都是同一個數據類型,後者的數組成員能夠是不一樣的數據類型。
目前,TypedArray視圖一共包括9種類型,每一種視圖都是一種構造函數。
這9個構造函數生成的數組,統稱爲TypedArray視圖。它們很像普通數組,都有length屬性,都能用方括號運算符([])獲取單個元素,全部數組的方法,在它們上面都能使用。
普通數組與TypedArray數組的差別:
構造函數有多種用法。
TypedArray(buffer, byteOffset=0, length?):
第一個參數(必需):視圖對應的底層ArrayBuffer對象。第二個參數(可選):視圖開始的字節序號,默認從0開始。第三個參數(可選):視圖包含的數據個數,默認直到本段內存區域結束。
同一個ArrayBuffer對象之上,能夠根據不一樣的數據類型,創建多個視圖。
// 建立一個8字節的ArrayBuffer var b = new ArrayBuffer(8); // 建立一個指向b的Int32視圖,開始於字節0,直到緩衝區的末尾 var v1 = new Int32Array(b); // 建立一個指向b的Uint8視圖,開始於字節2,直到緩衝區的末尾 var v2 = new Uint8Array(b, 2); // 建立一個指向b的Int16視圖,開始於字節2,長度爲2 var v3 = new Int16Array(b, 2, 2);
上面代碼在一段長度爲8個字節的內存(b)之上,生成了三個視圖:v一、v2和v3。v一、v2和v3是重疊的:v1[0]是一個32位整數,指向字節0~字節3;v2[0]是一個8位無符號整數,指向字節2;v3[0]是一個16位整數,指向字節2~字節3。只要任何一個視圖對內存有所修改,就會在另外兩個視圖上反應出來。
byteOffset必須與所要創建的數據類型一致,不然會報錯。
var buffer = new ArrayBuffer(8); var i16 = new Int16Array(buffer, 1); // Uncaught RangeError: start offset of Int16Array should be a multiple of 2
上面代碼中,新生成一個8個字節的ArrayBuffer對象,而後在這個對象的第一個字節,創建帶符號的16位整數視圖,結果報錯。由於,帶符號的16位整數須要兩個字節,因此byteOffset參數必須可以被2整除。
若是想從任意字節開始解讀ArrayBuffer對象,必須使用DataView視圖,由於TypedArray視圖只提供9種固定的解讀格式。
TypedArray(length):
視圖還能夠不經過ArrayBuffer對象,直接分配內存而生成。
var f64a = new Float64Array(8);//生成一個8個成員的Float64Array數組(共64字節) f64a[0] = 10;
上面代碼生成一個8個成員的Float64Array數組(共64字節),而後對成員賦值。這時,視圖構造函數的參數就是成員的個數。能夠看到,視圖數組的賦值操做與普通數組的操做毫無兩樣。
TypedArray(typedArray):
TypedArray數組的構造函數,能夠接受另外一個TypedArray實例做爲參數。
var typedArray = new Int8Array(new Float64Array(5)); //Int8Array構造函數接受一個Uint8Array實例做爲參數。 typedArray.byteLength //5
上面代碼中生成的新數組,只是複製了參數數組的值,對應的底層內存是不同的。新數組會開闢一段新的內存儲存數據,不會在原數組的內存之上創建視圖。
若是想基於同一段內存,構造不一樣的視圖,能夠採用下面的寫法。
var x = new Int8Array([1, 1]); var y = new Int8Array(x.buffer); x[0] // 1 y[0] // 1 x[0] = 2; y[0] // 2
TypedArray(arrayLikeObject):
構造函數的參數也能夠是一個普通數組,而後直接生成TypedArray實例。
var typedArray = new Uint8Array([1, 2, 3, 4]);
這時TypedArray視圖會從新開闢內存,不會在原數組的內存上創建視圖。
TypedArray數組也能夠轉換回普通數組。
var normalArray = Array.prototype.slice.call(typedArray);
普通數組的操做方法和屬性,對TypedArray數組徹底適用(除了concat方法,由於TypedArray數組沒有concat方法)。另外,TypedArray數組與普通數組同樣,部署了Iterator接口,因此能夠被遍歷。
字節序指的是數值在內存中的表示方式。
var buffer = new ArrayBuffer(16); var int32View = new Int32Array(buffer); for (var i = 0; i < int32View.length; i++) { int32View[i] = i * 2; } //上面代碼生成一個16字節的ArrayBuffer對象,而後在它的基礎上,創建了一個32位整數的視圖。因爲每一個32位整數佔據4個字節,因此一共能夠寫入4個整數,依次爲0,2,4,6。 //在這段數據上接着創建一個16位整數的視圖,則能夠讀出徹底不同的結果。 var int16View = new Int16Array(buffer); for (var i = 0; i < int16View.length; i++) { console.log("Entry " + i + ": " + int16View[i]); } // Entry 0: 0 // Entry 1: 0 // Entry 2: 2 // Entry 3: 0 // Entry 4: 4 // Entry 5: 0 // Entry 6: 6 // Entry 7: 0
因爲每一個16位整數佔據2個字節,因此整個ArrayBuffer對象如今分紅8段。而後,因爲x86體系的計算機都採用小端字節序(little endian),相對重要的字節排在後面的內存地址,相對不重要字節排在前面的內存地址,因此就獲得了上面的結果。
好比,一個佔據四個字節的16進制數0x12345678,決定其大小的最重要的字節是「12」,最不重要的是「78」。小端字節序將最不重要的字節排在前面,儲存順序就是78563412;大端字節序則徹底相反,將最重要的字節排在前面,儲存順序就是12345678。目前,全部我的電腦幾乎都是小端字節序,因此TypedArray數組內部也採用小端字節序讀寫數據。
與普通數組相比,TypedArray數組的最大優勢就是能夠直接操做內存,不須要數據類型轉換,因此速度快得多。
每一種視圖的構造函數,都有一個BYTES_PER_ELEMENT屬性,表示這種數據類型佔據的字節數。
Int8Array.BYTES_PER_ELEMENT // 1 Uint8Array.BYTES_PER_ELEMENT // 1 Int16Array.BYTES_PER_ELEMENT // 2 Uint16Array.BYTES_PER_ELEMENT // 2 Int32Array.BYTES_PER_ELEMENT // 4 Uint32Array.BYTES_PER_ELEMENT // 4 Float32Array.BYTES_PER_ELEMENT // 4 Float64Array.BYTES_PER_ELEMENT // 8
這個屬性在TypedArray實例上也能獲取,即有TypedArray.prototype.BYTES_PER_ELEMENT。
ArrayBuffer轉爲字符串,或者字符串轉爲ArrayBuffer,有一個前提,即字符串的編碼方法是肯定的。假定字符串採用UTF-16編碼(JavaScript的內部編碼方式),能夠本身編寫轉換函數。
// ArrayBuffer轉爲字符串,參數爲ArrayBuffer對象 function ab2str(buf) { return String.fromCharCode.apply(null, new Uint16Array(buf)); } // 字符串轉爲ArrayBuffer對象,參數爲字符串 function str2ab(str) { var buf = new ArrayBuffer(str.length * 2); // 每一個字符佔用2個字節 var bufView = new Uint16Array(buf); for (var i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; }
不一樣的視圖類型,所能容納的數值範圍是肯定的。超出這個範圍,就會出現溢出。好比,8位視圖只能容納一個8位的二進制值,若是放入一個9位的值,就會溢出。
TypedArray數組的溢出處理規則,簡單來講,就是拋棄溢出的位,而後按照視圖類型進行解釋。
var uint8 = new Uint8Array(1); uint8[0] = 256; uint8[0] // 0 uint8[0] = -1; uint8[0] // 255
上面代碼中,uint8是一個8位視圖,而256的二進制形式是一個9位的值100000000,這時就會發生溢出。根據規則,只會保留後8位,即00000000。uint8視圖的解釋規則是無符號的8位整數,因此00000000就是0。
負數在計算機內部採用「2的補碼」表示,也就是說,將對應的正數值進行否運算,而後加1。好比,-1對應的正值是1,進行否運算之後,獲得11111110,再加上1就是補碼形式11111111。uint8按照無符號的8位整數解釋11111111,返回結果就是255。
一個簡單轉換規則,能夠這樣表示。
正向溢出(overflow):當輸入值大於當前數據類型的最大值,結果等於當前數據類型的最小值加上餘值,再減去1。
負向溢出(underflow):當輸入值小於當前數據類型的最小值,結果等於當前數據類型的最大值減去餘值,再加上1。
上面的「餘值」就是模運算的結果,即 JavaScript 裏面的%運算符的結果。
var int8 = new Int8Array(1); int8[0] = 128; int8[0] // -128 int8[0] = -129; int8[0] // 127
上面例子中,int8是一個帶符號的8位整數視圖,它的最大值是127,最小值是-128。輸入值爲128時,至關於正向溢出1,根據「最小值加上餘值(128除以127的餘值是1),再減去1」的規則,就會返回-128;輸入值爲-129時,至關於負向溢出1,根據「最大值減去餘值(-129除以-128的餘值是1),再加上1」的規則,就會返回127。
Uint8ClampedArray視圖的溢出規則,與上面的規則不一樣。它規定,凡是發生正向溢出,該值一概等於當前數據類型的最大值,即255;若是發生負向溢出,該值一概等於當前數據類型的最小值,即0。
TypedArray.prototype.buffer返回整段內存區域對應的ArrayBuffer對象。該屬性爲只讀屬性。
var a = new Float32Array(64); var b = new Uint8Array(a.buffer);
上面代碼的a視圖對象和b視圖對象,對應同一個ArrayBuffer對象,即同一段內存。
byteLength屬性返回TypedArray數組佔據的內存長度,單位爲字節。byteOffset屬性返回TypedArray數組從底層ArrayBuffer對象的哪一個字節開始。這兩個屬性都是隻讀屬性。
length屬性表示TypedArray數組含有多少個成員。注意將byteLength屬性和length屬性區分,前者是字節總長度,後者是成員長度。
var a = new Int16Array(4); a.length // 4 a.byteLength // 8
TypedArray數組的set方法用於複製數組(普通數組或TypedArray數組),也就是將一段內容徹底複製到另外一段內存。set方法的第二個參數,表示從b對象的哪個成員開始複製a對象。
var a = new Uint16Array(8); var b = new Uint16Array(10); b.set(a, 2) //從b[2]開始,將複製a數組的內容到b數組,它是整段內存的複製,比一個個拷貝成員的那種複製快得多。
subarray方法是對於TypedArray數組的一部分,再創建一個新的視圖。方法的第一個參數是起始的成員序號,第二個參數是結束的成員序號(不含該成員),若是省略則包含剩餘的所有成員。
TypeArray實例的slice方法,能夠返回一個指定位置的新的TypedArray實例。slice方法的參數,表示原數組的具體位置,開始生成新數組。負值表示逆向的位置,即-1爲倒數第一個位置,-2表示倒數第二個位置,以此類推。
TypedArray數組的全部構造函數,都有一個靜態方法of,用於將參數轉爲一個TypedArray實例。
下面三種方法都會生成一樣一個TypedArray數組。
// 方法一 let tarr = new Uint8Array([1,2,3]); // 方法二 let tarr = Uint8Array.of(1,2,3); // 方法三 let tarr = new Uint8Array(3); tarr[0] = 1; tarr[1] = 2; tarr[2] = 3;
靜態方法from接受一個可遍歷的數據結構(好比數組)做爲參數,返回一個基於這個結構的TypedArray實例。
from方法還能夠接受一個函數,做爲第二個參數,用來對每一個元素進行遍歷,功能相似map方法。
Int8Array.of(127, 126, 125).map(x => 2 * x) // Int8Array [ -2, -4, -6 ] Int16Array.from(Int8Array.of(127, 126, 125), x => 2 * x) // Int16Array [ 254, 252, 250 ]
上面的例子中,from方法沒有發生溢出,這說明遍歷不是針對原來的8位整數數組。也就是說,from會將第一個參數指定的TypedArray數組,拷貝到另外一段內存之中,處理以後再將結果轉成指定的數組格式。
因爲視圖的構造函數能夠指定起始位置和長度,因此在同一段內存之中,能夠依次存放不一樣類型的數據,這叫作「複合視圖」。
var buffer = new ArrayBuffer(24); var idView = new Uint32Array(buffer, 0, 1); var usernameView = new Uint8Array(buffer, 4, 16); var amountDueView = new Float32Array(buffer, 20, 1);
上面代碼將一個24字節長度的ArrayBuffer對象,分紅三個部分:
一段數據包括多種類型可經過創建ArrayBuffer對象的複合視圖或DataView視圖進行操做。
ArrayBuffer對象的各類TypedArray視圖,是用來向網卡、聲卡之類的本機設備傳送數據,因此使用本機的字節序就能夠了;而DataView視圖的設計目的,是用來處理網絡設備傳來的數據,因此大端字節序或小端字節序是能夠自行設定的。
DataView視圖自己也是構造函數,接受一個ArrayBuffer對象做爲參數,生成視圖。
DataView(ArrayBuffer buffer [, 字節起始位置 [, 長度]]);
DataView實例有如下屬性:
DataView實例提供8個方法讀取內存:
這一系列get方法的參數都是一個字節序號(不能是負數,不然會報錯),表示從哪一個字節開始讀取
var buffer = new ArrayBuffer(24); var dv = new DataView(buffer); // 從第1個字節讀取一個8位無符號整數 var v1 = dv.getUint8(0); // 從第2個字節讀取一個16位無符號整數 var v2 = dv.getUint16(1); // 從第4個字節讀取一個16位無符號整數 var v3 = dv.getUint16(3);
若是一次讀取兩個或兩個以上字節,就必須明確數據的存儲方式,究竟是小端字節序仍是大端字節序。默認狀況下,DataView的get方法使用大端字節序解讀數據,若是須要使用小端字節序解讀,必須在get方法的第二個參數指定true。
DataView視圖提供8個方法寫入內存:
這一系列set方法,接受兩個參數,第一個參數是字節序號,表示從哪一個字節開始寫入,第二個參數爲寫入的數據。對於那些寫入兩個或兩個以上字節的方法,須要指定第三個參數,false或者undefined表示使用大端字節序寫入,true表示使用小端字節序寫入。
傳統上,服務器經過AJAX操做只能返回文本數據,即responseType屬性默認爲text。XMLHttpRequest第二版XHR2容許服務器返回二進制數據,這時分紅兩種狀況。若是明確知道返回的二進制數據類型,能夠把返回類型(responseType)設爲arraybuffer;若是不知道,就設爲blob。
網頁Canvas元素輸出的二進制像素數據,就是TypedArray數組。
var canvas = document.getElementById('myCanvas'); var ctx = canvas.getContext('2d'); var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); var uint8ClampedArray = imageData.data;
須要注意的是,上面代碼的uint8ClampedArray雖然是一個TypedArray數組,可是它的視圖類型是一種針對Canvas元素的專有類型Uint8ClampedArray。這個視圖類型的特色,就是專門針對顏色,把每一個字節解讀爲無符號的8位整數,即只能取值0~255,並且發生運算的時候自動過濾高位溢出。這爲圖像處理帶來了巨大的方便。
WebSocket能夠經過ArrayBuffer,發送或接收二進制數據。
var socket = new WebSocket('ws://127.0.0.1:8081'); socket.binaryType = 'arraybuffer'; // Wait until socket is open socket.addEventListener('open', function (event) { // Send binary data var typedArray = new Uint8Array(4); socket.send(typedArray.buffer); }); // Receive binary data socket.addEventListener('message', function (event) { var arrayBuffer = event.data; // ··· });
Fetch API取回的數據,就是ArrayBuffer對象。
fetch(url) .then(function(request){ return request.arrayBuffer() }) .then(function(arrayBuffer){ // ... });
若是知道一個文件的二進制數據類型,也能夠將這個文件讀取爲ArrayBuffer對象。
var fileInput = document.getElementById('fileInput'); var file = fileInput.files[0]; var reader = new FileReader(); reader.readAsArrayBuffer(file); reader.onload = function () { var arrayBuffer = reader.result; // ··· };
JavaScript 是單線程的,web worker 引入了多進程,每一個進程的數據都是隔離的,經過postMessage()通訊,即通訊的數據是複製的。若是數據量比較大,這種通訊的效率顯然比較低。
//主進程新建了一個 Worker 進程 var w = new Worker('myworker.js'); //主進程經過w.postMessage向 Worker 進程發消息,同時經過message事件監聽 Worker 進程的迴應。 w.postMessage('hi'); w.onmessage = function (ev) { console.log(ev.data); } //Worker 進程也是經過監聽message事件,來獲取主進程發來的消息,並做出反應。 onmessage = function (ev) { console.log(ev.data); postMessage('ho'); }
主進程與 Worker 進程之間,能夠傳送各類數據,不只僅是字符串,還能夠傳送二進制數據。如有大量數據要傳送,留出一塊內存區域,主進程與 Worker 進程共享,兩方均可以讀寫,那麼就會大大提升效率。
ES2017 引入SharedArrayBuffer,容許多個 Worker 進程與主進程共享內存數據。SharedArrayBuffer的 API 與ArrayBuffer如出一轍,惟一的區別是後者沒法共享。
// 新建 1KB 共享內存 var sharedBuffer = new SharedArrayBuffer(1024); // 主窗口發送數據 w.postMessage(sharedBuffer); // 本地寫入數據 const sharedArray = new Int32Array(sharedBuffer);
共享內存也能夠在 Worker 進程建立,發給主進程。SharedArrayBuffer自己是沒法讀寫,必須在上面創建視圖,而後經過視圖讀寫。
Worker 進程直接改寫共享內存是不正確的。有兩個緣由,一是可能發生兩個進程同時改寫該地址,二是改寫之後沒法同步到其餘 Worker 進程。因此,必須使用Atomics.add()方法進行改寫。SharedArrayBuffer API 提供了Atomics對象,保證全部共享內存的操做都是「原子性」的,而且能夠在全部進程內同步。
Atomics對象有如下方法:
SIMD(發音/sim-dee/)是「Single Instruction/Multiple Data」的縮寫,意爲「單指令,多數據」。它是 JavaScript 操做 CPU 對應指令的接口。與它相對的是 SISD(「Single Instruction/Single Data」),即「單指令,單數據」。
SIMD 的含義是使用一個指令,完成多個數據的運算;SISD 的含義是使用一個指令,完成單個數據的運算,這是 JavaScript 的默認運算模式。顯而易見,SIMD 的執行效率要高於 SISD,因此被普遍用於3D圖形運算、物理模擬等運算量超大的項目之中。
var a = SIMD.Float32x4(1, 2, 3, 4); var b = SIMD.Float32x4(5, 6, 7, 8); var c = SIMD.Float32x4.add(a, b); // Float32x4[6, 8, 10, 12]
上面代碼之中,數組a和b的四個成員的各自相加,只用一條指令就完成了。
一次 SIMD 運算,能夠處理多個數據,這些數據被稱爲「通道」(lane)。上面代碼中,一次運算了四個數據,所以就是四個通道。
SIMD 一般用於矢量運算。
SIMD 提供12種數據類型,總長度都是128個二進制位。
每種數據類型被x符號分隔成兩部分,後面的部分表示通道數,前面的部分表示每一個通道的寬度和類型。好比,Float32x4就表示這個值有4個通道,每一個通道是一個32位浮點數。
每一個通道之中,能夠放置四種數據:
每種 SIMD 的數據類型都是一個函數方法,能夠傳入參數,生成對應的值。注意,這些數據類型方法都不是構造函數,前面不能加new,不然會報錯。
var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0); //變量a就是一個128位、包含四個32位浮點數(即四個通道)的值。
每種數據類型都有一系列運算符,支持基本的數學運算。
abs方法接受一個SIMD值做爲參數,將它的每一個通道都轉成絕對值,做爲一個新的SIMD值返回。
var a = SIMD.Float32x4(-1, -2, 0, NaN); SIMD.Float32x4.abs(a) // Float32x4[1, 2, 0, NaN]
neg方法接受一個SIMD值做爲參數,將它的每一個通道都轉成負值,做爲一個新的SIMD值返回。
var a = SIMD.Float32x4(-1, -2, 3, 0); SIMD.Float32x4.neg(a) // Float32x4[1, 2, -3, -0]
add方法接受兩個SIMD值做爲參數,將它們的每一個通道相加,做爲一個新的SIMD值返回。
addSaturate方法跟add方法的做用相同,都是兩個通道相加,可是溢出的處理不一致。對於add方法,若是兩個值相加發生溢出,溢出的二進制位會被丟棄; addSaturate方法則是返回該數據類型的最大值。
注意,Uint32x4和Int32x4這兩種數據類型沒有addSaturate方法。
sub方法接受兩個SIMD值做爲參數,將它們的每一個通道相減,做爲一個新的SIMD值返回。
var a = SIMD.Float32x4(-1, -2, 3, 4); var b = SIMD.Float32x4(3, 3, 3, 3); SIMD.Float32x4.sub(a, b) // Float32x4[-4, -5, 0, 1]
subSaturate方法跟sub方法的做用相同,都是兩個通道相減,可是溢出的處理不一致。對於sub方法,若是兩個值相減發生溢出,溢出的二進制位會被丟棄; subSaturate方法則是返回該數據類型的最小值。
mul方法接受兩個SIMD值做爲參數,將它們的每一個通道相乘,做爲一個新的SIMD值返回。
div方法接受兩個SIMD值做爲參數,將它們的每一個通道相除,做爲一個新的SIMD值返回。
sqrt方法接受一個SIMD值做爲參數,求出每一個通道的平方根,做爲一個新的SIMD值返回。
reciprocalApproximation方法接受一個SIMD值做爲參數,求出每一個通道的倒數(1 / x),做爲一個新的SIMD值返回。
reciprocalSqrtApproximation方法接受一個SIMD值做爲參數,求出每一個通道的平方根的倒數(1 / (x^0.5)),做爲一個新的SIMD值返回。
注意,只有浮點數的數據類型纔有這兩個方法。
shiftLeftByScalar方法接受一個SIMD值做爲參數,而後將每一個通道的值左移指定的位數,做爲一個新的SIMD值返回。若是左移後,新的值超出了當前數據類型的位數,溢出的部分會被丟棄。
注意,只有整數的數據類型纔有這個方法。
shiftRightByScalar方法接受一個SIMD值做爲參數,而後將每一個通道的值右移指定的位數,返回一個新的SIMD值。
若是原來通道的值是帶符號的值,則符號位保持不變,不受右移影響。若是是不帶符號位的值,則右移後頭部會補0。
check方法用於檢查一個值是否爲當前類型的SIMD值。若是是的,就返回這個值,不然就報錯。
extractLane方法用於返回給定通道的值。它接受兩個參數,分別是SIMD值和通道編號。
var t = SIMD.Float32x4(1, 2, 3, 4); SIMD.Float32x4.extractLane(t, 2) // 3
replaceLane方法用於替換指定通道的值,並返回一個新的SIMD值。它接受三個參數,分別是原來的SIMD值、通道編號和新的通道值。
load方法用於從二進制數組讀入數據,生成一個新的SIMD值。load方法接受兩個參數:一個二進制數組和開始讀取的位置(從0開始)。若是位置不合法(好比-1或者超出二進制數組的大小),就會拋出一個錯誤。
var b = new Int32Array([1,2,3,4,5,6,7,8]); SIMD.Int32x4.load(a, 2); // Int32x4[3, 4, 5, 6]
這個方法還有三個變種load1()、load2()、load3(),表示從指定位置開始,只加載一個通道、二個通道、三個通道的值。
store方法用於將一個SIMD值,寫入一個二進制數組。它接受三個參數,分別是二進制數組、開始寫入的數組位置、SIMD值。它返回寫入值之後的二進制數組。
var t2 = new Int32Array(8); var v2 = SIMD.Int32x4(1, 2, 3, 4); SIMD.Int32x4.store(t2, 2, v2) // Int32Array[0, 0, 1, 2, 3, 4, 0, 0]
這個方法還有三個變種store1()、store2()和store3(),表示只寫入一個通道、二個通道和三個通道的值。
splat方法返回一個新的SIMD值,該值的全部通道都會設成同一個預先給定的值。若是省略參數,全部整數型的SIMD值都會設定0,浮點型的SIMD值都會設成NaN。
swizzle方法返回一個新的SIMD值,從新排列原有的SIMD值的通道順序。swizzle方法的第一個參數是原有的SIMD值,後面的參數對應將要返回的SIMD值的四個通道。
var t = SIMD.Float32x4(1, 2, 3, 4); SIMD.Float32x4.swizzle(t, 1, 2, 0, 3); // Float32x4[2,3,1,4]
上面代碼中,後面的參數的意思是新的SIMD的四個通道,依次是原來SIMD值的1號通道、2號通道、0號通道、3號通道。因爲SIMD值最多能夠有16個通道,因此swizzle方法除了第一個參數之外,最多還能夠接受16個參數。
shuffle方法從兩個SIMD值之中取出指定通道,返回一個新的SIMD值。
var a = SIMD.Float32x4(1, 2, 3, 4); var b = SIMD.Float32x4(5, 6, 7, 8); SIMD.Float32x4.shuffle(a, b, 1, 5, 7, 2); // Float32x4[2, 6, 8, 3]
上面代碼中,a和b一共有8個通道,依次編號爲0到7。shuffle根據編號,取出相應的通道,返回一個新的SIMD值。
equal方法用來比較兩個SIMD值a和b的每個通道,根據二者是否精確相等(a === b),獲得一個布爾值。最後,全部通道的比較結果,組成一個新的SIMD值,做爲掩碼返回。notEqual方法則是比較兩個通道是否不相等(a !== b)。
var a = SIMD.Float32x4(1, 2, 3, 9); var b = SIMD.Float32x4(1, 4, 7, 9); SIMD.Float32x4.equal(a,b) // Bool32x4[true, false, false, true] SIMD.Float32x4.notEqual(a,b); // Bool32x4[false, true, true, false]
greatThan方法用來比較兩個SIMD值a和b的每個通道,若是在該通道中,a較大就獲得true,不然獲得false。最後,全部通道的比較結果,組成一個新的SIMD值,做爲掩碼返回。greaterThanOrEqual則是比較a是否大於等於b。
lessThan方法用來比較兩個SIMD值a和b的每個通道,若是在該通道中,a較小就獲得true,不然獲得false。最後,全部通道的比較結果,會組成一個新的SIMD值,做爲掩碼返回。lessThanOrEqual方法則是比較a是否小於等於b。
select方法接受掩碼和兩個SIMD值做爲參數,返回一個新生成的SIMD值。當某個通道對應的掩碼爲true時,會選擇第一個SIMD值的對應通道,不然選擇第二個SIMD值的對應通道。這個方法一般與比較運算符結合使用。
var a = SIMD.Float32x4(0, 12, 3, 4); var b = SIMD.Float32x4(0, 6, 7, 50); var mask = SIMD.Float32x4.lessThan(a,b); // Bool32x4[false, false, true, true] var result = SIMD.Float32x4.select(mask, a, b); // Float32x4[0, 6, 3, 4]
上面代碼中,先經過lessThan方法生成一個掩碼,而後經過select方法生成一個由每一個通道的較小值組成的新的SIMD值。
allTrue方法接受一個SIMD值做爲參數,而後返回一個布爾值,表示該SIMD值的全部通道是否都爲true。anyTrue方法則是隻要有一個通道爲true,就返回true,不然返回false。
注意,只有四種布爾值數據類型(Bool32x四、Bool16x八、Bool8x1六、Bool64x2)纔有這兩個方法。
min方法接受兩個SIMD值做爲參數,將二者的對應通道的較小值,組成一個新的SIMD值返回。若是有一個通道的值是NaN,則會優先返回NaN。minNum方法與min的做用如出一轍,惟一的區別是若是有一個通道的值是NaN,則會優先返回另外一個通道的值。
max方法接受兩個SIMD值做爲參數,將二者的對應通道的較大值,組成一個新的SIMD值返回。若是有一個通道的值是NaN,則會優先返回NaN。maxNum方法與max的做用如出一轍,惟一的區別是若是有一個通道的值是NaN,則會優先返回另外一個通道的值。
and方法接受兩個SIMD值做爲參數,返回二者對應的通道進行二進制AND運算(&)後獲得的新的SIMD值。
or方法接受兩個SIMD值做爲參數,返回二者對應的通道進行二進制OR運算(|)後獲得的新的SIMD值。
xor方法接受兩個SIMD值做爲參數,返回二者對應的通道進行二進制」異或「運算(^)後獲得的新的SIMD值。
not方法接受一個SIMD值做爲參數,返回每一個通道進行二進制」否「運算(~)後獲得的新的SIMD值。
SIMD提供如下方法,用來將一種數據類型轉爲另外一種數據類型:
帶有Bits後綴的方法,會原封不動地將二進制位拷貝到新的數據類型;不帶後綴的方法,則會進行數據類型轉換。
var t = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0); SIMD.Int32x4.fromFloat32x4(t); // Int32x4[1, 2, 3, 4] SIMD.Int32x4.fromFloat32x4Bits(t); // Int32x4[1065353216, 1073741824, 1077936128, 1082130432]
上面代碼中,fromFloat32x4是將浮點數轉爲整數,而後存入新的數據類型;fromFloat32x4Bits則是將二進制位原封不動地拷貝進入新的數據類型,而後進行解讀。
Bits後綴的方法,還能夠用於通道數目不對等的拷貝(原通道的數據大小可小於目標通道的最大寬度)。若是數據轉換時,原通道的數據大小,超過了目標通道的最大寬度,就會報錯。
SIMD.%type%.prototype.toString()返回一個SIMD值的字符串形式。
var a = SIMD.Float32x4(11, 22, 33, 44); a.toString() // "SIMD.Float32x4(11, 22, 33, 44)"
參考自:ECMAScript 6 入門