博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
js中this关键字测试集锦
阅读量:5311 次
发布时间:2019-06-14

本文共 8995 字,大约阅读时间需要 29 分钟。

this是Javascript语言的一个关键字它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。定义:this是包含它的函数作为方法被调用时所属的对象。总原则,this指的是,调用函数的那个对象,this永远指向函数运行时所在的对象!而非创建时的。

 以下是基于浏览器环境做的测试:

作为函数调用: 

1
2
3
4
5
6
7
function $(){
    
this
.count = 1;
    
return 
this
;
}
window.onload = function(){
    
console.info($());
}

控制台返回结果如下:

一个window对象。

对于内部函数,即声明在另外一个函数体内的函数,这种绑定到全局对象的方式会产生一个问题,即它会隐式声明全局变量。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var 
point = {
    
x: 0,
    
y: 0,
    
moveTo: function(x, y) {
        
var 
fn1 = function(x) {
            
this
.x = 
this
.x + x;
            
return 
this
.x;
        
};
        
var 
fn2 = function(y) {
            
this
.y = 
this
.y + y;
        
};
        
return 
fn1();
    
}
}
console.log(point.moveTo());

结果是:

而若将fn1中return的值改为this的话,打印结果:

一个window全局对象。这属于 JavaScript 的设计缺陷,正确的设计方式是内部函数的 this 应该绑定到其外层函数对应的对象上,这个设计错误错误的后果是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权。为了规避这一设计缺陷,聪明的 JavaScript 程序员想出了变量替代的方法,约定俗成,该变量一般被命名为 that。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var 
point = {
    
x: 0,
    
y: 0,
    
moveTo: function(x, y) {
        
var 
that = 
this
;
        
var 
x = x;
        
var 
y = y;
        
var 
fn1 = function(x) {
            
that.x = that.x + x;
            
return 
that;
        
};
        
var 
fn2 = function(y) {
            
that.y = that.y + y;
        
};
        
return 
fn1(x);
    
}
}
console.log(point.moveTo(1,1));  

返回结果:

函数调用中,this总是指向函数的直接调用者(而非间接调用者)。如果在严格模式下,有可能输出undefined,严格模式下,禁止this关键字指向全局对象。如下:

1
2
3
4
5
6
7
8
'use strict'
;
    
console.log(
this 
=== window); 
// true
    
var 
foo = function() {
        
console.log(
this 
=== window);
        
console.log(
'this:'
,
this
);
    
};
    
foo();
    
window.foo();

控制台打印结果是:

 使用that的另一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var 
myObj = {
    
value: 0,
    
increment: function(inc) {
        
this
.value += 
typeof 
inc === 
'number' 
? inc: 1;
    
}
}
myObj.increment();
document.writeln(myObj.value);
myObj.increment(2);
document.writeln(myObj.value);
function add(num1, num2){
    
return 
num1 + num2;
}
console.log(add(40,27));
myObj.
double 
= function() {
    
var 
that = 
this
;
    
var 
helper = function() {
        
that.value = add(that.value, that.value);
    
}
    
helper();
}
myObj.getValue = function() {
    
var 
that = 
this
;
    
return 
that.value;
}
myObj.
double
();
document.writeln(myObj.getValue());

 

作为对象方法调用:

如果一个调用表达式包含一个属性存取表达式(即一个.点表达式或者[subscript]下标表达式),那么它被当做一个方法来调用。另一段代码:

1
2
3
4
5
6
7
8
var 
o = {};
o.fn = $;
function $(){
    
console.log(
this
);
}
window.onload = function(){
    
o.fn();
}

控制台返回结果如下:

调用函数的o对象。

在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window;

另外一种嵌套调用:

 

1
2
3
4
5
6
7
8
9
10
11
var 
personA={
    
name:
"xl"
,
    
showName:function(){
        
console.log(
this
.name);
    
}
}
var 
personB={
    
name:
"XL"
,
    
sayName:personA.showName
}
personB.sayName();

