在js中this有4种指向,分别为:
- 作为对象的方法调用
- 作为普通函数调用
- 构造器调用
- Function.prototype.call或Function.prototype.apply调用
1、当作为对象的方法调用时,该方法中的this就指向了该对象
代码语言:javascript复制1var obj = {
2 name: 'nitx',
3 getName: function(){
4 console.log(this === obj) //true
5 console.log(this.name) //nitx
6 }
7}
8obj.getName();
2、作为普通函数调用时,函数中的this指向全局对象,在浏览器环境中,指向的就是全局对象window
代码语言:javascript复制 1window.name = 'globalName'
2
3var func = function(){
4 console.log(this.name)
5}
6
7func(); //globalName
8
9//或者
10
11var obj = {
12 name: 'nitx',
13 getName: function(){
14 console.log(this.name)
15 }
16}
17
18var func2 = obj.getName;
19func2() //globalName
20/*这里打印结果依然是指向全局对象的原因是: 将obj对象中的getName方法赋值给新的变量func2时,func2就是一个全局作用域中的普通函数,而非obj对象中的方法,已经与getName方法是两个完全独立的方法,拥有完全不同的作用域上下文*/
3、在构造器中调用this 先要理解js中的构造器。 除了宿主环境中的内置函数外,大多数函数都既能当普通函数,也能当构造函数。区别在于调用方法:
当函数名加括号的调用时就是普通函数,
当用new运算符调用函数时就是构造函数,并且该构造函数调用时总是会返回一个对象,即实例对象,该构造函数中的this就是指向这个返回的实例对象。
代码语言:javascript复制1var Func = function(name, age){
2 this.name = name
3 this.age = age
4}
5var func = new Func('nitx', 30)
6console.log(func.name) //nitx
这里还需要注意一个问题,如果构造函数显式的返回一个Object类型的对象,则new 构造函数名()
的运算结果是返回这个对象,而不是原先new出来的实例对象,所以返回出来的这个对象中的this指向需要注意是指向这个返回对象的。
1var Func = function(){
2 this.name = 'nitx'
3 return {
4 name: 'sxm'
5 }
6}
7var func2 = new Func()
8console.log(func2.name); //sxm
如果构造器不显式的返回任何数据,或者返回一个非对象类型的数据,就不会出现上述问题。
4、Function.prototype.call或Function.prototype.apply调用this 通过call或apply,可以动态改变传入函数的this
代码语言:javascript复制 1var obj1 = {
2 name: 'nitx',
3 getName: function(){
4 return this.name
5 }
6}
7
8var obj2 = {
9 name: 'sxm'
10}
11
12console.log(obj1.getName()) //nitx
13console.log(obj1.getName.call(obj2)) //sxm
对于call和apply的理解
要想理解上文第4点中的call调用改变this的具体实现原理,需要先了解call和apply的作用。
Function.prototype.call或Function.prototype.apply,它们的作用是完全一样的,都是改变函数的this指向。区别在于两者传入参数的不同。
apply接收两个参数,第一个参数指定了调用apply的函数体内this对象的指向,第二个参数是一个带下标的集合,该集合可以是数组,也可以是类数组,apply方法把这个集合中的所有元素作为参数依次传递给调用apply的函数:
代码语言:javascript复制1var func = function(a, b, c){
2 console.log([a, b, c])
3}
4
5func.apply(null, [1, 2, 3]) //[1, 2, 3]
call方法传入的参数中,第一个参数也是指定调用call的函数体内this对象的指向,从第二个参数开始往后,每个参数被依次传入函数中。
当使用apply或call时,如果传入的第一个参数是null,则函数体内的this会指向默认的宿主对象,在浏览器中就是window,但在严格模式下,函数体内的this还是为null:
代码语言:javascript复制 1var func = function(){
2 //非严格模式下,函数调用apply或call时,第一个参数设为null时,函数体内的this指向全局对象
3 console.log(this === window) //true
4}
5
6func.apply(null)
7
8var func = function(){
9 'use strict' //严格模式下this依然指向null
10 console.log(this === null) //true
11}
12
13func.apply(null)
所以有时如果使用apply或call的目的不是指定函数体内的this指向,而只是借用该函数方法进行某种运算时,可以传入null来代替某个具体对象。
代码语言:javascript复制1Math.max.apply(null, [1, 2, 3, 4, 5]) //借用Math.max方法来计算数据[1,2,3,4,5]中的最大值
再来回顾下本文重点:
this在不同的调用情况下指向也不同。
当在对象方法内调用时指向该对象;
当在普通函数内调用时指向宿主环境中的全局对象;
当在构造器中调用时分为两种情况。构造器无return返回值或返回值不为对象类型数据时,构造器中的this指向被构造器new出来的实例对象;构造器有return返回值且值为Object对象类型的数据时,this指向该构造器运算后返回出来的对象值;
当在Function.prototype.call或Function.prototype.apply情况下,前面调用apply或call的函数体内的this原有指向被更改为指向apply或call方法中的第一个参数。
关于apply或call,两者的作用完全一致,都是更改调用apply或call的函数体内的this对象指向。 区别仅在于两者的第二个参数传入不同:
代码语言:javascript复制1func.apply(
2 [参数一:将调用apply方法的函数体内的this对象指向改为指向本参数],
3 [参数二:传入调用apply方法的函数体内的参数集合(数组或类数组)]
4 )
代码语言:javascript复制1func.call(
2 [参数一:将调用call方法的函数体内的this对象指向改为指向本参数],
3 [参数二:传入调用call方法的函数体内的参数1] //从第二个参数开始,每个参数被依次传入函数func中
4 [参数三:传入调用call方法的函数体内的参数2]
5 [参数四:传入调用call方法的函数体内的参数3]
6 ...
7 )
如果只是想通过apply或call来借用某个函数方法进行某种运算,则只需将apply或call的第一个参数设为null来代替某个具体对象。
原因?
因为在非严格模式下,此时调用apply或call的函数体内的this会指向宿主环境中的全局对象;在严格模式下此时调用apply或call的函数体内的this会指向null。
延伸应用:
理解了this、call、apply后,在实际js开发中,可以很方便的实现对象的继承
继承demo1:
代码语言:javascript复制 1var Parent = function(){
2 this.name = 'nitx';
3 this.job = 'frontEnd'
4}
5
6var child = {};
7
8console.log(child) //空对象 {}
9
10Parent.call(child)
11
12console.log(child) //已继承Parent对象的child {name: "nitx", job: "frontEnd"}
继承demo2:
代码语言:javascript复制 1//子类继承父类的属性和方法
2
3//定义父类
4var Person = function(name, job){
5 this.name = name;
6 this.job = job;
7}
8
9Person.prototype.showName = function(){
10 console.log(this.name)
11}
12
13Person.prototype.showJob = function(){
14 console.log(this.job)
15}
16
17//定义子类
18var Worker = function(name, job, age){
19 Person.call(Worker, name, job) //把父类Person中的this对象指向改为指向子类Worker运算new出来的实例对象
20 this.age = age;
21}
22
23//子类从父类(父原型)继承方法有三种,会全部列出来,但我推荐使用第三种方法,原因在注释中
24
25//方法一:缺点 --子类原型与父类原型本质都是指针,各自指向内存中作为它们原型的对象,通过赋值操作会将子类原型指针指向父类原型对象,产生的问题就是如果修改子类原型方法(也叫实例方法),父类的原型方法(实例方法)也会发生同步改变
26//Worker.prototype = Person.prototype
27
28//方法二:将子类的原型指向父类的实例对象,缺点不够优雅
29//Worker.prototype = new Person()
30
31//方法三:将父类方法通过for...in...枚举进子类原型方法中
32for(var i in Person.prototype){
33 Worker.prototype[i] = Person.prototype[i]
34}
35
36Worker.prototype.showAge = function(){
37 console.log(this.age)
38}
39
40
41var p1 = new Person('sxm', 'count')
42var w1 = new Worker('nitx', 'frontend', 30)
43
44console.log(p1)
45console.log(w1)
46
47w1.showName(); //nitx
48w1.showJob(); //frontend
49w1.showAge(); //30
50
51p1.showName(); //sxm
52p1.showJob(); //count
53p1.showAge(); //error 显示没有这个方法