Javascript call、apply、bind 的原理与应用

发布于:2024-06-06 ⋅ 阅读:(147) ⋅ 点赞:(0)

call

call 函数改变函数执行时的 this 指向并立即执行,可以分开传递参数给函数。

/**
 * 实现一个 call,改变 this 指向并立即执行,分开传递传参
 * 1. 任意一个方法都能调用 call 函数;
 * 2. 该方法中的 this 修改为 call 函数的第一个参数;
 * 3. 调用该方法,并将 call 函数的参数分别传递进去;
 * @param {Object} context 
 * @return 
 */
Function.prototype.myCall = function (context) {
    context = context ? Object(context) : window; // context 为 null 或 undefined 时,this 指向 window,普通值转换为对象
    context.fn = this; // 将 this 代表的函数定义到新上下文的属性上
    let args = []; 
    for (let i = 1, len = arguments.length; i < len; i++) { // 注意 arguments 包含了所有参数,所以要去除第一个参数
        args.push(arguments[i]); 
    } // 将参数转换为数组
    let result = context.fn(...args); // 传入参数,执行上下文的函数,通过 this 机制的隐式绑定,函数中的 this 指向执行上下文 
    delete context.fn;
    return result;
}

console.log(Math.min.myCall(myCreate(null), 1, 4, 2, 8));

apply

apply 函数改变函数执行时的 this 指向并立即执行,可以传递数组形式参数给函数。

/**
 * 实现一个 apply,改变 this 指向并立即执行,传递数组传参
 * 1. 任意一个方法都能调用 apply 函数;
 * 2. 该方法中的 this 修改为 apply 函数的第一个参数;
 * 3. 调用该方法,并将 apply 函数的参数分别传递进去;
 * @param {Object} context 
 * @return 
 */
Function.prototype.myApply = function (context, arr) {
    context = context ? Object(context) : window;
    context.fn = this; // 将 this 代表的函数定义到新上下文的属性上
    let result;
    if (!arr) {
        result = context.fn();
    } else {
        result = context.fn(...arr); 
    }
    delete context.fn;
    return result;
}
console.log(Math.min.myApply(myCreate(null), [1, 4, 2, 8]));

bind

bind,与 call 和 apply 功能类似,只是不立即执行,而是返回一个更换了 this 指向的函数。可以调用该函数并传参,只有 new 调用才能改变函数的 this 指向。

原理

/* 
 * 实现 bind,与 call 和 apply 功能类似,只是不立即执行,而是返回一个更换了 this 指向的函数。可以调用该函数并传参,只有 new 调用才能改变函数的 this 指向。
 * 返回一个函数,绑定 this,传递预置参数
 * bind 返回的函数可以作为构造函数使用。故作为构造函数时应使得 this 失效,但是传入的参数依然有效
*/
Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('bind 只能用于函数');
    }
    let target = this;
    let args = Array.prototype.slice.myCall(arguments, 1);
    var fn = function () {};
    let bound = function () {
        let newArgs = Array.prototype.slice.myCall(arguments);
        console.log(target, target.constructor);
        // 1.this.constructor取的实例对象的原型上的属性。
        // 2.判断是否等于构造函数用 target,而不是 fn。因为虽然new 执行的是 fn,但实际内部执行的方法是 target。
        // 3.如果是 new 方式调用则用当前 this,否则用 context 作为上下文。
        return target.myApply(this instanceof bound ? this : context, args.concat(newArgs));
    };
    // 上面仅仅只是改了new 调用的 this 指向,还得构造原型链。即新函数的原型对象为旧函数的原型对象。
    // 不过做一下寄生中转,以防被旧函数的实例通过 __proto__ 修改原型。但其实还是可以通过 _proto__.__proto__ 修改原型的,只是稍微做一下防护。
    if (this.prototype) {
        // Function.prototype doesn't have a prototype property
        fn.prototype = this.prototype; 
    }
    // 下⾏的代码使bound.prototype是fNOP的实例,因此
    // 返回的bound若作为new的构造函数,new⽣成的新对象作为this传⼊bound,新对象的__proto__就是fn的实例
    bound.prototype = new fn();
    return bound;
}

// let min = Math.min.myBind(myCreate(null), 1);
// console.log(new min(4, 2, 8));

// var z = 0;
// var obj = {
//     z: 1
// };

// function fn(x, y) {
//     this.name = '听风是风';
//     console.log(this.z);
//     console.log(x);
//     console.log(y);
// };
// fn.prototype.age = 26;

// var bound = fn.myBind(obj, 2);
// var person = new bound(3); //undefined 2 3

// console.log(person.name); //听风是风
// console.log(person.age); //26
// person.__proto__.age = 18;
// var person = new fn();
// console.log(person.age); //26

应用

业务场景一 表单校验

结合 Vue3 + TS + Element-Plus。

自定义校验规则校验手机号码。因为 Element-Plus 的表单校验函数只能接收 rule, value, cb 三个参数,当使用 TS 进行类型限制时,无法传入业务需要的其它参数,可以通过 bind 函数调用返回一个已经有初始化参数的校验函数,即将业务需要的参数作为初始化参数进行赋值。

// 业务参数
const errorMessage = ref('')

const rules = reactive<FormRules>({
  phone: [
    {
      required: true,
      trigger: [],
      // bind 返回的函数已经去除初始化参数,所以 TS 类型校验正确
      validator: validatePhoneNumber.bind(this, errorMessage)
    }
  ]
})
// 手机号校验
export function validatePhoneNumber(
  message: Ref,
  rule: any,
  value: any,
  cb: any
) {
  if (message.value !== '') {
    cb(new Error(message.value))
  } else if (value === '') {
    cb(new Error('请输入手机号'))
  } else if (!regExpPhone.test(value)) {
    cb(new Error('手机号格式错误'))
  } else {
    cb()
  }
}

validatePhoneNumber 原函数形参个数,为4。

bind 原函数形参个数
validatePhoneNumber 通过 bind 创建的新函数形参个数,为 3。说明已经去除初始化传入的参数,所以符合 validator 的 TS 参数声明。
bind 新函数形参个数
PS:除以上方法之外,还可以通过以下方法传递自定义校验参数

const errorMessage = ref('')

const rules = reactive<FormRules>({
  phone: [
    {
      required: true,
      trigger: [],
      validator: validator: (rule, value, callback) => {
        validatePhoneNumber(errorMessage, rule, value, callback)
      }
    }
  ]
})

网站公告

今日签到

点亮在社区的每一天
去签到