call/apply/bind的实现

196 阅读2分钟

1、实现call

call()方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法。

通过 call 方法,你可以在一个对象上借用另一个对象上的方法

语法fun.call(thisArg[,arg1[,arg2,…]]);

举个例子:

var foo = {
    value: 1
};

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

bar.call(foo); // 1

当调用 call 的时候,把 foo 对象改造成如下

var foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
};

foo.bar(); // 1
  • 模拟实现第一步
    • 将函数设为对象的属性
    • 执行该函数
    • 删除该函数

代码实现如下:

function myCall(context){
	// 首先要获取调用call的函数,用this可以获取(this代表bar函数)
    context.fn = this;
    context.fn();
    delete context.fn;
}
  • 模拟实现第二步

call 函数还能给定参数执行函数,我们可以从 Arguments 对象中取值,取出第二个到最后一个参数,然后放到一个数组里。

var foo = {
    value: 1
};

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

bar.call(foo); // 1
function myCall(context){
	// 首先要获取调用call的函数,用this可以获取(this代表bar函数)
    context.fn = this;
    var args = [];
    // es 方法获取参数并执行
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    eval('context.fn(' + args +')');
    
    // // es6 方法获取参数并执行
    // for(var i = 1, len = arguments.length; i < len; i++) {
    //     args.push(arguments[i]);
    // }
    // context.fn(...args)
    
    delete context.fn;
}
  • 模拟实现第三步
    • this 参数可以传 null,当为 null 的时候,视为指向 window。
    • 函数是可以有返回值的
    • 避免fn重名
var foo = {
    value: 1
};

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

bar.call(foo); // 1
// 生成UUID 通用唯一识别码
function generateUUID(){
    var i, random;
    var uuid = '';
    for (i = 0; i < 32; i++) {
        random = Math.random() * 16 | 0;
        if (i === 8 || i === 12 || i === 16 || i === 20) {
            uuid += '-';
        }
        uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
            .toString(16);
    }
    return uuid;
}
//简单模拟Symbol属性
function mySymbol(obj) {
    var unique_proper = "00" + Math.random();
    if (obj.hasOwnProperty(unique_proper)) {
        arguments.callee(obj)//如果obj已经有了这个属性,递归调用,直到没有这个属性
    } else {
        return unique_proper;
    }
}
// call 函数实现
function myCall(context){
	var context = context || widow
	// es3 方法 可以写个uuid 或者 fn + new Date().getTime();
    var fn = generateUUID()
    // // 自己实现Symbol方法
    // var fn = mySymbol(context)
	// // e6 方法
	// var fn = Symbol()
	// 首先要获取调用call的函数,用this可以获取(this代表bar函数)
    context[fn] = this;
    var args = [];
    var result
    // es 方法获取参数并执行
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
   		result = eval('context[fn](' + args +')');
    
    // // es6 方法获取参数并执行
    // for(var i = 1, len = arguments.length; i < len; i++) {
    //     args.push(arguments[i]);
    // }
    // result = context[fn](...args)
    
    delete context.fn;
    
    return result
}

2、实现apply

语法与 call() 方法的语法几乎完全相同,唯一的区别在于,apply的第二个参数必须是一个包含多个参数的数组(或类数组对象)。apply的这个特性很重要,

语法:fun.apply(thisArg[, argsArray])

可以根据上面call函数实现来完善以下代码。

function myApply(context, arr) {
    var context = context || window;
    context.fn = this;
    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }
    delete context.fn
    return result;
}

3、实现bind

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

语法:fun.bind(thisArg[, arg1[, arg2[, ...]]])

bind会创建一个新函数(称之为绑定函数),原函数的一个拷贝,也就是说不会像call和apply那样立即执行。

当这个绑定函数被调用时,它的this值传递给bind的一个参数,执行的参数是传入bind的其它参数和执行绑定函数时传入的参数。

  • bind是ES5新增的一个方法
  • 传参和call或apply类似
  • 不会执行对应的函数,call或apply会自动执行对应的函数
  • 返回对函数的引用

兼容写法

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, // this在这里指向的是目标函数
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP
                 ? this //此时的this就是new出的obj
                 : oThis || this,//如果传递的oThis无效,就将fBound的调用者作为this
               //将通过bind传递的参数和调用时传递的参数进行合并,并作为最终的参数传递
               aArgs.concat(Array.prototype.slice.call(arguments)));
        };
    fNOP.prototype = this.prototype;
    //将目标函数的原型对象拷贝到新函数中,因为目标函数有可能被当作构造函数使用
    fBound.prototype = new fNOP();
    //返回fBond的引用,由外部按需调用
    return fBound;
  };
}