Sets up a duplex object stream over window.postMessage 在window.postMessage上設置一個雙工對象流javascript
因此咱們先學習一下window.postMessage:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessagehtml
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow:其餘窗口的一個引用 message:要發送的數據信息 targetOrigin:該信息要發送給的目標窗口,"*" 則表示無限制。若是你明確的知道消息應該發送到哪一個窗口,那麼請始終提供一個有確切值的targetOrigin,而不是*。不提供確切的目標將致使數據泄露到任何對數據感興趣的惡意站點 transfer(可選):是一串和message 同時傳遞的 Transferable 對象. 這些對象的全部權將被轉移給消息的接收方,而發送一方將再也不保有全部權。
而後其餘窗口就經過監聽來接受別的窗口發來的數據:java
window.addEventListener("message", receiveMessage, false); function receiveMessage(event) { // For Chrome, the origin property is in the event.originalEvent // object. // 這裏不許確,chrome沒有這個屬性 // var origin = event.origin || event.originalEvent.origin; var origin = event.origin if (origin !== "http://example.org:8080")//若是你確實但願從其餘網站接收message,請始終使用origin和source屬性驗證發件人的身份 return; // ... }
message 的屬性,即receiveMessage中的event:node
data 從其餘 window 中傳遞過來的對象。 origin 調用 postMessage 時消息發送方窗口的 origin . 這個字符串由 協議、「://「、域名、「 : 端口號」拼接而成。例如 「https://example.org (隱含端口 443)」、「http://example.net (隱含端口 80)」、「http://example.com:8080」。請注意,這個origin不能保證是該窗口的當前或將來origin,由於postMessage被調用後可能被導航到不一樣的位置。 source 對發送消息的窗口對象的引用; 您可使用此來在具備不一樣origin的兩個窗口之間創建雙向通訊。
/* * A窗口的域名是<http://example.com:8080>,如下是A窗口的script標籤下的代碼: */ var popup = window.open(...popup details...); // 若是彈出框沒有被阻止且加載完成 // 這行語句沒有發送信息出去,即便假設當前頁面沒有改變location(由於targetOrigin設置不對) popup.postMessage("The user is 'bob' and the password is 'secret'", "https://secure.example.net"); // 假設當前頁面沒有改變location,這條語句會成功添加message到發送隊列中去(targetOrigin設置對了) popup.postMessage("hello there!", "http://example.org"); function receiveMessage(event) { // 咱們能相信信息的發送者嗎? (也許這個發送者和咱們最初打開的不是同一個頁面). if (event.origin !== "http://example.org") return; // event.source 是咱們經過window.open打開的彈出頁面 popup // event.data 是 popup發送給當前頁面的消息 "hi there yourself! the secret response is: rheeeeet!" } window.addEventListener("message", receiveMessage, false);
/* * 彈出頁 popup 域名是<http://example.org>,如下是script標籤中的代碼: */ //當A頁面postMessage被調用後,這個function被addEventListenner調用 function receiveMessage(event) { // 咱們能信任信息來源嗎? if (event.origin !== "http://example.com:8080") return; // event.source 就當前彈出頁的來源頁面 // event.data 是 "hello there!" // 假設你已經驗證了所受到信息的origin (任什麼時候候你都應該這樣作), 一個很方便的方式就是把enent.source // 做爲回信的對象,而且把event.origin做爲targetOrigin event.source.postMessage("hi there yourself! the secret response " + "is: rheeeeet!", event.origin); } window.addEventListener("message", receiveMessage, false);
繼續post-message-stream的學習git
var streamA = new PostMessageStream({ name: 'thing one', target: 'thing two', }) var streamB = new PostMessageStream({ name: 'thing two', target: 'thing one', }) streamB.on('data', (data) => console.log(data)) streamA.write(chunk)
var messageStream = new PostMessageStream({ // required // name of stream, used to differentiate // when multiple streams are on the same window name: 'source', // name of target stream target: 'sink', // optional // window to send the message to // default is `window` window: iframe.contentWindow, })
其源代碼:github
const DuplexStream = require('readable-stream').Duplex const inherits = require('util').inherits module.exports = PostMessageStream inherits(PostMessageStream, DuplexStream) function PostMessageStream (opts) { DuplexStream.call(this, { objectMode: true,//若是想建立一個的能夠壓入任意形式數據的可讀流,只要在建立流的時候設置參數爲便可,例如:。 }) this._name = opts.name this._target = opts.target this._targetWindow = opts.targetWindow || window this._origin = (opts.targetWindow ? '*' : location.origin) // initialization flags this._init = false this._haveSyn = false window.addEventListener('message', this._onMessage.bind(this), false)//監聽消息 // send syncorization message this._write('SYN', null, noop)//先將要進行第一次握手發送的SYN或收到第一次握手後發送第二次握手的syn準備好 this.cork()/強制把全部寫入的數據都緩衝到內存中 } // private PostMessageStream.prototype._onMessage = function (event) { var msg = event.data // validate message if (this._origin !== '*' && event.origin !== this._origin) return if (event.source !== this._targetWindow) return if (typeof msg !== 'object') return if (msg.target !== this._name) return if (!msg.data) return if (!this._init) {//若是該流都尚未初始化,那就說明如今首先要進行三次握手來鏈接 // listen for handshake if (msg.data === 'SYN') {//收到syn說明收到的是第一次握手或第二次 this._haveSyn = true this._write('ACK', null, noop)//而後寫ACK生成第二次握手或第三次發送,此時已經成功同步 } else if (msg.data === 'ACK') {//收到ACK說明收到的是第二次或第三次握手 this._init = true //說明初始化鏈接已經成功 if (!this._haveSyn) {//若是_haveSyn爲false,那就說明收到的是第二次握手 this._write('ACK', null, noop) //因此還須要再發送一次ACK進行第三次握手 } this.uncork()//輸出被緩衝的數據 } } else {//不然就是已經鏈接上了 // forward message try { this.push(msg.data)//將post來的數據push到流中,將調用下面的_write函數 } catch (err) { this.emit('error', err)//出錯則觸發error事件 } } } // stream plumbing PostMessageStream.prototype._read = noop PostMessageStream.prototype._write = function (data, encoding, cb) { var message = { target: this._target, data: data, } this._targetWindow.postMessage(message, this._origin) cb() } // util function noop () {}objectModetrueReadable({ objectMode: true })
擴展知識:chrome
writable.cork()
writable.cork() 方法會強制把全部寫入的數據都緩衝到內存中。 當調用 stream.uncork() 或 stream.end() 方法時,被緩衝的數據纔會被輸出。
當寫入大量小塊數據到流時(由於緩存是有大小的,若都是小塊數據佔據了大內存,剩下的又不能裝入一個數據,這樣就會浪費內存),內部緩衝可能失效,從而致使性能降低,writable.cork() 主要用於避免這種狀況。
writable.uncork()
writable.uncork() 方法會輸出 stream.cork() 方法被調用後緩衝的所有數據。
當使用 writable.cork() 和 writable.uncork() 來管理流寫入緩存,建議使用 process.nextTick() 來延遲調用 writable.uncork()。 經過這種方式,能夠對單個 Node.js 事件循環中調用的全部 writable.write() 方法進行批處理。api
stream.cork(); stream.write('一些 '); stream.write('數據 '); process.nextTick(() => stream.uncork());
若是一個流上屢次調用 writable.cork() 方法,則必須調用一樣次數的 writable.uncork() 方法才能輸出緩衝的數據。緩存
stream.cork(); stream.write('一些 '); stream.cork(); stream.write('數據 '); process.nextTick(() => { stream.uncork(); // 數據不會被輸出,直到第二次調用 uncork()。 stream.uncork(); });
util.inherits(constructor, superConstructor)是一個實現對象間原型繼承的函數app
舉例說明:
var util = require('util'); function father() { this.name = 'John'; //這爲三個在構造函數中定義的屬性 this.age = 1979; this.showProperty = function() { console.log('got five house'); }; } father.prototype.showName = function() { //這個是在原型中定義的函數 console.log(this.name); }; function son() { this.name = 'bob'; this.age = 1997; } util.inherits(son, father); //使用util.inherits,因此son僅僅只能繼承father在原型中定義的showName函數 var dad = new father(); dad.showName(); dad.showProperty(); console.log(dad); var child = new son(); child.showName(); //成功 // child.showProperty(); //失敗 console.log(child);
返回:
userdeMacBook-Pro:stream-learning user$ node test.js John got five house father { name: 'John', age: 1979, showProperty: [Function] } bob
調用child.showProperty();會失敗:
/Users/user/stream-learning/test.js:23 child.showProperty(); ^ TypeError: child.showProperty is not a function
(3)對JavaScript prototype 使用介紹想了解的能夠去這個博客看看,寫的很好
(4)javascript:apply方法 以及和call的區別 (轉載)
https://github.com/nodejs/readable-stream
https://nodejs.org/dist/v10.11.0/docs/api/stream.html
You can swap your require('stream')
with require('readable-stream')
without any changes, if you are just using one of the main classes and functions.
const { Readable, Writable, Transform, Duplex, pipeline, finished } = require('readable-stream')
Note that require('stream')
will return Stream
, while require('readable-stream')
will return Readable
. We discourage using whatever is exported directly, but rather use one of the properties as shown in the example above.