ECMAScript3 给Function原型上定义了两个方法,他们是Function.prototype.call和Function.prototype.apply。数组
1 var func =function(a,b,c){ 2 alert([a,b,c]); 3 }; 4 func.apply(null,[1,2,3]);//apply 5 6 7 var func =function(a,b,c){ 8 alert([a,b,c]); 9 }; 10 func.call(null,1,2,3);//call
apply::两个参数,第一个参数:指定了函数体内this对象的指向;浏览器
第二个参数:一个带下标的集合,能够为数组,也能够为伪数组app
call:多个参数:第一个参数:指定了函数体内this对象的指向;ide
第二个参数开始日后,每个参数被依次传入函数;函数
call和apply传入的第一个参数为null,函数体内this会指向默认的宿主对象,在浏览器中则是windowthis
若是在严格模式下,函数体内的this仍是为null 1 var func = function(a,b,c){加密
2 alert(this === window);//true 3 }; 4 5 func.apply(null,[1,2,3]) 6 7 var func = function(a,b,c){ 8 "use stract";//若是在严格模式下,函数体内的this仍是为null 9 alert(this === null);//true 10 }; 11 func.apply(null,[1,2,3]);
1.改变this的指向 spa
1 var obj1 = { 2 name:"seven" 3 }; 4 5 var oj2 = { 6 name:"anne" 7 }; 8 9 window.name = "window"; 10 11 var getName = function(){ 12 alert(this.name) 13 }; 14 15 getName();//window 16 getName.call(obj1);//seven 17 getName.apply(obj2);//anne
在实际开发中,常常会遇到this指向被不经意改变的场景,好比,有一个div节点,div节点的onclick事件中的this原本指向这个div的:prototype
1 dcument.getElementById("div1").onclick=function(){ 2 alert(this.id);//div1 3 } 4 //加入改时间函数中有一个内部函数func,在实践内部调用func函数时,func函数体内的this就指向了window,而不是咱们预期的div 5 6 document.getElementById("div1").oonclick = function(){ 7 alert(this.id);//div1 8 var func = function(){ 9 alert(this.id) //undefined 10 }; 11 func(); 12 }; 13 //这时候咱们用call来修正func函数内的this,使其指向div 14 document.getElementById("div1").onclick=function(){ 15 var func = function(){ 16 alert(this.id);//div1 17 }; 18 func.call(this); 19 }
使用call来修正this的场景,咱们并非第一次遇到,code
1 document.getElementById = (function(){ 2 return function(){ 3 return func.apply(document,arguments); 4 } 5 })(document.getElementById); 6 7 var getId =document.getElementById; 8 var div = getId("div1"); 9 alert(div.id); //div1
2.Function.prototype.bind
大部分浏览器都实现了内置的Function.prototype.bind,用来指定函数内部的this指向,即便没有原生的Function.prototype.bind实现。
1 Function.prototype.bind= function(context){ 2 var self = this;//保存原函数 3 return function(){//返回一个新函数 4 return self.apply(context,arguments);//执行新的函数的时候,会把以前的context当作新函数体内的this 5 }; 6 } 7 8 var obj = { 9 name:"seve" 10 }; 11 12 var a = function(){ 13 alert(this.name);//seve 14 }.bind(obj); 15 16 a();
咱们经过Function.prototype.bind来“包装”func函数,而且传入一个对象context看成参数,这个context对象就是咱们想修正的this对象。
在Function.prototype.bind的内部实现中,咱们先把func函数的引用保存起来,而后返回一个新函数。当咱们在未来执行a函数时,实际上先执行的是这个刚刚返回的新函数。在新函数内部,self.apply(context,arguments)这句话代码是执行原来的a函数,而且指定context对象为a函数体内的this.
下面是复杂一点的实现:
1 Function.prototype.bind = function(){ 2 var self = this, 3 context = [].shift.call(arguments), 4 args = [].slice.call(arguments), 5 return function(){ 6 return self.apply(context,[].concat.call(args,[].slice.call(arguments))); 7 } 8 }; 9 var obj = { 10 name:"seven" 11 }; 12 13 var func = function(){ 14 alert(his.name); 15 alert([a,b,c,d]) 16 }.bind(obj,1,2); 17 18 func(3,4);
3.借用其余对象的方法
借用方法的第一种场景是“借用构造函数”,经过这种技术,能够实现一些相似继承的效果;
1 var A = function(name){ 2 this.name = name; 3 }; 4 5 var B = function(){ 6 A.apply(this,arguments); 7 }; 8 9 B.prototype.getName = function(){ 10 return this.name; 11 }; 12 13 var b = new B("seven"); 14 console.log(b.getName());
借用方法的第二种运用场景跟咱们的关系更加密切。
函数的参数列表arguments是一个类数组对象,虽然它也有“下标”,但它并不是真正的数组,因此也不像数组同样,进行排序操做或者往集合里添加一个新的元素。这种状况下,咱们经常会借用Array.prototype对象上的方法。好比想往arguments中添加一个新的元素,一般会借用Array.prototype.push:
(function(){ Array.prototype.push.call(arguments,3); console.log(arguments); })(1,2)//在操做arguments的时候,咱们常常很是频繁地找Array.prototype对象借用方法。
将数组转成真正的数组:Array.prototype.slice
截取arguments列表的头一个元素:Array.prototype.shift
v8的引擎源码:以Array.prototype.push为例
1 function ArrayPush(){ 2 var n = TO_UINT32(this.length);//被push的对象的length 3 var m = %_ArgumentsLength();//push的参数个数 4 for(var i =0;i<m;i++){ 5 this[i+n] = %_Arguments(i);//复制元素 (1) 6 } 7 this.length =n+m;//修正length属性的值 (2) 8 return this.length; 9 }; 10 //经过这段代码能够看到,Array.prototype.push其实是一个属性复制的过程,把参数按照下标一次添加到push的对象上面,顺便修改了这个对象的length属性。至于被修改的对象是谁,究竟是数组仍是类数组对象,这一点并不重要。 11 //由此能够推断出,咱们能够把“任意”对象传入Array.prototype.push: 12 var a = {}; 13 Array.prototype.push.call(a,"first"); 14 alert(a.length);//输出:1 15 alert(a[0]);//first 16 //这段代码在绝大部分浏览器里都能顺利执行,但因为引擎的内部实现存在差别,若是在低版本 的IE浏览器中执行,必须显示的给对象a设置length属性; 17 var a = { 18 length:0 19 };
前面咱们之因此把“任意”两个字加上引号,是由于能够借用Array.prototype.push方法的对象还要知足一下两个条件,从ArrayPush函数的(1)和(2)处也能够猜到,这个对象至少还要知足:
对象自己要能够存取属性;
对象的length属性可读写;