控制台输出结果:

从这里很明显的看出,在js中this关键字指向是直接调用它的对象,而非间接的。

 

作为函数及对象方法的混合调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var 
myObject={
 
    
foo : 
""
,
    
func : function(){
 
    
var 
self = 
this
;
    
console.log(
"outer func : this.foo = " 
this
.foo );
    
console.log(
"outer func : self.foo = " 
+ self.foo );
 
    
(function(){
        
console.log(
"inner func : this.foo = " 
this
.foo );
        
console.log(
"inner func : self.foo = " 
+ self.foo );
    
}());
}
}
myObject.func();

输出结果如下:

证明函数内部函数的调用是由全局对象引发的,这在上面有所阐述。

将构造函数作为函数调用:

1
2
3
4
5
6
7
8
9
function Person(name) {
    
this
.name = name;
}
var 
personA = Person(
"xl"
);
// console.log(personA.name);
console.log(window.name);
console.log(name);
var 
personB = 
new 
Person(
"xl"
);
console.log(personB.name);

控制台打印结果:

注释掉的console因为那么已经成为全局对象的属性,因此打印为未定义变量报错。第二个显式调用,第三个隐式调用。最后一个则是作为构造方法调用。

 

作为构造函数调用:

JavaScript 支持面向对象式编程,与主流的面向对象式编程语言不同,JavaScript 并没有类(class)的概念,而是使用基于原型(prototype)的继承方式。相应的,JavaScript 中的构造函数也很特殊,如果不使用 new 调用,则和普通函数一样。作为又一项约定俗成的准则,构造函数以大写字母开头,提醒调用者使用正确的方式调用。如果调用正确,this 绑定到新创建的对象上。

通过new实例化一个函数:

1
2
3
4
function $(){
    
console.log(
this
);
}
var 
fn = 
new 
$();

 

控制台输出如下:

this就指这个新对象。

 

使用apply或call调用:

使用apply方法((当然使用Function.call也是可以的)),另一个方法 call 也具备同样功能,不同的是最后的参数不是作为一个数组统一传入,而是分开传入的。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。很多 JavaScript 中的技巧以及类库都用到了该方法。

1
2
3
4
5
6
function $(){
    
console.log(
this
);
}
var 
o = {};
o.m = $;
o.m.apply();  

控制台打印结果:

注:Function.apply(obj,args)方法能接收两个参数

obj:这个对象将代替Function类里this对象

args:这个是数组,它将作为参数传给Function(args-->arguments)

apply()的参数为空时,默认调用全局对象。

如果将call的第一个destination的值设为一个对象,如下:

1
2
3
4
5
6
7
function $(){
    
console.log(
this
);
}
var 
u = {};
var 
o = {};
o.m = $;
o.m.apply(u,
null
);

控制台打印结果如下:

也即this指向了apply绑定的那个对象。

作为构造函数及apply/call的混用:

//下面这段代码模拟了new操作符(实例化对象)的内部过程    function person(name){        var o={};        o.__proto__=Person.prototype;  //原型继承        Person.call(o,name);        return o;    }    var personB=person("xl");        console.log(personB.name);  // 输出  xl

person里面首先创建一个空对象o,将o的proto指向Person.prototype完成对原型的属性和方法的继承。Person.call(o,name)这里即函数Person作为apply/call调用(具体内容下方),将Person对象里的this改为o,即完成了o.name=name操作。返回对象o。

 

 

作为回调函数的this:

我们来看看 this 在 JavaScript 中经常被误用的一种情况:回调函数。JavaScript 支持函数式编程,函数属于一级对象,可以作为参数被传递。请看下面的例子 myObject.handler 作为回调函数,会在 onclick 事件被触发时调用,但此时,该函数已经在另外一个执行环境(ExecutionContext)中执行了,this 自然也不会绑定到 myObject 对象上。

button.onclick = obj.handler;

代码如下:

1
<p id=
"p"
>click me</p>

执行结果如下:

