call、apply、bind

call、apply、bind的作用是改变函数运行时this的指向

方法调用模式:

  当一个函数被保存为对象的一个方法时,如果调用表达式包含一个提取属性的动作,那么他就是被当做一个方法来调用,此时的this被绑定到这个对象。例如

var a = 1;
var obj = {
    a: 2,
    fn: function(){
        console.log(this.a);
    }
}

obj.fn();

输出结果:2

  • 此时 this 是指 obj 这个对象,obj.fn()实际是 obj.fn.call(obj) 。
  • 事实上谁调用这个函数,this就是谁。
  • DOM对象绑定事件也属于方法调用模式,因此它绑定的this就是事件源DOM对象。如

document.addEventListener('click', function(e){
    console.log(this);
    setTimeout(function(){
        console.log(this);
    }, 200);
}, false);  

点击页面,依次输出:document 和 window对象

   解析:点击页面监听click事件属于方法调用,this指向事件源DOM对象,即 obj.fn.apply(obj),setTimeout内的函数属于回调函数,可以这么理解,f1.call(null, f2),所以this指向window。

函数调用模式:

  就是普通函数的调用,此时的this被绑定到window

最普通的函数调用

函数嵌套

把函数赋值后再调用

function fn(){
    console.log(this); //window
}
fn();

function fn1(){
    function fn2(){
        console.log(this); //window
    }
    fn2();
}
fn1();

var a = 1;
var obj = {
    a: 2,
    fn: function(){
        console.log(this.a);
    }
}
var fn1 = obj.fn;
fn1();//1

      obj.fn是一个函数 function(){console.log(this.a)} ,此时fn1就是不带任何修饰的函数调用,function(){console.log(this.a)}.call(undefined),按理说打印出来的this应该就是undefined,但是浏览器里有一条规则:"如果你传入的context是null或者undefined,那么window对象就是默认的context(严格模式下默认context是undefined)"。因此上面的this绑定的就是window,它被称为隐式绑定。如果希望打印出2,可以修改fn1()为fn1.call(obj);

回调函数 

改写代码

var a = 1;
function f1(fn){
    fn();
    console.log(a);//1
}
f1(f2);
function f2(){
    var a = 2;
}

var a = 1;
function f1(){
    (function(){
        var a = 2;
    })();
    console.log(a);//1
}

    仍旧是最普通的函数调用,f1.call(undefined),this指向window,打印出的是全局的a;借此可以解释为什么setTimeout总是丢失this了,因为它也就是一个回调函数而已。

setTimeout(function(){
    console.log(this); // window
    function fn(){
        console.log(this); // window
    }
    fn();
}, 0);

构造器调用模式:

    new一个函数时,背地里会创建一个连接到 prototype 成员的新对象,同时 this 会被绑定到那个新对象上

function Person(name, age){
    // 这里的this都是指向实例
    this.name  = name;
    this.age = age;
    this.sayAge = function(){
        console.log(this.age);
    }
}

var per = new Person('yw', 2);

per.sayAge(); // 2

call

    call方法第一个参数是要绑定给 this 的值,后面传入的是一个参数列表。当第一个参数为 null、undefined的时候,默认指向window。

var arr = [1, 2, 3, 89, 46];
var max = Math.max.call(null, arr[0], arr[1], arr[2], arr[3], arr[4]); // 89

可以这么理解

obj.fn();  => obj.fn.call(obj); 

fn(); => fn.call(null); 

f1(f2); => f1.call(null, f2);

来看一个例子:

var obj = { message: 'My name is: '};
function getName(firstName, lastName){
    console.log(this.message + firstName + ' ' + lastName);
}
getName.call(obj, 'yang', 'wei');

输出:My name is: yang wei

apply

    apply接收两个参数,第一个参数是要绑定给 this 的值,第二个参数是一个参数数组。当第一个参数为 null、undefined的时候,默认指向window。

var arr = [1, 2, 3, 89, 46];
var max = Math.max.apply(null, arr); // 89

可以这么理解

obj.fn();  => obj.fn.apply(obj); 

 fn(); => fn.apply(null);

f1(f2); => f1.apply(null, f2);

    事实上 apply 和 call 的用法几乎相同,唯一的差别在于:当函数需要传递多个变量时,apply可以接收一个数组作为参数输入,call则是接收一系列的单独变量。

例子:

