设计模式
2015-11-23 15:52:04

133
一.this
跟静态语言大相近庭的是,Javascript中this总是指向一个对象,而具体指向哪个对象,是在运行时根据执行环境(也就是被压栈的闭包函数)动态绑定的,而非其他语言中声明时的环境。
二.this指向
- 作为对象方法调用
- 作为普通函数调用
- 构造器调用
- call,apply调用
- this丢失
三.this指向示例
//1.作为对象方法调用 var obj={ a:1, getA:function(){ console.log(this==obj); //返回true console.log(this.a); //指向调用对象:obj } }; obj.getA();
//2.作为普通函数调用 window.name='globalname'; var getName=(function(name){ //use strict" //严格模式下,this指向undefined var name='private'; name='newgolbalname'; //name不会影响全局,因为这是一个闭包 console.log(name); //输出newgolbalname console.log(this.name); //指向window })(window.name); console.log(name); //输出globalname
//3.作为构造器调用 var MyClass=function(){ this.name='sven'; //this指向new返回对象:myClass return { //如果构造器不显示返回数据,或者返回的不是对象,则返回this,否则返回对象 name:'anne' } }; var myClass=new MyClass(); console.log(myClass.name);
//4.作为call,apply调用 var obj1={ a:'obj1', getA:function(){ console.log(this.a); //指向调用call参数:obj2 } }; var obj2={ a:'obj2' }; obj1.getA.call(obj2);
//5.this丢失 var obj3={ myName:'sven', getName:function(){ console.log(this.myName); } }; obj3.getName(); var getName2=obj3.getName; //把函数给了getName2,直接调用,this会指向window,因为这只是普通调用函数的方式(函数赋值只会赋值函数体的字符串) getName2();
四.this指向实例
我们是否觉得document.getElementById过长,试图封装简短函数代替它?比如prototype.js中
var getId=function(id){ return document.getElementById(id) };
我们也许思考过,为什么不用更简单的方式:
var getById=document.getElementById;
我们不妨花一分钟时间,再浏览器中看看效果(注意加粗倾斜部分)。
document.getElementById("unfavorite-btn") <li id="unfavorite-btn" data-toggle="tooltip" data-placement="top" data-original-title="取消收藏" data-url="/course/36/unfavorite" style="display:none;">…</li> var getId=document.getElementById undefined getId("unfavorite-btn") VM1163:2 Uncaught TypeError: Illegal invocation(…)
机智的你一定想到了,这发生了this丢失。在document.getElementById函数内部,this指向document(作为document对象方法调用)。而我们getId的函数体内this指向window,导致了这个错误,那么如何修改呢?
document.getElementById=(function(func){ return function(){ return func.apply(document,arguments) }; })(document.getElementById); var getById=document.getElementById; window.onload=function(){ alert(getById("test").id); };
我们重写了document.getElementById函数,让这个函数执行的时候,this永远指向document(注意:apply(document))。我们的getId也就是
function(){ return func.apply(document,arguments) };
当getId执行时候,会执行document.getElementById.apply(document,arguments)这段代码,我们把id作为参数传入到这个函数,并且将获得到的元素return,所以调用时候,只需要getId("domId")即可得到元素。
一.判断区操作索引
特点:省去了索引操作区代码。此方法在{}内无法获得正确索引。
var list=[1,2,3]; for(var i=0,item;item=list[i++];){ console.log("正序:"+item); }
二.迭代删除方法
特点:如果不需要再操作数据源,可以选择此方法,会删除掉了数组内容。
var list=[1,2,3]; for(;item=list[0];){ list.shift(); console.log("正序:"+item); }
三.倒叙排列
利用i==0返回false原理实现倒叙排列
var list=[1,2,3],i=list.length; while(i){ i--; console.log("倒序:"+list[i]); }
利用i==0返回false原理实现倒叙排列
var list=[1,2,3],i=list.length; for(;i--;){ console.log("倒序:"+list[i]); }
MarkdownPad Document
独自走向长坂坡,蘑菇不罗嗦,直接上代码。
1.apply最大的作用之一就是函数借调,修改this指向和传递参数
/* * 计算函数 * params:要计算的数值 * */ var calculate={ sum:function(){ //求和 var sum=0; for(var i= 0,item;item=arguments[i++];){ sum+=item; } return sum; }, //求积 mult:function(){ var sum=1; for(var i= 0,item;item=arguments[i++];){ sum*=item; } return sum; } }; /* * 计算数组函数,根据传入计算方式计算结果 * params:计算方式 * */ Array.prototype.mathOperate=(function(){ var cache={}; //这里对已经计算的值进行缓存 return function(){ var operate=[].shift.call(arguments); var vals=[].join.call(this); var key=operate.concat(vals); if(cache[key]){ return cache[key]; }else{ return cache[key]=calculate[operate].apply(arguments,this); //我们借调了其他对象的函数 } } })(); alert([1,2,3,4].mathOperate("sum")); alert([1,2,3,4].mathOperate("mult")); alert([1,2,3,4,5].mathOperate("sum")); alert([1,2,3,4,5].mathOperate("mult")); alert([1,2,3,4].mathOperate("sum")); //第二次计算,从缓存中读取 alert([1,2,3,4].mathOperate("mult")); //第二次计算,从缓存中读取
2.apply可以用来调用高阶函数。
以下是个单例调用函数的例子,第一次调用函数,执行函数,以后每次调用,获得第一次执行函数的返回值,以后我们会在单例模式中再次遇见它。
var getSingleFn=function(fn){ var ret; return function(){ //利用apply把参数传递给高阶函数 return ret||(ret=fn.apply(this,arguments)); } }; var getScript=getSingleFn(function(){ var script=document.createElement("script"),options=[].shift.call(arguments); options.src?script.src=options.src:script.innerHTML=options.html; //高阶函数可以直接获得getSinleFn传递过来的参数 return script; }); var script1=getScript({ html:"alert('hello')" }); var script2=getScript({ html:"alert('world')" //从缓存中读取,此处代码不会生效 }); alert("脚本是否相等:"+(script1==script2)); //第二次计算,从缓存中读取 window.onload=function(){ document.getElementsByTagName("head")[0].appendChild(script1); }
3.我们再来玩转复杂点的高阶函数,让我们看看如何用一个高阶函数的参数传递到另一个高阶函数内部。
Function.prototype.before=function(beforeFn){ var self=this; //这个this指向func对象 return function(){ if(typeof beforeFn=='function'){ beforeFn.apply(self,arguments); //把参数传递给before高阶函数beforeFn self.apply(self,arguments); } return self; } }; Function.prototype.after=function(afterFn){ var self=this; //这个this指向before函数的返回值 return function(){ //当我们调用func时,这个函数开始执行 if(typeof afterFn=='function'){ //加了判断为了防止直接调用after,没有执行before函数,context为空,并且this指向window。 var context=self.apply(this===window?self:this,arguments); //before函数的返回值开始执行,我们把参数(这里是1,2,3)传递给before函数的返回值 afterFn.apply(!context||context===window?self:context,arguments); //最后执行after函数 } } }; var func=function(){ console.log("函数开始执行,this:"+this,"arguments:"+[].join.call(arguments,",")); }; func=func.before(function(){ console.log("before开始执行,this:"+this,"arguments:"+[].join.call(arguments,",")); }).after(function(){ console.log("after开始执行,this:"+this,"arguments:"+[].join.call(arguments,",")); }); func(1,2,3);
4.apply其他的用途:数据借调,函数借用其他数据源对象数据。
var data={ list:[ {id:1,name:'test1'}, {id:2,name:'test2'} ] }; var ListBox=(function(){ var drawCount=0; return function(){ var self=this,args=arguments,list=data.list; list.forEach(function(item){ var options=args[0]; options["count"]=drawCount; //这里把this指向data,并且修改了传入参数,带上了绘制次数 drawCount=args.callee.draw.drawListItem.apply(item,[options]); }.bind(this)); } })(); ListBox.draw={ drawListItem:function(options){ console.log("绘制项:"+this.id+",绘制内容"+this.name+"绘制次数:"+(++options.count)+"是否可见:"+options.isVisible); return options.count; } }; ListBox({isVisible:true});
apply的用法还是非常广泛的,这里仅仅列举了一小部分,学好这些,你就可以正大光明去装x了。