call
- 定义:调用一个对象的一个方法,以另一个对象替换当前对象。call方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
- 使用:call([thisObj[,arg1[, arg2[, [,.argN]]]]])。
thisObj的取值有以下4种情况:
- 不传,或者传null,undefined, 函数中的this指向window对象。
- 传递另一个函数的函数名,函数中的this指向这个函数的引用。
- 传递字符串、数值或布尔类型等基础类型,函数中的this指向其对应的包装对象,如 String、Number、Boolean。
- 传递一个对象,函数中的this指向这个对象。
- 需求: 在一个方法中,通过this输出另一个对象中的某一属性
let ooo = {
name:'haha'
}
function showname(show,more) {
let newName = this.name;
console.log(newName)
console.log(show,more)
}
showname("show","more");
显然输出救过为undefined,show,more; 因为在showname中this指向的是showname本身,而showname并没有name这个属性。
- 解决方案(call方法实现): 如果我们将上述showname方法移到obj对象内部,成为obj内的一个属性,此时再调用showname时,showname内的this指向的是obj对象,那么this.name即为obj的name属性。
let ooo = {
name:'haha',
fn:function (show,more) {
let newName = this.name;
console.log(newName)
console.log(show,more)
}
}
ooo.fn("show","more");
此时输出haha,show,more。那么为了实现这个功能便出现了call方法。
Function.prototype.myCall = function(obj,...args) {
let ctx = obj || window;//没有指定指向对象则指向对象设为window
ctx.fn = this;//这个this指的是调用myCall的函数,将该函数绑定到ctx的fn属性上
ctx.fn(...args);
delete ctx.fn;
}
showname.myCall(ooo,"show","more");//haha show more
apply
apply函数和call函数功能是相同的,不同的只是,call函数第一个参数是需要绑定的对象,之后的参数是调用call函数的函数所需要的参数,以","分隔,而apply函数的第一个参数也是需要绑定的对象,之后的参数是调用call函数的函数所需要的参数,只不过这些参数被集中在一个数组中来传递。所以apply的实现与call大同小异,这里直接上代码。
Function.prototype.myApply = function (obj,args) {
let ctx = obj || window;//没有指定指向对象则指向对象设为window
ctx.fn = this;//这个this指的是调用myApply的函数,将该函数绑定到ctx的fn属性上
args? ctx.fn(...args): ctx.fn();
delete ctx.fn;
}
showname.myApply(ooo,["show","more"]);//haha show more
bind
- 定义:bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
- bind函数的特点:
- 返回一个函数
- 可以传入参数
- 需求:同call函数需求一样,在一个方法中,通过this输出另一个对象中的某一属性,只不过这次我们不直接执行调用绑定的函数,而是将调用绑定的函数的this指向改变后返回新的函数。
let ooo = {
name:'haha'
}
function showname(show,more) {
let newName = this.name;
console.log(newName)
console.log(show,more)
}
showname("show","more");
- 既然用到了对象绑定,那自然需要使用我们之前的call或apply方法来绑定新的对象。
- 这里需要注意的是,bind只能应用于方法,所以需要先做判断处理
Function.prototype.myBind = function (obj) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
};
//取出绑定时的参数,此时的arguments指的是调用myBind函数时传的参数
var args = Array.prototype.slice.call(arguments,1)[0];
var self = this;
var result = function() {
//取出调用时的参数,此时的arguments指的是result函数传的参数
var bindArgs = Array.prototype.slice.call(arguments)[0];
self.apply(obj,args.concat(bindArgs));
}
return result;
}
- 这样只是实现了当调用myBind的函数是个普通方法时的功能,但是bind函数还有一个特点,一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
- 所以我们在让this值重新指向的时候需要考虑,调用绑定函数的函数是否是个构造函数,如果是构造函数,那么就需要将this指向构造函数的实例而不是bind方法的第一个参数对象(这也符合了js中this的绑定原则,this的绑定分为new绑定,显示绑定call、apply、bind,隐式绑定对象中的方法的this隐式绑定为该对象,默认绑定window全局对象或严格模式的undefined)
- 我们使用一个中间函数,让他继承调用绑定函数的原型,再让生成的函数原型继承这个中间函数,这样,当我们改变生成函数的原型中的某个属性时,就不会影响到调用绑定函数的函数的属性,因为此时生成的函数与调用函数之间多了一层原型,就是中间函数的原型。
Function.prototype.myBind = function (obj) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
};
var args = Array.prototype.slice.call(arguments,1)[0];//取出绑定时的参数
var self = this;
var middleFun = function() {};//设置中间函数
var result = function() {
var bindArgs = Array.prototype.slice.call(arguments)[0];//取出调用时的总参数
/**
* 当绑定函数作为构造器时,this指向实例,可以让实例获得绑定函数的值
* 当绑定函数为普通函数时,this指向window,将绑定函数的this指向obj
*/
self.apply(this instanceof middleFun? this: obj,args.concat(bindArgs));
}
//中间函数继承调用绑定函数的函数
middleFun.prototype = self.prototype;
//生成的函数再继承中间函数,这样当我们改变生成函数的原型中的某个属性时,
//就不会影响到调用绑定函数的函数的属性,因为此时生成的函数与调用函数之间多了一层原型,
//就是中间函数的原型。
result.prototype = new middleFun();//使用new继承,可以使middleFun的this丢失,重新指向new的新对象
return result;
}
var aaa = showname.myBind(ooo,["show"]);//haha show more
aaa(["more"])
var bbb = new aaa(111);//undefined show 111