双11实例

双11过去了,双12还会远吗。。

对于双11web开发这一块有一些心得分享出来,希望能帮助到你

首先明确需求:

1.m站(也就是webapp,手机端网站)开发一个双11会场页(能够添加到购物车,有限时抢购功能),和一个双11预热页(纯展示的页面,没有交互)

2.ios和android的APP要镶嵌这两个页面,也要实现交互

对于公司唯一前端的我要面临的问题是:

1.需求给到我第一让我犯愁的是抢购倒计时,因为要倒计时肯定要用到定时器,android的还没什么事,但是ios在页面滑动的的过程中js是阻塞的(相当于alert),那么要解决这个首要问题,就要实现手动滑动,我选择了swiper.js,好了,这个暂且解决。

2.如果使用swiper.js就引发了另的个问题。容器和内容宽高要在实例化swiper时固定,这就要求内容的所有图片都要预先固定宽高,这个好说,跟运营、美工、ui好好商量一下就行了,不是什么大毛病。还有一个就是,M站是有头部导航和脚部菜单的,而APP有他们自己的头和脚,这就要求APP加载页面时要去掉头脚,然后再固定容器,实例化swiper,去掉就去掉,无非是判断一下设备,这个暂时解决。

3.会场与预热页本来预定是分开写的,但是对于一个有追求的前端怎么能够容忍,决定两个页面写在一块,但是预热页面虽然简单,有一些动画。但是谁能保证产品什么时候让你在加点别的啥的,对吧。如果多了js自然也不会少了,会场页和预热页的全局变量,函数,对象混杂在一起,第一容易乱,第二性能也不好,那么我们接下来就针对这个问题展开后续。

首先我们要知道一点,如果你经常阅读各类框架源码的话,对于自执行函数(IIFE)这个东西一定不会陌生,以jquery为例:

(function(w){
	var obj = {
		//....等等一些其他的东西
	}
	w.$ = w.jQuery = obj;
})(window,undefined)

源码差不多就是这么个意思。 作用: 1.函数内部的变量外部不可访问,避免全局的污染。 2.把window替换成w有利于压缩,其次是避免了多层作用域链的查找 3.这个undefined之所以这么写是因为在ES5以前的版本中,undefined是一个可写的属性,举个例子。

	var undefined = true;
	console.log(undefined)//true

在ES5以前版本中这么写是不会报错的,所以JQ开发人员为了避免事故发生就把undefined作为参数传入函数,就算你在全局定义了undefined = true,在函数内部undefined的值依然是undefined

你可能要说了,你说的这些我都知道啊,但这跟你的要解决的问题有毛线关系?

关系大了,我们可以模仿一下这种模块化的写法,把会场和和预热的js部分分割开来,他大概是这个样子的:

//访问的地址http://接口?system=值&style=值
	//system取值为android或ios或m; style取值为0代表会场或1代表预热
	function getName(name) {
        var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
        var r = window.location.search.substr(1).match(reg);
        if(r!=null)return  unescape(r[2]); return null;
    }

	(function(arr,code){
		arr[code]();
		console.log('执行完成之后的操作')
	})([function(){ console.log('会场js') },function(){console.log('预热js')}],getName('style'))

这样一来对应的js只会运行一次,且避免了全局的污染。 但是这么做产生了一个问题;那就是欠缺对于js异步操作(ajax)的考虑,我们看这一段代码:

  (function(fn){
	  fn();	
	  console.log('this')	
  })(function(){
	  setTimeout(function(){
		  console.log('time');
	  },1000)			
  });
//先打印出this,1秒后打印time

我用setTimeout模拟了ajax,问题显而易见,这种方法并不能保证代码的执行顺序(对于js异步先行百度,以后我们再聊),你可能想到了使用回调函数的方法不就ok了吗,没错,我们试一下:

(function(fn){
		fn(function(){			
			console.log('this')		
		});	
	})(function(callback){
		setTimeout(function(){
			console.log('time');
			callback&&callback();
		},2000)
	});