很显然指向是触发click事件的dom元素对象,而非obj对象。这是 JavaScript 新手们经常犯的一个错误,为了避免这种错误,许多 JavaScript 框架都提供了手动绑定 this 的方法。比如 Dojo 就提供了 lang.hitch,该方法接受一个对象和函数作为参数,返回一个新函数,执行时 this 绑定到传入的对象上。使用 Dojo,可以将上面的例子改为:button.onclick = lang.hitch(myObject, myObject.handler);在新版的 JavaScript 中,已经提供了内置的 bind 方法供大家使用。

 

eval方法中this的指向:

JavaScript 中的 eval 方法可以将字符串转换为 JavaScript 代码,使用 eval 方法时,this 指向哪里呢?答案很简单,看谁在调用 eval 方法,调用者的执行环境(ExecutionContext)中的 this 就被 eval 方法继承下来了。

var name="XL";

var person={
     name:"xl",
     showName:function(){
     eval("console.log(this.name)");
  }
}

person.showName(); //输出 "xl"

var a=person.showName;

a(); //输出 "XL"

 

第一次的执行环境是person对象,第二个是global全局环境。

 

 

Function.prototype.bind()方法:

1
1.
var 
name = 
"XL"
;

           function Person(name) {

              this.name = name;
               console.log(this);
              this.sayName = function() {
                  console.log(this);
                  setTimeout(function(){
                  console.log(this);
                  console.log("my name is " + this.name);
              },50)
           }
       }
var person = new Person("xl");
person.sayName();

1
2
3
4
5
6
7
8
9
10
11
2.
var 
name=
"XL"
;
    
function Person(name){
        
this
.name=name;
        
this
.sayName=function(){
            
setTimeout(function(){
                
console.log(
"my name is "
+
this
.name);
            
}.bind(
this
),50)  
//注意这个地方使用的bind()方法,绑定setTimeout里面的匿名函数的this一直指向Person对象
        
}
    
}
    
var 
person=
new 
Person(
"xl"
);
    
person.sayName(); 
//输出 “my name is xl”;

这里setTimeout(function(){console.log(this.name)}.bind(this),50);,匿名函数使用bind(this)方法后创建了新的函数,这个新的函数不管在什么地方执行,this都指向的调用它的对象,而非window。而如果不加bind在第一段代码中,window执行环境中创建了一个变量person,被赋值了Person的实例,此时的调用顺序是person调用了sayName这个方法,这个方法被赋值了一个函数(此处有问题,该赋值函数是匿名还是非匿名?下篇讨论),创建了一个执行环境,此时setTimeOut函数开始执行,创建一个环境。匿名函数及setTimeout/setInterval在非手动改变指向额情况下都在全局作用域当中。

SO,第一段代码的打印结果是:

sf的解释:setTimeout/setInterval/匿名函数执行的时候,this默认指向window对象,除非手动改变this的指向。在《javascript高级程序设计》当中,写到:“超时调用的代码(setTimeout)都是在全局作用域中执行的,因此函数中的this的值,在非严格模式下是指向window对象,在严格模式下是指向undefined”。本文都是在非严格模式下的情况。

第二段代码的打印结果是:

 

这相当于手动将this进行了强制转向。

 另一种手动转向的方法:

1
2
3
4
5
6
7
8
9
10
var 
name=
"XL"
;
    
function Person(){
        
this
.name=
"xl"
;
        
var 
that=
this
;
        
this
.showName=function(){
            
console.log(that.name);
        
}
        
setTimeout(
this
.showName,50)
    
}
    
var 
person=
new 
Person(); 
//输出 "xl"

借用了上面提到的that保存this指针值进行复用的技巧。

 

匿名函数中的this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var 
name=
"XL"
;
    
var 
person={
        
name:
"xl"
,
        
showName:function(){
            
console.log(
this
.name);
        
}
        
sayName:function(){
            
(function(callback){
                
callback();
            
})(
this
.showName)
        
}
    
}
    
person.sayName();  
//输出 XL
    
