call、apply、bind和this真是ES5众多坑中的一个,希望本篇文章能让你记住它们!

this的指向

此前在摸清JS中this的指向问题 ,这篇文章中我就尝试总结过this的指向,也顺带提及了call,bind,apply等,本篇文章为了完整性,我再总结一次。

开始之前,请一定要始终把握住关于this的一个概念和原理,心里默念三遍都可以:

this是JS中的关键字,函数运行时自动生成的一个内部对象,只能在函数内部使用。

this永远指向最后调用它的那个对象。

也就是说,this的指向并不是在函数创建的时候确定的,在ES5中,this永远指向最后调用它的那个对象记住这句话,后面很多疑难都一点就通哟。

往深一点说,Javascript是一门文本作用域的语言。也就是说,一个变量的作用域在你写这个变量时就已经确定了。而this关键字的加入是为了在JS中加入动态作用域而做的努力。所谓的动态作用域,也就是变量的作用范围是根据函数调用的位置而确定的。

这么说,你是不是对这句话理解更深一点呢~

详细的栗子请移步摸清JS中this的指向问题 ,这篇文章的1.0开始陈述了各类情况下this的指向

如何改变this的指向

改变this的指向,我总结有以下几种方法:

  • 使用ES6箭头函数
  • 在函数内部使用诸如_this = this
  • 使用apply、call、bind
  • 构造函数环境:用new实例化一个对象
var name = "windowsName";var a = {name : "isaac",func1: function () {console.log(this.name)     },func2: function () {setTimeout(  function () {this.func1()},100);}};a.func2()     // this.func1 is not a function
复制代码

这也是一个坑!因为匿名函数的this永远指向window。setTimeout中所执行函数是匿名函数,所以最后调用setTimeout的对象是window,但是在window中并没有func1函数。

我们在改变 this 指向这一节将把这个例子作为 demo 进行改造。

箭头函数

众所周知,ES6 的箭头函数是可以避免 ES5 中使用 this 的坑的。箭头函数的 this 始终指向函数定义时的 this,而非执行时。箭头函数需要记着这句话:

箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。

var name = "windowsName";var a = {name : "isaac",func1: function () {console.log(this.name)     },func2: function () {setTimeout( () => {this.func1()},100);}};a.func2() // isaac
复制代码

_this = this

如果不使用 ES6,那么这种方式应该是最简单的不会出错的方式了,我们是先将调用这个函数的对象保存在变量 _this 中,然后在函数中都使用这个 _this,这样 _this 就不会改变了。

var name = "windowsName";var a = {name : "isaac",func1: function () {console.log(this.name)     },func2: function () {var _this = this;setTimeout( function() {_this.func1()},100);}};a.func2() // isaac
复制代码

在这个例子中,func2中,首先设置var _this = this;,这里的 this是调用func2的对象a,为了防止在func2中的setTimeout中被延迟执行的函数被window调用而导致的在setTimeout中的this为window。我们将 this(指向变量 a) 赋值给一个变量 _this,这样,在 func2 中我们使用_this就是指向对象a了。

使用apply,call,bind

使用call

var a = {name : "isaac",func1: function () {console.log(this.name)},func2: function () {setTimeout(  function () {this.func1()}.call(a),100);}};a.func2() // isaac
复制代码

apply的用法与其一致。

使用bind

var a = {name : "isaac",func1: function () {console.log(this.name)},func2: function () {setTimeout(  function () {this.func1()}.bind(a)(), 100);}};a.func2() // isaac
复制代码

apply、call、bind 区别

刚刚我们已经介绍了 apply、call、bind 都是可以改变 this 的指向的,但是这三个函数稍有不同。

Javascript中关于callapply的用法主要有两个目的

  • 借用构造函数继承
  • 修改函数运行时的this指针

callapply方法的第一个实参是要调用函数的母对象,它是调用上下文,在函数体内通过this获取对它的引用。通俗的说,callapply的作用就在于赋能obj.func.call(thisObj),赋予thisObj这个对象obj.func这个方法(能力)。

注意,在ES5的严格模式中,callapply的第一个实参都会变成this的值,哪怕传入的是null或undefined。

在ES3中和非严格模式下,传入null和undefined都会被全局对象替代,而其他原始值则会被相应的包装对象所替代

而bind需要被手动调用,可以参考mdn的解释

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

call的原理

call的实现原理如下

f.call(o)
// 原理如下
o.m = f // 1. 将f作为o的某个临时属性m来储存
o.m() // 2. 然后执行m
delete o.m // 3. 执行完毕后删除m
复制代码

举个实例

function fn() {var a = 'a'this.b = 'b'this.sum = function (num1, num2) {return num1 + num2}
}function o() {this.c = 'c'
}fn.call(o)
复制代码

此时,o对象内已经包含了fn函数的所有属性和方法。也就是在o中拷贝了一份fn函数的属性与方法,同时修改了this 的指向,即指向新的对象。

栗子

function cat(){}
cat.prototype={     food:"fish",     say: function(){           alert("I love "+this.food);     }
}
var blackCat = new cat;
blackCat.say();
复制代码

