this、call和apply

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());//输出seven
console.log(obj1.getName.call(obj2));//输出anne

2.1.1 丢失的this

var obj={
  myName:"seven",
  getName:function(){
    return this.myName;
  }
}
console.log(obj.getName());//输出:seven
var 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);//输出seven
getName.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);//输出1
alert(a[0]);//first

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

var a={
  length:0
};

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值