this,call,apply,bind浅析
在JS中,this指向是一个难点,在本文中讲解几种常见的this指向问题,并介绍一下call,apply,bind这三个函数的用法。
一、常见的this指向情况
首先要明白一点就是,函数里面才会有this,而this指向是由函数执行的方式来决定的,并且this指向的一定是对象,常见的有三种情况:
- 函数名(),即函数名加括号自执行,该函数里面的this指向window对象;
- 对象.方法(),即对象点属性方法加括号执行,此方法内的this指向该对象;
- 无论什么形式的事件函数,函数内部的this指向触发事件的对象。
下面我们来看几个例子:
案例1:
var oBox = document.getElementById('box');
function a() {
console.log( this );
}
a();
document.onclick = a;
//oBox.onclick = a;
打开控制台,会看到打印了一个window,这是第6行a(),a函数自执行时,函数内部的this指向window对象。然后,我们点击一下页面,触发了document的onclick事件,此时控制台会再打印一个document,只是因为事件函数内部的this指向触发该事件的对象。同理,如果我们把第7行注释,第8行取消注释,代码功能换成给页面上一个id为‘box’的标签对象注册一个点击事件,那么当我们在页面上点击这个标签的时候,控制台会给我们打印出这个标签(如下图)。

案例二:
var obj = {
name:'Person',
fn:function(){
console.log(this);
}
}
obj.fn();
这个案例里,fn是对象obj的一个方法,当我们用obj.fn()执行的时候,fn里的this指向obj,我们会看到控制台打印的效果如下:

案例三:
var a = function () {
console.log( this );
};
function x() {
a();
}
x();
这个案例里的this指向的是window,即我们开篇说的第一种情况。这里可能有一个误导的地方就是,有人会觉得a在x中运行,this会指向x,其实我们只要再认真读一下我们之前说过的两点就会明白了,一点是this指向一定是一个对象,这里x不是对象,另一点就是函数名加括号自执行,this指向window,不管这个函数在放在哪里执行的,哪怕是放在一个对象里面,只要它是函数名加括号自执行的方式,那么它里面的this指向一定是window。
案例四:
function x() {
function a() {
console.log(this);
}
a();
}
x();
这里的this也是指向window,吃透了案例三,再看案例四就觉得简单了。
案例五:
var oBox = document.getElementById("box");
oBox.a = function () {
console.log(this);
};
oBox.a();
(oBox.a)();
document.onclick = oBox.a;
这个案例里,第7行和第8行打印的this为id是box的标签,当我们点击一下页面后,会打印document,因为无论什么形式的事件函数,里面的this都会指向触发该事件的对象。以下是控制台的打印结果:

this剖析:this本身就是一个对象,跟其他数据类型一样,JS也会在内存中给它分配一个地址,只是是动态的,不是固定的,它的变化是根据不同执行环境来动态分配的。而this是什么时候产生呢,就是this指向的对象是什么时候产生,this就是什么时候产生,我们前面总结的第一种情况,函数名加括号自执行,实际上是window.函数名()执行的,所以this会指向window,那么实际上和第二种情况对象.函数名()执行方式是一样的。
二、改变this指向
在我们的实际运用中,有时候我们需要改变this的指向,JS给我们提供了三个改变this执行的方法:call,apply和bind,让我们先来看看它们的用法和不同之处。
- call:
在函数执行阶段使用,可以改变this指向;
call的第一个参数代表函数的this指向;
原函数的第一个形参对应call的第二位实参,第二个形参对应call的第三位实参,以此类推。 - apply:
在函数执行阶段使用,可以改变this指向;
apply的第一个参数代表函数的this指向;
apply的第二个参数是个数组,数组第一位对应原函数第一个形参,以此类推。 - bind:(不支持IE8及以下)
并不会立刻帮助函数去自执行,当函数执行的时候去改变this指向;
参数形式和call一样。
它们的用法是:函数名.call(arg1,arg2,agr3......)、函数名.apply(arg1,[arg2,arg3......])、函数.bind(arg1,arg2,arg3......),主要的区别在于call和apply会让函数立即自执行,而bind不会让函数自执行;而call和apply的区别在于传参的不同,apply的第二个参数是一个数组。下面我们来看几个实际的例子:
案例六:
function a(x,y) {
console.log(x+y);
console.log(this);
}
a(1,2);
a.call(document,5,5);
a.apply(oBox,[5,1]);
这个案例里,第6行a函数自执行了一次,第7行用call来执行a函数,改变this指向为document,而第8行用apply来执行函数a,改变其this指向为oBox对象,并且这里传的第二个参数用了一个数组。最终的打印结果如下:

案例七:
function a(x) {
console.log(x);
console.log(this);
}
a.call(null,1);
a.apply(undefined,[2]);
当我们给call、apply和bind的第一个参数传null或者undefined时,this指向window,有时候我们只需要利用call、apply、bind传参而不需要改变this指向时,我们会这样用。下面我们一起来看打印的结果:

案例八:
var oBox = document.getElementById("box");
oBox.x = function () {
console.log(this);
};
oBox.x();
oBox.x.call(document);
对象的方法依然可以通过call、apply和bind改变this指向。效果如下:

下面我们来看看bind,它的用法和call是类似的,不同在于它不会立即执行函数,而是在函数被动执行的时候再去改变this指向,我们看下面的例子:
案例九:
var oBox = document.getElementById("box");
var oWrap = document.getElementById("wrap");
oBox.onclick = eFn.bind(oBox,200,150);
oWrap.onclick = eFn.bind(oWrap,150,200);
function eFn(x,y) {
this.style.width = x+"px";
this.style.height = y+"px";
}
页面上有两个div标签,一个id为box,一个id为wrap,我们希望实现一个功能,就是点击它们的时候,它们分别按照不同的值去改变宽高,因为功能一致,所以我们把它们的点击事件函数进行封装,在封装函数中改变当前点击对象的宽高,因为点击事件是被动触发的,所以此时我们会用到bind,通过bind巧妙传递参数并且改变this指向,达到我们想要实现的功能,这个案例是bind的一个经典运用。
值得一提的是,apply和bind除了改变this指向的功能,在传参方式上也给我们提供了一些好的解决办法,bind可以在传参的同时不主动执行这个函数,给我们在写事件函数传参和定时器函数传参时提供了方便,而apply用数组传参的方式也给我们提供了新的便利,比如下面的例子:
案例10:
我们需要找出一个数字数组中的最大值,我们知道Math有个max方法是找最大值的,但是max接收的参数是分开的一个个的数字,而不是数组,所以找出数组的最大值,通常我们的做法是这样的:
var arr = [3,16,5,6,9,12,32,21];
var max =arr[0];
for (var i = 1; i < arr.length; i++) {
if(arr[i]>max)
max = arr[i];
}
console.log(max);
但是apply给我们提供了一个更好的解决方式,如下:
var arr = [3,16,5,6,9,12,32,21]; var max = Math.max.apply(null,arr); console.log(max);
以上是本文的全部内容,感谢阅读!

浙公网安备 33010602011771号