#JS:this的指向及函数调用对this的影响
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中关于call
、apply
的用法主要有两个目的
- 借用构造函数继承
- 修改函数运行时的
this
指针
call
和apply
方法的第一个实参是要调用函数的母对象,它是调用上下文,在函数体内通过this获取对它的引用。通俗的说,call
和apply
的作用就在于赋能,obj.func.call(thisObj)
,赋予thisObj这个对象obj.func这个方法(能力)。
注意,在ES5的严格模式中,call
和apply
的第一个实参都会变成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中函数调用的几种方式
- 函数调用模式
- 方法调用模式
- 构造器调用模式
- 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;
}
复制代码
- 创建一个空对象 obj;
- 将新创建的空对象的隐式原型指向其构造函数的显示原型对象;
- 使用 call 改变 this 的指向,指向新建的空对象;
- 如果无返回值或者返回一个非对象值,则将 obj返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。
所以我们可以看到,在 new 的过程中,我们是使用 call 改变了 this 的指向。
作为函数方法间接调用函数
通过call()、apply()、bind()方法把对象绑定到this上,叫做显式绑定。对于被调用的函数来说,叫做间接调用。
- call、apply、bind三者的第一个参数都是this要指向的对象,
- bind只是返回函数,还未调用,所以如果要执行还得在后面加个();call、apply是立即执行函数;
- 三者后面都可以带参数,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, 它将使用全局对象替代。
参考资料:
- www.cnblogs.com/52fhy/p/511…
- www.zhihu.com/question/20…
两篇此前总结的文章
- JS函数的声明、调用及其对this的影响
- 摸清JS中this的指向问题
转载于:https://juejin.im/post/5bb8f662e51d450e425efda9
#JS:this的指向及函数调用对this的影响相关推荐
- JavaScript面向对象(一)——JS OOP基础与JS 中This指向详解
前 言 学过程序语言的都知道,我们的程序语言进化是从"面向机器".到"面向过程".再到"面向对象"一步步的发展而来.类似于汇编语言这样的面 ...
- 面试官问:JS的this指向
写于2018年12月25日,发布在掘金上阅读量近一万,现在发布到微信公众号申明原创. 前言 这是面试官问系列的第四篇,旨在帮助读者提升JS基础知识,包含new.call.apply.this.继承相关 ...
- Atitit.js this错误指向window的解决方案
Atitit.js this错误指向window的解决方案 1.1. 出现地点and解决之道1 1.2. call,apply和bind这三个方法2 1.2.1. Function.prototype ...
- js中的4种函数调用模式:函数调用、方法调用、构造器调用、间接调用
全栈工程师开发手册 (作者:栾鹏) js系列教程4-函数.函数参数教程全解 js中的4种函数调用模式 javascript一共有4种调用模式:函数调用模式.方法调用模式.构造器调用模式和间接调用模式. ...
- 关于Vue.js的v-for,key的顺序改变,影响过渡动画表现
关于Vue.js的v-for,key的顺序改变,影响过渡动画表现 这个问题是在写Message组件出现的,先看代码部分 子组件: #Notice:<transition :name=" ...
- 前端学习辑录(2):JS中this指向的问题
目录 this指向的不同场景: 一.普通函数调用 二.定时器函数 三.构造函数调用 四.对象方法调用 五.原型对象调用 总结 this指向的不同场景: 一.普通函数调用 普通函数中的this是谁?== ...
- 关于js中this指向复习
简单说this指向 谁调用指向谁 1.对象.方法() 中,方法中的this指向的就是对象 2.普通函数中的 this 指向window,这是因为 window 是JS中的顶级对象,可以通过window ...
- 详解js的this指向问题
以下内容来自<你不知道的js>读书笔记 this提供了一种更优雅的方式来隐式"传递"一个对象引用,因此可以将API设计得更加简洁并且易于复用. this与函数的调用位置 ...
- js中this指向问题总结
1. 默认绑定规则 (1) 全局作用下 this指向window console.log(this); (2) 函数独立调用下 this指向window function test(){ consol ...
最新文章
- 例题3-6 环状序列(Circular Sequence, ACM/ICPC Seoul 2004, UVa1584)
- zenoss core
- threadlocal_了解ThreadLocal背后的概念
- symfony php 亿万,php – symfony中的内存不足错误
- 手机电量剩一半就充比较好,还是快没电了再充比较好?为什么?
- Halcon数据类型
- 格林时间转yyyy-MM-dd hh:mm:ss
- WinMM.dll 函数汇总
- 新华三PRIMERA,开启存储新纪元
- plsql 备份还原 教程_PLSQL导出还原数据库
- Extjs6 学习(一)
- 计算机网络的定义以及分类
- 6PSS并联结构的运动学正逆解推导
- 一级域名怎么申请二级域名?
- 零厨艺也只需10分钟就能让厨房剩货大变身——牛肉饭
- 别头疼了,你要的算法和数据结构的学习路线来了!
- 烟雨江湖服务器维护,烟雨江湖初始属性选择推荐 烟雨江湖出生攻略
- 2021最新Android常用开源库总结,建议收藏
- 分治算法在排课系统中的分析与应用的改进
- SpringBoot banner图样
热门文章
- PostgreSQL 模式删除背后的代码
- Java连接Oracle数据库示例
- Jfinal 不同版本下的前端模版的数据取值输出
- golang import后带“_”下划线的意义
- 技术不是工程师能力的全部:闲看《因为所谓的代码性能不高而被离职的程序员》...
- Hibernate总结
- HTML5 Audio标签方法和函数API介绍
- Virtual Lab. For Probability and Statistics
- 《自适应软件开发》—从奴隶主到领袖 4 领导—协作
- Netty消息接收类故障案例分析