模擬實現call、apply

1. 知識點補充:

首先在模擬實現前,先Mark一些我以前不知道的知識:數組

a. eval(string)函數:可計算某個字符串,並執行其中的JavaScript代碼

其中,string是必需傳入的待計算或待執行的語句,而且必須是原始字符串的形式!app

eval(string)至關於<script> string </script>函數

b. 類數組對象(Array-like Object)

類數組對象是一個對象,好比:arguments、DOM API返回的NodeList對象都屬於類數組對象,具備指向對象元素的數組index下標和length屬性,可是它們不能使用push/pop/shift/unshift等數組方法!測試

可是如何能將類數組轉換爲真正的數組呢?有以下方法:this

  1. Array.prototype.slice.call( arguments )  // 在低版本IE下不支持
  2. [].slice.call( arguments )      // 等同於1
  3. let arr = Array.from( arguments )  // ES6,可將類數組對象和可遍歷對象轉爲真正的數組
  4. let arr = [ ...arguments ]

如下例爲例演示:spa

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的一個方法,而後在外層執行這個函數,而後再刪掉該函數便可!prototype

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;code

context.fn();即執行bar這個函數;對象

delete刪掉該函數。blog

 

可是如今的模擬中有幾個問題:

  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;
    }
相關文章
相關標籤/搜索