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.name
是pig
,原因是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
浏览器支持情况:
Browser | Version 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的问题,比如…
- 怎么利用call、apply来求一个数组中最大或者最小值
- 如何利用call、apply来做继承
- 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指向(****************************************)的更多相关文章
- this指向及改变this指向的方法
一.函数的调用方式决定了 this 的指向不同,但总的原则,this指的是调用函数的那个对象: 1.普通函数调用,此时 this 指向 全局对象window function fn() { conso ...
- 前端js中this指向及改变this指向的方法
js中this指向是一个难点,花了很长时间来整理和学习相关的知识点. 一. this this是JS中的关键字, 它始终指向了一个对象, this是一个指针; 参考博文: JavaScript函数中的 ...
- this(this的4种指向和改变this指向的方式)
this是Javascript语言的一个关键字. 随着函数使用场合的不同,this的值会发生变化.但是有一个总的原则,那就是this指的是,调用函数的那个对象. 1.this指向的形式4种 a.如果是 ...
- 可以改变this指向的方法
this一般指向的是当前被调用者,但也可以通过其它方式来改变它的指向,下面将介绍三种方式: 1.call用作继承时: function Parent(age){ this.name=['mike',' ...
- 改变this指向的三种方法
call.apply.bind三者为改变this指向的方法. 共同点:第一个参数都为改变this的指针.若第一参数为null/undefined,this默认指向window call(无数个参数) ...
- js中改变this指向的call、apply、bind 方法使用
前言: 由于js 中this的指向受函数运行环境的影响,指向经常改变,使得开发变得困难和模糊,所以在封装sdk,写一些复杂函数的时候经常会用到this 指向绑定,以避免出现不必要的问题,call.ap ...
- $.on()方法和addEventListener改变this指向
jQuery $.on()方法和addEventListener改变this指向 标签(空格分隔): jQuery JavaScript jQuery $.on() jq的绑定事件使用$([selec ...
- (三十七)js改变this指向的方法
最近又遇到了JacvaScript中的call()方法和apply()方法,而在某些时候这两个方法还确实是十分重要的,那么就让我总结这两个方法的使用和区别吧. 1.改变函数内部的this指向的三种方法 ...
- 五、React事件方法(自写一个方法(函数),然后用按钮onClick触发它、自写方法改变this指向3种写法、
上接:https://www.cnblogs.com/chenxi188/p/11782349.html 项目目录: my-app/ README.md node_modules/ package.j ...
随机推荐
- document.domain跨子域
document.domain 用来得到当前网页的域名.比如在地址栏里输入: javascript:alert(document.domain); //www.315ta.com 我们也可以给docu ...
- 谋哥:App推广最有效的是自推广
[谋哥每天一原创,第一百五十二篇] 目前市场上,各类App已经覆盖到所有你能想到的领域,并且各个山头也被占得差不多了,网上 的说法就是布局已经完成.如果你想现在再插那么一杠子进去,就得看你的真本事了, ...
- 史上最权威的 Activiti 框架学习
Activiti5 是 由 Alfresco 软件在 2010 年 5 月 17 日发布的业务流程管理( BPM) 框架,它是覆盖了业务流程管理.工作流.服务协作等领域 的一个开源的.灵活的. ...
- 解决This application failed to start because it could not find or load the Qt platform plugin "windows
解决方案:所在环境python根目录下qt.conf,重新设置path即可,此类问题通常在目录转移之后出现.
- Leetcode 521.最长特殊序列I
最长特殊序列 I 给定两个字符串,你需要从这两个字符串中找出最长的特殊序列.最长特殊序列定义如下:该序列为某字符串独有的最长子序列(即不能是其他字符串的子序列). 子序列可以通过删去字符串中的某些字符 ...
- Linux下MySQL c++ connector示例
最近在学习数据库的内容,起先是在windows下用mysql c++ connector进行编程,之所以选用c++而不是c的api,主要是考虑到c++ connector是按照JDBC的api进行实现 ...
- php+mysqli预处理技术实现添加、修改及删除多条数据的方法
本文实例讲述了php+mysqli预处理技术实现添加.修改及删除多条数据的方法.分享给大家供大家参考.具体分析如下: 首先来说说为什么要有预处理(预编译)技术?举个例子:假设要向数据库添加100个用户 ...
- [luogu_P2045]方格取数加强版
[luogu_P2045]方格取数加强版 试题描述 给出一个 \(n \times n\) 的矩阵,每一格有一个非负整数 \(A_{i,j},(A_{i,j} \le 1000)\) 现在从 \((1 ...
- Scala学习之路----基础入门
一.Scala解释器的使用 REPL:Read(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循环) scala解释器也被称为REPL,会快速编译s ...
- 15个变态的Google面试题以及答案
在当前经济形势不景气的情况下,谷歌招聘新员工是一件令人振奋的事,特别是对那些在当前金融风暴中渴望找到安全港的年轻经理们和软件开发商们来说是个好消息. 不过,也不要高兴太早,谷歌在招聘新员工时,更加青睐 ...