如果我们有一个对象whiteDog = {food:"bone"},我们不想对它重新定义say方法,那么我们可以通过call或apply用blackCat的say方法:blackCat.say.call(whiteDog);

所以,可以看出call和apply是为了动态改变this而出现的(改变某个函数运行时的 context 即上下文),当一个object没有某个方法,但是其他的有,我们可以借助call或apply对其它对象进行赋能

用的比较多的,是通过document.getElementsByTagName选择的dom 节点是一种类似类数组对象。它不能应用Array下的push,pop等方法·。我们可以通过:

var domNodes =  Array.prototype.slice.call(document.getElementsByTagName("*"));
复制代码

这里用slice方法来操作DOMList。因为slice方法返回值就是一个数组本身。 这样domNodes就可以应用Array下的所有方法了。

这里分享一位知乎网友的精辟比喻:

本身不难理解,看下MDN就知道了,但是不常用,遇到了,还要脑回路回转下。或者时间长了,还是要确定下去看下文档,为了方便记忆:猫吃鱼,狗吃肉,奥特曼打小怪兽。有天狗想吃鱼了猫.吃鱼.call(狗,鱼)狗就吃到鱼了猫成精了,想打怪兽奥特曼.打小怪兽.call(猫,小怪兽)就这样记住了。

JS中函数的调用

此前在这篇文章,3.x.2/JS函数的声明、调用及其对this的影响 中,记录过JS中函数调用的几种方式

  1. 函数调用模式
  2. 方法调用模式
  3. 构造器调用模式
  4. call、apply间接调用模式。

函数调用模式

function add(x, y) {return x + y
}
var sum = add(3, 4)
console.log(sum)
复制代码

这是一个最简单的函数,不属于任何一个对象的属性或方法,仅仅是一个函数。

this指向

注意,使用函数调用模式调用函数时,非严格模式下,this会被绑定到全局对象下;严格模式下则是undefined。

function add(x, y) {console.log(this) // window
}
复制代码

另外,因为在函数调用模式的函数中this绑定到全局对象,所以会发生全局属性被重写的情况:

var a = 0
function fn() {this.a = 100
}
fn()
console.log(this, this.a, a) //window, 100, 100
复制代码

因为容易发生命名冲突,所以我们极少这么使用。

方法调用模式

所以说更多的情况是将函数作为对象的方法调用。当一个函数被保存为对象的一个属性时,我们称之为方法。当一个方法被调用时,this被绑定到直接对象上。因此,我们在调用表达式时需要一个提取属性的写法,来调用函数。

