call、apply和bind的实现

call方法

基础版, 只能修改指向,不能传参

Function.prototype.myCall = function(context) {
    // 获取调用者,这里为bar
    context.fn = this;
    // 运行函数
    context.fn();
    // 删除缓存
    delete context.fn;
}

let foo = {
    value: 1
}

function bar() {
    console.log(this.value);
}

bar.myCall(foo);
// 1

eval版本

Function.prototype.myCall = function(context) {
    // 获取调用者,这里为sayHi
    // 将无调用者的状况转换为有调用者的状况
    // 有调用者那么函数内部的this就指向调用者
    context.fn = this;
    var len = arguments.length;
    // 保存传递的参数
    var args = Array(len - 1);
    for (var i = 1; i < len; i++) {
        // 这里只是把获取参数的字符串拼接了,供eval调用
        args[i - 1] = 'arguments[' + i + ']';
    }
    // 不直接调用而用eval是由于参数个数不必定
    // 在这里至关因而在执行context.fn(arguments[1],arguments[2]);
    eval('context.fn('+ args +')');
    delete context.fn;
}

let person = {
    name: 'Wango'
}

function sayHi(age, sex) {
    console.log(this.name, age, sex)
}

sayHi.myCall(person, 24, 'male');
// Wango 24 male

ES6 版本

Function.prototype.myCall = function(context) {
    context.fn = this;
    // 将arguments转换为数组并调用slice方法去除第一个参数
    // 再使用...将数组打散
    context.fn(...Array.from(arguments).slice(1));
    delete context.fn;
}

let person = {
    name: 'Wango'
}

function sayHi(age, sex) {
    console.log(this.name, age, sex);
}

sayHi.myCall(person, 24, 'male');
// Wango 24 male

ES6 升级版

Function.prototype.myCall = function(context, ...args) {
    context.fn = this;
    // 相比使用arguments,这里只是用了ES6的剩余参数
    context.fn(...args);
    delete context.fn;
}

let person = {
    name: 'Wango'
}

function sayHi(age, sex) {
    console.log(this.name, age, sex);
}

sayHi.myCall(person, 24, 'male');
// Wango 24 male

最终版

以前的版本若是传入的对象本来的fn属性或方法会被覆盖,而后被删除;并且传入第一个参数若是不是对象,会报错,因此还须要一些容错处理javascript

// 保存一个全局变量做为默认值
const root = this;

Function.prototype.myCall = function(context, ...args) {
    if (typeof context === 'object') {
        // 若是参数是null,使用全局变量
        context = context || root;
    } else {
        // 参数不是对象的建立一个空对象
        context = Object.create(null);
    }
    // 使用Symbol建立惟一值做为函数名
    let fn = Symbol();
    context[fn] = this;
    context[fn](...args);
    delete context[fn];
}

let person = {
    name: 'Wango',
    fn: function() {
        console.log(this.name);
    }
}

function sayHi(age, sex) {
    console.log(this.name, age, sex);
}

sayHi.myCall(person, 24, 'male');
// Wango 24 male
sayHi.myCall(null, 24, 'male');
// undefined 24 male
sayHi.myCall(123, 24, 'male');
// undefined 24 male
// 原函数不受影响
person.fn();
// Wango

call的实现最核心的部分就是将没有调用者的状况转换为有调用者的状况,函数内部的this天然就指向调用者java

apply方法

apply的实现思路和call方法是同样的,只是只接收两个参数,第二个参数为类数组git

const root = this;

Function.prototype.myApply = function(context, arg) {
    if (typeof context === 'object') {
        context = context || root;
    } else {
        context = Object.create(null);
    }

    const fn = Symbol();
    context[fn] = this;
    context[fn](...arg);
    delete context[fn];
}

let person = {
    name: 'Wango',
    fn: function() {
        console.log(this.name);
    }
}

function sayHi(age, sex) {
    console.log(this.name, age, sex);
}

sayHi.myApply(person, [24, 'male']);
// Wango 24 male

bind方法

// bind 改变this指向可是不当即执行函数,而是返回一个绑定了this的函数

// bind方法在绑定this指向的同时也能够传递参数
Function.prototype.myBind = function(context, ...innerArgs) {
    // 不须要对参数类型进行判断,后边调用call方法时会进行处理
    const fn = this;
    // 不执行函数,而是返回一个函数等待调用
    return function(...finalArgs) {
        // 经过已经实现的call方法实现对this指向的改变,并传入参数执行
        return fn.call(context, ...innerArgs, ...finalArgs);
    }
}

const person = {
    name: 'Wango'
}

function sayHi(age, sex) {
    console.log(this.name, age, sex);
}

const personSayHi_1 = sayHi.myBind(person, 24, 'male');
personSayHi_1();
// Wango 24 male
const personSayHi_2 = sayHi.myBind(person);
personSayHi_2(24, 'male');
// Wango 24 male

bind方法的核心在于在返回的函数中调用call方法es6

参考网址:call、apply和bind的实现github

相关文章
相关标签/搜索