JavasScript中,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();
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,如要执行还得再加()。
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)
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")