1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > #JS:this的指向及函数调用对this的影响

#JS:this的指向及函数调用对this的影响

时间:2022-04-23 11:53:43

相关推荐

#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中关于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. 然后执行mdelete 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 = 0function 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) // 1o.n() // 2console.log(o.n().a) // 2复制代码

使用构造函数调用函数

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

// 构造函数:function myFunction(arg1, arg2) {this.firstName = arg1;this.lastName = arg2;}// This creates a new objectvar 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, 它将使用全局对象替代。

参考资料:

/52fhy/p/511…/question/20…

两篇此前总结的文章

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。