JavaScript——this的理解和call、apply、bind的使用

JavasScript中,this是一个很神奇的关键字,有时候,我们可能会看到一大堆this而把头的绕晕了,不过,只要理解了this的,相信以后就不会把自己绕进去了。这里,记录一下我对于this的理解和this相关运用。

一、理解this

Javascript中this总是指向调用它所在方法的对象。this的值通常是由所在函数的执行环境决定,也就是说要看函数是如何被调用的。同一个函数每一次调用,this都可能指向不同的对象。

1. 普通函数中的this

第一段代码:

function fn(){
  console.log(this) ; //this指向顶层对象,在浏览器中是window对象,在node中是global对象
}
fn(); 

第二段代码:

var name = "Bob";
var obj = {
    name: "Dancy",
    fn: function() {    
         console.log(this); //this指向obj对象
         console.log(this.name); //Dancy
    }
}
 obj.fn(); 

第三段代码:

Array.prototype.add = function(arr) {
   console.log(this); // this指向数组[1, 2, 3],注意,这里是调用此方法的对象,而不是Array.prototype
};
[1, 2, 3].add();

2. 构造函数中的this
构造函数中的this指向新创建的对象本身。

function Person(obj) {
     this.name = obj.name;
     this.age = obj.age;
     this.height = obj.height;
     console.log(this); //当前new的对象
}
var p1 = new Person({name: "Bob", age: 25, height: 166})
console.log(p1); //与上面的一样

上面代码中,p1为new关键字创建的一个实例对象,那为什么说构造函数中this指向的是创建的对象本身呢?实际上就是和new关键字有关,我们在new一个实例的对象时,new做了一下几件事情:

a. 创建一个对象obj
b. 改变构造函数中的this指向,让其指向当前new的对象obj
c. 在我们添加name、age等属性之后,构造函数最后默认返回这个对象

所以,在new的时候,上面构造函数的代码相当于

//注意:仅供理解
function Person(obj) {
    var newObj = {}; //就是上面构造函数的this指向的对象
    newObj.name = obj.name;
    newObj.age = obj.age;
    newObj.height = obj.height;
    return newObj;
}

3. ES6箭头函数中没有this

var name = "Bob";
var obj = {
    name: "Dancy",
    fn: () => { 
         console.log(this); //顶层对象,在浏览器中是window对象
    }
}
 obj.fn(); 

二、call apply 和bind的理解

1. 基本概念和作用

这三个方法都继承自Function.prototype,所以所有的函数函数都可以调用这三个方法使用。
他们的作用如下:

call和apply: 指定函数内部的this指向,然后在所指定的作用域中,调用该函数,会立即执行该函数。

bind: 指定函数内部的this指向,然后返回一个新函数。bind方法不会立即执行一个函数,不兼容低版本IE(低于IE9)

2. call apply bind的联系与区别

联系:

a. 三者第一个参数都是指定函数的this指向
b. 都可以在函数调用时传递参数。

区别:

a. call和apply会自动执行调用的函数,bind是返回一个函数,需要再加()才能执行该函数。
b. call和apply没有兼容问题,bind不兼容低版本IE浏览器。
c. call和bind给函数传参都是依次写在后面,而apply只有两个参数,第二个参数是一个数组,为函数传的参数都写在该数组中。

call的参数:call(this指向,arg1, arg2, arg3, …)
apply的参数:apply(this指向,[arg1, arg2, arg3, …])
bind的参数:bind(this指向,arg1, arg2, arg3, …)

例:

var obj = {
    a: 2
}
function fn(a, b, c) {
    console.log(this.a) ; // 2
    console.log(a + b + c); // 6
}
fn.call(obj, 1, 2, 3);
fn.apply(obj, [1, 2, 3])
fn.bind(obj, 1, 2, 3)(); //注意bind返回的是一个函数,所以要加()才能执行

上面的例子,通过call apply bind分别修改函数fn内部的this指向对象obj。三者的作用一样,只是传递参数时不同。使用call和apply的时候,改变fn中this指向的同时会执行函数fn,而bind只是返回函数fn,如要执行还得再加()。

三、call apply 和bind的应用场景

1. 找出数组中的最大数

Javascript中是没有提供找出数组中最大值的方法的,结合使用继承自Function.prototype的apply和Math.max方法,就可以返回数组的最大值。

代码:

var a = [2, 4, 5, 7, 8, 10];
console.log(Math.max.apply(null, a)); 
console.log(Math.max.call(null, ...a)); //这里的...是ES6新增的语法,具体请百度查询理解
//这里相当于Math.max(...a)

2. 将数组的空元素变为undefined

空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined和null。因此,遍历内部元素的时候,会得到不同的结果。

代码:

var a = [1, , 3];
a.forEach(function(index) {
   console.log(index); //1,3 ,跳过了空元素。
})
Array.apply(null,a).forEach(function(index){
   console.log(index);    ////1,undefined,3  ,将空元素设置undefined
})

3. 转换类似数组的对象

比如函数中的arguments不定参,arguments是一个类数组对象,但不是数组,没有数组的方法,如果我们想要arguments能够使用数组的方法,则call apply就派上用场了。

代码:

function sum(a, b, c) {
    var aArgs = Array.prototype.slice.call(arguments);
    console.log(aArgs); // [1, 2, 3]
}
sum(1, 2, 3);

arguments虽然没有数组的方法,但是他的数据结构和数组很相似,而slice底层实现是通过循环来实现的,又由于slice来源于Array.prototype,所有数组都可调用该方法,所以内部是通过循环this来得到结果的,循环时this指向调用函数slice的对象也就是当前数组,而我们的arguments也同样可以像数组一样进行循环,所以,只要把slice内部的this指向arguments则可以实现arguments的slice方法了。

4. 定时器中使用bind
定时器的第一个参数是一个函数,当这个函数需要改变内部的this指向时,只能使用bind,因为call和apply在改变函数的同时会执行函数,而无法达到定时器这个函数参数想要的效果了,bind会返回这个函数,正好是定时器需要的。

代码:

obj = { 
    index: 0, 
    time: 0 
} 
function fn() { 
    this.index ++; 
    this.time += 1000; 
    console.log(this)
}
setInterval(fn.bind(obj), 1000)

执行结果:
JavaScript——this的理解和call、apply、bind的使用_第1张图片

5. 写一个bind函数的兼容

代码:

if (!Function.prototype.bind) {
    Function.prototype.bind = function(obj) {
        if (typeof this !== 'function') {
            throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
        }

        var aArgs   = Array.prototype.slice.call(arguments, 1), // 不定参arguments复制为一个数组,从1开始是因为bind第一个参数为this的指向,后面才是函数的参数
        that = this;

        return function() { // bind的结果是返回一个函数
            that.apply(obj, aArgs)
        };
    };
}

6. 一个继承的例子

代码:

function Father(n) {
     this.name = n.name;
     this.age = n.age;
     this.length = n.length;
}
Father.prototype = {
     constructor: Father,
     getValue(value) {
          alert(value)
     }
}

function Son(n) {
     Father.call(this, n) //使用call实现子类继承父类的方法
}

function C() { }
C.prototype = Father.prototype;
Son.prototype = new C();

var son = new Son({name: "son"})
son.getValue("2")

你可能感兴趣的:(javascript原生之美)