/***************************************实现call************************************************/
//目标:将this指向传入第一个对象,参数不定,且立即执行
Function.prototype.myCall = function (obj){
var args = Array.prototype.apply(arguments,[1]);
obj.fn = this;
obj.fn(...args);
delete obj.fn;
}
//我们使用不带apply和bind的写法
var foo ={
value:1
}
function bar(){
console.log(this.value)
}
bar.call(foo);//1
/*
* 我们现在为了改变this指向,通过上面的例子可以发现, 只需要把当前对象bar放到foo里面就可以改变指向了
* 但是这样会增加额外的属性,所以最后我们再删除即可
* 因此我们大致的模拟步骤为
* 1. foo.fn = bar; //添加属性
* 2. foo.fn(); //执行
* 3. delete foo.fn(); //删除该属性
*/
//第一版实现this指向
Function.prototype.myCall1 = function(context){
context.fn = this; //获取调用myCall的函数,用this
context.fn();
delete context.fn;
}
//call还可以传入参数,但是传入参数不确定咋办。因此我们可以利用arguments,从中取值,
//取出第二个到最后一个参数,放到一个数组里面
//第二版
Function.prototype.myCall2 = function(context){
context.fn = this;
var args = [];
for(let i = 1 , len = arguments.length ; i < len ; i++ ){
args.push('arguments[' + i + ']');
}
eval('context.fn(' + i + ')'); //把这个参数数组放到要执行的函数的参数里面
delete context.fn;
}
//还有两个问题,this可以传null,当为null时,视为window
//另一个问题是函数是有返回值的
//第三版
Function.prototype.myCall3 = function(context){
var context = context || window;
context.fn = this;
var args = [];
for(let i = 1 , len = arguments.length ; i < len ; i++){
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + i + ')');
delete context.fn;
return result;
}
//到此就算完了,我们最后优化一下代码,使用ES6的写法
//前面的主要是用来学习思想,我们主要记下面的就可以了
//最终版
Function.prototype.myCall = function(context = window , ...args){
context.fn = this;
let result = context.fn(...args);
delete context.fn;
return result;
}
/***************************************实现apply************************************************/
//apply和call的区别主要就是传参的问题,apply把call里面第二个以后的参数作为一个数组传入
//因此同样的思想我们实现一下apply
Function.prototype.myApply = function(context = window , arr){
context.fn = this;
let result ;
if(!arr){
result = context.fn;
}else{
result = context.fn(...arr);
}
//上面的if语句可以用三目运算符代替
//let result = !arr ? context.fn : obj.fn(...arr);
delete context.fn;
return result;
}
/***************************************实现bind************************************************/
/* 同样基于call来实现bind函数,但是bind不是立即调用,所以需要返回一个函数
* 但是要注意:bind 函数有个特点,就是在绑定的时候可以传参,返回的函数还可以继续传参
* 如下情况:
* var person = {
name: 'jayChou'
};
var say = function(p1, p2) {
console.log(this.name, p1, p2);
}
var foo = say.myBind(person, 18);
foo(20); // jayChou 18 20
*
* 因此我们只需要把两次传参合在一起,并让结果返回一个函数即可
*/
Function.prototype.myBind = function(context = window , ...arg){
let _this = this;
let bound = function(){
return _this.apply(this instanceof _this ? this : context , arg.concat([...arguments]));
}
bound.prototype = new this();
return bound;
}
/*
* 如果我们的bind调用的时候指向了一个新对象,上面的办法显然就不行了,所以我们实现了一下继承
*/
Function.prototype.myBind = function(context = window , ...arg1){
context.fn = this;
let bound = function(){
let args = [...arg1].concat(...arguments); //concat用于把多个数组合并.
context.fn = this instanceof context.fn ? this : context; //调用bind的时候可能会发生this改变
let result = context.fn(...args);
delete context.fn;
return result;
}
bound.prototype = new this(); //实现继承,因为调用的时候可能会用new创建一个新对象
return bound;
}
2020-02-17(手写实现call,apply,bind)
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。