setTimeout系列(1)----弄清setTimeout中this的指向问题及解决方案

38 篇文章 0 订阅
6 篇文章 0 订阅

准备对setTimeout函数的使用做一个归纳总结,主要 涉及以下几个问题:

1. this 的指向问题。
2. setTimeout中变量作用域的问题。
3. setTimeout中第三个参数的问题。
4. setTimeout中延时时间写0的问题。

本篇主要讲第一个问题:setTimeout中this指向的问题,及解决方案。

首先,记住这两段话,它们非常的关键和重要:
“《JavaScript高级程序设计》第二版中,写到:“超时调用的代码都是在全局作用域中执行的,因此函数中this的值在非严格模式下指向window对象,在严格模式下是undefined”。

“我们说,setTimeout中有两个this。第一,调用环境下的this,称之为第一个this;第二,把延迟执行函数中的this称之为第二个this;第一个this的指向是需要根据上下文来确定的,默认为window;第二个this就是指向window。”

下面通过一些列子来说明:

一、在setTimeout中回调对象方法。

let obj = {
    a: 1,
    name: 'A.L',
    init: function () {
        console.log('this in init: ', this);
    }
}
obj.getName = function () {
    console.log('this in getName: ', this.name);
}

// 这两句调用时绝对ok的
// no problem-------------------------
obj.init();   //obj
obj.getName();   //alice

当我们需求变成了,延迟1秒钟显示名字,于是:
1.2

//problem--------------------
setTimeout(obj.init, 1000); 	//window
setTimeout(obj.getName, 1000); 	//undefine

问题出现了!对象方法中的this变成了全局变量windows.

1.2代码可以分解为如下1.3,套用第一段话,超时调用的代码都是在全局作用域中执行的,因此函数中this的值在非严格模式下指向window对象。所以这里两个延时回调函数中的this,都是window。

setTimeout(function () {
        				console.log('this in init: ', this);
   				 }, 1000); 	//window
setTimeout(function () {
    					console.log('this in getName: ', this.name);
				}, 1000); 	//undefine

还可以再继续改造代码:

function f1() {
       console.log('this in init: ', this);
}
function f2() {
    	console.log('this in getName: ', this.name);
}
setTimeout(f1, 1000); 	//window
setTimeout(f2, 1000); 	//undefine

好,下面来看解决方案:
在这里插入图片描述
对应链接:https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout

1. 方法1,wrapper function:

setTimeout(function () {
    obj.init()   //保留了obj对init()方法的调用。
}, 1500);

setTimeout(function () {
     obj.getName()  //保留了obj对getName()方法的调用。
 }, 1500);

2. 方法2,熟悉的箭头函数,专治this指向不固定问题
在这里箭头函数作用不明显,大致意思跟方法1中funtion的用途一样。一会我会在【原型中使用setTimeout的例子】中做讲解。

setTimeout(() => {
    obj.init()
}, 1000);

setTimeout(() => {
    obj.getName()
}, 1000);

3. 方法3,call, apply, bind,改变方法中this的指向

setTimeout(obj.init.call(obj), 1000);
setTimeout(obj.init.apply(obj), 1000);
setTimeout(obj.init.bind(obj)(), 1000);

setTimeout(obj.getName.call(obj), 1000);
setTimeout(obj.getName.apply(obj), 1000);
setTimeout(obj.getName.bind(obj)(), 1000);

对 call,apply,bind 不熟悉的童鞋,我后续会再总结篇这方面的文章。

4、方法4,当然还有最土的方法,用变量that来暂存this。
这个例子貌似也不怎么用得上。会在【原型中使用setTimeout的例子】中讲解。

二、在原型方法中调用setTimeout方法

定义了一个构造函数,Animal。

// code 2.1
function Animal(name) {
    this.name = name;
}
Animal.prototype.type = "All";
Animal.prototype.bark = function () {
    console.log('in barking....this: ', this, );
}
Animal.prototype.eat = function () {
    console.log('in eating...this: ', this);
    this.bark();
}

