手写call,apply,bind

手写call,apply,bind

1.手写call函数

实现call函数首先了解call函数的作用:call()方法使用一个指定的this单独给出的一个或者多个参数调用一个函数

1
2
3
4
5
6
7
8
9
function func(numA,numB){
console.log(this);
return numA + numB
}
function sum(){
return '定义一个sum函数'
}
const res = func.call(sum,1,3) //这里会直接调用func函数,并且func里的this指向sum
console.log('返回值为',res);

手写call函数,任何函数都可以调用它,在函数Function的原型上加上自己手写的call函数,具体操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Function.prototype.myCall=function(ctx,...args){
// 如果ctx是undefined或者null指向全局对象,否则以对象方式
ctx = (ctx===undefined||ctx===null?globalThis:Object(ctx))
// 用Symbol来防止ctx有相同属性
let key = Symbol('test')
// 使用属性描述符将调用的函数(func)放到ctx属性里面
Object.defineProperty(ctx,key,{
enumerable:false,
value:this,
configurable:true
})
// 调用函数 此时this指向ctx,
let result = ctx[key](...args)
delete ctx[key]
// 函数有返回值就返回返回值
return result
}

function func(numA,numB){
console.log(this);
return numA + numB
}
function sum(){
return '定义一个sum函数'
}
const res2 = func.myCall(sum,1,3)
console.log('返回值为',res);

tips:Symbol是ES6新增的一种数据类型,每个从Symbol()返回的Symbol值都是唯一的,用来作为对象属性的标志符。

1
2
3
4
5
6
const s1 = Symbol()
const s2 = Symbol()
console.log(s1===s2); // false
const s3 = Symbol('test')
const s4 = Symbol('test')
console.log(s3===s4); // false

2.手写apply函数

apply函数和call函数非常类似,只是第二个参数是一个数组。apply函数的使用:

1
2
3
4
5
6
7
8
9
function func(numA,numB){
console.log(this);
return numA + numB
}
function sum(){
return '定义一个sum函数'
}
const res = func.apply(sum,[1,3]) // 这里第二个参数是一个数组
console.log('返回值为',res);

手写apply函数也跟call大致相似。在函数对象原型上定义的myApply第二参数不需要用剩余参数的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 在函数原型上定义myApply,myApply第二个参数是一个数组
Function.prototype.myApply = function(ctx,args){
ctx = (ctx===undefined||ctx===null?globalThis:Object(ctx))
let key = Symbol('key')
Object.defineProperty(ctx,key,{
enumerable:false,
value:this,
configurable:true
})
console.log(...args);
let result = ctx[key](...args)
delete ctx[key]
return result
}

function func(numA,numB){
console.log(this);
return numA + numB
}
function sum(){
return '定义一个sum函数'
}

const res = func.myApply(sum,[1,3])
console.log('返回值为',res);

3.手写bind函数

bind函数也可以改变函数的this值,指向第一个参数。具体用法:

1
2
3
4
5
6
7
8
9
function func(a,b,c,d){
console.log(a,b,c,d);
console.log(this);
return 111
}

const newFn = func.myBind('ctx',1,2) //将func里的this指向了'ctx',并传入了1,2给a,b
let result = newFn(3,4) // 将3,4传给剩余的参数c,d
console.log(result);

与call,aplly不同,bind会创建一个新的函数实例,需要再次调用函数。

手写apply,先从函数原型上绑定自定的myBind,核心就是获取改变调用函数的this,指向传入的第一个参数,然后获取两次传递的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Function.prototype.myBind = function(ctx){
// Array.prototype.slice.call(arguments, index) 从第index个开始转换成数组,arguments继承了数组的原型方法中的slice方法
var args = Array.prototype.slice.call(arguments,1) //获得第一次除了指定this的其它参数
var fn = this // 这里的this指向调用myBind的函数,并保存传递给fn
// bind 会返回一个新的函数,所以return一个函数
return function A(){
//获取第二次调用时的参数
var restArgs = Array.prototype.slice.call(arguments)
//将两次参数拼接
allArgs = args.concat(restArgs)
// 接下来就是判断是否是通过new来创建函数实例的
//如果是返回new
if(Object.getPrototypeOf(this)===A.prototype){
return new fn(...allArgs)
}else{
// 如果不是直接返回
return fn.apply(ctx,allArgs)
}
}
}


const newFn =func.myBind('ctx',1,2)
let result = new newFn(3,4)
console.log(result);

tips:关于判断是否用new创建实例,这里要了解new创建实例的步骤,

​ 1.


手写call,apply,bind
http://example.com/2023/09/07/call-apply-bind/
作者
AlongSunsea
发布于
2023年9月7日
许可协议