专栏首页前端一会划重点:js中的this、call、apply

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

在js中this有4种指向,分别为:

  • 作为对象的方法调用
  • 作为普通函数调用
  • 构造器调用
  • Function.prototype.call或Function.prototype.apply调用

1、当作为对象的方法调用时,该方法中的this就指向了该对象

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

 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就是指向这个返回的实例对象。

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

 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的函数:

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:

 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来代替某个具体对象。

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对象指向。 区别仅在于两者的第二个参数传入不同:

1func.apply(
2    [参数一:将调用apply方法的函数体内的this对象指向改为指向本参数], 
3    [参数二:传入调用apply方法的函数体内的参数集合(数组或类数组)]
4    )
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:

 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:

 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 显示没有这个方法

本文分享自微信公众号 - 前端小二(frontendxiao2),作者:小二君

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-10-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 《JavaScript函数式编程》的读后总结二:this指向

    解释:等同于window.a(),而this指向的是在函数执行时最终调用它的那个对象,在本例中就是window调用的,而window对象中又没有userName...

    前端_AWhile
  • 《你不知道的JavaScript》:js委托设计的真实案例与总结

    实际需求,web开发中有一个典型的前端场景,创建UI控件(按钮、下拉列表等)。用jq的选择器来简化选择过程,与实现思路不冲突。

    前端_AWhile
  • 《你不知道的JavaScript》:js面向委托设计

    回顾下js原型继承,js版的继承与传统面向对象的继承的区别主要是不复制对象,而是通过对象的内置属性[[Propertype]]来关联需要“继承”的对象,这样当引...

    前端_AWhile
  • Python-Django

    一般在urls.py中配置url,在models.py中配置model,在views.py中配置View。

    用户2398817
  • 一文理解 this、call、apply、bind

    记得差不多在两年多之前写过一篇文章 两句话理解js中的this,当时总结的两句话原话是这样的:

    木子星兮
  • 再见乱码:5 分钟读懂 MySQL 字符集设置

    作者: 程序猿小卡_casper 原文:https://segmentfault.com/a/1190000012775484 一、内容概述 在MySQL的使...

    程序员宝库
  • JavaScript面向对象精要(一)

    JavaScript虽然没有类的概念,但依然存在两种类型:原始类型和引用类型。 原始类型保存为简单数据值;引用类型则保存为对象,其本质是指向内存位置的引用。

    奋飛
  • jQuery将form列表转JSON

    week
  • 【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)

    在Spring3.0以后,官方推荐我们使用注解去驱动Spring应用。那么很多人就一下子懵了,不需要xml配置文件了,那我的那些配置项怎么办呢? @Confi...

    BAT的乌托邦
  • group by 和聚合函数

    group by 的基本用法                                                                  ...

    Ryan-Miao

扫码关注云+社区

领取腾讯云代金券