this、call和apply

来源:互联网 发布:云计算港股上市公司 编辑:程序博客网 时间:2024/05/22 02:27

2.1 this
javascript总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定,而非函数被声明时的环境。

2.11 this的指向
(1)作为对象调用
(2)作为普通函数调用
(3)构造器调用
(4)Function.prototype.call或Function.prototype.apply调用

1.作为对象的方法调用

当函数作为对象的方法被调用时,this指向对象:

var obj={  a:1,  getA:function(){    alert(this===obj);//输出true    alert(this.a);//输出1  }}obj.getA();

2.作为普通函数调用
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this总是指向全局对象。在浏览器的javascript里,这个全局对象是window对象。

window.name="globalName";var getName=function(){  return this.name;}console.log(getName());

callback函数
div节点的事件函数内部,有一个局部的callback方法,callback被作为普通函数调用时,callback内部的this指向了window,但我们往往是想让它指向该div节点。此时有一个简单的解决方案,可以用一个变量保存一个div节点的引用:

window.id="window";document.getElementById("div").onclick=function(){  var that=this;//保存div的引用  var callback=function(){    alert(that.id);//输出div  }  callback();}

3.构造器调用
javascript没有类,但是可以从构造器中创建对象,同时也提供了new运算符,使构造器更像是一个类。
除了宿主提供的一些内置函数,大部分的javascript函数都可以当做构造器使用。构造器的外表和普通函数一模一样,它们的区别在于被调用的方式。当做new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象。

var MyClass=function(){  this.name="seven";}var obj=new MyClass();alert(obj.name);//输出seven
var MyClass=function(){  this.name="seven";  return{//显示的返回一个对象    name:"anne"  }}var obj=new MyClass();alert(obj.name);//输出anne

4.Function.prototype.call和Function.prototype.apply
call和apply方法能很好的体现javascript的函数语言特性,在javascript中,几乎每一次编写函数式语言风格都离不开call和apply。跟普通的函数调用相比,用Function.prototype.call或Function.prototype.apply可以动态的改变传入的this.

var obj1={  name:"seven",  getName:function(){    return this.name;  }}var obj2={  name:"anne"};console.log(obj1.getName());//输出sevenconsole.log(obj1.getName.call(obj2));//输出anne

2.1.1 丢失的this

var obj={  myName:"seven",  getName:function(){    return this.myName;  }}console.log(obj.getName());//输出:sevenvar getName2=obj.getName();console.log(getName2());//输出undefined

当调用obj.getName时,getName方法是作为对象的属性被调用。this指向obj对象。
当用另外一个变量getName2来引用obj.getName,并且调用getName2,此时是普通函数的调用方式,this指向全局window,所以程序执行是undefined.

var getId=document.getElementById;getId("div1");

document.h方法内部实现需要用到this,这个this本来被期望指向document,当getElementId方法作为document对象的属性被调用时,方法内部的this确实是指向document的。
但当用getId来引用document.getElementById之后,再调用getId,此时就成了普通函数调用,函数内部的this指向了window,而不是原来的document。
我们可以把apply将document当做this传入getId函数,帮助修正this:

var getId=document.getElementById;getId("div1");document.getElementById=(function(func){  return function(){    return func.apply(document,arguments);  }})(document.getElementById);var getId=document.getElementById;var div=getId("div1");alert(div.id);//输出div1

2.2 call和apply
Function原型有两个方法,它们是Function.prototype.call和Function.prototype.apply。
2.2.1 call和apply的区别
它们的作用一样,区别是在于传入参数的形式不同。
apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标签的集合,这个集合可以为数组,也可以为类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数:

var func=function(a,b,c){  alert([a,b,c]);}func.apply(null,[1,2,3]);//输出1,2,3

call传入的参数数量是不固定的,跟apply相同的是,第一个参数也是代表函数体内this的指向。从第二个参数开始往后,每个参数被依次传入函数:

var func=function(a,b,c){  alert([a,b,c]);}func.call(null,[1,2,3]);//输出1,2,3

当调用一个函数的时候,javascript的解释器并不会计较形参和实参的数量、类型以及顺序上的区别,javascript的参数内部就是用一个数组来表示的,只要用apply推进去就可以啦。
call是包裹在apply语法上的一颗语法糖,如果我们明确知道函数接受多少个参数,而且想一目了然地表达形参和实参的对应关系,那么也可用call来传送参数。
如果我们传入的第一个参数是null,函数体内的this会指向默认的宿主对象,在浏览器则是window.
有时候我们使用call和apply的目的不在于指向this,而是另有用处,比如借用其他对象的方法。那么我们可以传入null来代替某个具体对象。
例如:Math.max.apply(null,[1,2,3,4,5])//输出5

2.2.2 call和apply的用途
1.改变this的指向

var obj1={  name:"seven"};var obj2={  name:"anne"};window.name="window";var getName=function(){  alert(this.name);}getName();getName.call(obj1);//输出sevengetName.call(obj2);//输出anne

在实际开发中,经常会遇到this指向被不经意改变的场景,比如有一个div节点,div节点的onclick事件中的this本来是指向这个div的:

document.getElementById("div1").onclick=function(){  alert(this.id);//输出div1}

假如该事件有一个内部函数func,在事件内部调用func函数时,func函数体内的this就直接指向了window,而不是我们预期的div,见如下代码:

document.getElementById("div1").onclick=function(){  alert(this.id);//输出div1  var func=function(){    alert(this.id);//输出undefined  }  func();}

这个时候我们使用call来修正this的场景。

document.getElementById("div1").onclick=function(){  alert(this.id);//输出div1  var func=function(){    alert(this.id);//输出div1  }  func.call(this);}

2.Function.prototype.bind
大部分的浏览器都实现了内置的Function.prototype.bind,用来指定函数内部的this指向,即使没有原生的Function.prototype.bind来实现,我们来模拟一个也不是难事,代码如下:

Function.prototype.bind=function(context){  var self=this;//保存原函数  return function(){//返回一个新的函数    return self.apply(context,arguments);    //执行新的函数的时候,会把之前传入的context    //当做新的函数体内this  }};var obj={  name:"sven"};var func=function(){  alert(this.name);//输出seven}.bind(obj);func();

我们通过Function.prototype.bind来包装func函数,并且传入一个对象context当做参数,这个context对象就是我们想要修正的this对象。
在Function.prototype.bind内部实现中,我们先把func函数的引用保存起来,然后返回一个新的函数。当我们在将来执行func函数时,实际上先指向的是刚刚返回的这函数。在新的函数内部,

self.apply(context,arguments)这句代码才是执行原来的func函数,并且指定context对象为func函数体内的this.Function.prototype.bind=function(){  var self=this,//保存原函数  context=[].shift.call(arguments),//需要绑定的this上下文  args=[].slice.call(arguments);//剩余的参数转成数组  return function(){//返回一个新函数    return self.apply(context,[].concat.call(args,[].slice.call(arguments)));  //执行新的函数的时候,会把之前传入的context当做新函数体内的this  //并且组合两次分别传入参数,作为新函数的参数  }};var obj={  name:"seven"}var func=function(a,b,c,d){  alert(this.name);//输出seven  alert([a,b,c,d]);//输出1,2,3,4}.bind(obj,1,2);func(3,4);

3.借用其他对象的方法
借用方法的第一步场景是“借用构造函数”,通过这种技术,可以实现一些类似继承的场景:

var A=function(name){  this.name=name;}var B=function(){  A.apply(this,arguments);}B.prototype.getName=function(){  return this.name;}var b=new B("SVEN");console.log(b.getName());//输出seven

函数的参数列表arguments是一个类数组对象,虽然它也有下标,但它并非是真正的数组,所以不能像数组一样,进行排序操作或者往集合里添加一个新的元素。这种情况下,我们常常会借用Array.prototype对象上的方法。比如想往arguments中添加一个新的元素,通常会借用

Array.prototype.push:(function(){  Array.prototype.push.call(arguments,3);  console.log(arguments);//输出1,2,3})(1,2);

在操作arguments的时候,我们经常非常频繁的找Array.prototype对象借用方法。
想把arguments转成真正的数组的时候,可以借用Array.prototype.slice方法;想截去arguments列表中的头一个元素时,又可以借用Array.prototype.shift方法。

Array.prototype.push实际上是一个属性复制的过程,把参数按照下标一次添加到被push的对象上面,顺便修改了这个对象的length属性,至于被修改的对象是谁,到底是数组还是类数组对象,这并不重要。
由此可以判断,我们可以将“任意”对象传入Array.prototype.push;

var a={};Array.prototype.push.call(a,"first");alert(a.length);//输出1alert(a[0]);//first

而在低版本的IE浏览器中执行,必须显式的给对象a设置length属性:

var a={  length:0};

借用Array.prototype.push方法的对象还要满足一下两个条件:
(1)对象本身要可以存储属性
(2)对象的length属性可读写
比如number类型的属性不可能借用到Array.prototype.push方法:

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 驾驶证过期了可副业丢了怎么办 外地人北京驾驶本到期换本怎么办 报考驾照时电话号码填错了怎么办 报考驾照时电话填错了怎么办 邢台开三轮车驾证扣12分怎么办 新c1驾照扣满6分怎么办 b2驾照酒驾降级后再次酒驾怎么办 我b2驾照扣了9分怎么办 开别人的车出了事故怎么办 骑摩托车行驶证年检过期了怎么办 在两个城市车船税交重复了怎么办 车子被撞对方全责不赔钱怎么办 驾驶证暂扣期间该审证了怎么办 号码预约被不小心取消了怎么办 老婆出轨要跟我离婚我不想离怎么办 老婆要离婚我不想离电话拉黑怎么办 驾驶证到期换证体检有色弱怎么办 在长沙雨花区考的驾照丢了怎么办 两个户口注销了一个驾证怎么办 车管所发的初始密码弄丢了怎么办? 网上预约驾照考试密码忘记了怎么办 车是温州牌照掉了行驶证怎么办? 触犯了刑法第80条伪造印章怎么办 使用假行驶证被交警查扣车怎么办 摩托车被交警扣了没行驶证怎么办 摩托车行驶证丢了被交警抓到怎么办 摩托车没有行驶证被交警扣了怎么办 有小车执照要大在执照怎么办 报考的南京驾照不退学费怎么办 驾照报名可以退吗?不退怎么办 科目三学时已满公里数未满怎么办 预约驾照考试收不到验证码怎么办 a2驾照违章被扣24分怎么办 出车祸后划分赔偿对方不鉴字怎么办 驾驶证过期了被交警抓到怎么办 摩托车驾驶证6年到期在增驾怎么办 驾照年检时色盲图过不了怎么办 驾驶证扣12分超过两年怎么办 驾驶证被注销后超过两年怎么办 a2照驾驶证年审过期一个月怎么办 车被别人追尾行驶证过期怎么办