【R1-2】this、call、apply

程昱
• 阅读 969

1. this

除去不常用的 withevalthis 的指向大致分为以下4种:

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

1.1 作为对象的方法调用

当函数作为对象的方法调用时,this 指向该对象。如果对象的方法起了别名,this 指向全局对象,具体可见如下代码。
const person = {
    userName: 'Mango',
    sayName () {
        console.log(this);
        console.log(this.userName);
    }
}
person.sayName();       // Mango
// 起别名
let sayName = person.sayName;    
sayName();              // undefined 此时 this 指向全局对象

1.2 普通函数调用

普通函数分为:ES5 函数 和 箭头函数(ES6)。

  • ES5中的函数,this 总是指向全局对象;
  • ES6的箭头函数,this 与定义时的上下文相关
<body>
<div id="myDiv"> 我是一个DIV </div>

<script>
    const divDom = document.querySelector('#myDiv');

    divDom.addEventListener('click', function () {
        // this 指向 divDom
        console.log('匿名函数的 this: ', this);
        let callback = function () {
            // this 指向 window
            console.log('匿名函数中callback this: ', this);
        }
        let callback2 = () => {
            // this 指向 divDom
            console.log('匿名函数中callback(箭头函数) this: ', this);
        }
        callback();
        callback2();
    })
    divDom.addEventListener('click', () => { // 此时上下文在全局环境中
        // this 指向 window
        console.log('箭头函数的 this: ', this);
        let callback = function () {
            // this 指向 window
            console.log('箭头函数中callback this: ', this);
        }
        callback();
    })

    const obj = {
        addEvent () {
            divDom.addEventListener('click', () => { // 此时上下文在 obj 对象环境中
                // this 指向 obj 对象
                console.log('在对象内部箭头函数的 this: ', this);
                let callback = function () {
                    // this 指向 window
                    console.log('对象内部匿名函数中callback this: ', this);
                }
                callback();
            })
        }
    }
    obj.addEvent();
</script>
</body>

1.3 构造器调用

当用 new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器中的 this 指向返回的这个对象。

const Person = function () {
    this.name = 'Mango';
}

const person = new Person();
console.log('person name: ', person.name);

1.4 callapply

Function.prototype.callFunction.prototype.apply 可以动态改变 this 指向,两者唯一区别是调用方式不同。

const obj1 = {
    name: 'Mango',
    getName () {
        return this.name;
    }
}
const obj2 = {
    name: 'Mango2'
}
console.log(obj1.getName());    // Mango
console.log(obj1.getName.call(obj2));       // Mango2

1.5 消失的 this

在 1.1 节中,对象的方法赋值给一个变量时(类似起别名),this 不再指向该对象,而是指向全局对象。如下:

const person = {
    userName: 'Mango',
    sayName () {
        console.log(this);
        console.log(this.userName);
    }
}
let sayName = person.sayName;    
sayName();  // undefined

这种情况可以使用 apply 方法改变 this 指向,最简单的是直接改变指向,如下:

// 接上面案例
sayName.apply(person);  // Mango

为了保证 sayNamethis 不丢失,可以再创建的时候就通过apply指明 this,如下:

// 使用了闭包
let sayName = (function (func, target) {
                    return function () {
                        return func.apply(target, arguments);
                    } 
                })(person.sayName, person);

// 也有比较简单的方式
let sayName = function () { 
    return person.sayName();
}

可以将起别名抽象为一个函数,可以指定 this 指向:

function methodAlias (func, target) {
    return function () {
        return func.apply(target, arguments);
    }
}

let sayName3 = methodAlias(person.sayName, person);
sayName3();

同样的例子还有给 document.querySelector 等函数起别名,如下所示:

// Error: 该方法内部会使用指向 document 的 this
let queryElem = document.querySelector;
queryElem('#id');   // Error

// 使用自定义的起别名方法,指明 this 指向
let queryElem = methodAlias(document.querySelector, document);
queryElem('#id');

使用 bind, 推荐

在大部分浏览器中都实现了 Function.prototype.bind 方法,可以如下使用:

let sayName4 = person.sayName.bind(person);
sayName4();     // Mango

let queryElem = document.querySelector.bind(document);
queryElem('#id');

bind方法的实现原理如下:

Function.prototype.bind = function (context) {
    let self = this;
    return function () {
        self.apply(context, arguments);
    }
}

2. call 与 apply

2.1 简述

callapply 只是在形式是不同,如下所示:

const func = function (a, b, c) {
    console.log([a, b, c]);
}
// apply 的第二个参数是数组或类数组
func.apply(null, [1, 2, 3]);
// call 会将函数的参数依次往后排,call 是 apply 的一个语法糖
func.call(null, 1, 2, 3);
}

callapply 的第一个参数为 null 时,函数(普通函数与对象方法)体内的 this 会指向默认的宿主对象,在浏览器中是 window

window.age = 200;
const obj = {
    age: 28,
    func2: function () {
        console.log('func2: ', this.age);
    }
}
const func1 = function () {
    console.log('func1: ', this.age);
}
func1.apply(null);  // 200 this指向window
func1.apply(obj);   // 28 this指向 obj

obj.func2.apply(null);  // 200 this 指向 window
obj.func2.apply(obj);   // 28 this指向obj

2.2 call 和 apply 的其他用途

借用其他对象的方法

最典型的就是类数组借用 Array.prototype 中的方法。
比如函数的参数列表 arguments 是一个类数组对象,虽然它也有“下标”,但它不是真正的数组,所以不能像数组一样使用数组的方法。
这种情况就可以使用 Array.prototype 中的方法。
一般情况下,对象有“下标”和length属性即可使用数组的原型方法。

常用方法如下所示:

// 将类数组转为数组
Array.prototype.slice.apply(arguments);

// 往类数组中添加元素
Array.prototype.push.apply(arguments, ['one']);

//...
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Karen110 Karen110
3年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
巴拉米 巴拉米
4年前
bind、call、apply 区别?如何实现一个bind?
一、作用call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向那么什么情况下需要改变this的指向呢?下面举个例子var name"lucy";const obj{    name:"martin",    say:function (){        co
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
JS 中的this指向问题和call、apply、bind的区别
this的指向问题一般情况下this对象指向调用函数的对象,全局环境中执行函数this对象指向window。functiona(){console.log(this);//输出函数a中的this对象}functionb(){};varc{name:"call"}
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(