setTimeout改变this指向

Posted 最骚的就是你

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了setTimeout改变this指向相关的知识,希望对你有一定的参考价值。

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			var name = "李四";

			function Coder(name) {
				this.name = name;

				function alerts() {
					alert(this.name);
				}
				this.getName = function() {
					console.log(this.name)
				};
				this.delayGetName = function() {
					setTimeout(function() {
						alert(this.name);
					}, 1000); //李四
				};
			}
			var me = new Coder(\'张三\')
			me.delayGetName();
		</script>
	</head>

	<body>
	</body>

</html>

 上面的 setTimeout  里面的this 指向window;

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			var name = "李四";

			function Coder(name) {
				this.name = name;

				function alerts() {
					alert(this.name);
				}
				this.getName = function() {
					console.log(this.name)
				};
				this.delayGetName = function() {
					var that=this;   //改变this指向
					setTimeout(function() {
						alert(that.name);
					}, 1000); //张三
				};
			}
			var me = new Coder(\'张三\')
			me.delayGetName();
		</script>
	</head>

	<body>
	</body>

</html>

  

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			var name = "李四";

			function Coder(name) {
				this.name = name;

				function alerts() {
					alert(this.name);
				}
				this.getName = function() {
					console.log(this.name)
				};
				this.delayGetName = function() {
					
					setTimeout(function() {
						var that=this;   //setTimeout 里面的this 指向window
						alert(that.name);
					}, 1000); //李四
				};
			}
			var me = new Coder(\'张三\')
			me.delayGetName();
		</script>
	</head>

	<body>
	</body>

</html>
<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			var name = "李四";

			function Coder(name) {
				this.name = name;

				function alerts() {
					alert(this.name);
				}
				this.getName = function() {
					console.log(this.name)
				};
				this.delayGetName = function() {
					setTimeout(function() {
						alert(this.name);
					}.bind(this), 1000); // 张三
				};
			}
			var me = new Coder(\'张三\')
			me.delayGetName();
		</script>
	</head>

	<body>
	</body>

</html>
<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			var name = "李四";

			function Coder(name) {
				this.name = name;

				function alerts() {
					alert(this.name);
				}
				this.getName = function() {
					console.log(this.name)
				};
				this.delayGetName = function() {
					setTimeout(()=>{   //使用箭头函数
						alert(this.name);
					}, 1000); // 张三
				};
			}
			var me = new Coder(\'张三\')
			me.delayGetName();
		</script>
	</head>

	<body>
	</body>

</html>

 

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			function Coder(name) {
				this.name = name;

				function alerts() {
					alert(this.name);
				}
				this.getName = function() {
					console.log(this.name)
				};
				this.delayGetName = function() {
					setTimeout(alerts.bind(this), 1000);  //张三
				};
			}
			var me = new Coder(\'张三\')
			me.delayGetName(); //延迟一秒输出Jins
		</script>
	</head>

	<body>
	</body>

</html>

再来看看匿名函数的this:

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript" src="js/jquery-3.0.0.min.js"></script>
		<script type="text/javascript">
			$(function() {
				var name = "李四";
				var obj2 = {
						name: "张三",
						fun: function() {
							console.log(this);
							console.log(this.name);
						}
					}
					/*
					$(document).click(function() {
						console.log(this.name); //李四
					}.bind(window));  
					*/
					/*
					$(document).click(function() {
						console.log(window.name);//李四
					});   
					*/
					/*
					$(document).click(function() {
						var that = window;
						console.log(that.name); //李四
					});
					*/
					//$(document).click(obj2.fun.bind(obj2));  //张三
				$(document).click(obj2.fun); //document  undefined
				    //$(document).click($.proxy(obj2.fun, obj2)); //张三
			})
		</script>
	</head>

	<body>
	</body>

</html>

  

 

bind顾名思义,绑定。

bind()方法会创建一个新函数,当这个新函数被调用时,它的this值是传递给bind()的第一个参数,它的参数是bind()的其他参数和其原本的参数。

上面这个定义最后一句有点绕,我们来理一下。

bind()接受无数个参数,第一个参数是它生成的新函数的this指向,比如我传个window,不管它在何处调用,这个新函数中的this就指向window,这个新函数的参数就是bind()的第二个、第三个、第四个....第n个参数加上它原本的参数。(行吧,我自己都蒙圈了)

我们还是看看栗子比较好理解,举个bind()最基本的使用方法:

复制代码
this.x = 9; 
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 返回 81

var retrieveX = module.getX;
retrieveX(); // 返回 9, 在这种情况下,"this"指向全局作用域

// 创建一个新函数,将"this"绑定到module对象
// 新手可能会被全局的x变量和module里的属性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81
复制代码

这里很明显,我们在window对象下调用retrieveX,得到的结果肯定是window下的x,我们把module对象绑定到retrieveX的this上,问题就解决了,不管它在何处调用,this都是指向module对象。

还有bind()的其他参数,相信第一次接触bind()的朋友看到上面的定义都会蒙圈。

还是举个栗子:

复制代码
function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// 创建一个拥有预设初始参数的函数
var leadingThirtysevenList = list.bind(undefined,[69,37],{a:2});

var list2 = leadingThirtysevenList(); // [[69,37],{a:2}]
var list3 = leadingThirtysevenList(1, 2, 3); // [[69,37],{a:2}, 1, 2, 3]
复制代码

list函数很简单,把传入的每个参数插入到一个数组里,我们用bind()给list函数设置初始值,因为不用改变list中this的指向,所以直接传undefined,从第二个参数开始,就是要传入list函数的值,list2和list3的返回值很好的说明了一切。

我自己一般使用的bind()的场景是配合setTimeout函数,因为在执行setTimeout时,this会默认指向window对象,在使用bind()之前,我是这么做的:

复制代码
    function Coder(name) {
        var that = this;
        that.name = name;
        that.getName = function() {
            console.log(that.name)
        };
        that.delayGetName = function() {
            setTimeout(that.getName,1000)
        };
    }
    var me = new Coder(\'Jins\')
    me.delayGetName()//延迟一秒输出Jins
复制代码

在函数内顶层定义一个that缓存this的指针,这样不论怎么调用,that都是指向 Coder的实例,但是多定义一个变量总是让人不太舒服。

使用bind()就简单多了:

复制代码
    function Coder(name) {
        this.name = name;
        this.getName = function() {
            console.log(this.name)
        };
        this.delayGetName = function() {
            setTimeout(this.getName.bind(this),1000)
        };
    }
    var me = new Coder(\'Jins\')
    me.delayGetName()//延迟一秒输出Jins
复制代码

 这样就OK了,直接把setTimeout的this绑定到外层的this,这肯定是我们想要的!

行吧,先聊这么多,坚持学习!

 

 

 

 

 

 

 

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

       实际使用中我们经常会碰到这样的问题:

var name = "pig";
function Person(name){
    this.name = name;
    this.getName = function(){
        setTimeout(function(){
            console.log("Hello,my name is "+this.name);
        },100);
    }
}
var weiqi = new Person("卫旗");
weiqi.getName();    
//Hello,my name is pig

 

       这个时候输出this.namepig,原因是this的指向是在运行函数时确定的,而不是在定义函数时确定的,再因为setTimeout是在全局环境下只想,所以this就指向了window

       以前解决这个问题的办法通常是缓存this,例如:

var name = "pig";
function Person(name){
    this.name = name;
    this.getName = function(){
        //在这里缓存一个this
        var self = this;
        setTimeout(function(){
            //在这里是有缓存this的self
            console.log("Hello,my name is "+self.name);
        },100);
    }
}
var weiqi = new Person("卫旗");
weiqi.getName();
//Hello,my name is 卫旗

 

       这样就解决了这个问题,非常方便,因为它使得setTimeout函数中可以访问Person的上下文。

       现在有一个更好的解决办法,可以使用bind()函数,上面的例子可以被更新为:

var name = "pig";
function Person(name){
    this.name = name;
    this.getName = function(){
        setTimeout(function(){
            console.log("Hello,my name is "+this.name);
        }.bind(this),100);
        //注意上面这一行,添加了bind(this)
    }
}
var weiqi = new Person("卫旗");
weiqi.getName();
//Hello,my name is 卫旗

 

       bind()最简单的用法是创建一个函数,使得这个函数无论怎么样调用都拥有同样的this值。JavaScript新手经常犯的一个错误就是将一个方法从一个对象中拿出来,然后再调用,希望方法中的this是原来的对象(比如在回调函数中传入这个方法)。如果不做特殊处理的话,一般会丢失原来的对象。从原来的函数和原来的对象创建一个绑定函数,则可以很漂亮的解决这个问题:

//定义全局变量x
var x = "window";
//在module内部定义x
var module = {
    x:"module",
    getX:function(){
        console.log(this.x);
    }
}
module.getX();  
//返回module,因为在module内部调用getX()

var getX = module.getX;
getX();
//返回window,因为这个getX()是在全局作用域中调用的

//绑定getX()并将this值设为module
var boundGetX = getX.bind(module);
boundGetX();
//返回module,绑定以后this值始终为module

 

浏览器支持情况:

BrowserVersion support
Chrome 7
FireFox(Gecko) 4.0(2)
Internet Explorer 9
Opera 11.60
Safari 5.14

       很不幸,Function.prototype.bind在IE8及以下版本中不被支持,所以如果没有一个备选方案的话,可能会在运行时出现问题。bind函数在ECMA-262第五版才被加入。它可能不无法在所有浏览器上运行。你可以在脚本部分加入如下代码,让不支持的浏览器也能使用bind()功能。

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis || window,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

语法

       fun.bind(thisArg[, arg1[, arg2[, …]]])

参数

       thisArg,当绑定函数被调用时,该参数会作为原函数运行时的this指向,当使用new操作符调用绑定函数时,该参数无效。

       arg1, arg2, …,当绑定函数被调用时,这些参数加上绑定函数本身的参数会按照顺序作为原函数运行时的参数。

描述

       bind()函数会创建一个新的函数(一个绑定的函数)有同样的函数体(在ECMAScript 5 规范内置Call属性),当该函数(绑定函数的原函数)被调用时this值绑定到bind()的第一个参数,该参数不能被重写。绑定函数被调用时,bind()也接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的this值被忽略,同事调用的参数被提供给模拟函数。

 

 

总结:

<!DOCTYPE html>

<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			var name = "李四";

			function Coder(name) {
				this.name = name;

				function alerts() {
					console.log(\'alert:\' + this.name);
				}
				this.getName = function() {
					console.log(\'this.getName\'+this.name)
				};
				this.delayGetName = function() {
					setTimeout(function() {
						console.log(\'--:\' + this.name)
					}, 1000);
				};
				this.delayGetName0 = function() {
					setTimeout(() => {
						console.log(\'0:\' + this.name);
					}, 1000);
				};
				this.delayGetName1 = function() {
					var that = this;
					setTimeout(function() {
						console.log(\'1:\' + that.name);
					}, 1000);
				};
				this.delayGetName2 = function() {
					setTimeout(function() {
						console.log(\'2:\' + this.name);
					}.bind(this), 1000);
				};
				this.delayGetName3 = function() {
					setTimeout(function() {
						console.log(\'3:\' + this.name);
					}.call(this), 1000);
				};
				this.delayGetName4 = function() {
					setTimeout(function() {
						console.log(\'4:\' + this.name);
					}.apply(this), 1000);
				};
				this.delayGetName5 = function() {
					setTimeout(alerts.bind(this), 1000);
				};
				this.delayGetName6 = function() {
					setTimeout(this.getName.bind(this), 1000);
				};
			}
			var me = new Coder(\'张三\');
			me.delayGetName();
			me.delayGetName0();
			me.delayGetName1();
			me.delayGetName2();
			me.delayGetName3();
			me.delayGetName4();
			me.delayGetName5();
			me.delayGetName6();
		</script>
	</head>

	<body>
	</body>

</html>

  

前言

回想起之前的一些面试,几乎每次都会问到一个js中关于call、apply、bind的问题,比如…

  1. 怎么利用call、apply来求一个数组中最大或者最小值
  2. 如何利用call、apply来做继承
  3. apply、call、bind的区别和主要应用场景

虽然网上有很多关于这方面的博客和文章,但还是决定写一篇自己对这方面知识的理解。

作用

首先问个问题,这三个函数的存在意义是什么?答案是改变函数执行时的上下文,再具体一点就是改变函数运行时的this指向。有了这个认识,接下来我们来看一下,怎么使用这三个函数。

举个栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name){
this.name = name;
}
 
Person.prototype = {
constructor: Person,
showName: function(){
console.log(this.name);
}
}
 
var person = new Person(\'qianlong\');
person.showName();

上面的代码中person调用showName方法后会在浏览器的控制台输出qianlong

接下来

1
2
3
var animal = {
name: \'cat\'
}

上面代码中有一个对象字面量,他没有所谓的showName方法,但是我还是想用?怎么办?(坑爹了,这好像在让巧媳妇去做无米之炊),不过没关系,call、apply、bind可以帮我们干这件事。

1
2
3
4
5
6
// 1 call
person.showName.call(animal);
// 2 apply
person.showName.apply(animal);
// 3 bind
person.showName.bind(animal)();

啦啦啦,有木有很神奇,控制台输出了三次cat

我们拿别人的showName方法,并动态改变其上下文帮自己输出了信息,说到底就是实现了复用

区别

上面看起来三个函数的作用差不多,干的事几乎是一样的,那为什么要存在3个家伙呢,留一个不就可以。所以其实他们干的事从本质上讲都是一样的动态的改变this上下文,但是多少还是有一些差别的..

call、apply与bind的差别

call和apply改变了函数的this上下文后便执行该函数,而bind则是返回改变了上下文后的一个函数。

call、apply的区别

他们俩之间的差别在于参数的区别,call和aplly的第一个参数都是要改变上下文的对象,而call从第二个参数开始以参数列表的形式展现,apply则是把除了改变上下文对象的参数放在一个数组里面作为它的第二个参数。

1
2
3
 
fn.call(obj, arg1, arg2, arg3...);
fn.apply(obj, [arg1, arg2, arg3...]);

应用

知道了怎么使用和他们之间的区别,接下来我们来了解一下通过call、apply、bind的常见应用场景。

  • 求数组中的最大和最小值
1
2
3
4
5
6
7
var arr = [34,5,3,6,54,6,-67,5,7,6,-8,687];
 
Math.max.apply(Math, arr);
Math.max.call(Math, 34,5,3,6,54,6,-67,5,7,6,-8,687);
 
Math.min.apply(Math, arr);
Math.min.call(Math, 34,5,3,6,54,6,-67,5,7,6,-8,687);
  • 将伪数组转化为数组

js中的伪数组(例如通过document.getElementsByTagName获取的元素)具有length属性,并且可以通过0、1、2…下标来访问其中的元素,但是没有Array中的push、pop等方法。我们可以利用call、apply来将其转化为真正的数组这样便可以方便地使用数组方法了。

1
2
3
4
5
6
var arrayLike = {
0: \'qianlong\',
1: \'ziqi\',
2: \'qianduan\',
length: 3
}

上面就是一个普通的对象字面量,怎么把它变成一个数组呢?最简单的方法就是

1
var arr = Array.prototype.slice.call(arrayLike);

上面arr便是一个包含arrayLike元素的真正的数组啦( 注意数据结构必须是以数字为下标而且一定要有length属性 )

  • 数组追加

在js中要往数组中添加元素,可以直接用push方法,

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

判断变量类型

对于对象型的数据类型,我们可以借助call来得知他的具体类型,例如数组

1
2
3
4
5
6
function isArray(obj){
return Object.prototype.toString.call(obj) == \'[object Array]\';
}
 
isArray([]) // true
isArray(\'qianlong\') // false

以上是关于setTimeout改变this指向的主要内容,如果未能解决你的问题,请参考以下文章

setTimeout中this指向的问题

箭头函数与this指向问题

箭头函数与this指向问题

改变this指向&闭包特性

Vue Q

详解JS中定时器setInterval和setTImeout的this指向问题