var 
name=
"XL"
;
    
var 
person={
        
name:
"xl"
,
        
showName:function(){
            
console.log(
this
.name);
        
}
        
sayName:function(){
            
var 
that=
this
;
            
(function(callback){
                
callback();
            
})(that.showName)
        
}
    
}
    
person.sayName() ;  
//输出  "xl"

此处采用了that技巧保存this指针。

 

箭头函数():

 箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。示例代码:

var obj = {

     birth: 1990,
     getAge: function (year) {
     var b = this.birth; // 1990
     var fn = (y) => y - this.birth; // this.birth仍是1990
     return fn.call({birth:2000}, year);
}
};
console.log(obj.getAge(2015)); // 25

控制台打印结果:

 

this的四种使用场景

面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象。一般在编译期确定下来,或称为编译期绑定。而在 JavaScript 中,this 是动态绑定,或称为运行期绑定,这就导致 JavaScript 中的 this 关键字有能力具备多重含义,它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式,带来灵活性也带来困惑。js“超级”迟绑定( very late binding)使得函数可以对this高度复用。通过this可取得它们所属对象的上下文的方法称为公共方法。使用this关键字在面向对象语言中多数情况下是为了避免命名冲突。总的来说,JavaScript 中函数的调用有以上几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用 apply 或 call 调用。JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因。

 

函数的执行环境

JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因。一个函数被执行时,会创建一个执行环境(ExecutionContext),函数的所有的行为均发生在此执行环境中,构建该执行环境时,JavaScript 首先会创建 arguments变量,其中包含调用函数时传入的参数。接下来创建作用域链。然后初始化变量,首先初始化函数的形参表,值为 arguments变量中对应的值,如果 arguments变量中没有对应值,则该形参初始化为 undefined。如果该函数中含有内部函数,则初始化这些内部函数。如果没有,继续初始化该函数内定义的局部变量,需要注意的是此时这些变量初始化为 undefined,其赋值操作在执行环境(ExecutionContext)创建成功后,函数执行时才会执行,这点对于我们理解 JavaScript 中的变量作用域非常重要,鉴于篇幅,我们先不在这里讨论这个话题。最后为 this变量赋值,如前所述,会根据函数调用方式的不同,赋给 this全局对象,当前对象等。至此函数的执行环境(ExecutionContext)创建成功,函数开始逐行执行,所需变量均从之前构建好的执行环境(ExecutionContext)中读取。

 

本文是综合了互联网多篇文章结论之后亲测的结论,目的也在于细化自己的知识体系,并进而更深刻的理解js的内核实现。经过此番梳理,对this的灵活指向已经有了一个比较清晰的认识,不过还需在实际的工程中不断运用以达到完全掌握。不过经过此一番,没必要死记硬背以上多种场景,若需使用的时候,在对印位置打印出来看看不就知道了?

转载于:https://www.cnblogs.com/libin-1/p/6231306.html

你可能感兴趣的文章
北航第十一届程序设计竞赛网络预赛题解
查看>>
数据库复习⑨
查看>>
总结 — 无光驱安装操作系统win2003
查看>>
工作笔记-千分位分割
查看>>
七大技巧保护无线网络,蹭网卡什么的都是浮云!
查看>>
《第一行代码》学习笔记3-活动Activity(1)
查看>>
脚本模式下的填报表制作
查看>>
HDU 2564 词组缩写
查看>>
WCF信道绑定代码
查看>>
Grunt构建工具
查看>>
android实现点击按钮实现home键的功能
查看>>
[jQuery] 初识jQuery
查看>>
完成登录与注册页面的前端
查看>>
python3 进行字符串、日期、时间、时间戳相关转换
查看>>
fckeditor的使用和struts2的上传配置
查看>>
判断两个对象相等
查看>>
[洛谷P1962]斐波那契数列
查看>>
从后台拿到echarts的数据值,求出百分比
查看>>
我们对于历史的尺度
查看>>
redis主从模式
查看>>