• 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的实现

  • 相关阅读:
    css技巧和经验列表
    CSS3嵌入字体@font-face调用字体
    新闻列表单行滚动(多行显示)简洁向上滚动js效果
    打破构图的平衡!增强设计感染力
    何以双十(补昨天)
    MySQL5.7 基于二进制包的安装
    Nginx Log日志统计分析常用命令
    MySQL错误代码大全
    MVC4中的Display Mode简介
    ReadOnly关键字修饰的变量可以修改,只是不能重新分配
  • 原文地址:https://www.cnblogs.com/hycstar/p/14362676.html
Copyright © 2020-2023  润新知