var o = {m: function() {console.log(this === o) // truereturn 1}
}
o.m() // 1,这就是调用的方法
复制代码

这里o对象通过.调用了m方法。然后我们一直记住的那句话this 永远指向最后调用它的那个对象,所以在m中的this就是指向o的。

this指向

方法调用模式下,this指代调用函数本身所属的对象,如上例中的o。因此,通过this我们可以从对象中取值或对对象进行修改。this到对象的绑定发生在调用的时候。通过this可取得它们所属对象的上下文的方法称为公共方法

var o = {a: 1m: function() {return this}n: function() {this.a = 2}
}
console.log(o.m().a) // 1
o.n() // 2
console.log(o.n().a) // 2
复制代码

使用构造函数调用函数

如果函数调用前使用了 new 关键字, 则是调用了构造函数。 这看起来就像创建了新的函数,但实际上JavaScript函数是重新创建的对象:

// 构造函数:
function myFunction(arg1, arg2) {this.firstName = arg1;this.lastName  = arg2;
}// This    creates a new object
var a = new myFunction("Li","isaac");
a.lastName;  // 返回 "isaac"
复制代码

这就有要说另一个面试经典问题:new 的过程了,(ಥ_ಥ) 这里就简单的来看一下 new 的过程吧: 伪代码表示:

var a = new myFunction("Li","isaac");new myFunction{var obj = {};obj.__proto__ = myFunction.prototype;var result = myFunction.call(obj,"Li","isaac");return typeof result === 'obj'? result : obj;
}
复制代码
  1. 创建一个空对象 obj;
  2. 将新创建的空对象的隐式原型指向其构造函数的显示原型对象;
  3. 使用 call 改变 this 的指向,指向新建的空对象;
  4. 如果无返回值或者返回一个非对象值,则将 obj返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。

所以我们可以看到,在 new 的过程中,我们是使用 call 改变了 this 的指向。

作为函数方法间接调用函数

通过call()、apply()、bind()方法把对象绑定到this上,叫做显式绑定。对于被调用的函数来说,叫做间接调用。

  1. call、apply、bind三者的第一个参数都是this要指向的对象,
  2. bind只是返回函数,还未调用,所以如果要执行还得在后面加个();call、apply是立即执行函数;
  3. 三者后面都可以带参数,call后面的参数用逗号隔开,apply后面的参数以数组的形式传入;bind则可以在指定对象的时候传参,和call一样,以逗号隔开,也可以在执行的时候传参,写到后面的括号中;func.call(obj,value1,value2); func.apply(obj,[value1,value2]); func.bind(obj,value1,value2)(); func.bind(obj)(value1,value2);

在 JavaScript 中, 函数是对象。

JavaScript 函数有它的属性和方法。call() 和 apply() 是预定义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象本身

在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即使该参数不是一个对象。在 JavaScript 非严格模式(non-strict mode)下, 如果第一个参数的值是 null 或 undefined, 它将使用全局对象替代。


参考资料:

  1. www.cnblogs.com/52fhy/p/511…
  2. www.zhihu.com/question/20…

两篇此前总结的文章

  1. JS函数的声明、调用及其对this的影响
  2. 摸清JS中this的指向问题

转载于:https://juejin.im/post/5bb8f662e51d450e425efda9

#JS:this的指向及函数调用对this的影响相关推荐

  1. JavaScript面向对象(一)——JS OOP基础与JS 中This指向详解

    前  言 学过程序语言的都知道,我们的程序语言进化是从"面向机器".到"面向过程".再到"面向对象"一步步的发展而来.类似于汇编语言这样的面 ...

  2. 面试官问:JS的this指向

    写于2018年12月25日,发布在掘金上阅读量近一万,现在发布到微信公众号申明原创. 前言 这是面试官问系列的第四篇,旨在帮助读者提升JS基础知识,包含new.call.apply.this.继承相关 ...

  3. Atitit.js this错误指向window的解决方案

    Atitit.js this错误指向window的解决方案 1.1. 出现地点and解决之道1 1.2. call,apply和bind这三个方法2 1.2.1. Function.prototype ...

  4. js中的4种函数调用模式:函数调用、方法调用、构造器调用、间接调用

    全栈工程师开发手册 (作者:栾鹏) js系列教程4-函数.函数参数教程全解 js中的4种函数调用模式 javascript一共有4种调用模式:函数调用模式.方法调用模式.构造器调用模式和间接调用模式. ...

  5. 关于Vue.js的v-for,key的顺序改变,影响过渡动画表现

    关于Vue.js的v-for,key的顺序改变,影响过渡动画表现 这个问题是在写Message组件出现的,先看代码部分 子组件: #Notice:<transition :name=" ...

  6. 前端学习辑录(2):JS中this指向的问题

    目录 this指向的不同场景: 一.普通函数调用 二.定时器函数 三.构造函数调用 四.对象方法调用 五.原型对象调用 总结 this指向的不同场景: 一.普通函数调用 普通函数中的this是谁?== ...

  7. 关于js中this指向复习

    简单说this指向 谁调用指向谁 1.对象.方法() 中,方法中的this指向的就是对象 2.普通函数中的 this 指向window,这是因为 window 是JS中的顶级对象,可以通过window ...

  8. 详解js的this指向问题

    以下内容来自<你不知道的js>读书笔记 this提供了一种更优雅的方式来隐式"传递"一个对象引用,因此可以将API设计得更加简洁并且易于复用. this与函数的调用位置 ...

  9. js中this指向问题总结

    1. 默认绑定规则 (1) 全局作用下 this指向window console.log(this); (2) 函数独立调用下 this指向window function test(){ consol ...

最新文章

  1. 例题3-6 环状序列(Circular Sequence, ACM/ICPC Seoul 2004, UVa1584)
  2. zenoss core
  3. threadlocal_了解ThreadLocal背后的概念
  4. symfony php 亿万,php – symfony中的内存不足错误
  5. 手机电量剩一半就充比较好,还是快没电了再充比较好?为什么?
  6. Halcon数据类型
  7. 格林时间转yyyy-MM-dd hh:mm:ss
  8. WinMM.dll 函数汇总
  9. 新华三PRIMERA,开启存储新纪元
  10. plsql 备份还原 教程_PLSQL导出还原数据库
  11. Extjs6 学习(一)
  12. 计算机网络的定义以及分类
  13. 6PSS并联结构的运动学正逆解推导
  14. 一级域名怎么申请二级域名?
  15. 零厨艺也只需10分钟就能让厨房剩货大变身——牛肉饭
  16. 别头疼了,你要的算法和数据结构的学习路线来了!
  17. 烟雨江湖服务器维护,烟雨江湖初始属性选择推荐 烟雨江湖出生攻略
  18. 2021最新Android常用开源库总结,建议收藏
  19. 分治算法在排课系统中的分析与应用的改进
  20. SpringBoot banner图样

热门文章

  1. PostgreSQL 模式删除背后的代码
  2. Java连接Oracle数据库示例
  3. Jfinal 不同版本下的前端模版的数据取值输出
  4. golang import后带“_”下划线的意义
  5. 技术不是工程师能力的全部:闲看《因为所谓的代码性能不高而被离职的程序员》...
  6. Hibernate总结
  7. HTML5 Audio标签方法和函数API介绍
  8. Virtual Lab. For Probability and Statistics
  9. 《自适应软件开发》—从奴隶主到领袖 4 领导—协作
  10. Netty消息接收类故障案例分析