实现call
// 前置知识
// @1 call 方法第一个参数函数的目标 this,其余参数用逗号分隔
// @2 call 方法会直接执行函数
function fn(x, y) {
console.log(this);
return x + y;
}
var obj = {
name: 'obj'
}
fn.call(obj, 1, 2);
fn.call(null, 1, 2);
fn.call(undefined, 1, 2);
fn.call(false, 1, 2);
重写 call
// 注意点
// @1 软绑:目标 this 传 null 或 undefined,则函数内部 this -> window (非严格模式)
// @2 原始值增加属性不会报错,但访问不到 比如 var n = 1; n.x = 'w'; n.x // undefined
// @2 函数返回值要带出去
// @3 小技巧:记录当前 函数 到目标 this 上,这样调用的话,this 就为目标 this 了
// @4 注意 key 用 Symbol,这样的话不会被覆盖掉
// @5 用完之后要把目标 this 复原,注意此时打印 this 还是包含 Symbel(): f(){}
// 不过浏览器有个特点,展开的时候看到的永远都是新的堆内存,所以展开后我们就能复
// 原后的目标 this
Function.prototype._call = function(totalThis, ...params) {
totalThis == null && (totalThis = window);
// 如果是原始值 要变成对象类型的值
if (typeof totalThis !== 'object' && typeof totalThis !== 'function') {
totalThis = Object(totalThis);
}
let key = Symbol();
totalThis[key] = this; // 把函数暂时挂载目标 this 对象上
let res = totalThis[key](...params);
delete totalThis[key];
return res;
}
实现apply
// 前置知识
// @1 apply 方法第一个参数函数的目标 this,其余参数用数组包装
// @2 apply 方法会直接执行函数
function fn(x, y) {
console.log(this);
return x + y;
}
var obj = {
name: 'obj'
}
fn.apply(obj, [1, 2]);
fn.apply(null, [1, 2]);
fn.apply(undefined, [1, 2]);
fn.apply(false, [1, 2]);
重写 apply 其实 apply 和 call 的区别仅仅在于除了第一个参数外,剩余参数是逗号分隔还是数组。
// 改下参数接收的处理即可,...params -> params
Function.prototype._apply = function(totalThis, params) {
totalThis == null && (totalThis = window);
if (typeof totalThis !== 'object' && typeof totalThis !== 'function') {
totalThis = Object(totalThis);
}
let key = Symbol();
totalThis[key] = this;
let res = totalThis[key](...params);
delete totalThis[key];
return res;
}
实现bind
// 前置知识
// @1 bind 方法第一个参数函数的目标 this,其余参数用逗号分隔
// @2 bind 方法不会直接执行函数
function func(x, y, event) {
console.log(this, x, y, event);
return x + y;
}
var obj = {
name: 'obj'
}
// this->body x->事件对象 y->undefined event->undefined
document.body.onclick = func;
// 比如我有个需求,点击 body 的时候,把 func 执行,
// 但是方法中的 this 想改成 obj,并且传递 1 和 10
document.body.onclick = func.bind(obj, 1, 2); // bind 返回的函数会再接收一个 event
// 如果不用 bind 呢,怎么实现,我们只能再包一层
document.body.onclick = function(event) {
func.call(obj, 1, 2, event); // 合并参数
}
重写 bind
Function.prototype._bind = function(totalThis, ...params) {
// this->func totalThis->obj params=[1, 2]
let self = this; // 函数本身
return function(...args) {
// this->body args->event 内层 this 不保险哦
self.call(totalThis, ...params, ...args);
}
}
其实可以看到,内层函数使用了外层函数保存的 this,这就又回到了我们函数式编程里的柯里化思想。
小试牛刀
了解了它们实现原理后,我们来看几道关于 call、bind、apply 的面试题。
题1
function fn1() {
console.log(1);
}
function fn2() {
console.log(2);
}
fn1.call(fn2); // 1
// 点操作符优先级 20,从左往右执行,fn1.call 拿到的是一个函数(fn1.__proto__.call)
// 也就是 Function.prototype.call.call(fn2);
// 类似于 Array.prototype.slice.call([1,2,3]);
// 把 fn2 作为 this,实际执行的是 fn2.call();
fn1.call.call(fn2); // 2
// 以下代码不输出
Function.prototype.call(fn1);
Function.prototype.call(fn2);
题2
var name = '杨帅';
function A(x, y) {
var res = x + y;
console.log(res, this.name);
}
function B(x, y) {
var res = x + y;
console.log(res, this.name);
}
B.call(A, 40, 30); // 70 'A'
// 无论有多少 call,访问的都是 Function.prototype.call
// 实际执行的是 A.call(20, 20);
// this -> 20, x -> 20, y -> undefined
// x + y => NAN
// this -> Number(20) -> this.name -> undefined
B.call.call.call(A, 20, 20); // NAN undefined
Function.prototype.call(A, 60, 50); // 无输出
// A.call(80, 70)
Function.prototype.call.call.call(A, 80, 70); // NAN undefined
你学废了么,手动狗头保命