在ECMAScript中,每一个函数都包含两个继承而来的方法:apply() 和 call(),这两个方法的用途都是在特定的做用域中调用函数,主要做用跟bind同样,用来改变函数体内this的指向,或者说是在函数调用时改变上下文。
文章尽可能使用大量实例进行讲解,它们的使用场景。同时,也会由浅入深的引导出一些理论,毕竟这几个经常使用方法,在MDN上都能找到合理的解释javascript
var fruit = { fruitName:"apple" } function getFruit() { console.log("I like "+this.fruitName) } getFruit(); // log I like undefined getFruit.call(fruit) // log I like apple getFruit.apply(fruit) // log I like apple var newBind = getFruit.bind(fruit) newBind(); // log I like apple
当 getFruit 并不是做为一个对象的属性,而是直接当作一个函数来调用,里面的this
就会被绑定到全局对象上,即window上, 因此直接调用 getFruit
,里面的this
指向了全局对象上,返回 undefined
。前端
在严格模式下,函数被调用后,里面的this默认是 undefined
后面,经过调用函数上的call
和apply
方法,该变this
指向,函数里面的this
指向fruit
。java
区别:bind
一样实现了改变this
指向的功能,可是它不会当即执行,而是会从新建立一个绑定函数,新函数被调用时,使用bind()
方法里面的第一个参数做为this
git
这三个方法,从接受的第二参数开始,都直接传递给函数,可是接受参数的方法却很大的不一样。github
call,从第二个参数开始,以参数列表的形式展现,数组
apply,则把传递的函数参数,放在一个数组里面做为第二个参数。闭包
fn.call(obj,arg1,arg2); fn.apply(obj,[arg1,arg2])
bind,从第二个参数开始,一样以参数列表的形式,可是会提早放在新绑定函数的参数以前app
var foo = function(name,age){ console.log("name: "+name+"- age: "+age) } var p1 = foo.bind(this,"popo"); // "popo" 做为新函数的第一个参数。 p1(13); // logs name: popo- age: 13 p1("bobo",14) // logs name: popo- age: bobo
$('.div-class').on('click',function(event) { /*TODO*/ }.bind(this)); } }
一般,咱们在改变函数上下文以前,都会使用相似that = this
,或者self,_this
,来把this赋值给一个变量。利用.bind()
,能够传入外层的上下文。函数
循环中利用闭包来处理回调ui
for(var i = 0;i < 10;i++){ (function(j){ setTimeout(function(){ console.log(j); },600); })(i) }
每次循环,都会产生一个当即执行的函数,函数内部的局部变量j保存不一样时期i的值,循环过程当中,setTimeout回调按顺序放入消息队列中,等for循环结束后,堆栈中没有同步的代码,就去消息队列中,执行对应的回调,打印出j的值。
同理,能够利用bind
,每次都建立新的函数,而且已经预先设置了参数,传入不一样的指针
function func(i) { console.log(i) } for(var i =0 ;i< 10;i++) { setTimeout(func.bind(null,i),600) }
var Person = function(name,age) { this.name = name; this.age = age; } var P1 = function(name,age) { // 借用构造函数的方式实现继承 // 利用call 继承了Person Person.call(this,name,age) } P1.prototype.getName = function() { console.log("name: "+this.name+", age: "+this.age); } var newPerson = new P1("popo",20); // logs name: popo, age: 20 newPerson.getName();
实质上,能够当作经过call()
或者apply()
方法,在即将新建的对象,即这里的newPerson
上,执行超类型的构造函数,分别在当前上下文this
上添加name
和age
属性。
function isArray(value) { return Object.prototype.toString.call(value) == "[object Array]" }
借用了Object原生的toString()方法,打印出对应变量的构造函数名,
// 实现一个简单的数组 'unshift'方法 Array.prototype.unshift = function(){ this.splice.apply(this, [0,0].concat(Array.prototype.slice.apply(arguments))); return this.length; }
首先,利用this.splice.apply()
,其中splice
,能够直接从数组中移除或者插入变量。apply()
则以数组的形式传递参数,须要利用concat
拼接数组。
当函数被调用时,在函数内部会获得类数组arguments
,它拥有一个length属性,可是没有任何数组的方法。因此,将slice
方法中的this
指向arguments
,获取到arguments
的长度,从而肯定方法的start
和end
下标,获得一个数组变量。
一样适用的还有,DOM里面的NodeList对象,它也是一种类数组对象。
bind
方法在ECMAScript5里面被引入,前面提到过,调用该方法时,返回一个新的函数,能够简单使用下面方法实现其改变this
指向的功能。
Function.prototype.bind = function(scope) { var fn = this; return function() { return fn.apply(scope) } }
接着,就能够利用concat
把bind传递的预置参数拼接到新函数的参数列表中。
Function.prototype.bind = function(scope) { var args = Array.prototype.slice.call(arguments,1) var fn = this return function() { return fn.apply(scope,args.concat(Array.prototype.slice.call(arguments))) } }
参考连接