var obj = { message: 'My name is: '};
function getName(firstName, lastName){
    console.log(this.message + firstName + ' ' + lastName);
}
getName.apply(obj, ['yang', 'wei']);

输出:My name is: yang wei

    可以看到,obj是作为函数上下文的对象,函数 getName 中 this 指向了 obj 这个对象,参数 firstName 和 lastName 是放在数组中传入 getName 函数。

    call 和 apply 可用来借用别的对象的方法,这里以call()为例

var Person1 = function(){
    this.name = 'yang';
}
var Person2 = fucntion(){
    this.getName = function(){
        console.log(this.name);
    }
    Person1.call(this);
}

var person = new Person2();

person.getName();  //yang

Person2实例化出来的对象 person 通过 getName 方法拿到了Person1 中的name。

因为Person2中,Person1.call(this) 的作用就是使用Person1对象代替 this 对象,

那么Person2 就有了 Person1 中的所有属性和方法了,

相当于 Person2 继承了 Person1 的属性和方法。

    对于什么时候用什么方法?如果参数本来就存在一个数组中,那就用 apply ,如果参数比较散乱相互之间没有什么关联,就用call。

bind

    和 call 很相似,第一个参数是 this 的指向,从第二个参数开始是接收的参数列表。区别在于 bind 方法返回值是函数以及 bind 接收的参数列表的使用。

(1)bind返回值函数

var obj = { name: 'yangwei' };
function printName(){
    console.log(this.name);
}
var yw = printName.bind(obj);

console.log(yw);    //function(){ ... }
yw(); // yangwei

bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。而原函数 printName 中的 this 并没有被改变,依旧指向全局对象 window。

(2)参数的使用

function fn(a, b, c){
    console.log(a, b, c);
}
var fn1 = fn.bind(null, 'yangwei');
fn('A', 'B', 'C');       // A B C
fn1('A', 'B', 'C');     // yangwei A B
fn1('B', 'C');           // yangwei B C
fn.call(null, 'yw');   // yw undefined undefined    

call 是把第二个及以后的参数作为 fn 方法的实参传进去,

而 fn1 方法的实参则是在 bind 中参数中的基础上再往后排

    有时候我们也用 bind 方法实现函数 珂里化,以下是一个简单的示例

var add = function(x){

    return function(y){

        return x + y;

    }

}

var increment = add(1);

var addTen = add(10);

increment(2);

// 3

addTen(2);

// 12

    在低版本浏览器没有 bind 方法,我们也可以自己实现:

if( !Function.prototype.bind ){
    Function.prototype.bind = function(){
        var self = this;                                       // 保存原函数
              context = [].shift.call(arguments),    // 保存需要绑定的this上下文
              args = [].slice.call(arguments);          // 剩余的参数转为数组
        return function(){                                   // 返回一个新函数
            self.apply(context, [].concat.call(args, [].slice.call(arguments)));
        }
    }
}

应用场景

求数组中的最大和最小值

var arr = [1, 2, 3, 89, 46];
var max = Math.max.apply(null, arr);  // 89
var min = Math.min.apply(null, arr); // 1

将类数组转为数组

var trueArr = Array.prototype.slice.call( arrayLike );

数组追加

var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
var total = [].push.apply(arr1, arr2);  // 6
// arr1 = [1, 2, 3, 4, 5, 6]
// arr2 = [4, 5, 6]

判断变量类型

function isArray(obj){
    return Object.prototype.toString.call(obj) == '[Object Array]';
}
// isArray([])  => true
// isArray('yw') => false

利用call和apply做继承

fucntion Person(name, age){
    // 这里的this都指向实例
    this.name = name;
    this.age = age;
    this.sayAge = function(){
        console.log(this.age)
    }
}
function Female(){
    Person.apply(this, arguments); // 将父元素所有方法在这里执行一遍就继承了
}
var female = new Female('yw', 27);

使用log代理console.log  

function log(){
    console.log.apply(console, arguments);
}

总结

    bind返回对应函数,便于之后调用;apply、call则是立即调用。除此之外,在ES6的箭头函数下,call和apply将失效,对于箭头函数来说:

  • 箭头函数体内的 this 对象, 就是定义时所在的对象, 而不是使用时所在的对象;所以不需要类似于var _this = this这种丑陋的写法;
  • 箭头函数不可以当作构造函数,也就是说不可以使用 new 命令, 否则会抛出一个错误;
  • 箭头函数不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替;
  • 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值