一.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);
}
三.倒叙排列
var list=[1,2,3],i=list.length;
while(i){
i--;
console.log("倒序:"+list[i]);
}
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了。

