call 方法的实现
先看测试示例,搞懂call方法是干什么用的
代码语言:javascript复制const animal = {
name: "小鸟"
}
function foo(num1, num2) {
console.log(num1,num2);
console.log(this.name, "会飞翔");
}
foo.call(animal, 2, 3)
// 输出:
// 2,3
// 小鸟会飞翔
通过上面的测试用例,我们可以明白:
- call方法 修改了
foo
函数的this指向 (指向了我们上方定义的animal对象
) - 只有函数类型才可以调用该方法
- 调用call 方法之后 会立即执行原函数(调用者)
有了上方的总结之后,我们便可以实现一下call方法了
1. 定义方法 (如何定义)
代码语言:javascript复制//所有函数身上都有的方法 需要我们在Funtion 原型身上去定义
Function.prototype.myCall = function (){
...
}
2. 解决this 的指向问题
代码语言:javascript复制Function.prototype.myCall = function(ctx){
// 创建不会和this指向的对象里面的重名的属性(方法)
const key = Symbol()
// 这里其实就是将调用者(哪个函数调用) 的this 绑定到传进来的对象身上
ctx[key] = this // 这里的this就是foo() 相当于 给ctx 对象身上添加了一个函数foo
ctx[key]() // 然后进行调用
delete ctx[key] // 接着显式删除这个属性
}
/* ------测试用例------- */
const animal = {
name: "小鸟"
}
function foo() {
console.log(this);
console.log(this.name, "会飞翔");
}
foo.myCall(animal)
提几个东西,方便大家更好地去理解上方的代码
Symbol : 每个从 Symbol()
返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;
const s1 = Symbol()
const s2 = Symbol()
console.log(s1 === s2); // false
const s3 = Symbol("123")
const s4 = Symbol("123")
console.log(s3 === s4); //false
上方我们用到Symbol的目的就是为了避免和原对象身上的属性相冲突,所以需要一个唯一值.
其次就是ctx[key] = this 这段代码如何去理解
代码语言:javascript复制const a = {
color:"green"
}
function f() {
console.log(this.color);
}
a.say = f
a.say() // green
这里我们就可以将a.say = f 等同于 ctx[key] = this,说白了 就是将foo()函数 [this]
当作一个方法 挂载在 传入的那个对象animal
的身上, 这样的this 统一指向animal
foo.myCall(animal)
3. 解决传参的问题
首先我们要明白的一点是 参数的数量是不确定的
, 这是由调用者函数定义了几个参数来决定的.
... es6 展开运算符
代码语言:javascript复制Function.prototype.myCall = function (ctx,...args) {
// 生成唯一标识符
const key = Symbol('key')
// 这里的this会指向调用者(原函数的this)
ctx[key] = this // animal[key] = foo()
const res = ctx[key](...args) // 这里相当于 直接调用并执行foo() 原函数
delete ctx[key]
return res
}
现在已经实现了, 我们可以进行测试一下
代码语言:javascript复制const animal = {
name: "小鸟"
}
function foo(num1, num2) {
console.log(this); // {name: '小鸟', Symbol(key): ƒ}
let sum = num1 num2
console.log(this.name, "会飞翔"); // 小鸟 会飞翔
return sum;
}
const res = foo.myCall(animal)
console.log(res); // 5
apply方法的实现
其实apply 和 call 方法非常相似,唯一的区别就是再调用的时候传参的方式的不同
参数传递方式:
call
方法接受参数的方式是直接列出每个参数。例如,如果你要传递两个参数,你会这样写:func.call(context, arg1, arg2)
。apply
方法接受参数的方式是通过一个数组或者类数组对象。例如,如果你要传递多个参数,你会这样写:func.apply(context, [arg1, arg2])
。
foo.call(animal, 2, 3)
foo.apply(animal,[ 2, 3])
下面我们直接看代码实现吧
代码语言:javascript复制Function.prototype.myApply = function (context, args) {
const key = Symbol("key")
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}
相比较之前call 方法的实现唯一区别就是 接收参数时候的不同, 因为第二个参数是一个数组
bind方法的实现
代码语言:javascript复制这里我们通过调用call 直接修改返回新函数的this指向
// 1. 返回一个新函数(this指向已经修改) 指向调用者(原函数)
// 2. 参数分批传递
Function.prototype.myBind = function (context, ...args) {
// 返回一个新的函数
return (...reargs) => {
// 这里的this 我们需要绑定一下 即外层的函数的this
// 参数呢 就是我们不确定外层函数传入多少个参数, 还有 返回的新函数 又传入多少个参数
// 那么我们便可以使用...展开运算符
const res = this.call(context, ...args, ...reargs)
// 因为新函数 需要返回值,所以我们
return res
}
}
const food = {
name: 'rice'
}
function a(num1, num2, num3, num4) {
console.log(num1, num2, num3, num4);
console.log(this.name);
return num1 num2 num3 num4;
}
const b = a.myBind(food, 1)
const res = b(2, 3, 4)
console.log(res); // 10