• 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属性或方法会被覆盖,然后被删除;而且传入第一个参数如果不是对象,会报错,所以还需要一些容错处理

    // 保存一个全局变量作为默认值
    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自然就指向调用者

    apply方法

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

    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方法

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

  • 相关阅读:
    三、 复杂对象类型的WebService
    Axis2.x WebService开发指南目录索引
    eclipse/MyEclipse 日期格式、注释日期格式、时区问题
    IE6、IE7、IE8的CSS、JS兼容
    一、CXF WebService准备工作
    十二、用Axis操作 Header头部信息
    六、 跨多个WebService管理Session
    jQuery autocomplete 自扩展插件、自动补全示例
    二、Axis2的简单WebService示例
    六、传递、返回复杂类型的对象
  • 原文地址:https://www.cnblogs.com/hycstar/p/14362676.html
Copyright © 2020-2023  润新知