1.call()方法
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
理解call()的原理,首先要理解this的指向问题,即谁调用函数,函数的this就指向谁。
let obj = {
a: 1
}
function bar() {
let a = 10;
console.log(this.a); // 10
}
bar(); //10
bar.call(obj); // 1
上述例子可以看出来,当没有使用call()的时候,this.a打印出来的是10,是通过this的上下文取到了a的值,而通过call()方法之后,改变了bar的this指向,结果打印出了1;
关于call()方法,相信大家都会使用,在这里我们就不多叙述了,主要是讲怎么从原理来理解call()方法;
通过观察上述例子,可以发现,调用call(),其实就是改变this的指向,而bar.call(obj)就是讲bar的this指向obj,那么通俗来讲就是讲bar函数放进obj对象中,即:
let obj = {
a: 1,
bar() {
return this.a;
}
}
此时调用obj.bar(); 则this.a = 1;
即:
Function.prototype.call2 = function(context) {
context.fn = this;
context.fn();
delete context.fn;
}
bar.call2(obj); //1
已经初步实现了一个call()方法,但是我们知道,cal(),方法可接收多个参数,所以我们还要根据接收参数来做处理。
Function.prototype.call3 = function(context) {
context.fn = this;
let args = [];
for ( let i = 1; i < arguments.length; i++ ) {
args.push('arguments['+ i + ']');
}
eval('context.fn(' + args +')');
delete context.fn;
}
let obj = {
a: 1
}
function baz(b, c) {
return this.a + b + c
}
baz.call3(obj, 3,5) //9
到这一步,我们基本已经封装好了call()方法,但是call()的第一个参数也可以传入null,当传入null的时候,this是指向window的,所以我们还要讲第一个参数为null,指向window的情况考虑进去;
Function.prototype.call4 = function(context) {
var context = context || window;
context.fn = this;
let args = [];
for ( let i = 1; i < arguments.length; i++ ) {
args.push('arguments['+ i + ']');
}
eval('context.fn(' + args +')');
delete context.fn;
}
let a = 10;
function baz(b, c) {
return this.a + b + c
}
baz.call3(null, 3,5) //18
到这里已经是一个完整的call()函数,但是在上述call()方法中,我们是用eval和arguments来执行了代码,在ES6发布之后,我们是有…扩展运算符,所以我们可以用…扩展运算符来写出更简单的call()方法,也方便大家更容易理解。
Function.prototype.call5 = function(context, ...args) {
let obj = context || window;
obj.fn = this;
const result = obj.fn(...args);
delete obj.fn;
return result;
}
2.apply()方法
apply() 方法的作用和call()方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。在此我们就不叙述怎样去使用apply()方法了,相信大家都会使用。
和call()方法一样,只是apply()方法只有2个参数,第二个参数为数组,所以我们要先判断第二个参数是否为数组。若不是数组则抛出错误;
Function.prototype.apply2 = function(context, array) {
var context = context || window;
context.fn = this;
var result;
if ( !array ) {
result = context.fn();
} else {
if ( !Array.isArray(array) ) throw new ('params must be array');
var args = [];
for ( var i = 0; i < array.length; i++ ) {
args.push('array[' + i + ']')
}
result = eval('context.fn(' + args + ')')
}
delete context.fn;
return result;
}
也可以使用ES6的…扩展运算符来封装apply方法
Function.prototype.applySource = function(obj, array) {
let context = obj || window;
context.fn = this;
let result;
if ( !array ) {
result = context.fn();
} else {
if ( !Array.isArray(array) ) throw new Error('参数必须是数组')
result = context.fn(...array)
}
delete context.fn;
return result;
}
3.bind()方法
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
返回的函数是原函数的拷贝,并且拥有指定this值和初始参数。
bind() 函数会创建一个新的绑定函数。绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数。
那么在没有bind函数的时候,我们又是怎样模拟bind函数的呢。
if (!Function.prototype.bind) (function(){
Function.prototype.bindSource = function(oThis) {
if ( typeof this !== 'function' ) throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
var aArgs = Array.prototype.slice.call(arguments, 1);
var fToBind = this;
var fNOP = function() {};
var fBound = function() {
return fToBind.apply(this instanceof fBound? this: oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
}
if ( this.prototype ) fNOP.prototype = this.prototype;
fBound.prototype= new fNOP();
return fBound;
}
})();