//先1秒后打印time,在打印出this

这个显然解决了我们的燃眉之急,但是我们又忽略了一个问题,那就是多个异步这个方法还好使吗?代码不用写,想想就知道肯定不行,但是我们又不能保证一个模块里只有一个ajax请求,那么我们就来换个写法搞他

(function(fn){
		fn(function(sum){
			this.sum = sum;	
			this.now = 0;
			this.count = function(){
				this.now++;
				if(this.now==this.sum){
					this.callback();
				}
			}
			this.callback = function(){
				console.log('this')
			}
		});	
	})(function(ObFn){
		var ob = new ObFn(3);
		setTimeout(function(){
			console.log('time1');
			ob.count();
		},2000)
		setTimeout(function(){
			console.log('time2');
			ob.count();
		},5000)
		setTimeout(function(){
			console.log('time3');
			ob.count();
		},3000)
	});

这里我们把回调函数换成了一个构造函数,而回调函数被添加到了构造函数的属性里。然后在模块开头new一个实例出来,每次遇到异步请求的回调里写上ob.count(),确定回调的个数后添加在new的实例的参数里,每当请求成功都会触发一次ob.count()进行检测,当全部回调完成时触发console.log('this'),这个模式模仿的观察者模式。有兴趣的可以百度一下观察者模式。

好了,style确定完成,接下来我们该说说console.log('this')这一部分该写些什么了,废话不多说,上代码:

html部分
<body>
	<button onclick="o.appCart(5)">点击</button>
</body>

js回调函数部分
this.callback = function(){	
		 var u = getName('system');
		 var f = {
		       //单独的方法写在这里
		      android:{
		           allFn:function (n) {
		                 //安卓专用的方法
		            }
		       },
		       ios:{
		            allFn:function (n) {
		                  //ios专用的方法
		            }
		        },
		        m:{
		            allFn:function (n){
		            	//m站专用的方法
		            }
		        }
		    }
		    //通用的方法写在这里
		    f[u].appCart = function (n) {
		         this.allFn(n);
		    };
		    window.o = f[u];
}

仔细看一下,是不是和我们开篇说的jq的模式有类似的地方呢。哈哈,大功告成,最后附上完整代码:

html部分
<body>
	<button onclick="o.appCart(5)">点击</button>
</body>
js部分
    function getName(name) {
        var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
        var r = window.location.search.substr(1).match(reg);
        if(r!=null)return  unescape(r[2]); return null;
    }

	(function(arr,code){
		arr[code](function(sum){
			this.sum = sum;	
			this.now = 0;
			this.count = function(){
				this.now++;
				if(this.now==this.sum){
					this.callback();
				}
			}
			this.callback = function(){
				
				var u = getName('system');

				var f = {
		            //单独的方法写在这里
		            android:{
		                allFn:function (n) {
		                    //安卓专用的方法
		                }
		            },
		            ios:{
		                allFn:function (n) {
		                    //ios专用的方法
		                }
		            },
		            m:{
		            	allFn:function (n){
		            		//m站专用的方法
		            	}
		            }
		        }

		        //通用的方法写在这里
		        f[u].appCart = function (n) {
		            this.allFn(n);
		        };
		        window.o = f[u];
			}
		});	
	})([function(ObFn){
		var ob = new ObFn(2);
		$.ajax({
			url:'接口1',
			async: true,
			success:function(res){
				ob.count();
			}
		})
		$.ajax({
			url:'接口2',
			async: true,
			success:function(res){
				ob.count();
			}
		})
		
	},function(ObFn){
		var ob = new ObFn(2);
		$.ajax({
			url:'接口1',
			async: true,
			success:function(res){
				ob.count();
			}
		})
		$.ajax({
			url:'接口2',
			async: true,
			success:function(res){
				ob.count();
			}
		})
		
	}],getName('style'));