手写JavaScript apply,call,bind
写在前面 调用apply,call,bind方法的函数,都不能作为构造函数
let aTestObj = {
a:1,
b:2,
c:3,
name:'aTestObj'
}
let countFn = function(d,e,f){
console.log(this.a+this.b+this.c+d+e+f);
}
function consTest(q,w,e){
this.a=q;
this.b=w;
this.c=e;
}
console.log(new consTest.bind(aTestObj)(7,8,9));
console.log(new consTest.call(aTestObj,7,8,9));
console.log(new consTest.apply(aTestObj,[7,8,9]));
//TypeError: consTest.bind(call/apply) is not a constructor
手写call: 坑点:①call后的第一个参数context不一定是Object形式(可能是基本类型如Number,String,Boolean等),所以一定要用Object包装context ②要让context调用函数,所以这个函数应成为context的属性,同时属性名绝对不能产生覆盖,故选择Symbol类型
//手写call
Function.prototype.myCall = function(context,...args){
//由于myCall一定是经过某个function调用的,所以myCall中的this指向调用它的那个function
if(!(this instanceof Function)) return false;
//用对象包装context,确保context包含对象方法
if(typeof context !== 'object'){
context = Object(context); //如果context是一个Number类型,相当于new Number(context)
}
//在这个对象上创建一个方法,内容是这个调用myCall的function,这样就能将function的上下文绑定在context对象上
//但是这个方法绝对不能跟context上的已有方法重名,避免覆盖,因此采用Symbol属性
let symbolProFunc = Symbol();
context[symbolProFunc] = this;
//此时通过context调用function,function中的this会指向context
let res = context[symbolProFunc](...args);
//删去这个属性,避免冗余
delete context[symbolProFunc];
return res;
}
手写apply:
//手写apply(只需处理数组形式的args)
Function.prototype.myApply = function(context,args){
if(!(this instanceof Function)) return false;
if(typeof context !== 'object'){
context = Object(context);
}
let symbolProFunc = Symbol();
context[symbolProFunc] = this;
let res = context[symbolProFunc](...args);
delete context[symbolProFunc];
return res;
}
可见apply和call本质上是完全一样的。
在手写bind之前,需要再次强调bind函数的几个特点(坑点): ①bind的参数列表可以分两次传入 ②new的优先级高于bind(在绑定this优先级方面),当遇到bind返回的函数作为构造函数的情况时,自动忽略经由bind的第一个参数context(以及其绑定的this)
手写bind:
//手写bind
Function.prototype.myBind = function(context,...someArgs){
//this指向调用myBind的function
let fn = this;
return function bindReturn(...restArgs){
if(this instanceof bindReturn){
//bindReturn通过new调用的情况
return new fn(...someArgs,...restArgs); // new后面的构造函数是fn,即调用bind的函数,bind方法的上下文this
}else{
return fn.myCall(context,...someArgs,...restArgs);
}
}
}
同时,在bindReturn作构造函数的情况下,new产生对象的原型为调用bind的函数,和bindReturn无关