前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >划重点:js中的this、call、apply

划重点:js中的this、call、apply

作者头像
前端_AWhile
发布2019-08-29 13:15:25
9500
发布2019-08-29 13:15:25
举报
文章被收录于专栏:前端一会前端一会

在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指向需要注意是指向这个返回对象的。

代码语言:javascript
复制
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 显示没有这个方法
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-10-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端小二 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档