JavaScript鏈式結構序列化詳解
一、概述
在JavaScript中,鏈式模式代碼,太多太多,如下:
if_else:
- if(...){
- //TODO
- }else if(...){
- //TODO
- }else{
- //TODO
- }
switch:
- switch(name){
- case ...:{
- //TODO
- break;
- }
- case ...:{
- //TODO
- break;
- }
- default:{
- //TODO
- }
- }
疑問:諸如上述這些鏈式代碼,倘若,我們想將其扁平化鏈式處理呢?如下:
- //fn1,f2,f3為處理函數
- _if(fn1)._elseIf(fn2)._else(fn3);
下面我們就來一起嘗試實現下唄。
二、鏈式代碼扁平化
假如,現在我們有如下鏈式代碼:
- if(name === 'Monkey'){
- console.log('yes, I am Monkey');
- }else if(name === 'Dorie'){
- console.log('yes, I am Dorie');
- }else{
- console.log('sorry, over for ending!');
- }
好了,現在我們一步一步將其”扁平化”。
其實看看上面的代碼,不難發現,if…else這種格式,其實就是數據結構中的單鏈表,那么,初步利用JavaScript實現單鏈表,如下:
- var thens = [];
- thens.resolve = function(name){
- for(var i = 0, len = this.length; i < len;i++){
- if(this[i](name) !== 'next'){
- break;
- }
- }
- }
- thens.push(f1, f2, f3);
其中f1,f2,f3為判斷函數,并且我們假設,如果諸如f1、f2、f3返回’next’時,就繼續往下查找,否則,停止往下查找。如下:
- function f1(name){
- if(name === 'Monkey'){
- console.log('yes, I am Monkey');
- }else{
- return 'next';
- }
- }
- function f2(name){
- if(name === 'Dorie'){
- console.log('yes, I am Dorie');
- }else{
- return 'next';
- }
- }
- function f3(){
- console.log('sorry, over for ending!');
- }
好了,這就是鏈表的模式。
但是,我們的最終目的是想實現如下這樣的呢?
- //fn1,f2,f3為處理函數
- _if(fn1)._elseIf(fn2)._else(fn3);
你可能會說,將上述代碼改成如下這樣,不就好了嗎?!!
- thens.push(f1).push(f2).push(f3).resolve();
But,JavaScript的push方法返回的是數組的新長度,而不是數組對象哦。
So,那我們只能新寫一個add方法,效果和push一樣,但是返回數組對象。如下:
- thens.add = function(f){
- if(typeof f === 'function'){
- this.push(f);
- return this;
- }
- }
測試代碼如下:
- var thens = [];
- thens.add = function(f){
- if(typeof f === 'function'){
- this.push(f);
- return this;
- }
- }
- thens.resolve = function(name){
- for(var i = 0, len = this.length; i < len;i++){
- if(this[i](name) !== 'next'){
- break;
- }
- }
- }
- thens.add(f1).add(f2).add(f3).resolve();
但是,這樣有個缺點,我們是將add、resolve方法綁定在全局變量thens中的,總不能每次創建一個數組時,都復制粘貼一遍方法吧,所以重構代碼如下:
- function Slink(){
- this.thens = [];
- this.thens.add = function(f){
- if(typeof f === 'function'){
- this.push(f);
- return this;
- }
- }
- this.thens.resolve = function(name){
- for(var i = 0, len = this.length; i < len;i++){
- if(this[i](name) !== 'next'){
- break;
- }
- }
- }
- }
顯然,add,resolve這種公共方法,在每次實例化時,都創建一遍是不科學的,so,利用prototype在原有的基礎上繼續變形,如下:
- function Slink(){
- this.thens = [];
- }
- Slink.prototype = {
- add: function(f){
- if(typeof f === 'function'){
- this.thens.push(f);
- return this;
- }
- },
- resolve: function(name){
- for(var i = 0, len = this.thens.length; i < len; i++){
- if(this.thens[i](name) !== 'next'){
- break;
- }
- }
- }
- }
測試代碼如下:
- var thens = new Slink();
- thens.add(f1).add(f2).add(f3);
- thens.resolve();
不錯,但是這樣,我們每次都得手動new一個Slink,有點麻煩,所以,我們將new Slink這個過程,封裝到函數中,如同jQuery一樣,如下:
- function $go(f){
- return new Slink(f);
- }
- function Slink(f){
- this.thens = [];
- this.thens.push(f);
- }
- Slink.prototype = {
- add: function(f){
- if(typeof f === 'function'){
- this.thens.push(f);
- return this;
- }
- },
- resolve: function(name){
- for(var i = 0, len = this.thens.length; i < len; i++){
- if(this.thens[i](name) !== 'next'){
- break;
- }
- }
- }
- }
測試代碼如下:
- $go(f1).add(f2).add(f3).resolve();
好了,大功告成,接下來就是語法糖滴問題咯,整理代碼如下:
- function _if(f){
- return new Slink(f);
- }
- function Slink(f){
- this.thens = [];
- this.thens.push(f);
- }
- Slink.prototype = {
- _elseIf: function(f){
- if(typeof f === 'function'){
- this.thens.push(f);
- return this;
- }
- },
- _else: function(f){
- return this._elseIf(f);
- },
- resolve: function(name){
- for(var i = 0, len = this.thens.length; i < len; i++){
- if(this.thens[i](name) !== 'next'){
- break;
- }
- }
- return this;
- }
- }
測試代碼如下:
- _if(f1)._elseIf(f2)._else(f3).resolve();
當然,除開利用數組這種方式,還可以利用閉包,實現鏈式扁平化效果,如下:
- var func = Function.prototype;
- func._else = func._elseIf = function(fn){
- var _this = this;
- return function(){
- var res = _this.apply(this,arguments);
- if(res==="next"){ //值為Boolean
- return fn.apply(this,arguments);
- }
- return res;
- }
- }
測試代碼如下:
- function f1(name){
- if(name === 'Monkey'){
- console.log('yes, I am Monkey');
- }else{
- return 'next';
- }
- }
- function f2(name){
- if(name === 'Dorie'){
- console.log('yes, I am Dorie');
- }else{
- return 'next';
- }
- }
- function f3(){
- console.log('sorry, over for ending!');
- }
- f1._elseIf(f2)._else(f3)('Dorie');
三、異步代碼鏈式扁平化
在上面我們討論的都是同步過程,倘若,鏈式調用函數中有異步情況呢?
什么意思?如下:
- function f1(name){
- setTimeout(function(){
- if(name === 'Monkey'){
- console.log('yes, I am Monkey');
- }else{
- return 'next';
- }
- }, 2000);
- }
- function f2(name){
- if(name === 'Dorie'){
- console.log('yes, I am Dorie');
- }else{
- return 'next';
- }
- }
- function f3(){
- console.log('sorry, over for ending!');
- }
我們將f1利用setTimeout變成了異步,按照上述代碼的邏輯,應該是等f1完全執行完畢(包括setTimeout執行)后,判斷是否執行f2,但真的如此嗎?
測試代碼如下:
- _if(f1)._elseIf(f2)._else(f3).resolve();
執行代碼的結果就是,什么也不輸出。
Why?
因為JavaScript是單線程嘛。
那該怎么解決呢?
由于有異步代碼,且必須在異步代碼后處理后續的鏈,那么我們就等待異步代碼執行完畢后,才執行后續的鏈嘛,如下:
- function f1(name){
- setTimeout(function(){
- if(name === 'Monkey'){
- console.log('yes, I am Monkey');
- }else{
- //處理后續鏈
- this.resolve(name, 1);//1代表下一個需處理函數在數組中的位置
- }
- }.bind(this), 2000);
- }
好了,由于在函數中,我們使用了this,其代表Slink對象,且改變了resolve方法,固,需細微調整Slink構造函數和原型鏈,如下:
- function Slink(f){
- this.thens = [];
- this.thens.push(f.bind(this));
- }
- Slink.prototype = {
- _elseIf: function(f){
- if(typeof f === 'function'){
- this.thens.push(f.bind(this));
- return this;
- }
- },
- _else: function(f){
- return this._elseIf(f.bind(this));
- },
- resolve: function(name, flag){
- for(var i = flag, len = this.thens.length; i < len; i++){
- if(this.thens[i](name) !== 'next'){
- break;
- }
- }
- return this;
- }
- }
測試代碼如下:
- function f1(name){
- setTimeout(function(){
- if(name === 'Monkey'){
- console.log('yes, I am Monkey');
- }else{
- //處理后續鏈
- this.resolve(name, 1);//1代表下一個需處理函數在數組中的位置
- }
- }.bind(this), 2000);
- }
- function f2(name){
- if(name === 'Dorie'){
- console.log('yes, I am Dorie');
- }else{
- return 'next';
- }
- }
- function f3(){
- console.log('sorry, over for ending!');
- }
- _if(f1)._elseIf(f2)._else(f3).resolve('',0);
哈哈,如果你了解Promise,是不是感覺這么相似呢。
是的,宗旨都一樣,達到異步代碼扁平化目的,不過這里的代碼比Promise要簡約得多啦。