手撕call、bind、apply
返回值:
call/apply:fun
执行的结果 bind:返回fun
的拷贝,并拥有指定的this
值和初始参数
参数
thisArg
(可选):
- 若thisArg存在,
fun
的this
指向thisArg
对象 - 非严格模式下:若thisArg指定为null,undefined,fun中的this指向window对象.
- 严格模式下:
fun
的this
为指定的null或undefined - 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean
call() | apply() | bind() | |
---|---|---|---|
相同点 | 改变函数this 指向 | 改变函数this 指向 | 改变函数this 指向 |
是否调用函数 | 是 | 是 | 否 |
传递参数 | 逗号, 隔开 | 数组形式[] | 逗号, 隔开 |
应用场景 | 继承 | 与数组有关 | 不想调用函数 |
手撕call
注意:
值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean
思路:
- 根据call的规则设置上下文对象,也就是
this
的指向(指向window或自动包装对象)。 - 通过
添加obj
的临时属性,将函数的this指向隐式绑定到obj上 - 通过隐式绑定执行函数并传递参数。
- 删除临时属性,返回函数执行结果
function handle(...para) {
console.log( this, ...para) // do some thing
}//等同于 var handle = new Function('...para', `console.log...`)
obj=new Object();
obj.name="chun";
//handle实例的__proto__指向构造函数Function的prototype,在这上面添加myCall
Function.prototype.myCall = function (obj, ...args) {
// 判断上下文
if (obj === null || obj === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
obj = window
} else {
obj = Object(obj) // 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean
}
//node环境下绑定的是global
// 将函数设置为对象的属性
obj.fn = this;//this指向handle这个函数
// 执行这个函数,并拿到返回值
const res = obj.fn(...args);
// 删除这个函数属性
delete obj.fn;
// 返回值
return res;
};
handle.myCall(obj,1,2,3,4);// { name: 'chun', fn: [Function: handle] } 1 2 3 4
使用Symbol
临时储存函数,直接在对象上绑定有跟上下文对象的原属性冲突的风险。
const specialPrototype = Symbol('特殊属性Symbol') // 用于临时储存函数
context[specialPrototype] = this; // 函数的this指向隐式绑定到context上
Function.prototype.myCall = function (context, ...args) {
context = context || window//为null或者undefined的话则绑定到window上
// 创建唯一的属性防止污染
const key = Symbol()
// this 就是绑定的那个函数
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}
手撕apply
apply
第二个参数是以数组形式传递的,所以基本步骤与call
一致,不同的是函数执行的时候需要进行判断是否传入了第二个参数。如果有,将其传入并执行;若没有,直接执行。
function handle(...para) {
console.log( this, ...para) // do some thing
}//等同于 var handle = new Function('...para', `console.log...`)
obj=new Object();
obj.name="chun";
//handle实例的__proto__指向构造函数Function的prototype,在这上面添加myCall
Function.prototype.myApply = function (obj, arg) {
// 判断上下文
if (obj === null || obj === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
obj = window
} else {
obj = Object(obj) // 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean
}
//node环境下绑定的是global
// 将函数设置为对象的属性
obj.fn = this;//this指向handle这个函数
let res;
if(arg){//判断是否传入数组参数
res =obj.fn(...arg);
}
else{
res =obj.fn();
}
// 执行这个函数,并拿到返回值
// 删除这个函数属性
delete obj.fn;
// 返回值
return res;
};
handle.myApply(obj,[4,2,3]);// { name: 'chun', fn: [Function: handle] } 4 2 3
handle.myApply(obj) //{ name: 'chun', fn: [Function: handle] }
Function.prototype.myApply = function (context, args) {
if(!Array.isArray(args)) {
new Error('参数必须是数组')
}
context = context || window
// 创建唯一的属性防止污染
const key = Symbol()
// this 就是绑定的那个函数
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}
手撕bind
步骤:
1.拷贝源函数:
- 通过变量thisfn储存源函数
2.定义拷贝的函数fToBind
- this指向判断:通过
instanceof
判断this是否是fToBind函数的实例 (即是否通过new
调用),来决定绑定的realobj
(new会覆盖传入的objThis) - 返回 源函数thisfn.call(realobj,...fistparam,...secondparam)的调用,返回源函数的执行结果
3.
- 使用
Object.create
复制源函数的prototype给fToBind (实现深拷贝,存储不同的内存) - 最后返回函数fToBind
bind返回的函数有两种调用方式,通过new调用或者直接调用
细读以下代码(可延申至原型链、赋值与深浅拷贝的区别、new的实现、this隐式绑定)
function handle(...para) {
name:'handlename'
this.name='newfuncname'
console.log( this.name,this.objname,...para)
}//等同于 var handle = new Function('...para', `console.log...`)
obj=new Object();
obj.objname="chun";
//handle实例的__proto__指向构造函数Function的prototype,在这上面添加myBall
Function.prototype.myBind = function (objThis, ...params) {
const thisFn = this; // 存储源函数以及上方的params(函数参数)
// 对返回的函数 secondParams 二次传参
let fToBind = function (...secondParams) {
// 判断this是否是fToBind的实例,也就是判断返回的fToBind函数是否通过new调用,或是直接调用
const isNew = this instanceof fToBind
// new调用就绑定到this上,否则就绑定到传入的objThis上
const realobj = isNew ? this : Object(objThis)
// 用call调用源函数绑定this的指向并传递参数,返回执行结果
return thisFn.call(realobj, ...params, ...secondParams);
};
if (thisFn.prototype) {
// 复制源函数的prototype给fToBind 一些情况下函数没有prototype,比如箭头函数
fToBind.prototype = Object.create(thisFn.prototype);
}
return fToBind; // 返回拷贝的函数
};
let Handle = handle.myBind(obj,1,2,3);
//bind返回函数,后面根据new或直接调用判断this的指向
let c = new Handle('a','b');
//用new调用bind返回的函数,丢失obj,this指向实例对象 输出: newfuncname undefined 1 2 3 a b
let cc= new handle('c','d')
//直接new调用handle函数 输出: newfuncname undefined c d
Handle();//直接调用bind返回的函数,this指向obj, 输出:newfuncname chun 1 2 3
手写尝试:
Function.prototype.myBind = function (objThis, ...params) {
let thisfn=this;//保存源函数
let fToBind=function (...sencondparams) {//定义ftb
//判断this指向,此处this是调用fToBind的对象,如果是new则指向this,否则为传入的objThis
let realobj = this instanceof fToBind ? this : Object(objThis);
//通过call调用并返回
return thisfn.call(realobj, ...params,...sencondparams);
}
if(this.prototype){//深拷贝原型对象
fToBind.prototype=Object.create(this.prototype);
}
return fToBind;
};
阅读剩余
版权声明:
作者:chun
链接:https://chun53.top/871.html
文章版权归作者所有,未经允许请勿转载。
THE END