• 模拟实现call、apply


    1. 知识点补充:

    首先在模拟实现前,先Mark一些我之前不知道的知识:

    a. eval(string)函数:可计算某个字符串,并执行其中的JavaScript代码

    其中,string是必需传入的待计算或待执行的语句,并且必须是原始字符串的形式!

    eval(string)相当于<script> string </script>

    b. 类数组对象(Array-like Object)

    类数组对象是一个对象,比如:arguments、DOM API返回的NodeList对象都属于类数组对象,具有指向对象元素的数组index下标和length属性,但是它们不能使用push/pop/shift/unshift等数组方法!

    但是如何能将类数组转换为真正的数组呢?有如下方法:

    1. Array.prototype.slice.call( arguments )  // 在低版本IE下不支持
    2. [].slice.call( arguments )      // 等同于1
    3. let arr = Array.from( arguments )  // ES6,可将类数组对象和可遍历对象转为真正的数组
    4. let arr = [ ...arguments ]

    以下例为例演示:

    var foo = {
        value: 1
    };
    
    function bar(name, age) {
        console.log(this.value);
       console.log(name)
       console.log(age) } bar.call(foo, 'ning', 20);

    这里可以考虑将bar这个函数作为foo的一个方法,然后在外层执行这个函数,然后再删掉该函数即可!

    2. call的模拟实现

        Function.prototype.call2 = function (context) {
            context.fn = this;  // context是foo,this是bar也就是调用call的那个函数
            context.fn();
            delete context.fn;
        }
    
        // 使用下例来验证call2是否可行
        var foo = {
            value: 1
        }
        function bar() {
            console.log(this.value);
        }
    
        bar.call2(foo);

    content.fn = this;这句首先获取到了调用call的函数,本例这里也就是bar;

    context.fn();即执行bar这个函数;

    delete删掉该函数。

    但是现在的模拟中有几个问题:

    1. 不能传入参数,因此我们将利用arguments,从Arguments对象中从第二个参数(因为第一个参数是this)开始取值,放到一个数组里,再把这个数组放到要执行的函数的参数里
    2. this参数传入null或者undefined时,我们需要将this指向window
    3. 当call2()内传的不是一个对象,而是一个基本数据类型时,如何处理?(在call实现时会自动调用Object()转换)
    4. 函数可以有返回值

    所以我们得到以下call2()代码:

        Function.prototype.call2 = function (context) {
            context = context ? Object(context) : window;  
            context.fn = this;
    
            var arr = [];
            for (var i = 1, len = arguments.length; i < len; i++) {
                arr.push('arguments[' + i + ']');
            }
    
            var result = eval('context.fn(' + arr + ')');
            delete context.fn;
            return result;
        }

    下面我们测试一下:

        var value = 'global';
        var foo = {
            value: 1
        }
        function bar(name, age) {
            console.log(this.value)
            return {
                value: this.value,
                name: name,
                age: age
            }
        }
    
        bar.call2(null)  // global
    
        console.log(bar.call2(foo, 'ning', 20))
        // 1
        // {value: 1, name: "ning", age: 20}

    说明两点:

    1. arr.push('arguments['+ i +']');这句得到的是(2) ["arguments[1]", "arguments[2]"]一个新数组,是我们想要的
    2. eval('context.fn('+ arr +')');这句中arr会自动调用arr.toString()得到一个字符串:arguments[1],arguments[2],然后进行字符串拼接

    下面给出ES6版本的:

        Function.prototype.call2 = function (context) {
            context = context ? Object(context) : window; 
            context.fn = this;
    
            let arr = [...arguments].slice(1);
            let result = context.fn(' + arr + ');
    
            delete context.fn;
            return result;
        }

    3. apply的模拟实现:

        Function.prototype.apply2 = function (context, arr) {
            context = context ? Object(context) : window;
            context.fn = this;
    
            var result = [];
            // 没有arr参数直接执行
            if (!arr) {
                result = context.fn();
                // 有arr参数则将参数拼接后执行
            } else {
                var args = [];
                for (var i = 0; i < arr.length; i++) {
                    args.push('arr[' + i + ']')
                }
                result = eval('context.fn(' + args + ')')
            }
    
            delete context.fn;
            return result;
        }

    下面给出ES6版本的:

        Function.prototype.apply2 = function (context, arr) {
            context = context ? Object(context) : window;
            context.fn = this;
    
            let result = [];
            if (!arr) {
                result = context.fn();
            } else {
                // ...arr的使用
                result = context.fn(...arr)
            }
    
            delete context.fn;
            return result;
        }
  • 相关阅读:
    在Vue构建的SPA网页里 刷新的话,显示404页面
    springboot2.x 设置404页面
    关于Typora不显示PicGo.app的问题
    DBeaver中table插入新的数据
    DBeaver修改table的column名字
    Zeal
    Android Studio 快速创建 Toast
    使用VSCode调试单个JavaScript文件
    使用maven打包普通的java项目
    在命令行界面实现彩色字符输出 -- 并且介绍和这个相关的比较好用的java类库
  • 原文地址:https://www.cnblogs.com/ningyn0712/p/11761349.html
Copyright © 2020-2023  润新知