手写apply,call,bind函数

156 阅读4分钟

基本用法

问题引出

因为 js 是有【定义时上下文】【运行时上下文】【上下文是可以改变的】

function fruits() {}

fruits.prototype = {
color: "red",
say: function() {
console.log("My color is " + this.color);
}
}

var apple = new fruits;
apple.say(); //My color is red

使用 call 和 apply 方法,使 this 指向 banana;
banana = {
color: "yellow"
}
apple.say.call(banana); //My color is yellow
apple.say.apply(banana); //My color is yellow

call 和 apply 区别

func.call(this, arg1, arg2); func.apply(this, [arg1, arg2])

call 和 apply 实例

数组之间追加

var array1 = [12 , "foo" , {name:"Joe"} , -2458];
var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
// array1 值为 [12 , "foo" , {name:"Joe"} , -2458 , "Doe" , 555 , 100]

获取数组的最大值和最小值

var numbers = [5, 458 , 120 , -215 ];
var maxInNumbers = Math.max.apply(Math, numbers), //458
maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458

类(伪)数组使用数组方法

var domNodes = Array.prototype.slice.call(document.getElementsByTagName("\*"));

console 的数据前面加上 app

function log(){ var args = Array.prototype.slice.call(arguments); args.unshift('(app)');

console.log.apply(console, args); };

bind

背景

var altwrite = document.write;
altwrite("hello");

输出结果:

Uncaught TypeError: Illegal invocation

原因: altwrite()函数改变 this 的指向 global 或 window 对象,导致执行时提示非法调用异常,正确的方案就是使用 bind()方法: altwrite.bind(document)("hello") 或者 altwrite.call(document, "hello")

绑定函数

bind 最简单的用法是创建一个函数,是这个函数不论怎么调用都有同样的 this 值。 常见的错误就像上面的例子一样,将方法从对象中拿出来,然后调用,并且希望 this 指向原来的对象,如果不做特殊处理,一般会丢失原来的对象。 然后使用 bind()方法能够很漂亮的解决这个问题

this.num = 9;
var mymodule = {
  num: 81,
  getNum: function() {
    console.log(this.num);
  }
};

mymodule.getNum(); // 81

var getNum = mymodule.getNum;
getNum(); // 9, 因为在这个例子中,"this"指向全局对象

var boundGetNum = getNum.bind(mymodule);
boundGetNum(); // 81

bind 函数与 apply/call 很相似,也是可以改变函数体内 this 的指向

MDN 的解释是 bind()方法会创建一个新函数,被称为绑定函数,当调用这个绑定函数时,绑定函数会议创建它时传入 bind()方法的第一个参数作为 this,传入 bind()方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数

var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
bar(); // undefined
var func = bar.bind(foo);
func(); // 3

手写call 函数

Function.prototype.myCall = function (context) {
    //content:要绑定的对象
    //this:要执行的函数
    if (typeof this !== 'function') {
        throw new TypeError('Error');
    }
    context = context || window;
    // 将要执行的函数传给context的下一步属性,是为了,下一步直接通过要绑定的对象进行调用,则this就指向了要绑定的函数
    context.fn = this;
    const args = [...arguments].slice(1);
    //将this指向context
    let result = context.fn(...args);
    delete context.fn;
    return result;
};

以下为调用实例

//case1:
var doThu = function (a, b) {
    console.log(this);
    console.log(this.name);
    console.log([a, b]);
};
var stu = {
    name: 'xiaoming',
    doThu: doThu,
};
stu.doThu(1, 2); // stu对象 xiaoming [1, 2]
doThu.myCall(stu, 1, 2); // stu对象 xiaoming [1, 2]

//  case2:
this.bar = 1;
let callObj = {
    bar: 2,
    content: function () {
        console.log(this.bar);
        return 3;
    },
};

let result = callObj.content;
result.myCall(callObj);

手写apply函数

function myApply(context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error');
    }
    context = context || window;
    context.fn = this;
    let result;
    //处理call和apply的不同处
    if (arguments[1]) {
        result = context.fn(...arguments[1]);
    } else {
        result = context.fn();
    }
    delete context.fn;
    return result;
}

手写bind函数

Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error');
    }
    //储存函数本身
    const _this = this;
    //去除其他参数,转成数组
    const args = [...arguments].slice(1);
    //bind返回了一个函数,对于函数来说,有两种调用方式,一种是直接调用,一种是通过new的方式
    return function F() {
        if (this instanceof F) {
            //来说通过new的方式,在之前的章节,我们学过如何判断this,对于new 的情况来说,不会被任何形式改变this,所以对于这种情况我们需要忽略传入的this
            return new _this(...args, ...arguments);
        }
        //此种为直接调用,这里选择了apply的方式实现,但是对于参数注意以下情况:因为bind函数可以实现类似这样的代码f.bind(obj,1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的实现(args.concat(...arguments))
        return _this.apply(context, args.concat(...arguments));
    };
};

case1:两次连续调用

this.num = 9;
var mymodule = {
    num: 81,
    getNum: function () {
        console.log(this.num);
    },
};

// mymodule.getNum(); // 81

var getNum = mymodule.getNum;
// getNum(); // 9, 因为在这个例子中,"this"指向全局对象

var boundGetNum = getNum.myBind(mymodule)(12);

case2:通过new的方式调用

function A(a,b){
    this.a=a;
    this.b=b
}
var a = A.bind({x:1},1)
var b = new a(2) // {a: 1, b: 2}