文章结构图:
1.this回忆
浏览器(解析器)调用函数时,每次都会向函数内部传递两个隐含的参数,不是普通的写好的实参传递给形参那种。
这两个隐含的参数就是函数的上下文对象 this
和封装实参的对象 arguments
。
this指向的是一个对象,即this的类型就是object。这个对象称为函数执行的上下文对象。
粗暴总结下(可自行编程实践下),根据函数的调用方式不同,this会指向不同的对象。
- 调用方式一:以全局函数的方式调用时,this永远是Window。
- 调用方式二:以方法(即对象内的函数)的方式调用时,this就是调用方法的那个对象。
- 调用方式三:在以构造函数方式调用时(使用构造函数创建一个新对象),该新对象就是函数的this
- 调用方法四:在使用函数对象的方法call()和apply()时,this是绑定的对象,
fun.call(obj);this是obj
。
2. this使用注意点
2.1 避免多层this
问题提出:见下面多层this代码
var data=123;
var A={
data:789,
f1:function(){
console.log("第一层",this.data);
var f2=function(){
console.log("第二层",this.data);
var f3=function(){
console.log("第三层",this.data);
}();
}();
}
}
A.f1();
//输出:
第一层 789 //表示f1的this指向A
第二层 123 //表示f2的this指向window
第三层 123 //表示f3的this指向window
为什么第一层的this是指向其调用方法的对象,而第二层以后就是指向第三层了呢?思考一下!同时可以运行上面代码试试(注意在node下运行,全局环境是global,而不是window,会得不到上面结果,使用浏览器运行)
问题分析:原因是其实际执行的代码和你说想像的不一样,看下面等价代码
//这里为了简化,就将原来三层换为现在两层
var data=123;
var temp=function(){
console.log("第二层",this==global);
};
var A={
data:789,
f1:function(){
console.log("第一层",this.data);
var f2=temp();
}
}
A.f1();
//输出
第一层 789
第二层 123
可以看出,在f1函数执行时,其中的f2函数的上下文对象其实已经改变了,其的上层对象已经变为了全局对象window。
解决方案
- 解决方案一:固定指向外层的this。方法在下面的代码中实现。
var data = 123;
var A = {
data: 789,
f1: function () {
console.log("第一层", this.data);
var that = this;
var f2 = function () {
console.log("第二层", that.data);
}();
}
}
A.f1();
//输出:
第一层 789
第二层 789
由于this的指向不确定,故固定了指向外层的this为that变量,然后在内部使用that,阻止了this指向的改变。(该方法非常常见,一定掌握)
- 解决方案二:使用严格模式(
'use strict'声明使用严格模式
)
var data = 123;
var A = {
data: 789,
f1: function () {
console.log("第一层", this.data);
var f2 = function () {
'use strict';
console.log("第二层", this.data);
}();
}
}
A.f1();
//输出:
TypeError: Cannot read property 'data' of undefined
使用严格模式就防止了其不明的指向发生。
2.2 避免数组处理方法中的this
问题提出:见下面forEach方法参数函数中this使用的代码
var A ={
Greetings:"happy new year",
friends:['张三',"李四"],
fun:function(){
this.friends.forEach(function(item){
console.log(this.Greetings,item);
});
}
}
A.fun();
//输出
undefined 张三
undefined 李四
上面的问候语为什么没有输出,而只输出了被问候的朋友?
问题分析:上面问候语没有出来,但是被问候的朋友出来了,说明this.friends的this指向了对象A,而forEach参数函数里的this指向了全局,而全局并没有friends数组,所以输出undefined。 所以到这里大家也能猜到了,这里的原因和上面多次this一样,是内部的this不指向外部而是指向了顶层对象。
解决方案
- 解决方案一:使用中间变量固定this
看代码
var A ={
Greetings:"happy new year",
friends:['张三',"李四"],
fun:function(){
var that=this;//中间变量绑定this
this.friends.forEach(function(item){
console.log(that.Greetings,item);
});
}
}
A.fun();
//输出
happy new year 张三
happy new year 李四
- 解决方案二:使用forEach方法的第二个参数
var A ={
Greetings:"happy new year",
friends:['张三',"李四"],
fun:function(){
this.friends.forEach(function(item){
console.log(this.Greetings,item);
},this);//第二个参数为this
}
}
A.fun();
forEach(callback,thisArg)
,有两个参数,因为 thisArg 参数(this)传给了 forEach(),每次调用时,它都被传给 callback 函数,作为它的 this 值。
此外,map方法也是类似的。
2.3 避免回调函数中的this
回调函数里的this也会改变指向。 举一个jQuery里回调函数的例子;
var A =new Object();
A.f=function(){
console.log(this===A);
}
$('#button').on('click',A.f);
//点击按钮后显示false
原因是这里的this指向的不是A对象,而是指向按钮的DOM对象,因为f方法是在按钮对象的环境中被调用的。
解决方法:使用apply(),call()或bind(),下一段粗略介绍其用法。
3. this的固定操作
JavaScript提供了call,apply,bind三个方法来切换或固定this的指向,下面就介绍下这三个方法的使用。
3.1 Function.prototype.call()
直接上代码
var n=123;
var obj={
n:456
};
var f=function(){
return this;
}
console.log(f()===window);//输出true
//没有使用call时this指向的是window
console.log(f.call(obj)===obj);//输出true
//使用了call后,call方法指定this对象为obj
var A=function(){
console.log(this.n);
}
//上面的函数使用了this,this原来是window
//使用call方法传入指定的this。
A.call(obj);//456
A.call();//123
A.call(null);//123
A.call(undefined);//123
A.call(window);//123
//上面表示了当call方法的参数为空,null,undefined时其传入的时window对象。
call方法另外的妙用:
- call方法可以有多个参数,第一个参数为this,第二个开始为函数调用时所需的参数。
var add = function(a,b){
return a+b;
}
console.log(add.call(this,2,3));
//输出5,表示后面的2,3对应形参a,b。
- 使类数组能够使用数组的方法
类数组就是和数组类数的对象(例如arguments对象),其中值以键值对形式存在,拥有length属性,但不拥有数组的其他方法。
var a = {'0':'a', '1':'b', '2':'c', length:3};
// An array-like object
Array.prototype.join.call(a, '+');
// => 'a+b+c'
Array.prototype.slice.call(a, 0);
// => ['a','b','c']: true array copy
Array.prototype.map.call(a, function(x) {
return x.toUpperCase();
});
// => ['A','B','C']:
3.2 Function.prototype.apply()
apply方法与call方法非常相似,在绑定this方面一模一样,重复的性质这里就不再介绍,这里说说其的不同点。
其第二个参数时一个数组,call时一个一个的去传参数,apply则必须以数组形式传递参数,将所有实参组成一个数组,否则报错。
var add = function(a,b){
return a+b;
}
console.log(add.apply(this,[2,3]));
//输出5
apply方法妙用:
- 计算出数组中最大元素(正好弥补js中没数组计算最大值的空缺)
var arr=[1,2,3,4,5,6,7,8,9];
var Max=Math.max.apply(null,arr);
console.log(Max);//输出9
- 将数组中的空元素变为
undefined
,forEach方法会跳过空元素但不会跳过undefined。利用Array构造函数将其参数变为数组,空元素则会变为undefined。
var arr=[1,,2,,3,,4];
function print(i){
console.log(i);
}
arr.forEach(print);
//输出:
// 1
// 2
// 3
// 4
Array.apply(null,arr).forEach(print);
//输出
// 1
// undefined
// 2
// undefined
// 3
// undefined
// 4
- 将类数组转化为真数组
类数组就是和数组类数的对象(例如arguments对象),其中值以键值对形式存在,拥有length属性,但不拥有数组的其他方法。
使用slice方法,可以将一个拥有length属性的对象转为真正的数组,没有length属性就只会返回一个空数组,默认length为0.
Array.prototype.slice.apply({0:1,length:1});
//[1]
Array.prototype.slice.apply({0:1});
//[]
Array.prototype.slice.apply({0:1,length:2});
//[1,undefined]
Array.prototype.slice.apply({length:1});
//[undefined]
3.3 Function.prototype.bind()
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。使用与部分特性和call()方法差不多,但值得注意的是其会返回一个新函数。
应用示例:
var demo={
friends:1,
who:function(){
console.log("张三和李四");
this.friends++;
}
};
var fun=demo.who.bind(demo);
console.log(demo.friends);//输出1
fun();//输出张三和李四
console.log(demo.friends);//输出2
上面代码demo里的who()方法赋值给了fun(),这时用bind()方法将who()内部的this绑定到了demo。根据上面两次friends值得输出,也能看出返回的新函数与对象里方法其实是同一个。
bind()方法妙用:
可以对一个函数预设初始参数:
function a() {
return Array.prototype.slice.call(arguments);
//将类数组转换成真正的数组
}
var b = a.bind(a, 15, 20)
console.log(b()); //输出[ 15, 20 ]
var s = b(25, 30);
console.log(s); //输出[ 15, 20, 25, 30 ]
上面的代码就是为函数预设了两个参数15和20,其都在类数组arguments里。
最后,补充一点,在es6中,可以使用箭头函数的方式来规避this指向的问题
var data = 123;
var A = {
data: 789,
f1: function () {
console.log("第一层", this.data);
var f2 = (()=> {
console.log("第二层", this.data);
})();
}
}
A.f1();
//输出:
第一层 789
第二层 789
//对比2.1节.就会发现使用箭头函数后this指向不在是全局对象了
箭头函数的this永远指向其父作用域,任何方法都改变不了(就是没有es5中this那么"灵活"?),包括call,apply,bind。 这是由于this在箭头函数中已经按照词法作用域绑定了,所以,用call或者apply调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略,只能用于传参数。
这就是我在阅读this相关介绍时总结的知识点了,欢迎点赞收藏!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!