call
在this指向中我们知道,要想知道方法中的this是谁,就看该函数被调用时有没有 ‘点’ -> 有‘点’,this就是‘点’前面的对象;没有点,就是window/undefined
例如:
const fn = function fn(x,y) {
console.log(this,x, y);
}
fn(10,20) // this -> window ,x -> 10, y-> 20
Function.prototype
+ call
+ apply
+ bind
所有的函数都是 Function 实例,所以所有函数都可以调用这三个方法,而这三个方法都是用来改变函数中的 THIS 指向
const fn = function fn(x,y) {
console.log(this,x, y);
}
let obj = {
fn, // es6 中 等价于 => fn: fn
name: 'zcm',
}
// 如何让 fn 执行, this 指向 obj 呢?
fn(10, 20)
// 答案之一:就可以通过call, fn.call(obj,10,20)
call VS apply
call VS apply
- 都是把函数
立即执行,改变函数中的 this 指向的 [第一个参数是谁,就是把 this 改为谁] - 唯一区别: apply 要求把传递给函数的实参,以数组的形式管理起来,[最终效果和 call 一样,也是把数组中的每一项一个个传递给函数]
- 真实项目中建议大家使用 call ,因为其性能好一些 [大佬们做过测试:三个及以上参数,call 的性能明显比 apply 好一些]
// 如何让 fn 执行, this 指向 obj 呢?
// 假设 obj 中没有fn 属性,我们直接obj.fn(10,20) ,会报错
const fn = function fn(x,y) {
console.log(this,x, y);
}
let obj = {
name: 'zcm',
}
obj.fn(10, 20);// obj.fn is not a function
fn.call(obj, 10, 20); // this -> obj, x -> 10, y-> 20
fn.apply(obj,[10,20]) // this -> obj, x -> 10, y-> 20
// fn.call()中,如果第一个改变this 参数不穿,直接传变量,默认把第一个变量给this,
// 不传参数[undefined]或者参数为null, this 都默认指向 window
fn.call(10,20); // this -> new Number(10); x -> 20 , y-> undefined
fn.call(); // window/undefined(严格模式)
fn.call(null); // window/undefined(严格模式)
call 的一个小应用
// 获取数组最大值
let arr = [23, 12, 32, 34, 43, 78, 21];
console.log(Math.max(arr)); // NaN, 函数 Math.max()需要一个个传参不能传数组
console.log(Math.max(23, 12, 32, 34, 43, 78, 21)); // 78
console.log(Math.max(...arr)); // 78
console.log(Math.max.apply(Math,arr)); // 78
重写call 方法思路
// 重写call方法
Function.prototype.call = function call(context,...params) {
// this -> fn context-> obj, params -> [10,20]
}
const fn = function fn(x,y) {
console.log(this, x, y);
return x+y
}
let obj = {
name: 'zcm',
}
let res = fn.call(obj, 10, 20);
// @1 fn 基于 __proto__ 找到 Function.prototype.call, 把 call 方法执行
// @2 在 call 方法执行的时候
// + context: obj 要改变的 THIS 指向 ,parmas: [10,20] 执行函数传递的实参信息 this: fn 要执行的函数
// + 它干的事情是: 立即把 fn (this)执行,并让fn (this) 中的 this 指向 obj(context),
// 把10,20(params)传递给 fn(this), 接收 fn(this) 执行的返回结果,作为最后的结果返回
重写call 基础版本
Function.prototype.call = function call(context,...params) {
// this -> fn context-> obj, params -> [10,20]
// 思路: 给 context 设置一个属性 [例如: xxx 新增的属性不要和原始 context 中的属性冲突(设置symbol唯一值属性)],
// 让属性值等于要执行的函数(既: this [fn] );后面就可以基于 context.xxx() 执行,这样既把函数执行了,
// 也让函数中的 this 改为 context 了
let self = this,
key = Symbol('KEY'),
result;
context[key] = self;
result = context[key](...params)
// 新增属性之后,函数执行结果之后,需要把新增的属性删除
Reflect.deleteProperty(context, key);// ES6中可以基于 Reflect.deleteProperty(对象,键名)
// delete context[key]; // 新增的属性用完后记得移除,可以兼容性
return result
}
const fn = function fn(x,y) {
console.log(this, x, y);
return x+y
}
let obj = {
name: 'zcm',
}
let res = fn.call(obj, 10, 20);
重写call 升级版
初始化参数
Function.prototype.call = function call(context,...params) {
// this -> fn context-> obj, params -> [10,20]
// 两个 == 包括undefined / null
if (context == null) context = window
if(!/^(function| object)$/.test(typeof context)) Object(context)
// 思路: 给 context 设置一个属性 [例如: xxx 新增的属性不要和原始 context 中的属性冲突(设置symbol唯一值属性)],
// 让属性值等于要执行的函数(既: this [fn] );后面就可以基于 context.xxx() 执行,这样既把函数执行了,
// 也让函数中的 this 改为 context 了
let self = this,
key = Symbol('KEY'),
result;
context[key] = self;
result = context[key](...params)
// 新增属性之后,函数执行结果之后,需要把新增的属性删除
Reflect.deleteProperty(context, key);// ES6中可以基于 Reflect.deleteProperty(对象,键名)
// delete context[key]; // 新增的属性用完后记得移除,可以兼容性
return result
}
const fn = function fn(x,y) {
console.log(this, x, y);
return x+y
}
let obj = {
name: 'zcm',
}
let res = fn.call(obj, 10, 20);
console.log(res);
// @1 fn 基于 __proto__ 找到 Function.prototype.call, 把 call 方法执行
// @2 在 call 方法执行的时候
// + context: obj 要改变的 THIS 指向 ,parmas: [10,20] 执行函数传递的实参信息 this: fn 要执行的函数
// + 它干的事情是: 立即把 fn (this)执行,并让fn (this) 中的 this 指向 obj(context),
// 把10,20(params)传递给 fn(this), 接收 fn(this) 执行的返回结果,作为最后的结果返回
bind
应用场景
需求:点击按钮,fn 方法执行,我们想让其中的this变为 obj,ev 事件对象也存在,再传递 10,20
const submit = document.querySelector('#submit');
const obj = {
name: 'zcm'
}
const fn = function fn(x, y, ev) {
console.log(this,x,y,ev);
}
// submit.onclick = fn; // this -> submit x-> PointerEvent
// 需求: 点击按钮,fn 方法执行,我们想让其中的this变为 obj,ev 事件对象也存在,再传递 10,20
submit.onclick = fn.call(obj, 10, 20)
// 这样处理是错误的,因为call 是把函数立即执行,
// 按钮还没有点击呢,fn 就执行了
// 修改方法
submit.onclick = function (ev) {
// 先给时间行为绑定匿名函数,当点击的时候,先执行匿名函数[获取到ev 事件对象];
// 在匿名函数执行的时候(this -> submit)我们再让真正要处理的 fn 函数执行,此时可以基于 call 去改变this了
fn.call(obj,10,20,ev)
}
bind代码
手写bind,基于 call
submit.onclick = fn.bind(obj, 10, 20); // 这样处理就可以了
Function.prototype.bind = function bind(context, ...params) {
// this -> fn, context-> obj, params -> [10,20]
let self = this;
return function operate(...args) {
// args -> [ev],点击事件,默认传了一个ev 事件对象
// this -> submit
self.call(context,...params.concat(args))
}
}