let a1 = new Animal();
a1.eat();            //in eating...this: Animal

需求变成了,改造Animal.prototype.eat()方法,让其能在1秒钟后正常调用this.bark()输出’in barking…this: Animal;

于是,写成如下2.2,出问题了this打印出来是window:

// code 2.2
 Animal.prototype.eat = function () {
    setTimeout(this.bark, 1000);    //回调函数内部的this: window
}

想起之前说的用1.1方法1 wrapper function的方法来解决,也不行了啊:

// code 2.3
Animal.prototype.eat = function () {
    setTimeout(function () {
        this.bark()       //这里的this: window
    }, 1000);
}

想想这是为什么?因为这里的延时回调函数中对bark()的调用是this指针,而之前的例子是obj.init()没有this指向的问题。

再想想重要的两段话,
“超时调用的代码都是在全局作用域中执行的,因此函数中this的值在非严格模式下指向window对象”,
“setTimeout中有两个this。第一,调用环境下的this,称之为第一个this;第二,把延迟执行函数中的this称之为第二个this;第一个this的指向是需要根据上下文来确定的,默认为window;第二个this就是指向window。”
是不是看出点啥。
延时回调函数里的this,永远指向window。

那我们继续改代码,改成如下,用箭头函数来解决这个this问题:

// code 2.4
Animal.prototype.eat = function () {
    setTimeout(() => {
        this.bark()  //这里的this: 实例对象
    }, 1000);
}

阮一峰es6中说到,箭头函数中没有自己的this的,而箭头函数会默认使用父级函数作用域的this。
setTimeout()中的this,也是它作用域上下文中的this。那不就是调用eat函数
的Animal的实例呀。所以这里的this,就是真正正确的指向,Animal的实例对象了。

那么,只要能改变this的指向,就能让this.bark()正确调用,还有哪些方法能实现。不就是1.3方法3中描述的call, apply, bind 三种方法。

Animal.prototype.eat = function () {
    setTimeout(this.bark.call(this), 1000);   //这里的第一个this: 实例对象,第二个this,也是实例对象
};
// 或者这样
Animal.prototype.eat = function () {
    setTimeout((function () {
        this.bark();          //这里的this: 实例对象
    }).call(this), 1000);
};

最后,就说说那个土方法,暂存this到that,同样能实现this的正确指向:

Animal.prototype.eat = function () {
    let that = this;
    setTimeout(function () {
        that.bark();
    }, 1000);
}

//----------------------------------------------------------------------------------
需要的话,还可以看看下面的例子,加深印象:
1. 示例1:

function foo(){
	setTimeout(function(){
		console.log(this);
	},100);
}

var obj ={a:1};
foo.call(obj);      		//window

2. 示例2:

function foo(){
	setTimeout(()=>{
		console.log(this);
	},100);
}
var obj ={a:1};
foo.call(obj);  		 //Object{a:1}

3.示例3:

function foo(){
	setTimeout(()=>{
		console.log(this);
	},100);
}
foo();    //window

foo()是被Window调用的,foo()函数作用域中的this也是windows。箭头函数跟父级函数共享this。所以,执行结果是window.

4.示例4:

setTimeout(function(){
	console.log(this);
},100);

setTimeout中的匿名函数,没有其它对象调用它。所以它的默认调用对象就是Window.

最后,文章上有什么不妥的地方,希望大家指正,欢迎探讨。

参考:
关于箭头函数:

  1. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions
  2. 箭头函数在对象中的this指向及适用环境
    http://www.cnblogs.com/githubzy/p/5780135.html
  3. 在定义对象内部的新方法时,如何使用箭头函数?https://segmentfault.com/q/1010000006944383
  4. 谈谈setTimeout的作用域以及this的指向问题
    http://www.cnblogs.com/hutaoer/p/3423782.html
  5. https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
  6. ES6中setTimeout函数的执行上下文
    https://blog.csdn.net/liwusen/article/details/56278944?utm_source=blogxgwz0
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值